[asterisk-commits] rmudgett: branch rmudgett/http_persistent r417188 - in /team/rmudgett/http_pe...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Tue Jun 24 12:12:17 CDT 2014


Author: rmudgett
Date: Tue Jun 24 12:12:09 2014
New Revision: 417188

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=417188
Log:
Initial persistent HTTP connection support.

Modified:
    team/rmudgett/http_persistent/UPGRADE.txt
    team/rmudgett/http_persistent/configs/http.conf.sample
    team/rmudgett/http_persistent/include/asterisk/http.h
    team/rmudgett/http_persistent/include/asterisk/tcptls.h
    team/rmudgett/http_persistent/main/http.c
    team/rmudgett/http_persistent/main/manager.c
    team/rmudgett/http_persistent/main/tcptls.c
    team/rmudgett/http_persistent/res/res_ari.c
    team/rmudgett/http_persistent/res/res_http_post.c
    team/rmudgett/http_persistent/res/res_http_websocket.c

Modified: team/rmudgett/http_persistent/UPGRADE.txt
URL: http://svnview.digium.com/svn/asterisk/team/rmudgett/http_persistent/UPGRADE.txt?view=diff&rev=417188&r1=417187&r2=417188
==============================================================================
--- team/rmudgett/http_persistent/UPGRADE.txt (original)
+++ team/rmudgett/http_persistent/UPGRADE.txt Tue Jun 24 12:12:09 2014
@@ -21,7 +21,7 @@
 ===
 ===========================================================
 
-From 12.3.0 to 12.4.0:
+From 12.3.2 to 12.4.0:
 
  - The safe_asterisk script was previously not installed on top of an existing
    version. This caused bug-fixes in that script not to be deployed. If your
@@ -34,6 +34,12 @@
    accordingly. The changed strings are:
    - "Exited on signal $EXITSIGNAL" => "Asterisk exited on signal $EXITSIGNAL."
    - "Asterisk Died" => "Asterisk on $MACHINE died (sig $EXITSIGNAL)"
+
+HTTP:
+ - Added support for persistent HTTP connections.  To enable persistent
+   HTTP connections configure the keep alive time between HTTP requests.  The
+   keep alive time between HTTP requests is configured in http.conf with the
+   session_keep_alive parameter.
 
 From 12.3.0 to 12.3.1:
 

Modified: team/rmudgett/http_persistent/configs/http.conf.sample
URL: http://svnview.digium.com/svn/asterisk/team/rmudgett/http_persistent/configs/http.conf.sample?view=diff&rev=417188&r1=417187&r2=417188
==============================================================================
--- team/rmudgett/http_persistent/configs/http.conf.sample (original)
+++ team/rmudgett/http_persistent/configs/http.conf.sample Tue Jun 24 12:12:09 2014
@@ -45,7 +45,13 @@
 ; Default: 30000
 ;session_inactivity=30000
 ;
-; Whether Asterisk should serve static content from http-static
+; session_keep_alive specifies the number of milliseconds to wait for
+; the next HTTP request over a persistent connection.
+;
+; Default: 0  (Disables persistent HTTP connections.)
+;session_keep_alive=15000
+;
+; Whether Asterisk should serve static content from static-http
 ; Default is no.
 ;
 ;enablestatic=yes
@@ -80,6 +86,9 @@
 ;
 ;[post_mappings]
 ;
+; NOTE: You need a valid HTTP AMI mansession_id cookie with the manager
+; config permission to POST files.
+;
 ; In this example, if the prefix option is set to "asterisk", then using the
 ; POST URL: /asterisk/uploads will put files in /var/lib/asterisk/uploads/.
 ;uploads = /var/lib/asterisk/uploads/

Modified: team/rmudgett/http_persistent/include/asterisk/http.h
URL: http://svnview.digium.com/svn/asterisk/team/rmudgett/http_persistent/include/asterisk/http.h?view=diff&rev=417188&r1=417187&r2=417188
==============================================================================
--- team/rmudgett/http_persistent/include/asterisk/http.h (original)
+++ team/rmudgett/http_persistent/include/asterisk/http.h Tue Jun 24 12:12:09 2014
@@ -66,7 +66,8 @@
 
 struct ast_http_uri;
 
-/*! \brief HTTP Callbacks
+/*!
+ * \brief HTTP Callbacks
  *
  * \note The callback function receives server instance, uri, http method,
  * get method (if present in URI), and http headers as arguments and should
@@ -141,26 +142,30 @@
 /*! \brief Unregister all handlers with matching key */
 void ast_http_uri_unlink_all_with_key(const char *key);
 
-/*!\brief Return http method name string
+/*!
+ * \brief Return http method name string
  * \since 1.8
  */
 const char *ast_get_http_method(enum ast_http_method method) attribute_pure;
 
-/*!\brief Return mime type based on extension
+/*!
+ * \brief Return mime type based on extension
  * \param ftype filename extension
  * \return String containing associated MIME type
  * \since 1.8
  */
 const char *ast_http_ftype2mtype(const char *ftype) attribute_pure;
 
-/*!\brief Return manager id, if exist, from request headers
+/*!
+ * \brief Return manager id, if exist, from request headers
  * \param headers List of HTTP headers
  * \return 32-bit associated manager session identifier
  * \since 1.8
  */
 uint32_t ast_http_manid_from_vars(struct ast_variable *headers) attribute_pure;
 
-/*! \brief Generic function for sending http/1.1 response.
+/*!
+ * \brief Generic function for sending HTTP/1.1 response.
  * \param ser TCP/TLS session object
  * \param method GET/POST/HEAD
  * \param status_code HTTP response code (200/401/403/404/500)
@@ -186,12 +191,14 @@
  *
  * \since 1.8
  */
-void ast_http_send(struct ast_tcptls_session_instance *ser, enum ast_http_method method, int status_code, const char *status_title, struct ast_str *http_header, struct ast_str *out, const int fd, unsigned int static_content);
-
-/*!\brief Send http "401 Unauthorized" response and close socket */
+void ast_http_send(struct ast_tcptls_session_instance *ser, enum ast_http_method method,
+	int status_code, const char *status_title, struct ast_str *http_header,
+	struct ast_str *out, const int fd, unsigned int static_content);
+
+/*! \brief Send http "401 Unauthorized" response and close socket */
 void ast_http_auth(struct ast_tcptls_session_instance *ser, const char *realm, const unsigned long nonce, const unsigned long opaque, int stale, const char *text);
 
-/*!\brief Send HTTP error message and close socket */
+/*! \brief Send HTTP error message and close socket */
 void ast_http_error(struct ast_tcptls_session_instance *ser, int status, const char *title, const char *text);
 
 /*!
@@ -203,7 +210,8 @@
 void ast_http_prefix(char *buf, int len);
 
 
-/*!\brief Get post variables from client Request Entity-Body, if content type is application/x-www-form-urlencoded.
+/*!
+ * \brief Get post variables from client Request Entity-Body, if content type is application/x-www-form-urlencoded.
  * \param ser TCP/TLS session object
  * \param headers List of HTTP headers
  * \return List of variables within the POST body
@@ -214,7 +222,8 @@
 
 struct ast_json;
 
-/*!\brief Get JSON from client Request Entity-Body, if content type is
+/*!
+ * \brief Get JSON from client Request Entity-Body, if content type is
  *        application/json.
  * \param ser TCP/TLS session object
  * \param headers List of HTTP headers

Modified: team/rmudgett/http_persistent/include/asterisk/tcptls.h
URL: http://svnview.digium.com/svn/asterisk/team/rmudgett/http_persistent/include/asterisk/tcptls.h?view=diff&rev=417188&r1=417187&r2=417188
==============================================================================
--- team/rmudgett/http_persistent/include/asterisk/tcptls.h (original)
+++ team/rmudgett/http_persistent/include/asterisk/tcptls.h Tue Jun 24 12:12:09 2014
@@ -210,7 +210,6 @@
 	FILE *f;    /*!< fopen/funopen result */
 	int fd;     /*!< the socket returned by accept() */
 	SSL *ssl;   /*!< ssl state */
-/*	iint (*ssl_setup)(SSL *); */
 	int client;
 	struct ast_sockaddr remote_address;
 	struct ast_tcptls_session_args *parent;
@@ -222,6 +221,8 @@
 	struct ast_str *overflow_buf;
 	/*! ao2 FILE stream cookie object associated with f. */
 	struct ast_tcptls_stream *stream_cookie;
+	/*! ao2 object private data of parent->worker_fn */
+	void *private_data;
 };
 
 #if defined(HAVE_FUNOPEN)

Modified: team/rmudgett/http_persistent/main/http.c
URL: http://svnview.digium.com/svn/asterisk/team/rmudgett/http_persistent/main/http.c?view=diff&rev=417188&r1=417187&r2=417188
==============================================================================
--- team/rmudgett/http_persistent/main/http.c (original)
+++ team/rmudgett/http_persistent/main/http.c Tue Jun 24 12:12:09 2014
@@ -71,7 +71,12 @@
 #define DEFAULT_PORT 8088
 #define DEFAULT_TLS_PORT 8089
 #define DEFAULT_SESSION_LIMIT 100
-#define DEFAULT_SESSION_INACTIVITY 30000	/* (ms) Idle time waiting for data. */
+/*! (ms) Idle time waiting for data. */
+#define DEFAULT_SESSION_INACTIVITY 30000
+/*! (ms) Min timeout for initial HTTP request to start coming in. */
+#define MIN_INITIAL_REQUEST_TIMEOUT	10000
+/*! (ms) Idle time between HTTP requests */
+#define DEFAULT_SESSION_KEEP_ALIVE 0
 
 /* See http.h for more information about the SSL implementation */
 #if defined(HAVE_OPENSSL) && (defined(HAVE_FUNOPEN) || defined(HAVE_FOPENCOOKIE))
@@ -80,6 +85,7 @@
 
 static int session_limit = DEFAULT_SESSION_LIMIT;
 static int session_inactivity = DEFAULT_SESSION_INACTIVITY;
+static int session_keep_alive = DEFAULT_SESSION_KEEP_ALIVE;
 static int session_count = 0;
 
 static struct ast_tls_config http_tls_cfg;
@@ -300,7 +306,9 @@
 		}
 	}
 
-	if ( (http_header = ast_str_create(255)) == NULL) {
+	http_header = ast_str_create(255);
+	if (!http_header) {
+		ast_http_error(ser, 500, "Server Error", "Out of memory");
 		close(fd);
 		return -1;
 	}
@@ -343,7 +351,9 @@
 		return -1;
 	}
 
-	if ( (out = ast_str_create(512)) == NULL) {
+	out = ast_str_create(512);
+	if (!out) {
+		ast_http_error(ser, 500, "Server Error", "Out of memory");
 		return -1;
 	}
 
@@ -397,9 +407,16 @@
 	.key= __FILE__,
 };
 
-
-/* send http/1.1 response */
-/* free content variable and close socket*/
+/*! HTTP tcptls worker_fn private data. */
+struct http_worker_private_data {
+	/*! TRUE if the HTTP request wants the connection closed when completed. */
+	unsigned int wants_closed:1;
+	/*! TRUE if the HTTP request has a body. */
+	unsigned int has_body:1;
+	/*! TRUE if the HTTP request body has been read. */
+	unsigned int body_read:1;
+};
+
 void ast_http_send(struct ast_tcptls_session_instance *ser,
 	enum ast_http_method method, int status_code, const char *status_title,
 	struct ast_str *http_header, struct ast_str *out, const int fd,
@@ -409,9 +426,35 @@
 	struct ast_tm tm;
 	char timebuf[80];
 	int content_length = 0;
-
-	if (!ser || 0 == ser->f) {
+	int close_connection;
+
+	if (!ser || !ser->f) {
+		/* The connection is not open. */
+		ast_free(http_header);
+		ast_free(out);
 		return;
+	}
+
+/* BUGBUG Relax close on non 2xx status codes? */
+	/*
+	 * XXX We may be able to relax the closing of the connection
+	 * if the status_code is other than 2xx because we are keeping
+	 * track of whether the request body is read or not.  The URI
+	 * handler functions would not need to return -1 for those cases.
+	 */
+	if (session_keep_alive <= 0 || status_code < 200 || 299 < status_code) {
+		close_connection = 1;
+	} else {
+		struct http_worker_private_data *request;
+
+		request = ser->private_data;
+		if (!request
+			|| request->wants_closed
+			|| (request->has_body && !request->body_read)) {
+			close_connection = 1;
+		} else {
+			close_connection = 0;
+		}
 	}
 
 	ast_strftime(timebuf, sizeof(timebuf), "%a, %d %b %Y %H:%M:%S GMT", ast_localtime(&now, &tm, "GMT"));
@@ -427,10 +470,11 @@
 	}
 
 	/* send http header */
-	fprintf(ser->f, "HTTP/1.1 %d %s\r\n"
+	fprintf(ser->f,
+		"HTTP/1.1 %d %s\r\n"
 		"Server: Asterisk/%s\r\n"
 		"Date: %s\r\n"
-		"Connection: close\r\n"
+		"%s"
 		"%s"
 		"Content-Length: %d\r\n"
 		"%s"
@@ -438,6 +482,7 @@
 		status_code, status_title ? status_title : "OK",
 		ast_get_version(),
 		timebuf,
+		close_connection ? "Connection: close\r\n" : "",
 		static_content ? "" : "Cache-Control: no-cache, no-store\r\n",
 		content_length,
 		http_header ? ast_str_buffer(http_header) : ""
@@ -448,33 +493,32 @@
 		if (out && ast_str_strlen(out)) {
 			if (fwrite(ast_str_buffer(out), ast_str_strlen(out), 1, ser->f) != 1) {
 				ast_log(LOG_ERROR, "fwrite() failed: %s\n", strerror(errno));
+				close_connection = 1;
 			}
 		}
 
 		if (fd) {
 			char buf[256];
 			int len;
+
 			while ((len = read(fd, buf, sizeof(buf))) > 0) {
 				if (fwrite(buf, len, 1, ser->f) != 1) {
 					ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
+					close_connection = 1;
 					break;
 				}
 			}
 		}
 	}
 
-	if (http_header) {
-		ast_free(http_header);
-	}
-	if (out) {
-		ast_free(out);
-	}
-
-	ast_tcptls_close_session_file(ser);
-	return;
-}
-
-/* Send http "401 Unauthorized" responce and close socket*/
+	ast_free(http_header);
+	ast_free(out);
+
+	if (close_connection) {
+		ast_tcptls_close_session_file(ser);
+	}
+}
+
 void ast_http_auth(struct ast_tcptls_session_instance *ser, const char *realm,
 	const unsigned long nonce, const unsigned long opaque, int stale,
 	const char *text)
@@ -485,6 +529,9 @@
 	if (!http_headers || !out) {
 		ast_free(http_headers);
 		ast_free(out);
+		if (ser && ser->f) {
+			ast_tcptls_close_session_file(ser);
+		}
 		return;
 	}
 
@@ -509,10 +556,8 @@
 		text ? text : "");
 
 	ast_http_send(ser, AST_HTTP_UNKNOWN, 401, "Unauthorized", http_headers, out, 0, 0);
-	return;
-}
-
-/* send http error response and close socket*/
+}
+
 void ast_http_error(struct ast_tcptls_session_instance *ser, int status_code, const char *status_title, const char *text)
 {
 	struct ast_str *http_headers = ast_str_create(40);
@@ -521,6 +566,9 @@
 	if (!http_headers || !out) {
 		ast_free(http_headers);
 		ast_free(out);
+		if (ser && ser->f) {
+			ast_tcptls_close_session_file(ser);
+		}
 		return;
 	}
 
@@ -536,14 +584,13 @@
 		"<hr />\r\n"
 		"<address>Asterisk Server</address>\r\n"
 		"</body></html>\r\n",
-			status_code, status_title, status_title, text);
+		status_code, status_title, status_title, text);
 
 	ast_http_send(ser, AST_HTTP_UNKNOWN, status_code, status_title, http_headers, out, 0, 0);
-	return;
-}
-
-/*! \brief
- * Link the new uri into the list.
+}
+
+/*!
+ * \brief Link the new uri into the list.
  *
  * They are sorted by length of
  * the string, not alphabetically. Duplicate entries are not replaced,
@@ -617,8 +664,7 @@
  * \return Associated header value.
  * \return \c NULL if header is not present.
  */
-static const char *get_header(struct ast_variable *headers,
-	const char *field_name)
+static const char *get_header(struct ast_variable *headers, const char *field_name)
 {
 	struct ast_variable *v;
 
@@ -706,13 +752,21 @@
 		return -1;
 	}
 
-	while (len--)
-	{
-		if (*s == '\x0D') {
+	while (len--) {
+		c = *s++;
+		if (c == '\x0D') {
 			return value;
 		}
+		if (c == ';') {
+			/* We have a chunk-extension that we don't care about. */
+			while (len--) {
+				if (*s++ == '\x0D') {
+					return value;
+				}
+			}
+			break;
+		}
 		value <<= 4;
-		c = *s++;
 		if (c >= '0' && c <= '9') {
 			value += c - '0';
 			continue;
@@ -730,6 +784,28 @@
 	}
 	/* end of string */
 	return -1;
+}
+
+/*!
+ * \internal
+ * \brief Determine if the HTTP request has a body.
+ *
+ * \param headers List of HTTP headers
+ *
+ * \return TRUE if the request has a body.
+ */
+static int http_has_request_body(struct ast_variable *headers)
+{
+	const char *transfer_encoding;
+
+	transfer_encoding = get_transfer_encoding(headers);
+	if (ast_strlen_zero(transfer_encoding)
+		|| strcasecmp(transfer_encoding, "chunked") != 0) {
+		return 0 < get_content_length(headers);
+	}
+
+	/* Has a chunked body. */
+	return 1;
 }
 
 /*!
@@ -744,14 +820,19 @@
 static char *ast_http_get_contents(int *return_length,
 	struct ast_tcptls_session_instance *ser, struct ast_variable *headers)
 {
+	struct http_worker_private_data *request;
 	const char *transfer_encoding;
 	int res;
 	int content_length = 0;
 	int chunk_length;
-	char chunk_header[8];
+	char header_line[4096];
 	int bufsize = 250;
 	char *buf;
 
+	/* Use errno to distinguish errors from no body */
+	errno = 0;
+
+	request = ser->private_data;
 	transfer_encoding = get_transfer_encoding(headers);
 
 	if (ast_strlen_zero(transfer_encoding) ||
@@ -760,6 +841,7 @@
 		content_length = get_content_length(headers);
 		if (content_length <= 0) {
 			/* no content - not an error */
+			request->body_read = 1;
 			return NULL;
 		}
 		if (content_length > MAX_POST_CONTENT - 1) {
@@ -784,6 +866,8 @@
 			ast_free(buf);
 			return NULL;
 		}
+
+		request->body_read = 1;
 		buf[content_length] = 0;
 		*return_length = content_length;
 		return buf;
@@ -795,22 +879,26 @@
 		return NULL;
 	}
 
-	/* handled chunked content */
-	do {
-		/* get the line of hexadecimal giving chunk size */
-		if (!fgets(chunk_header, sizeof(chunk_header), ser->f)) {
+	/* parse chunked-body */
+	for (;;) {
+		/* get the line of hexadecimal giving chunk-size w/ optional chunk-extension */
+		if (!fgets(header_line, sizeof(header_line), ser->f)) {
 			ast_log(LOG_WARNING,
 				"Short HTTP read of chunked header\n");
 			errno = EIO;
 			ast_free(buf);
 			return NULL;
 		}
-		chunk_length = chunked_atoh(chunk_header, sizeof(chunk_header));
+		chunk_length = chunked_atoh(header_line, sizeof(header_line));
 		if (chunk_length < 0) {
 			ast_log(LOG_WARNING, "Invalid HTTP chunk size\n");
 			errno = EIO;
 			ast_free(buf);
 			return NULL;
+		}
+		if (chunk_length == 0) {
+			/* parsed last-chunk */
+			break;
 		}
 		if (content_length + chunk_length > MAX_POST_CONTENT - 1) {
 			ast_log(LOG_WARNING,
@@ -822,8 +910,7 @@
 		}
 
 		/* insure buffer is large enough +1 */
-		if (content_length + chunk_length >= bufsize)
-		{
+		if (content_length + chunk_length >= bufsize) {
 			bufsize *= 2;
 			buf = ast_realloc(buf, bufsize);
 			if (!buf) {
@@ -831,7 +918,7 @@
 			}
 		}
 
-		/* read the chunk */
+		/* read the chunk-data */
 		res = fread(buf + content_length, 1, chunk_length, ser->f);
 		if (res < chunk_length) {
 			ast_log(LOG_WARNING, "Short HTTP chunk read (%d < %d)\n",
@@ -843,7 +930,7 @@
 		content_length += chunk_length;
 
 		/* insure the next 2 bytes are CRLF */
-		res = fread(chunk_header, 1, 2, ser->f);
+		res = fread(header_line, 1, 2, ser->f);
 		if (res < 2) {
 			ast_log(LOG_WARNING,
 				"Short HTTP chunk sync read (%d < 2)\n", res);
@@ -851,16 +938,41 @@
 			ast_free(buf);
 			return NULL;
 		}
-		if (chunk_header[0] != 0x0D || chunk_header[1] != 0x0A) {
+		if (header_line[0] != 0x0D || header_line[1] != 0x0A) {
 			ast_log(LOG_WARNING,
 				"Post HTTP chunk sync bytes wrong (%d, %d)\n",
-				chunk_header[0], chunk_header[1]);
+				header_line[0], header_line[1]);
 			errno = EIO;
 			ast_free(buf);
 			return NULL;
 		}
-	} while (chunk_length);
-
+	}
+
+	/*
+	 * Read and discard any trailer entity-header lines
+	 * which we don't care about.
+	 *
+	 * XXX In the future we may need to add the trailer headers
+	 * to the passed in headers list rather than discarding them.
+	 */
+	for (;;) {
+		if (!fgets(header_line, sizeof(header_line), ser->f) || feof(ser->f)) {
+			ast_log(LOG_WARNING,
+				"Short HTTP read of chunked trailer header\n");
+			errno = EIO;
+			ast_free(buf);
+			return NULL;
+		}
+
+		/* Trim trailing whitespace */
+		ast_trim_blanks(header_line);
+		if (ast_strlen_zero(header_line)) {
+			/* A blank line ends the chunked-body */
+			break;
+		}
+	}
+
+	request->body_read = 1;
 	buf[content_length] = 0;
 	*return_length = content_length;
 	return buf;
@@ -874,27 +986,21 @@
 	RAII_VAR(char *, buf, NULL, ast_free);
 	RAII_VAR(char *, type, get_content_type(headers), ast_free);
 
-	/* Use errno to distinguish errors from no body */
-	errno = 0;
-
-	if (ast_strlen_zero(type) || strcasecmp(type, "application/json")) {
-		/* Content type is not JSON */
+	buf = ast_http_get_contents(&content_length, ser, headers);
+	if (!buf
+		|| !content_length
+		|| ast_strlen_zero(type)
+		|| strcasecmp(type, "application/json")) {
+		/*
+		 * errno already set
+		 * or it is not an error to have zero content
+		 * or content type is not JSON
+		 */
 		return NULL;
 	}
 
-	buf = ast_http_get_contents(&content_length, ser, headers);
-	if (buf == NULL) {
-		/* errno already set */
-		return NULL;
-	}
-
-	if (!content_length) {
-		/* it is not an error to have zero content */
-		return NULL;
-	}
-
 	body = ast_json_load_buf(buf, content_length, NULL);
-	if (body == NULL) {
+	if (!body) {
 		/* Failed to parse JSON; treat as an I/O error */
 		errno = EIO;
 		return NULL;
@@ -913,20 +1019,19 @@
 	int content_length = 0;
 	struct ast_variable *v, *post_vars=NULL, *prev = NULL;
 	char *var, *val;
-	RAII_VAR(char *, buf, NULL, ast_free_ptr);
+	RAII_VAR(char *, buf, NULL, ast_free);
 	RAII_VAR(char *, type, get_content_type(headers), ast_free);
 
-	/* Use errno to distinguish errors from no params */
-	errno = 0;
-
-	if (ast_strlen_zero(type) ||
-	    strcasecmp(type, "application/x-www-form-urlencoded")) {
-		/* Content type is not form data */
-		return NULL;
-	}
-
 	buf = ast_http_get_contents(&content_length, ser, headers);
-	if (buf == NULL) {
+	if (!buf
+		|| !content_length
+		|| ast_strlen_zero(type)
+		|| strcasecmp(type, "application/x-www-form-urlencoded")) {
+		/*
+		 * errno already set
+		 * or it is not an error to have zero content
+		 * or content type is not form data
+		 */
 		return NULL;
 	}
 
@@ -992,9 +1097,13 @@
 	AST_RWLIST_TRAVERSE(&uri_redirects, redirect, entry) {
 		if (!strcasecmp(uri, redirect->target)) {
 			struct ast_str *http_header = ast_str_create(128);
+
+			if (!http_header) {
+				ast_http_error(ser, 500, "Server Error", "Out of memory");
+				break;
+			}
 			ast_str_set(&http_header, 0, "Location: %s\r\n", redirect->dest);
 			ast_http_send(ser, method, 302, "Moved Temporarily", http_header, NULL, 0, 0);
-
 			break;
 		}
 	}
@@ -1135,10 +1244,9 @@
 	return cookies;
 }
 
-static struct ast_http_auth *auth_create(const char *userid,
-	const char *password)
-{
-	RAII_VAR(struct ast_http_auth *, auth, NULL, ao2_cleanup);
+static struct ast_http_auth *auth_create(const char *userid, const char *password)
+{
+	struct ast_http_auth *auth;
 	size_t userid_len;
 	size_t password_len;
 
@@ -1164,7 +1272,6 @@
 	auth->password = auth->userid + userid_len;
 	strcpy(auth->password, password);
 
-	ao2_ref(auth, +1);
 	return auth;
 }
 
@@ -1228,55 +1335,42 @@
 	return NULL;
 }
 
+static int http_check_connection_close(struct ast_variable *headers)
+{
+	const char *connection = get_header(headers, "Connection");
+	int close_connection = 0;
+
+	if (connection && !strcasecmp(connection, "close")) {
+		close_connection = -1;
+	}
+	return close_connection;
+}
+
 /*! Limit the number of request headers in case the sender is being ridiculous. */
 #define MAX_HTTP_REQUEST_HEADERS	100
 
-static void *httpd_helper_thread(void *data)
-{
+static int httpd_process_request(struct ast_tcptls_session_instance *ser)
+{
+	RAII_VAR(struct ast_variable *, headers, NULL, ast_variables_destroy);
+	struct ast_variable *tail = headers;
+	char *uri;
+	char *method;
+	const char *transfer_encoding;
+	struct http_worker_private_data *request;
+	enum ast_http_method http_method = AST_HTTP_UNKNOWN;
+	int remaining_headers;
+	int res;
 	char buf[4096];
 	char header_line[4096];
-	struct ast_tcptls_session_instance *ser = data;
-	struct ast_variable *headers = NULL;
-	struct ast_variable *tail = headers;
-	char *uri, *method;
-	enum ast_http_method http_method = AST_HTTP_UNKNOWN;
-	const char *transfer_encoding;
-	int remaining_headers;
-	int flags;
-	struct protoent *p;
-
-	if (ast_atomic_fetchadd_int(&session_count, +1) >= session_limit) {
-		goto done;
-	}
-
-	/* here we set TCP_NODELAY on the socket to disable Nagle's algorithm.
-	 * This is necessary to prevent delays (caused by buffering) as we
-	 * write to the socket in bits and pieces. */
-	p = getprotobyname("tcp");
-	if (p) {
-		int arg = 1;
-		if( setsockopt(ser->fd, p->p_proto, TCP_NODELAY, (char *)&arg, sizeof(arg) ) < 0 ) {
-			ast_log(LOG_WARNING, "Failed to set TCP_NODELAY on HTTP connection: %s\n", strerror(errno));
-			ast_log(LOG_WARNING, "Some HTTP requests may be slow to respond.\n");
-		}
-	} else {
-		ast_log(LOG_WARNING, "Failed to set TCP_NODELAY on HTTP connection, getprotobyname(\"tcp\") failed\n");
-		ast_log(LOG_WARNING, "Some HTTP requests may be slow to respond.\n");
-	}
-
-	/* make sure socket is non-blocking */
-	flags = fcntl(ser->fd, F_GETFL);
-	flags |= O_NONBLOCK;
-	fcntl(ser->fd, F_SETFL, flags);
-
-	/* We can let the stream wait for data to arrive. */
-	ast_tcptls_stream_set_exclusive_input(ser->stream_cookie, 1);
-
-	ast_tcptls_stream_set_timeout_inactivity(ser->stream_cookie, session_inactivity);
-
-	if (!fgets(buf, sizeof(buf), ser->f) || feof(ser->f)) {
-		goto done;
-	}
+
+	if (!fgets(buf, sizeof(buf), ser->f)) {
+		return -1;
+	}
+
+	/* Initialize the request body flags. */
+	request = ser->private_data;
+	request->has_body = 1;/* For safety, assume the request has a body. */
+	request->body_read = 0;
 
 	/* Get method */
 	method = ast_skip_blanks(buf);
@@ -1300,7 +1394,6 @@
 	}
 
 	uri = ast_skip_blanks(uri);	/* Skip white space */
-
 	if (*uri) {			/* terminate at the first blank */
 		char *c = ast_skip_nonblanks(uri);
 
@@ -1309,7 +1402,7 @@
 		}
 	} else {
 		ast_http_error(ser, 400, "Bad Request", "Invalid Request");
-		goto done;
+		return -1;
 	}
 
 	/* process "Request Headers" lines */
@@ -1318,9 +1411,9 @@
 		char *name;
 		char *value;
 
-		if (!fgets(header_line, sizeof(header_line), ser->f) || feof(ser->f)) {
+		if (!fgets(header_line, sizeof(header_line), ser->f)) {
 			ast_http_error(ser, 400, "Bad Request", "Timeout");
-			goto done;
+			return -1;
 		}
 
 		/* Trim trailing characters */
@@ -1346,7 +1439,7 @@
 		if (!remaining_headers--) {
 			/* Too many headers. */
 			ast_http_error(ser, 413, "Request Entity Too Large", "Too many headers");
-			goto done;
+			return -1;
 		}
 		if (!headers) {
 			headers = ast_variable_new(name, value, __FILE__);
@@ -1364,7 +1457,7 @@
 			headers = NULL;
 
 			ast_http_error(ser, 500, "Server Error", "Out of memory");
-			goto done;
+			return -1;
 		}
 	}
 
@@ -1382,22 +1475,117 @@
 		strcasecmp(transfer_encoding, "chunked") != 0) {
 		/* Transfer encodings not supported */
 		ast_http_error(ser, 501, "Unimplemented", "Unsupported Transfer-Encoding.");
+		return -1;
+	}
+
+	res = http_check_connection_close(headers);
+	if (res) {
+		request->wants_closed = 1;
+	}
+	request->has_body = http_has_request_body(headers);
+
+	if (handle_uri(ser, uri, http_method, headers)) {
+		res = -1;
+	}
+	return res;
+}
+
+static void *httpd_helper_thread(void *data)
+{
+	struct ast_tcptls_session_instance *ser = data;
+	struct protoent *p;
+	int flags;
+	int timeout;
+
+	if (!ser || !ser->f) {
+		ao2_cleanup(ser);
+		return NULL;
+	}
+
+	if (ast_atomic_fetchadd_int(&session_count, +1) >= session_limit) {
+		ast_log(LOG_WARNING, "HTTP session count exceeded %d sessions.\n",
+			session_limit);
 		goto done;
 	}
 
-	handle_uri(ser, uri, http_method, headers);
+	/*
+	 * Here we set TCP_NODELAY on the socket to disable Nagle's algorithm.
+	 * This is necessary to prevent delays (caused by buffering) as we
+	 * write to the socket in bits and pieces.
+	 */
+	p = getprotobyname("tcp");
+	if (p) {
+		int arg = 1;
+
+		if (setsockopt(ser->fd, p->p_proto, TCP_NODELAY, (char *) &arg, sizeof(arg) ) < 0) {
+			ast_log(LOG_WARNING, "Failed to set TCP_NODELAY on HTTP connection: %s\n", strerror(errno));
+			ast_log(LOG_WARNING, "Some HTTP requests may be slow to respond.\n");
+		}
+	} else {
+		ast_log(LOG_WARNING, "Failed to set TCP_NODELAY on HTTP connection, getprotobyname(\"tcp\") failed\n");
+		ast_log(LOG_WARNING, "Some HTTP requests may be slow to respond.\n");
+	}
+
+	/* make sure socket is non-blocking */
+	flags = fcntl(ser->fd, F_GETFL);
+	flags |= O_NONBLOCK;
+	fcntl(ser->fd, F_SETFL, flags);
+
+	/* Setup HTTP worker private data to keep track of request body reading. */
+	ao2_cleanup(ser->private_data);
+	ser->private_data = ao2_alloc_options(sizeof(struct http_worker_private_data), NULL,
+		AO2_ALLOC_OPT_LOCK_NOLOCK);
+	if (!ser->private_data) {
+		ast_http_error(ser, 500, "Server Error", "Out of memory");
+		goto done;
+	}
+
+	/* Determine initial HTTP request wait timeout. */
+	timeout = session_keep_alive;
+	if (timeout <= 0) {
+		/* Persistent connections not enabled. */
+		timeout = session_inactivity;
+	}
+	if (timeout < MIN_INITIAL_REQUEST_TIMEOUT) {
+		timeout = MIN_INITIAL_REQUEST_TIMEOUT;
+	}
+
+	/* We can let the stream wait for data to arrive. */
+	ast_tcptls_stream_set_exclusive_input(ser->stream_cookie, 1);
+
+	for (;;) {
+		int ch;
+
+		/* Wait for next potential HTTP request message. */
+		ast_tcptls_stream_set_timeout_inactivity(ser->stream_cookie, timeout);
+		ch = fgetc(ser->f);
+		if (ch == EOF || ungetc(ch, ser->f) == EOF) {
+			/* Between request idle timeout */
+			ast_debug(1, "HTTP idle timeout closing session\n");
+			ast_log(LOG_NOTICE, "BUGBUG HTTP idle timeout closing session\n");
+			break;
+		}
+
+		ast_tcptls_stream_set_timeout_inactivity(ser->stream_cookie, session_inactivity);
+		if (httpd_process_request(ser) || !ser->f || feof(ser->f)) {
+			/* Break the connection or the connection closed */
+			break;
+		}
+
+		timeout = session_keep_alive;
+		if (timeout <= 0) {
+			/* Persistent connections not enabled. */
+			break;
+		}
+	}
 
 done:
 	ast_atomic_fetchadd_int(&session_count, -1);
 
-	/* clean up all the header information */
-	ast_variables_destroy(headers);
-
 	if (ser->f) {
 		ast_tcptls_close_session_file(ser);
 	}
 	ao2_ref(ser, -1);
-	ser = NULL;
 	return NULL;
 }
 
@@ -1474,7 +1662,7 @@
 	int http_tls_was_enabled = 0;
 
 	cfg = ast_config_load2("http.conf", "http", config_flags);
-	if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
+	if (!cfg || cfg == CONFIG_STATUS_FILEUNCHANGED || cfg == CONFIG_STATUS_FILEINVALID) {
 		return 0;
 	}
 
@@ -1506,65 +1694,72 @@
 
 	session_limit = DEFAULT_SESSION_LIMIT;
 	session_inactivity = DEFAULT_SESSION_INACTIVITY;
-
-	if (cfg) {
-		v = ast_variable_browse(cfg, "general");
-		for (; v; v = v->next) {
-
-			/* read tls config options while preventing unsupported options from being set */
-			if (strcasecmp(v->name, "tlscafile")
-				&& strcasecmp(v->name, "tlscapath")
-				&& strcasecmp(v->name, "tlscadir")
-				&& strcasecmp(v->name, "tlsverifyclient")
-				&& strcasecmp(v->name, "tlsdontverifyserver")
-				&& strcasecmp(v->name, "tlsclientmethod")
-				&& strcasecmp(v->name, "sslclientmethod")
-				&& strcasecmp(v->name, "tlscipher")
-				&& strcasecmp(v->name, "sslcipher")
-				&& !ast_tls_read_conf(&http_tls_cfg, &https_desc, v->name, v->value)) {
-				continue;
-			}
-
-			if (!strcasecmp(v->name, "enabled")) {
-				enabled = ast_true(v->value);
-			} else if (!strcasecmp(v->name, "enablestatic")) {
-				newenablestatic = ast_true(v->value);
-			} else if (!strcasecmp(v->name, "bindport")) {
-				if (ast_parse_arg(v->value, PARSE_UINT32 | PARSE_IN_RANGE | PARSE_DEFAULT, &bindport, DEFAULT_PORT, 0, 65535)) {
-					ast_log(LOG_WARNING, "Invalid port %s specified. Using default port %"PRId32, v->value, DEFAULT_PORT);
-				}
-			} else if (!strcasecmp(v->name, "bindaddr")) {
-				if (!(num_addrs = ast_sockaddr_resolve(&addrs, v->value, 0, AST_AF_UNSPEC))) {
-					ast_log(LOG_WARNING, "Invalid bind address %s\n", v->value);
-				}
-			} else if (!strcasecmp(v->name, "prefix")) {
-				if (!ast_strlen_zero(v->value)) {
-					newprefix[0] = '/';
-					ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
-				} else {
-					newprefix[0] = '\0';
-				}
-			} else if (!strcasecmp(v->name, "redirect")) {
-				add_redirect(v->value);
-			} else if (!strcasecmp(v->name, "sessionlimit")) {
-				if (ast_parse_arg(v->value, PARSE_INT32|PARSE_DEFAULT|PARSE_IN_RANGE,
-							&session_limit, DEFAULT_SESSION_LIMIT, 1, INT_MAX)) {
-					ast_log(LOG_WARNING, "Invalid %s '%s' at line %d of http.conf\n",
-							v->name, v->value, v->lineno);
-				}
-			} else if (!strcasecmp(v->name, "session_inactivity")) {
-				if (ast_parse_arg(v->value, PARSE_INT32 |PARSE_DEFAULT | PARSE_IN_RANGE,
-					&session_inactivity, DEFAULT_SESSION_INACTIVITY, 1, INT_MAX)) {
-					ast_log(LOG_WARNING, "Invalid %s '%s' at line %d of http.conf\n",
-						v->name, v->value, v->lineno);
-				}
+	session_keep_alive = DEFAULT_SESSION_KEEP_ALIVE;
+
+	v = ast_variable_browse(cfg, "general");
+	for (; v; v = v->next) {
+		/* read tls config options while preventing unsupported options from being set */
+		if (strcasecmp(v->name, "tlscafile")
+			&& strcasecmp(v->name, "tlscapath")
+			&& strcasecmp(v->name, "tlscadir")
+			&& strcasecmp(v->name, "tlsverifyclient")
+			&& strcasecmp(v->name, "tlsdontverifyserver")
+			&& strcasecmp(v->name, "tlsclientmethod")
+			&& strcasecmp(v->name, "sslclientmethod")
+			&& strcasecmp(v->name, "tlscipher")
+			&& strcasecmp(v->name, "sslcipher")
+			&& !ast_tls_read_conf(&http_tls_cfg, &https_desc, v->name, v->value)) {
+			continue;
+		}
+
+		if (!strcasecmp(v->name, "enabled")) {
+			enabled = ast_true(v->value);
+		} else if (!strcasecmp(v->name, "enablestatic")) {
+			newenablestatic = ast_true(v->value);
+		} else if (!strcasecmp(v->name, "bindport")) {
+			if (ast_parse_arg(v->value, PARSE_UINT32 | PARSE_IN_RANGE | PARSE_DEFAULT,
+				&bindport, DEFAULT_PORT, 0, 65535)) {
+				ast_log(LOG_WARNING, "Invalid port %s specified. Using default port %" PRId32 "\n",
+					v->value, DEFAULT_PORT);
+			}
+		} else if (!strcasecmp(v->name, "bindaddr")) {
+			if (!(num_addrs = ast_sockaddr_resolve(&addrs, v->value, 0, AST_AF_UNSPEC))) {
+				ast_log(LOG_WARNING, "Invalid bind address %s\n", v->value);
+			}
+		} else if (!strcasecmp(v->name, "prefix")) {
+			if (!ast_strlen_zero(v->value)) {
+				newprefix[0] = '/';
+				ast_copy_string(newprefix + 1, v->value, sizeof(newprefix) - 1);
 			} else {
-				ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
-			}
-		}
-
-		ast_config_destroy(cfg);
-	}
+				newprefix[0] = '\0';
+			}
+		} else if (!strcasecmp(v->name, "redirect")) {
+			add_redirect(v->value);
+		} else if (!strcasecmp(v->name, "sessionlimit")) {
+			if (ast_parse_arg(v->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE,
+				&session_limit, DEFAULT_SESSION_LIMIT, 1, INT_MAX)) {
+				ast_log(LOG_WARNING, "Invalid %s '%s' at line %d of http.conf\n",
+					v->name, v->value, v->lineno);
+			}
+		} else if (!strcasecmp(v->name, "session_inactivity")) {
+			if (ast_parse_arg(v->value, PARSE_INT32 | PARSE_DEFAULT | PARSE_IN_RANGE,
+				&session_inactivity, DEFAULT_SESSION_INACTIVITY, 1, INT_MAX)) {
+				ast_log(LOG_WARNING, "Invalid %s '%s' at line %d of http.conf\n",
+					v->name, v->value, v->lineno);
+			}
+		} else if (!strcasecmp(v->name, "session_keep_alive")) {
+			if (sscanf(v->value, "%30d", &session_keep_alive) != 1
+				|| session_keep_alive < 0) {
+				session_keep_alive = DEFAULT_SESSION_KEEP_ALIVE;
+				ast_log(LOG_WARNING, "Invalid %s '%s' at line %d of http.conf\n",
+					v->name, v->value, v->lineno);
+			}
+		} else {
+			ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
+		}
+	}
+
+	ast_config_destroy(cfg);
 
 	if (strcmp(prefix, newprefix)) {
 		ast_copy_string(prefix, newprefix, sizeof(prefix));

Modified: team/rmudgett/http_persistent/main/manager.c
URL: http://svnview.digium.com/svn/asterisk/team/rmudgett/http_persistent/main/manager.c?view=diff&rev=417188&r1=417187&r2=417188
==============================================================================
--- team/rmudgett/http_persistent/main/manager.c (original)
+++ team/rmudgett/http_persistent/main/manager.c Tue Jun 24 12:12:09 2014
@@ -6820,9 +6820,11 @@
 {
 	struct mansession s = { .session = NULL, .tcptls_session = ser };
 	struct mansession_session *session = NULL;
-	uint32_t ident = 0;
+	uint32_t ident;
 	int blastaway = 0;
-	struct ast_variable *v, *cookies, *params = get_params;
+	int res = -1;
+	struct ast_variable *v;
+	struct ast_variable *params = get_params;
 	char template[] = "/tmp/ast-http-XXXXXX";	/* template for temporary file */
 	struct ast_str *http_header = NULL, *out = NULL;
 	struct message m = { 0 };
@@ -6834,16 +6836,7 @@
 		return -1;
 	}
 
-	cookies = ast_http_get_cookies(headers);
-	for (v = cookies; v; v = v->next) {
-		if (!strcasecmp(v->name, "mansession_id")) {
-			sscanf(v->value, "%30x", &ident);
-			break;
-		}
-	}
-	if (cookies) {
-		ast_variables_destroy(cookies);
-	}
+	ident = ast_http_manid_from_vars(headers);
 
 	if (!(session = find_session(ident, 1))) {
 
@@ -6858,12 +6851,14 @@
 		ao2_lock(session);
 		session->send_events = 0;
 		session->inuse = 1;
-		/*!\note There is approximately a 1 in 1.8E19 chance that the following
+		/*!
+		 * \note There is approximately a 1 in 1.8E19 chance that the following
 		 * calculation will produce 0, which is an invalid ID, but due to the
 		 * properties of the rand() function (and the constantcy of s), that
 		 * won't happen twice in a row.
 		 */
-		while ((session->managerid = ast_random() ^ (unsigned long) session) == 0);
+		while ((session->managerid = ast_random() ^ (unsigned long) session) == 0) {
+		}
 		session->last_ev = grab_last();
 		AST_LIST_HEAD_INIT_NOLOCK(&session->datastores);
 	}
@@ -6896,19 +6891,21 @@
 
 	if (method == AST_HTTP_POST) {
 		params = ast_http_get_post_vars(ser, headers);
-	}
-
-	if (!params) {
-		switch (errno) {
-		case EFBIG:
-			ast_http_send(ser, AST_HTTP_POST, 413, "Request Entity Too Large", NULL, NULL, 0, 0);
-			break;
-		case ENOMEM:
-			ast_http_send(ser, AST_HTTP_POST, 500, "Internal Server Error", NULL, NULL, 0, 0);
-			break;
-		case EIO:
-			ast_http_send(ser, AST_HTTP_POST, 400, "Bad Request", NULL, NULL, 0, 0);
-			break;
+		if (!params) {
+			switch (errno) {
+			case EFBIG:
+				ast_http_error(ser, 413, "Request Entity Too Large", "Body too large");
+				close_mansession_file(&s);
+				goto generic_callback_out;
+			case ENOMEM:
+				ast_http_error(ser, 500, "Server Error", "Out of memory");
+				close_mansession_file(&s);
+				goto generic_callback_out;
+			case EIO:
+				ast_http_error(ser, 400, "Bad Request", "Error parsing request body");
+				close_mansession_file(&s);
+				goto generic_callback_out;
+			}
 		}
 	}
 
@@ -6945,7 +6942,6 @@
 
 	ast_str_append(&http_header, 0,
 		"Content-type: text/%s\r\n"
-		"Cache-Control: no-cache;\r\n"
 		"Set-Cookie: mansession_id=\"%08x\"; Version=1; Max-Age=%d\r\n"
 		"Pragma: SuppressEvents\r\n",
 		contenttype[format],
@@ -7010,7 +7006,9 @@
 	ao2_unlock(session);
 
 	ast_http_send(ser, method, 200, NULL, http_header, out, 0, 0);
-	http_header = out = NULL;
+	http_header = NULL;
+	out = NULL;
+	res = 0;
 
 generic_callback_out:
 	ast_mutex_destroy(&s.lock);
@@ -7030,7 +7028,7 @@
 		session->f = NULL;
 	}
 
-	return 0;
+	return res;
 }
 
 static int auth_http_callback(struct ast_tcptls_session_instance *ser,
@@ -7045,7 +7043,7 @@
 	struct ast_variable *v, *params = get_params;
 	char template[] = "/tmp/ast-http-XXXXXX";	/* template for temporary file */
 	struct ast_str *http_header = NULL, *out = NULL;
-	size_t result_size = 512;
+	size_t result_size;
 	struct message m = { 0 };
 	unsigned int idx;
 	size_t hdrlen;
@@ -7055,6 +7053,7 @@
 	struct ast_http_digest d = { NULL, };
 	struct ast_manager_user *user = NULL;
 	int stale = 0;
+	int res = -1;
 	char resp_hash[256]="";
 	/* Cache for user data */
 	char u_username[80];
@@ -7240,6 +7239,22 @@
 
 	if (method == AST_HTTP_POST) {
 		params = ast_http_get_post_vars(ser, headers);
+		if (!params) {
+			switch (errno) {
+			case EFBIG:
+				ast_http_error(ser, 413, "Request Entity Too Large", "Body too large");
+				close_mansession_file(&s);
+				goto auth_callback_out;
+			case ENOMEM:
+				ast_http_error(ser, 500, "Server Error", "Out of memory");
+				close_mansession_file(&s);
+				goto auth_callback_out;
+			case EIO:
+				ast_http_error(ser, 400, "Bad Request", "Error parsing request body");
+				close_mansession_file(&s);
+				goto auth_callback_out;
+			}
+		}
 	}
 
 	for (v = params; v && m.hdrcount < ARRAY_LEN(m.headers); v = v->next) {
@@ -7268,15 +7283,13 @@

[... 344 lines stripped ...]



More information about the asterisk-commits mailing list