Browse Source

Merge pull request #887 from vowstar/dev-all-in-one

Add HTTP client module and documents.
Johny Mattsson 8 years ago
parent
commit
52e121f469
8 changed files with 1049 additions and 1 deletions
  1. 42 0
      README.md
  2. 3 1
      app/Makefile
  3. 47 0
      app/http/Makefile
  4. 628 0
      app/http/httpclient.c
  5. 97 0
      app/http/httpclient.h
  6. 1 0
      app/include/user_modules.h
  7. 1 0
      app/modules/Makefile
  8. 230 0
      app/modules/http.c

+ 42 - 0
README.md

@@ -289,6 +289,7 @@ Comment-out the #define statement for unused modules. Example:
 // #define LUA_USE_MODULES_BMP085
 // #define LUA_USE_MODULES_BMP085
 #define LUA_USE_MODULES_TSL2561
 #define LUA_USE_MODULES_TSL2561
 // #define LUA_USE_MODULES_HX711
 // #define LUA_USE_MODULES_HX711
+#define LUA_USE_MODULES_HTTP
 
 
 #endif /* LUA_USE_MODULES */
 #endif /* LUA_USE_MODULES */
 ```
 ```
@@ -628,6 +629,47 @@ json_text = cjson.encode(value)
 -- Returns: '[true,{"foo":"bar"}]'
 -- Returns: '[true,{"foo":"bar"}]'
 ```
 ```
 
 
+####HTTP client
+```lua
+-- Support HTTP and HTTPS, For example
+-- HTTP POST Example with JSON header and body
+http.post("http://somewhere.acceptjson.com/",
+           "Content-Type: application/json\r\n", 
+           "{\"hello\":\"world\"}", 
+            function(code, data) 
+                print(code) 
+                print(data) 
+            end)
+-- HTTPS GET Example with NULL header
+http.get("https://www.vowstar.com/nodemcu/","",
+            function(code, data) 
+                print(code) 
+                print(data) 
+            end)
+-- You will get
+-- > 200 
+-- hello nodemcu
+-- HTTPS DELETE Example with NULL header and body
+http.delete("https://10.0.0.2:443","","",
+            function(code, data) 
+                print(code) 
+                print(data) 
+            end)
+-- HTTPS PUT Example with NULL header and body
+http.put("https://testput.somewhere/somewhereyouput.php","","",
+            function(code, data) 
+                print(code) 
+                print(data) 
+            end)
+-- HTTP RAW Request Example, use more HTTP/HTTPS request method
+http.request("http://www.apple.com:80/library/test/success.html","GET","","",
+            function(code, data) 
+                print(code) 
+                print(data) 
+            end)
+
+```
+
 ####Read an HX711 load cell ADC.
 ####Read an HX711 load cell ADC.
 Note: currently only chanel A with gain 128 is supported.
 Note: currently only chanel A with gain 128 is supported.
 The HX711 is an inexpensive 24bit ADC with programmable 128x, 64x, and 32x gain.
 The HX711 is an inexpensive 24bit ADC with programmable 128x, 64x, and 32x gain.

+ 3 - 1
app/Makefile

@@ -41,7 +41,8 @@ SUBDIRS= 					\
 	cjson 					\
 	cjson 					\
 	crypto 					\
 	crypto 					\
 	dhtlib					\
 	dhtlib					\
-	tsl2561
+	tsl2561					\
+	http
 
 
 endif # } PDIR
 endif # } PDIR
 
 
@@ -86,6 +87,7 @@ COMPONENTS_eagle.app.v6 = 			\
 	crypto/libcrypto.a 			\
 	crypto/libcrypto.a 			\
 	dhtlib/libdhtlib.a 			\
 	dhtlib/libdhtlib.a 			\
 	tsl2561/tsl2561lib.a			\
 	tsl2561/tsl2561lib.a			\
+	http/libhttp.a				\
 	modules/libmodules.a			\
 	modules/libmodules.a			\
 
 
 # Inspect the modules library and work out which modules need to be linked.
 # Inspect the modules library and work out which modules need to be linked.

+ 47 - 0
app/http/Makefile

@@ -0,0 +1,47 @@
+
+#############################################################
+# Required variables for each makefile
+# Discard this section from all parent makefiles
+# Expected variables (with automatic defaults):
+#   CSRCS (all "C" files in the dir)
+#   SUBDIRS (all subdirs with a Makefile)
+#   GEN_LIBS - list of libs to be generated ()
+#   GEN_IMAGES - list of images to be generated ()
+#   COMPONENTS_xxx - a list of libs/objs in the form
+#     subdir/lib to be extracted and rolled up into
+#     a generated lib/image xxx.a ()
+#
+ifndef PDIR
+GEN_LIBS = libhttp.a
+endif
+
+
+#############################################################
+# Configuration i.e. compile options etc.
+# Target specific stuff (defines etc.) goes in here!
+# Generally values applying to a tree are captured in the
+#   makefile at its root level - these are then overridden
+#   for a subtree within the makefile rooted therein
+#
+#DEFINES += 
+
+#############################################################
+# Recursion Magic - Don't touch this!!
+#
+# Each subtree potentially has an include directory
+#   corresponding to the common APIs applicable to modules
+#   rooted at that subtree. Accordingly, the INCLUDE PATH
+#   of a module can only contain the include directories up
+#   its parent path, and not its siblings
+#
+# Required for each makefile to inherit from the parent
+#
+
+INCLUDES := $(INCLUDES) -I $(PDIR)include
+INCLUDES += -I ./
+INCLUDES += -I ./include
+INCLUDES += -I ../include
+INCLUDES += -I ../../include
+PDIR := ../$(PDIR)
+sinclude $(PDIR)Makefile
+

+ 628 - 0
app/http/httpclient.c

@@ -0,0 +1,628 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * Martin d'Allens <martin.dallens@gmail.com> wrote this file. As long as you retain
+ * this notice you can do whatever you want with this stuff. If we meet some day,
+ * and you think this stuff is worth it, you can buy me a beer in return.
+ * ----------------------------------------------------------------------------
+ */
+
+/*
+ * FIXME: sprintf->snprintf everywhere.
+ * FIXME: support null characters in responses.
+ */
+
+#include "osapi.h"
+#include "user_interface.h"
+#include "espconn.h"
+#include "mem.h"
+#include "limits.h"
+#include "httpclient.h"
+
+/* Internal state. */
+typedef struct request_args_t {
+	char		* hostname;
+	int		port;
+	bool		secure;
+	char		* method;
+	char		* path;
+	char		* headers;
+	char		* post_data;
+	char		* buffer;
+	int		buffer_size;
+	int		timeout;
+	os_timer_t	timeout_timer;
+	http_callback_t callback_handle;
+} request_args_t;
+
+static char * ICACHE_FLASH_ATTR esp_strdup( const char * str )
+{
+	if ( str == NULL )
+	{
+		return(NULL);
+	}
+	char * new_str = (char *) os_malloc( os_strlen( str ) + 1 ); /* 1 for null character */
+	if ( new_str == NULL )
+	{
+		HTTPCLIENT_DEBUG( "esp_strdup: malloc error" );
+		return(NULL);
+	}
+	os_strcpy( new_str, str );
+	return(new_str);
+}
+
+
+static int ICACHE_FLASH_ATTR
+esp_isupper( char c )
+{
+	return(c >= 'A' && c <= 'Z');
+}
+
+
+static int ICACHE_FLASH_ATTR
+esp_isalpha( char c )
+{
+	return( (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') );
+}
+
+
+static int ICACHE_FLASH_ATTR
+esp_isspace( char c )
+{
+	return(c == ' ' || c == '\t' || c == '\n' || c == '\12');
+}
+
+
+static int ICACHE_FLASH_ATTR
+esp_isdigit( char c )
+{
+	return(c >= '0' && c <= '9');
+}
+
+
+/*
+ * Convert a string to a long integer.
+ *
+ * Ignores `locale' stuff.  Assumes that the upper and lower case
+ * alphabets and digits are each contiguous.
+ */
+long ICACHE_FLASH_ATTR
+esp_strtol( nptr, endptr, base )
+const char *nptr;
+
+
+char	**endptr;
+int	base;
+{
+	const char	*s = nptr;
+	unsigned long	acc;
+	int		c;
+	unsigned long	cutoff;
+	int		neg = 0, any, cutlim;
+
+
+	/*
+	 * Skip white space and pick up leading +/- sign if any.
+	 * If base is 0, allow 0x for hex and 0 for octal, else
+	 * assume decimal; if base is already 16, allow 0x.
+	 */
+	do
+	{
+		c = *s++;
+	}
+	while ( esp_isspace( c ) );
+	if ( c == '-' )
+	{
+		neg	= 1;
+		c	= *s++;
+	} else if ( c == '+' )
+		c = *s++;
+	if ( (base == 0 || base == 16) &&
+	     c == '0' && (*s == 'x' || *s == 'X') )
+	{
+		c	= s[1];
+		s	+= 2;
+		base	= 16;
+	} else if ( (base == 0 || base == 2) &&
+		    c == '0' && (*s == 'b' || *s == 'B') )
+	{
+		c	= s[1];
+		s	+= 2;
+		base	= 2;
+	}
+	if ( base == 0 )
+		base = c == '0' ? 8 : 10;
+
+
+	/*
+	 * Compute the cutoff value between legal numbers and illegal
+	 * numbers.  That is the largest legal value, divided by the
+	 * base.  An input number that is greater than this value, if
+	 * followed by a legal input character, is too big.  One that
+	 * is equal to this value may be valid or not; the limit
+	 * between valid and invalid numbers is then based on the last
+	 * digit.  For instance, if the range for longs is
+	 * [-2147483648..2147483647] and the input base is 10,
+	 * cutoff will be set to 214748364 and cutlim to either
+	 * 7 (neg==0) or 8 (neg==1), meaning that if we have accumulated
+	 * a value > 214748364, or equal but the next digit is > 7 (or 8),
+	 * the number is too big, and we will return a range error.
+	 *
+	 * Set any if any `digits' consumed; make it negative to indicate
+	 * overflow.
+	 */
+	cutoff	= neg ? -(unsigned long) LONG_MIN : LONG_MAX;
+	cutlim	= cutoff % (unsigned long) base;
+	cutoff	/= (unsigned long) base;
+	for ( acc = 0, any = 0;; c = *s++ )
+	{
+		if ( esp_isdigit( c ) )
+			c -= '0';
+		else if ( esp_isalpha( c ) )
+			c -= esp_isupper( c ) ? 'A' - 10 : 'a' - 10;
+		else
+			break;
+		if ( c >= base )
+			break;
+		if ( any < 0 || acc > cutoff || acc == cutoff && c > cutlim )
+			any = -1;
+		else 
+		{
+			any	= 1;
+			acc	*= base;
+			acc	+= c;
+		}
+	}
+	if ( any < 0 )
+	{
+		acc = neg ? LONG_MIN : LONG_MAX;
+/*		errno = ERANGE; */
+	} else if ( neg )
+		acc = -acc;
+	if ( endptr != 0 )
+		*endptr = (char *) (any ? s - 1 : nptr);
+	return(acc);
+}
+
+static int ICACHE_FLASH_ATTR http_chunked_decode( const char * chunked, char * decode )
+{
+	int	i		= 0, j = 0;
+	int	decode_size	= 0;
+	char	*str		= (char *) chunked;
+	do
+	{
+		char * endstr = NULL;
+		/* [chunk-size] */
+		i = esp_strtol( str + j, endstr, 16 );
+		HTTPCLIENT_DEBUG( "Chunk Size:%d\r\n", i );
+		if ( i <= 0 )
+			break;
+		/* [chunk-size-end-ptr] */
+		endstr = (char *) os_strstr( str + j, "\r\n" );
+		/* [chunk-ext] */
+		j += endstr - (str + j);
+		/* [CRLF] */
+		j += 2;
+		/* [chunk-data] */
+		decode_size += i;
+		os_memcpy( (char *) &decode[decode_size - i], (char *) str + j, i );
+		j += i;
+		/* [CRLF] */
+		j += 2;
+	}
+	while ( true );
+
+	/*
+	 *
+	 * footer CRLF
+	 *
+	 */
+
+	return(j);
+}
+
+
+static void ICACHE_FLASH_ATTR http_receive_callback( void * arg, char * buf, unsigned short len )
+{
+	struct espconn	* conn	= (struct espconn *) arg;
+	request_args_t	* req	= (request_args_t *) conn->reverse;
+
+	if ( req->buffer == NULL )
+	{
+		return;
+	}
+
+	/* Let's do the equivalent of a realloc(). */
+	const int	new_size = req->buffer_size + len;
+	char		* new_buffer;
+	if ( new_size > BUFFER_SIZE_MAX || NULL == (new_buffer = (char *) os_malloc( new_size ) ) )
+	{
+		HTTPCLIENT_DEBUG( "Response too long (%d)\n", new_size );
+		req->buffer[0] = '\0';                                                                  /* Discard the buffer to avoid using an incomplete response. */
+		if ( req->secure )
+			espconn_secure_disconnect( conn );
+		else
+			espconn_disconnect( conn );
+		return;                                                                                 /* The disconnect callback will be called. */
+	}
+
+	os_memcpy( new_buffer, req->buffer, req->buffer_size );
+	os_memcpy( new_buffer + req->buffer_size - 1 /*overwrite the null character*/, buf, len );      /* Append new data. */
+	new_buffer[new_size - 1] = '\0';                                                                /* Make sure there is an end of string. */
+
+	os_free( req->buffer );
+	req->buffer		= new_buffer;
+	req->buffer_size	= new_size;
+}
+
+
+static void ICACHE_FLASH_ATTR http_send_callback( void * arg )
+{
+	struct espconn	* conn	= (struct espconn *) arg;
+	request_args_t	* req	= (request_args_t *) conn->reverse;
+
+	if ( req->post_data == NULL )
+	{
+		HTTPCLIENT_DEBUG( "All sent\n" );
+	}
+	else  
+	{
+		/* The headers were sent, now send the contents. */
+		HTTPCLIENT_DEBUG( "Sending request body\n" );
+		if ( req->secure )
+			espconn_secure_send( conn, (uint8_t *) req->post_data, strlen( req->post_data ) );
+		else
+			espconn_send( conn, (uint8_t *) req->post_data, strlen( req->post_data ) );
+		os_free( req->post_data );
+		req->post_data = NULL;
+	}
+}
+
+
+static void ICACHE_FLASH_ATTR http_connect_callback( void * arg )
+{
+	HTTPCLIENT_DEBUG( "Connected\n" );
+	struct espconn	* conn	= (struct espconn *) arg;
+	request_args_t	* req	= (request_args_t *) conn->reverse;
+
+	espconn_regist_recvcb( conn, http_receive_callback );
+	espconn_regist_sentcb( conn, http_send_callback );
+
+	char post_headers[32] = "";
+
+	if ( req->post_data != NULL ) /* If there is data then add Content-Length header. */
+	{
+		os_sprintf( post_headers, "Content-Length: %d\r\n", strlen( req->post_data ) );
+	}
+
+	if(req->headers == NULL) /* Avoid NULL pointer, it may cause exception */
+	{
+		req->headers = (char *)os_malloc(sizeof(char));
+		req->headers[0] = '\0';
+	}
+
+	char buf[69 + strlen( req->method ) + strlen( req->path ) + strlen( req->hostname ) +
+		 strlen( req->headers ) + strlen( post_headers )];
+	int len = os_sprintf( buf,
+			      "%s %s HTTP/1.1\r\n"
+			      "Host: %s:%d\r\n"
+			      "Connection: close\r\n"
+			      "User-Agent: ESP8266\r\n"
+			      "%s"
+			      "%s"
+			      "\r\n",
+			      req->method, req->path, req->hostname, req->port, req->headers, post_headers );
+
+	if ( req->secure )
+		espconn_secure_send( conn, (uint8_t *) buf, len );
+	else
+		espconn_send( conn, (uint8_t *) buf, len );
+	if(req->headers != NULL)
+		os_free( req->headers );
+	req->headers = NULL;
+	HTTPCLIENT_DEBUG( "Sending request header\n" );
+}
+
+
+static void ICACHE_FLASH_ATTR http_disconnect_callback( void * arg )
+{
+	HTTPCLIENT_DEBUG( "Disconnected\n" );
+	struct espconn *conn = (struct espconn *) arg;
+
+	if ( conn == NULL )
+	{
+		return;
+	}
+
+	if ( conn->proto.tcp != NULL )
+	{
+		os_free( conn->proto.tcp );
+	}
+	if ( conn->reverse != NULL )
+	{
+		request_args_t	* req		= (request_args_t *) conn->reverse;
+		int		http_status	= -1;
+		char		* body		= "";
+
+		// Turn off timeout timer
+		os_timer_disarm( &(req->timeout_timer) );
+
+		if ( req->buffer == NULL )
+		{
+			HTTPCLIENT_DEBUG( "Buffer shouldn't be NULL\n" );
+		}
+		else if ( req->buffer[0] != '\0' )
+		{
+			/* FIXME: make sure this is not a partial response, using the Content-Length header. */
+			const char * version = "HTTP/1.1 ";
+			if ( os_strncmp( req->buffer, version, strlen( version ) ) != 0 )
+			{
+				HTTPCLIENT_DEBUG( "Invalid version in %s\n", req->buffer );
+			}
+			else  
+			{
+				http_status	= atoi( req->buffer + strlen( version ) );
+				body		= (char *) os_strstr( req->buffer, "\r\n\r\n" ) + 4;
+				if ( os_strstr( req->buffer, "Transfer-Encoding: chunked" ) )
+				{
+					int	body_size = req->buffer_size - (body - req->buffer);
+					char	chunked_decode_buffer[body_size];
+					os_memset( chunked_decode_buffer, 0, body_size );
+					/* Chuncked data */
+					http_chunked_decode( body, chunked_decode_buffer );
+					os_memcpy( body, chunked_decode_buffer, body_size );
+				}
+			}
+		}
+		if ( req->callback_handle != NULL ) /* Callback is optional. */
+		{
+			req->callback_handle( body, http_status, req->buffer );
+		}
+		os_free( req->buffer );
+		os_free( req->hostname );
+		os_free( req->method );
+		os_free( req->path );
+		os_free( req );
+	}
+	/* Fix memory leak. */
+	espconn_delete( conn );
+	os_free( conn );
+}
+
+
+static void ICACHE_FLASH_ATTR http_error_callback( void *arg, sint8 errType )
+{
+	HTTPCLIENT_DEBUG( "Disconnected with error\n" );
+	http_disconnect_callback( arg );
+}
+
+
+static void ICACHE_FLASH_ATTR http_timeout_callback( void *arg )
+{
+	HTTPCLIENT_DEBUG( "Connection timeout\n" );
+	struct espconn * conn = (struct espconn *) arg;
+	if ( conn == NULL )
+	{
+		return;
+	}
+	if ( conn->reverse == NULL )
+	{
+		return;
+	}
+	request_args_t * req = (request_args_t *) conn->reverse;
+	/* Call disconnect */
+	if ( req->secure )
+		espconn_secure_disconnect( conn );
+	else
+		espconn_disconnect( conn );
+}
+
+
+static void ICACHE_FLASH_ATTR http_dns_callback( const char * hostname, ip_addr_t * addr, void * arg )
+{
+	request_args_t * req = (request_args_t *) arg;
+
+	if ( addr == NULL )
+	{
+		HTTPCLIENT_DEBUG( "DNS failed for %s\n", hostname );
+		if ( req->callback_handle != NULL )
+		{
+			req->callback_handle( "", -1, "" );
+		}
+		os_free( req );
+	}
+	else  
+	{
+		HTTPCLIENT_DEBUG( "DNS found %s " IPSTR "\n", hostname, IP2STR( addr ) );
+
+		struct espconn * conn = (struct espconn *) os_malloc( sizeof(struct espconn) );
+		conn->type			= ESPCONN_TCP;
+		conn->state			= ESPCONN_NONE;
+		conn->proto.tcp			= (esp_tcp *) os_malloc( sizeof(esp_tcp) );
+		conn->proto.tcp->local_port	= espconn_port();
+		conn->proto.tcp->remote_port	= req->port;
+		conn->reverse			= req;
+
+		os_memcpy( conn->proto.tcp->remote_ip, addr, 4 );
+
+		espconn_regist_connectcb( conn, http_connect_callback );
+		espconn_regist_disconcb( conn, http_disconnect_callback );
+		espconn_regist_reconcb( conn, http_error_callback );
+
+		/* Set connection timeout timer */
+		os_timer_disarm( &(req->timeout_timer) );
+		os_timer_setfn( &(req->timeout_timer), (os_timer_func_t *) http_timeout_callback, conn );
+		os_timer_arm( &(req->timeout_timer), req->timeout, false );
+
+		if ( req->secure )
+		{
+			espconn_secure_set_size( ESPCONN_CLIENT, 5120 ); /* set SSL buffer size */
+			espconn_secure_connect( conn );
+		} 
+		else 
+		{
+			espconn_connect( conn );
+		}
+	}
+}
+
+
+void ICACHE_FLASH_ATTR http_raw_request( const char * hostname, int port, bool secure, const char * method, const char * path, const char * headers, const char * post_data, http_callback_t callback_handle )
+{
+	HTTPCLIENT_DEBUG( "DNS request\n" );
+
+	request_args_t * req = (request_args_t *) os_malloc( sizeof(request_args_t) );
+	req->hostname		= esp_strdup( hostname );
+	req->port		= port;
+	req->secure		= secure;
+	req->method		= esp_strdup( method );
+	req->path		= esp_strdup( path );
+	req->headers		= esp_strdup( headers );
+	req->post_data		= esp_strdup( post_data );
+	req->buffer_size	= 1;
+	req->buffer		= (char *) os_malloc( 1 );
+	req->buffer[0]		= '\0';                                         /* Empty string. */
+	req->callback_handle	= callback_handle;
+	req->timeout		= HTTP_REQUEST_TIMEOUT_MS;
+
+	ip_addr_t	addr;
+	err_t		error = espconn_gethostbyname( (struct espconn *) req,  /* It seems we don't need a real espconn pointer here. */
+						       hostname, &addr, http_dns_callback );
+
+	if ( error == ESPCONN_INPROGRESS )
+	{
+		HTTPCLIENT_DEBUG( "DNS pending\n" );
+	}
+	else if ( error == ESPCONN_OK )
+	{
+		/* Already in the local names table (or hostname was an IP address), execute the callback ourselves. */
+		http_dns_callback( hostname, &addr, req );
+	}
+	else  
+	{
+		if ( error == ESPCONN_ARG )
+		{
+			HTTPCLIENT_DEBUG( "DNS arg error %s\n", hostname );
+		}else  {
+			HTTPCLIENT_DEBUG( "DNS error code %d\n", error );
+		}
+		http_dns_callback( hostname, NULL, req ); /* Handle all DNS errors the same way. */
+	}
+}
+
+
+/*
+ * Parse an URL of the form http://host:port/path
+ * <host> can be a hostname or an IP address
+ * <port> is optional
+ */
+void ICACHE_FLASH_ATTR http_request( const char * url, const char * method, const char * headers, const char * post_data, http_callback_t callback_handle )
+{
+	/*
+	 * FIXME: handle HTTP auth with http://user:pass@host/
+	 * FIXME: get rid of the #anchor part if present.
+	 */
+
+	char	hostname[128]	= "";
+	int	port		= 80;
+	bool	secure		= false;
+
+	bool	is_http		= os_strncmp( url, "http://", strlen( "http://" ) ) == 0;
+	bool	is_https	= os_strncmp( url, "https://", strlen( "https://" ) ) == 0;
+
+	if ( is_http )
+		url += strlen( "http://" );             /* Get rid of the protocol. */
+	else if ( is_https )
+	{
+		port	= 443;
+		secure	= true;
+		url	+= strlen( "https://" );        /* Get rid of the protocol. */
+	} 
+	else 
+	{
+		HTTPCLIENT_DEBUG( "URL is not HTTP or HTTPS %s\n", url );
+		return;
+	}
+
+	char * path = os_strchr( url, '/' );
+	if ( path == NULL )
+	{
+		path = os_strchr( url, '\0' );  /* Pointer to end of string. */
+	}
+
+	char * colon = os_strchr( url, ':' );
+	if ( colon > path )
+	{
+		colon = NULL;                   /* Limit the search to characters before the path. */
+	}
+
+	if ( colon == NULL )                    /* The port is not present. */
+	{
+		os_memcpy( hostname, url, path - url );
+		hostname[path - url] = '\0';
+	}
+	else  
+	{
+		port = atoi( colon + 1 );
+		if ( port == 0 )
+		{
+			HTTPCLIENT_DEBUG( "Port error %s\n", url );
+			return;
+		}
+
+		os_memcpy( hostname, url, colon - url );
+		hostname[colon - url] = '\0';
+	}
+
+
+	if ( path[0] == '\0' ) /* Empty path is not allowed. */
+	{
+		path = "/";
+	}
+
+	HTTPCLIENT_DEBUG( "hostname=%s\n", hostname );
+	HTTPCLIENT_DEBUG( "port=%d\n", port );
+	HTTPCLIENT_DEBUG( "method=%s\n", method );
+	HTTPCLIENT_DEBUG( "path=%s\n", path );
+	http_raw_request( hostname, port, secure, method, path, headers, post_data, callback_handle );
+}
+
+
+/*
+ * Parse an URL of the form http://host:port/path
+ * <host> can be a hostname or an IP address
+ * <port> is optional
+ */
+void ICACHE_FLASH_ATTR http_post( const char * url, const char * headers, const char * post_data, http_callback_t callback_handle )
+{
+	http_request( url, "POST", headers, post_data, callback_handle );
+}
+
+
+void ICACHE_FLASH_ATTR http_get( const char * url, const char * headers, http_callback_t callback_handle )
+{
+	http_request( url, "GET", headers, NULL, callback_handle );
+}
+
+
+void ICACHE_FLASH_ATTR http_delete( const char * url, const char * headers, const char * post_data, http_callback_t callback_handle )
+{
+	http_request( url, "DELETE", headers, post_data, callback_handle );
+}
+
+
+void ICACHE_FLASH_ATTR http_put( const char * url, const char * headers, const char * post_data, http_callback_t callback_handle )
+{
+	http_request( url, "PUT", headers, post_data, callback_handle );
+}
+
+
+void ICACHE_FLASH_ATTR http_callback_example( char * response, int http_status, char * full_response )
+{
+	os_printf( "http_status=%d\n", http_status );
+	if ( http_status != HTTP_STATUS_GENERIC_ERROR )
+	{
+		os_printf( "strlen(full_response)=%d\n", strlen( full_response ) );
+		os_printf( "response=%s<EOF>\n", response );
+	}
+}

+ 97 - 0
app/http/httpclient.h

@@ -0,0 +1,97 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * Martin d'Allens <martin.dallens@gmail.com> wrote this file. As long as you retain
+ * this notice you can do whatever you want with this stuff. If we meet some day,
+ * and you think this stuff is worth it, you can buy me a beer in return.
+ * ----------------------------------------------------------------------------
+ */
+
+#ifndef __HTTPCLIENT_H__
+#define __HTTPCLIENT_H__
+
+#if defined(GLOBAL_DEBUG_ON)
+#define HTTPCLIENT_DEBUG_ON
+#endif
+#if defined(HTTPCLIENT_DEBUG_ON)
+#define HTTPCLIENT_DEBUG(format, ...) os_printf(format, ##__VA_ARGS__)
+#else
+#define HTTPCLIENT_DEBUG(format, ...)
+#endif
+
+#if defined(USES_SDK_BEFORE_V140)
+#define espconn_send espconn_sent
+#define espconn_secure_send espconn_secure_sent
+#endif
+
+/*
+ * In case of TCP or DNS error the callback is called with this status.
+ */
+#define HTTP_STATUS_GENERIC_ERROR  (-1)
+
+/*
+ * Size of http responses that will cause an error.
+ */
+#define BUFFER_SIZE_MAX            (1024)
+
+/*
+ * Timeout of http request.
+ */
+#define HTTP_REQUEST_TIMEOUT_MS    (10000)
+
+/*
+ * "full_response" is a string containing all response headers and the response body.
+ * "response_body and "http_status" are extracted from "full_response" for convenience.
+ *
+ * A successful request corresponds to an HTTP status code of 200 (OK).
+ * More info at http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
+ */
+typedef void (* http_callback_t)(char * response_body, int http_status, char * full_response);
+
+/*
+ * Call this function to skip URL parsing if the arguments are already in separate variables.
+ */
+void ICACHE_FLASH_ATTR http_raw_request(const char * hostname, int port, bool secure, const char * method, const char * path, const char * headers, const char * post_data, http_callback_t callback_handle);
+
+/*
+ * Request data from URL use custom method.
+ * The data should be encoded as any format.
+ * Try:
+ * http_request("http://httpbin.org/post", "OPTIONS", "Content-type: text/plain", "Hello world", http_callback_example);
+ */
+void ICACHE_FLASH_ATTR http_request(const char * url, const char * method, const char * headers, const char * post_data, http_callback_t callback_handle);
+
+/*
+ * Post data to a web form.
+ * The data should be encoded as any format.
+ * Try:
+ * http_post("http://httpbin.org/post", "Content-type: application/json", "{\"hello\": \"world\"}", http_callback_example);
+ */
+void ICACHE_FLASH_ATTR http_post(const char * url, const char * headers, const char * post_data, http_callback_t callback_handle);
+
+/*
+ * Download a web page from its URL.
+ * Try:
+ * http_get("http://wtfismyip.com/text", NULL, http_callback_example);
+ */
+void ICACHE_FLASH_ATTR http_get(const char * url, const char * headers, http_callback_t callback_handle);
+/*
+ * Delete a web page from its URL.
+ * Try:
+ * http_delete("http://wtfismyip.com/text", NULL, http_callback_example);
+ */
+void ICACHE_FLASH_ATTR http_delete(const char * url, const char * headers, const char * post_data, http_callback_t callback_handle);
+/*
+ * Update data to a web form.
+ * The data should be encoded as any format.
+ * Try:
+ * http_put("http://httpbin.org/post", "Content-type: application/json", "{\"hello\": \"world\"}", http_callback_example);
+ */
+void ICACHE_FLASH_ATTR http_put(const char * url, const char * headers, const char * post_data, http_callback_t callback_handle);
+
+/*
+ * Output on the UART.
+ */
+void http_callback_example(char * response, int http_status, char * full_response);
+
+#endif // __HTTPCLIENT_H__

+ 1 - 0
app/include/user_modules.h

@@ -23,6 +23,7 @@
 //#define LUA_USE_MODULES_ENDUSER_SETUP // USE_DNS in dhcpserver.h needs to be enabled for this module to work.
 //#define LUA_USE_MODULES_ENDUSER_SETUP // USE_DNS in dhcpserver.h needs to be enabled for this module to work.
 #define LUA_USE_MODULES_FILE
 #define LUA_USE_MODULES_FILE
 #define LUA_USE_MODULES_GPIO
 #define LUA_USE_MODULES_GPIO
+#define LUA_USE_MODULES_HTTP
 //#define LUA_USE_MODULES_HX711
 //#define LUA_USE_MODULES_HX711
 #define LUA_USE_MODULES_I2C
 #define LUA_USE_MODULES_I2C
 #define LUA_USE_MODULES_MQTT
 #define LUA_USE_MODULES_MQTT

+ 1 - 0
app/modules/Makefile

@@ -50,6 +50,7 @@ INCLUDES += -I ../spiffs
 INCLUDES += -I ../smart
 INCLUDES += -I ../smart
 INCLUDES += -I ../cjson
 INCLUDES += -I ../cjson
 INCLUDES += -I ../dhtlib
 INCLUDES += -I ../dhtlib
+INCLUDES += -I ../http
 PDIR := ../$(PDIR)
 PDIR := ../$(PDIR)
 sinclude $(PDIR)Makefile
 sinclude $(PDIR)Makefile
 
 

+ 230 - 0
app/modules/http.c

@@ -0,0 +1,230 @@
+/******************************************************************************
+ * HTTP module for NodeMCU
+ * vowstar@gmail.com
+ * 2015-12-29
+*******************************************************************************/
+#include "module.h"
+#include "lauxlib.h"
+#include "platform.h"
+#include "cpu_esp8266.h"
+#include "httpclient.h"
+
+static int http_callback_registry  = LUA_NOREF;
+static lua_State * http_client_L = NULL;
+
+static void http_callback( char * response, int http_status, char * full_response )
+{
+#if defined(HTTPCLIENT_DEBUG_ON)
+  c_printf( "http_status=%d\n", http_status );
+  if ( http_status != HTTP_STATUS_GENERIC_ERROR )
+  {
+    c_printf( "strlen(full_response)=%d\n", strlen( full_response ) );
+    c_printf( "response=%s<EOF>\n", response );
+  }
+#endif
+  if (http_callback_registry != LUA_NOREF)
+  {
+    lua_rawgeti(http_client_L, LUA_REGISTRYINDEX, http_callback_registry);
+
+    lua_pushnumber(http_client_L, http_status);
+    if ( http_status != HTTP_STATUS_GENERIC_ERROR )
+    {
+      lua_pushstring(http_client_L, response);
+    }
+    else
+    {
+      lua_pushnil(http_client_L);
+    }
+    lua_call(http_client_L, 2, 0); // With 2 arguments and 0 result
+
+    luaL_unref(http_client_L, LUA_REGISTRYINDEX, http_callback_registry);
+    http_callback_registry = LUA_NOREF;
+  }
+}
+
+// Lua: http.request( url, method, header, body, function(status, reponse) end )
+static int http_lapi_request( lua_State *L )
+{
+  int length;
+  http_client_L        = L;
+  const char * url     = luaL_checklstring(L, 1, &length);
+  const char * method  = luaL_checklstring(L, 2, &length);
+  const char * headers = NULL;
+  const char * body    = NULL;
+
+  // Check parameter
+  if ((url == NULL) || (method == NULL))
+  {
+    return luaL_error( L, "wrong arg type" );
+  }
+
+  if (lua_isstring(L, 3))
+  {
+    headers = luaL_checklstring(L, 3, &length);
+  }
+  if (lua_isstring(L, 4))
+  {
+    body = luaL_checklstring(L, 4, &length);
+  }
+
+  if (lua_type(L, 5) == LUA_TFUNCTION || lua_type(L, 5) == LUA_TLIGHTFUNCTION) {
+  lua_pushvalue(L, 5);  // copy argument (func) to the top of stack
+    if (http_callback_registry != LUA_NOREF)
+      luaL_unref(L, LUA_REGISTRYINDEX, http_callback_registry);
+    http_callback_registry = luaL_ref(L, LUA_REGISTRYINDEX);
+  }
+
+  http_request(url, method, headers, body, http_callback);
+  return 0;
+}
+
+// Lua: http.post( url, header, body, function(status, reponse) end )
+static int http_lapi_post( lua_State *L )
+{
+  int length;
+  http_client_L        = L;
+  const char * url     = luaL_checklstring(L, 1, &length);
+  const char * headers = NULL;
+  const char * body    = NULL;
+
+  // Check parameter
+  if ((url == NULL))
+  {
+    return luaL_error( L, "wrong arg type" );
+  }
+
+  if (lua_isstring(L, 2))
+  {
+    headers = luaL_checklstring(L, 2, &length);
+  }
+  if (lua_isstring(L, 3))
+  {
+    body = luaL_checklstring(L, 3, &length);
+  }
+
+  if (lua_type(L, 4) == LUA_TFUNCTION || lua_type(L, 4) == LUA_TLIGHTFUNCTION) {
+  lua_pushvalue(L, 4);  // copy argument (func) to the top of stack
+    if (http_callback_registry != LUA_NOREF)
+      luaL_unref(L, LUA_REGISTRYINDEX, http_callback_registry);
+    http_callback_registry = luaL_ref(L, LUA_REGISTRYINDEX);
+  }
+
+  http_post(url, headers, body, http_callback);
+  return 0;
+}
+
+// Lua: http.put( url, header, body, function(status, reponse) end )
+static int http_lapi_put( lua_State *L )
+{
+  int length;
+  http_client_L        = L;
+  const char * url     = luaL_checklstring(L, 1, &length);
+  const char * headers = NULL;
+  const char * body    = NULL;
+
+  // Check parameter
+  if ((url == NULL))
+  {
+    return luaL_error( L, "wrong arg type" );
+  }
+
+  if (lua_isstring(L, 2))
+  {
+    headers = luaL_checklstring(L, 2, &length);
+  }
+  if (lua_isstring(L, 3))
+  {
+    body = luaL_checklstring(L, 3, &length);
+  }
+
+  if (lua_type(L, 4) == LUA_TFUNCTION || lua_type(L, 4) == LUA_TLIGHTFUNCTION) {
+  lua_pushvalue(L, 4);  // copy argument (func) to the top of stack
+    if (http_callback_registry != LUA_NOREF)
+      luaL_unref(L, LUA_REGISTRYINDEX, http_callback_registry);
+    http_callback_registry = luaL_ref(L, LUA_REGISTRYINDEX);
+  }
+
+  http_put(url, headers, body, http_callback);
+  return 0;
+}
+
+// Lua: http.delete( url, header, body, function(status, reponse) end )
+static int http_lapi_delete( lua_State *L )
+{
+  int length;
+  http_client_L        = L;
+  const char * url     = luaL_checklstring(L, 1, &length);
+  const char * headers = NULL;
+  const char * body    = NULL;
+
+  // Check parameter
+  if ((url == NULL))
+  {
+    return luaL_error( L, "wrong arg type" );
+  }
+
+  if (lua_isstring(L, 2))
+  {
+    headers = luaL_checklstring(L, 2, &length);
+  }
+  if (lua_isstring(L, 3))
+  {
+    body = luaL_checklstring(L, 3, &length);
+  }
+
+  if (lua_type(L, 4) == LUA_TFUNCTION || lua_type(L, 4) == LUA_TLIGHTFUNCTION) {
+  lua_pushvalue(L, 4);  // copy argument (func) to the top of stack
+    if (http_callback_registry != LUA_NOREF)
+      luaL_unref(L, LUA_REGISTRYINDEX, http_callback_registry);
+    http_callback_registry = luaL_ref(L, LUA_REGISTRYINDEX);
+  }
+
+  http_delete(url, headers, body, http_callback);
+  return 0;
+}
+
+// Lua: http.get( url, header, function(status, reponse) end )
+static int http_lapi_get( lua_State *L )
+{
+  int length;
+  http_client_L        = L;
+  const char * url     = luaL_checklstring(L, 1, &length);
+  const char * headers = NULL;
+
+  // Check parameter
+  if ((url == NULL))
+  {
+    return luaL_error( L, "wrong arg type" );
+  }
+
+  if (lua_isstring(L, 2))
+  {
+    headers = luaL_checklstring(L, 2, &length);
+  }
+
+  if (lua_type(L, 3) == LUA_TFUNCTION || lua_type(L, 3) == LUA_TLIGHTFUNCTION) {
+  lua_pushvalue(L, 3);  // copy argument (func) to the top of stack
+    if (http_callback_registry != LUA_NOREF)
+      luaL_unref(L, LUA_REGISTRYINDEX, http_callback_registry);
+    http_callback_registry = luaL_ref(L, LUA_REGISTRYINDEX);
+  }
+
+  http_get(url, headers, http_callback);
+  return 0;
+}
+
+// Module function map
+static const LUA_REG_TYPE http_map[] = {
+  { LSTRKEY( "request" ),         LFUNCVAL( http_lapi_request ) },
+  { LSTRKEY( "post" ),            LFUNCVAL( http_lapi_post ) },
+  { LSTRKEY( "put" ),             LFUNCVAL( http_lapi_put ) },
+  { LSTRKEY( "delete" ),          LFUNCVAL( http_lapi_delete ) },
+  { LSTRKEY( "get" ),             LFUNCVAL( http_lapi_get ) },
+
+  { LSTRKEY( "OK" ),              LNUMVAL( 0 ) },
+  { LSTRKEY( "ERROR" ),           LNUMVAL( HTTP_STATUS_GENERIC_ERROR ) },
+  
+  { LNILKEY, LNILVAL }
+};
+
+NODEMCU_MODULE(HTTP, "http", http_map, NULL);