[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