diff --git a/UPGRADE.txt b/UPGRADE.txt index abd435f8de..87256ef25a 100644 --- a/UPGRADE.txt +++ b/UPGRADE.txt @@ -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 @@ -41,6 +41,11 @@ From 12.3.0 to 12.4.0: client is slow to process the received data, the socket may be disconnected. In such cases, it may be necessary to adjust this value. Default is 100 ms. + - 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. + - Added a 'force_avp' option to chan_pjsip which will force the usage of 'RTP/AVP', 'RTP/AVPF', 'RTP/SAVP', or 'RTP/SAVPF' as the media transport type in SDP offers depending on settings, even when DTLS is used for media diff --git a/configs/http.conf.sample b/configs/http.conf.sample index 98c672b2ae..c59a67e1c2 100644 --- a/configs/http.conf.sample +++ b/configs/http.conf.sample @@ -45,7 +45,13 @@ bindaddr=127.0.0.1 ; 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 @@ bindaddr=127.0.0.1 ; ;[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/ diff --git a/include/asterisk/http.h b/include/asterisk/http.h index 0642cfa9bf..ed0e0dfb6f 100644 --- a/include/asterisk/http.h +++ b/include/asterisk/http.h @@ -66,28 +66,34 @@ enum ast_http_method { 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 - * use the ast_http_send() function for sending content allocated with ast_str - * and/or content from an opened file descriptor. + * \param ser TCP/TLS session object + * \param urih Registered URI handler struct for the URI. + * \param uri Remaining request URI path (also with the get_params removed). + * \param method enum ast_http_method GET, POST, etc. + * \param get_params URI argument list passed with the HTTP request. + * \param headers HTTP request header-name/value pair list + * + * \note Should use the ast_http_send() function for sending content + * allocated with ast_str and/or content from an opened file descriptor. * * Status and status text should be sent as arguments to the ast_http_send() * function to reflect the status of the request (200 or 304, for example). * Content length is calculated by ast_http_send() automatically. * - * Static content may be indicated to the ast_http_send() function, to indicate - * that it may be cached. + * Static content may be indicated to the ast_http_send() function, + * to indicate that it may be cached. * - * \verbatim - * The return value may include additional headers at the front and MUST - * include a blank line with \r\n to provide separation between user headers - * and content (even if no content is specified) - * \endverbatim + * For a need authentication response, the ast_http_auth() function + * should be used. * - * For an error response, the ast_http_error() function may be used. -*/ + * For an error response, the ast_http_error() function should be used. + * + * \retval 0 Continue and process the next HTTP request. + * \retval -1 Fatal HTTP connection error. Force the HTTP connection closed. + */ typedef int (*ast_http_callback)(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_params, struct ast_variable *headers); /*! \brief Definition of a URI handler */ @@ -141,26 +147,30 @@ void ast_http_uri_unlink(struct ast_http_uri *urihandler); /*! \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 +196,14 @@ uint32_t ast_http_manid_from_vars(struct ast_variable *headers) attribute_pure; * * \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); +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, int fd, unsigned int static_content); -/*!\brief Send http "401 Unauthorized" response and close socket */ +/*! \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); /*! @@ -202,8 +214,42 @@ void ast_http_error(struct ast_tcptls_session_instance *ser, int status, const c */ void ast_http_prefix(char *buf, int len); +/*! + * \brief Request the HTTP connection be closed after this HTTP request. + * \since 12.4.0 + * + * \param ser HTTP TCP/TLS session object. + * + * \note Call before ast_http_error() to make the connection close. + * + * \return Nothing + */ +void ast_http_request_close_on_completion(struct ast_tcptls_session_instance *ser); -/*!\brief Get post variables from client Request Entity-Body, if content type is application/x-www-form-urlencoded. +/*! + * \brief Update the body read success status. + * \since 12.4.0 + * + * \param ser HTTP TCP/TLS session object. + * \param read_success TRUE if body was read successfully. + * + * \return Nothing + */ +void ast_http_body_read_status(struct ast_tcptls_session_instance *ser, int read_success); + +/*! + * \brief Read and discard any unread HTTP request body. + * \since 12.4.0 + * + * \param ser HTTP TCP/TLS session object. + * + * \retval 0 on success. + * \retval -1 on error. + */ +int ast_http_body_discard(struct ast_tcptls_session_instance *ser); + +/*! + * \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 +260,8 @@ struct ast_variable *ast_http_get_post_vars(struct ast_tcptls_session_instance * 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 diff --git a/include/asterisk/tcptls.h b/include/asterisk/tcptls.h index 3356a92ccd..0e8d9d042f 100644 --- a/include/asterisk/tcptls.h +++ b/include/asterisk/tcptls.h @@ -210,7 +210,6 @@ struct ast_tcptls_session_instance { 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_tcptls_session_instance { 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) diff --git a/main/http.c b/main/http.c index 9eac086e6a..9ac8cec26a 100644 --- a/main/http.c +++ b/main/http.c @@ -71,7 +71,26 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #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 + +/*! Maximum application/json or application/x-www-form-urlencoded body content length. */ +#if !defined(LOW_MEMORY) +#define MAX_CONTENT_LENGTH 4096 +#else +#define MAX_CONTENT_LENGTH 1024 +#endif /* !defined(LOW_MEMORY) */ + +/*! Maximum line length for HTTP requests. */ +#if !defined(LOW_MEMORY) +#define MAX_HTTP_LINE_LENGTH 4096 +#else +#define MAX_HTTP_LINE_LENGTH 1024 +#endif /* !defined(LOW_MEMORY) */ /* See http.h for more information about the SSL implementation */ #if defined(HAVE_OPENSSL) && (defined(HAVE_FUNOPEN) || defined(HAVE_FOPENCOOKIE)) @@ -80,6 +99,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") 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; @@ -231,7 +251,7 @@ static int static_callback(struct ast_tcptls_session_instance *ser, if (method != AST_HTTP_GET && method != AST_HTTP_HEAD) { ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method"); - return -1; + return 0; } /* Yuck. I'm not really sold on this, but if you don't deliver static content it makes your configuration @@ -300,9 +320,12 @@ static int static_callback(struct ast_tcptls_session_instance *ser, } } - if ( (http_header = ast_str_create(255)) == NULL) { + http_header = ast_str_create(255); + if (!http_header) { + ast_http_request_close_on_completion(ser); + ast_http_error(ser, 500, "Server Error", "Out of memory"); close(fd); - return -1; + return 0; } ast_str_set(&http_header, 0, "Content-type: %s\r\n" @@ -323,11 +346,12 @@ static int static_callback(struct ast_tcptls_session_instance *ser, out404: ast_http_error(ser, 404, "Not Found", "The requested URL was not found on this server."); - return -1; + return 0; out403: + ast_http_request_close_on_completion(ser); ast_http_error(ser, 403, "Access Denied", "You do not have permission to access the requested URL."); - return -1; + return 0; } static int httpstatus_callback(struct ast_tcptls_session_instance *ser, @@ -340,11 +364,14 @@ static int httpstatus_callback(struct ast_tcptls_session_instance *ser, if (method != AST_HTTP_GET && method != AST_HTTP_HEAD) { ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method"); - return -1; + return 0; } - if ( (out = ast_str_create(512)) == NULL) { - return -1; + out = ast_str_create(512); + if (!out) { + ast_http_request_close_on_completion(ser); + ast_http_error(ser, 500, "Server Error", "Out of memory"); + return 0; } ast_str_append(&out, 0, @@ -397,23 +424,63 @@ static struct ast_http_uri staticuri = { .key= __FILE__, }; +enum http_private_flags { + /*! TRUE if the HTTP request has a body. */ + HTTP_FLAG_HAS_BODY = (1 << 0), + /*! TRUE if the HTTP request body has been read. */ + HTTP_FLAG_BODY_READ = (1 << 1), + /*! TRUE if the HTTP request must close when completed. */ + HTTP_FLAG_CLOSE_ON_COMPLETION = (1 << 2), +}; + +/*! HTTP tcptls worker_fn private data. */ +struct http_worker_private_data { + /*! Body length or -1 if chunked. Valid if HTTP_FLAG_HAS_BODY is TRUE. */ + int body_length; + /*! HTTP body tracking flags */ + struct ast_flags flags; +}; -/* send http/1.1 response */ -/* free content variable 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, + struct ast_str *http_header, struct ast_str *out, int fd, unsigned int static_content) { struct timeval now = ast_tvnow(); struct ast_tm tm; char timebuf[80]; int content_length = 0; + int close_connection; - if (!ser || 0 == ser->f) { + if (!ser || !ser->f) { + /* The connection is not open. */ + ast_free(http_header); + ast_free(out); return; } + /* + * We shouldn't be sending non-final status codes to this + * function because we may close the connection before + * returning. + */ + ast_assert(200 <= status_code); + + if (session_keep_alive <= 0) { + close_connection = 1; + } else { + struct http_worker_private_data *request; + + request = ser->private_data; + if (!request + || ast_test_flag(&request->flags, HTTP_FLAG_CLOSE_ON_COMPLETION) + || ast_http_body_discard(ser)) { + 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")); /* calc content length */ @@ -427,20 +494,22 @@ void ast_http_send(struct ast_tcptls_session_instance *ser, } /* 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" "%s" "Content-Length: %d\r\n" - "%s" "\r\n", 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) : "" + http_header ? ast_str_buffer(http_header) : "", + content_length ); /* send content */ @@ -448,33 +517,35 @@ void ast_http_send(struct ast_tcptls_session_instance *ser, 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_free(http_header); + ast_free(out); - ast_tcptls_close_session_file(ser); - return; + if (close_connection) { + ast_debug(1, "HTTP closing session. status_code:%d\n", status_code); + ast_tcptls_close_session_file(ser); + } else { + ast_debug(1, "HTTP keeping session open. status_code:%d\n", status_code); + } } -/* Send http "401 Unauthorized" responce 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) @@ -485,6 +556,10 @@ void ast_http_auth(struct ast_tcptls_session_instance *ser, const char *realm, if (!http_headers || !out) { ast_free(http_headers); ast_free(out); + if (ser && ser->f) { + ast_debug(1, "HTTP closing session. Auth OOM\n"); + ast_tcptls_close_session_file(ser); + } return; } @@ -509,10 +584,8 @@ void ast_http_auth(struct ast_tcptls_session_instance *ser, const char *realm, 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 +594,10 @@ void ast_http_error(struct ast_tcptls_session_instance *ser, int status_code, co if (!http_headers || !out) { ast_free(http_headers); ast_free(out); + if (ser && ser->f) { + ast_debug(1, "HTTP closing session. error OOM\n"); + ast_tcptls_close_session_file(ser); + } return; } @@ -536,14 +613,13 @@ void ast_http_error(struct ast_tcptls_session_instance *ser, int status_code, co "