libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

commit e4e05d001ce05928d72aa07f34a3a7581107d842
parent cb11cac74d2a5dca1039dd2ab70d42e8fe1d501b
Author: Christian Grothoff <christian@grothoff.org>
Date:   Wed,  1 Jan 2014 20:43:44 +0000

enable use of keep-alive with http 1.0 if explicitly requested by the client

Diffstat:
MChangeLog | 5+++++
Msrc/examples/demo.c | 6+++++-
Msrc/microhttpd/connection.c | 256++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
3 files changed, 179 insertions(+), 88 deletions(-)

diff --git a/ChangeLog b/ChangeLog @@ -1,3 +1,8 @@ +Wed Jan 1 21:38:18 CET 2014 + Allow Keep-Alive with HTTP 1.0 (if explicitly requested), + and automatically set "Connection: Keep-Alive" in response + in this case as well. -CG + Tue Dec 24 12:27:39 CET 2013 Adding explicit annotations to hide symbols that are not for export in the C code (gcc 4.0 or higher only). -CG diff --git a/src/examples/demo.c b/src/examples/demo.c @@ -43,7 +43,7 @@ * Number of threads to run in the thread pool. Should (roughly) match * the number of cores on your system. */ -#define NUMBER_OF_THREADS 4 +#define NUMBER_OF_THREADS 8 /** * How many bytes of a file do we give to libmagic to determine the mime type? @@ -362,9 +362,11 @@ update_directory () rdc.buf, MHD_RESPMEM_MUST_FREE); mark_as_html (response); +#if FORCE_CLOSE (void) MHD_add_response_header (response, MHD_HTTP_HEADER_CONNECTION, "close"); +#endif update_cached_response (response); } @@ -775,7 +777,9 @@ generate_page (void *cls, } } if (0 == strcmp (method, MHD_HTTP_METHOD_GET)) + { return return_directory_response (connection); + } /* unexpected request, refuse */ return MHD_queue_response (connection, diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c @@ -484,8 +484,56 @@ try_ready_chunked_body (struct MHD_Connection *connection) /** + * Are we allowed to keep the given connection alive? We can use the + * TCP stream for a second request if the connection is HTTP 1.1 and + * the "Connection" header either does not exist or is not set to + * "close", or if the connection is HTTP 1.0 and the "Connection" + * header is explicitly set to "keep-alive". If no HTTP version is + * specified (or if it is not 1.0 or 1.1), we definitively close the + * connection. If the "Connection" header is not exactly "close" or + * "keep-alive", we proceed to use the default for the respective HTTP + * version (which is conservative for HTTP 1.0, but might be a bit + * optimistic for HTTP 1.1). + * + * @param connection the connection to check for keepalive + * @return #MHD_YES if (based on the request), a keepalive is + * legal + */ +static int +keepalive_possible (struct MHD_Connection *connection) +{ + const char *end; + + if (NULL == connection->version) + return MHD_NO; + end = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_CONNECTION); + if (0 == strcasecmp (connection->version, + MHD_HTTP_VERSION_1_1)) + { + if (NULL == end) + return MHD_YES; + if (0 == strcasecmp (end, "close")) + return MHD_NO; + return MHD_YES; + } + if (0 == strcasecmp (connection->version, + MHD_HTTP_VERSION_1_0)) + { + if (NULL == end) + return MHD_NO; + if (0 == strcasecmp (end, "Keep-Alive")) + return MHD_YES; + return MHD_NO; + } + return MHD_NO; +} + + +/** * Check if we need to set some additional headers - * for http-compiliance. + * for HTTP-compiliance. * * @param connection connection to check (and possibly modify) */ @@ -493,6 +541,7 @@ static void add_extra_headers (struct MHD_Connection *connection) { const char *have_close; + const char *have_keepalive; const char *client_close; const char *have_encoding; char buf[128]; @@ -502,12 +551,18 @@ add_extra_headers (struct MHD_Connection *connection) MHD_HEADER_KIND, MHD_HTTP_HEADER_CONNECTION); /* we only care about 'close', everything else is ignored */ - if ( (NULL != client_close) && (0 != strcasecmp (client_close, "close")) ) + if ( (NULL != client_close) && + (0 != strcasecmp (client_close, "close")) ) client_close = NULL; have_close = MHD_get_response_header (connection->response, MHD_HTTP_HEADER_CONNECTION); - if ( (NULL != have_close) && (0 != strcasecmp (have_close, "close")) ) + have_keepalive = have_close; + if ( (NULL != have_close) && + (0 != strcasecmp (have_close, "close")) ) have_close = NULL; + if ( (NULL != have_keepalive) && + (0 != strcasecmp (have_keepalive, "keep-alive")) ) + have_keepalive = NULL; connection->have_chunked_upload = MHD_NO; add_close = MHD_NO; if (MHD_SIZE_UNKNOWN == connection->response->total_size) @@ -518,9 +573,10 @@ add_extra_headers (struct MHD_Connection *connection) { /* 'close' header doesn't exist yet, see if we need to add one; if the client asked for a close, no need to start chunk'ing */ - if ((NULL == client_close) && - (NULL != connection->version) && - (0 == strcasecmp (connection->version, MHD_HTTP_VERSION_1_1))) + if ( (NULL == client_close) && + (MHD_YES == keepalive_possible (connection)) && + (0 == strcasecmp (connection->version, + MHD_HTTP_VERSION_1_1)) ) { connection->have_chunked_upload = MHD_YES; have_encoding = MHD_get_response_header (connection->response, @@ -578,7 +634,15 @@ add_extra_headers (struct MHD_Connection *connection) } if (MHD_YES == add_close) MHD_add_response_header (connection->response, - MHD_HTTP_HEADER_CONNECTION, "close"); + MHD_HTTP_HEADER_CONNECTION, + "close"); + if ( (NULL == have_keepalive) && + (NULL == have_close) && + (MHD_NO == add_close) && + (MHD_YES == keepalive_possible (connection)) ) + MHD_add_response_header (connection->response, + MHD_HTTP_HEADER_CONNECTION, + "Keep-Alive"); } @@ -669,6 +733,7 @@ build_header_response (struct MHD_Connection *connection) const char *reason_phrase; uint32_t rc; int must_add_close; + const char *end; EXTRA_CHECK (NULL != connection->version); if (0 == strlen (connection->version)) @@ -709,16 +774,17 @@ build_header_response (struct MHD_Connection *connection) } else { + /* 2 bytes for final CRLF of a Chunked-Body */ size = 2; kind = MHD_FOOTER_KIND; off = 0; } + end = MHD_get_response_header (connection->response, + MHD_HTTP_HEADER_CONNECTION); must_add_close = ( (MHD_CONNECTION_FOOTERS_RECEIVED == connection->state) && (MHD_YES == connection->read_closed) && - (0 == strcasecmp (connection->version, - MHD_HTTP_VERSION_1_1)) && - (NULL == MHD_get_response_header (connection->response, - MHD_HTTP_HEADER_CONNECTION)) ); + (MHD_YES == keepalive_possible (connection)) && + (NULL == end) ); if (must_add_close) size += strlen ("Connection: close\r\n"); for (pos = connection->response->first_header; NULL != pos; pos = pos->next) @@ -729,7 +795,8 @@ build_header_response (struct MHD_Connection *connection) if (NULL == data) { #if HAVE_MESSAGES - MHD_DLOG (connection->daemon, "Not enough memory for write!\n"); + MHD_DLOG (connection->daemon, + "Not enough memory for write!\n"); #endif return MHD_NO; } @@ -834,8 +901,10 @@ MHD_connection_update_event_loop_info (struct MHD_Connection *connection) while (1) { #if DEBUG_STATES - MHD_DLOG (connection->daemon, "%s: state: %s\n", - __FUNCTION__, MHD_state_to_string (connection->state)); + MHD_DLOG (connection->daemon, + "%s: state: %s\n", + __FUNCTION__, + MHD_state_to_string (connection->state)); #endif switch (connection->state) { @@ -1194,15 +1263,16 @@ parse_cookie_header (struct MHD_Connection *connection) equals = sce + 1; quotes = 0; semicolon = equals; - while ((semicolon[0] != '\0') && - ((quotes != 0) || - ((semicolon[0] != ';') && (semicolon[0] != ',')))) + while ( ('\0' != semicolon[0]) && + ( (0 != quotes) || + ( (';' != semicolon[0]) && + (',' != semicolon[0]) ) ) ) { - if (semicolon[0] == '"') + if ('"' == semicolon[0]) quotes = (quotes + 1) & 1; semicolon++; } - if (semicolon[0] == '\0') + if ('\0' == semicolon[0]) semicolon = NULL; if (NULL != semicolon) { @@ -1210,7 +1280,8 @@ parse_cookie_header (struct MHD_Connection *connection) semicolon++; } /* remove quotes */ - if ((equals[0] == '"') && (equals[strlen (equals) - 1] == '"')) + if ( ('"' == equals[0]) && + ('"' == equals[strlen (equals) - 1]) ) { equals[strlen (equals) - 1] = '\0'; equals++; @@ -1232,7 +1303,8 @@ parse_cookie_header (struct MHD_Connection *connection) * @return #MHD_YES if the line is ok, #MHD_NO if it is malformed */ static int -parse_initial_message_line (struct MHD_Connection *connection, char *line) +parse_initial_message_line (struct MHD_Connection *connection, + char *line) { char *uri; char *http_version; @@ -1243,7 +1315,7 @@ parse_initial_message_line (struct MHD_Connection *connection, char *line) uri[0] = '\0'; connection->method = line; uri++; - while (uri[0] == ' ') + while (' ' == uri[0]) uri++; http_version = strchr (uri, ' '); if (NULL != http_version) @@ -1292,9 +1364,9 @@ call_connection_handler (struct MHD_Connection *connection) processed = 0; connection->client_aware = MHD_YES; if (MHD_NO == - connection->daemon->default_handler (connection->daemon-> - default_handler_cls, - connection, connection->url, + connection->daemon->default_handler (connection->daemon-> default_handler_cls, + connection, + connection->url, connection->method, connection->version, NULL, &processed, @@ -1336,12 +1408,12 @@ process_request_body (struct MHD_Connection *connection) do { instant_retry = MHD_NO; - if ((connection->have_chunked_upload == MHD_YES) && - (connection->remaining_upload_size == MHD_SIZE_UNKNOWN)) + if ( (MHD_YES == connection->have_chunked_upload) && + (MHD_SIZE_UNKNOWN == connection->remaining_upload_size) ) { - if ((connection->current_chunk_offset == - connection->current_chunk_size) - && (connection->current_chunk_offset != 0) && (available >= 2)) + if ( (connection->current_chunk_offset == connection->current_chunk_size) && + (0 != connection->current_chunk_offset) && + (available >= 2) ) { /* skip new line at the *end* of a chunk */ i = 0; @@ -1448,12 +1520,13 @@ process_request_body (struct MHD_Connection *connection) used = processed; connection->client_aware = MHD_YES; if (MHD_NO == - connection->daemon->default_handler (connection->daemon-> - default_handler_cls, - connection, connection->url, + connection->daemon->default_handler (connection->daemon->default_handler_cls, + connection, + connection->url, connection->method, connection->version, - buffer_head, &processed, + buffer_head, + &processed, &connection->client_context)) { /* serious internal error, close connection */ @@ -1510,7 +1583,7 @@ do_read (struct MHD_Connection *connection) connection->read_buffer_offset); if (bytes_read < 0) { - if ((EINTR == errno) || (EAGAIN == errno)) + if ((EINTR == errno) || (EAGAIN == errno) || (ECONNRESET == errno)) return MHD_NO; #if HAVE_MESSAGES #if HTTPS_SUPPORT @@ -1521,7 +1594,8 @@ do_read (struct MHD_Connection *connection) else #endif MHD_DLOG (connection->daemon, - "Failed to receive data: %s\n", STRERROR (errno)); + "Failed to receive data: %s\n", + STRERROR (errno)); #endif CONNECTION_CLOSE_ERROR (connection, NULL); return MHD_YES; @@ -1539,6 +1613,7 @@ do_read (struct MHD_Connection *connection) return MHD_YES; } + /** * Try writing data to the socket from the * write buffer of the connection. @@ -1750,12 +1825,13 @@ parse_connection_headers (struct MHD_Connection *connection) char *end; parse_cookie_header (connection); - if ((0 != (MHD_USE_PEDANTIC_CHECKS & connection->daemon->options)) - && (NULL != connection->version) - && (0 == strcasecmp (MHD_HTTP_VERSION_1_1, connection->version)) - && (NULL == - MHD_lookup_connection_value (connection, MHD_HEADER_KIND, - MHD_HTTP_HEADER_HOST))) + if ( (0 != (MHD_USE_PEDANTIC_CHECKS & connection->daemon->options)) && + (NULL != connection->version) && + (0 == strcasecmp (MHD_HTTP_VERSION_1_1, connection->version)) && + (NULL == + MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_HOST)) ) { /* die, http 1.1 request without host and we are pedantic */ connection->state = MHD_CONNECTION_FOOTERS_RECEIVED; @@ -1765,7 +1841,7 @@ parse_connection_headers (struct MHD_Connection *connection) "Received `%s' request without `%s' header.\n", MHD_HTTP_VERSION_1_1, MHD_HTTP_HEADER_HOST); #endif - EXTRA_CHECK (connection->response == NULL); + EXTRA_CHECK (NULL == connection->response); response = MHD_create_response_from_buffer (strlen (REQUEST_LACKS_HOST), REQUEST_LACKS_HOST, @@ -1798,8 +1874,9 @@ parse_connection_headers (struct MHD_Connection *connection) { #if HAVE_MESSAGES MHD_DLOG (connection->daemon, - "Failed to parse `%s' header `%s', closing connection.\n", - MHD_HTTP_HEADER_CONTENT_LENGTH, clen); + "Failed to parse `%s' header `%s', closing connection.\n", + MHD_HTTP_HEADER_CONTENT_LENGTH, + clen); #endif CONNECTION_CLOSE_ERROR (connection, NULL); return; @@ -1868,7 +1945,8 @@ MHD_connection_handle_read (struct MHD_Connection *connection) { #if DEBUG_STATES MHD_DLOG (connection->daemon, "%s: state: %s\n", - __FUNCTION__, MHD_state_to_string (connection->state)); + __FUNCTION__, + MHD_state_to_string (connection->state)); #endif switch (connection->state) { @@ -1925,7 +2003,8 @@ MHD_connection_handle_write (struct MHD_Connection *connection) { #if DEBUG_STATES MHD_DLOG (connection->daemon, "%s: state: %s\n", - __FUNCTION__, MHD_state_to_string (connection->state)); + __FUNCTION__, + MHD_state_to_string (connection->state)); #endif switch (connection->state) { @@ -1949,7 +2028,8 @@ MHD_connection_handle_write (struct MHD_Connection *connection) break; #if HAVE_MESSAGES MHD_DLOG (connection->daemon, - "Failed to send data: %s\n", STRERROR (errno)); + "Failed to send data: %s\n", + STRERROR (errno)); #endif CONNECTION_CLOSE_ERROR (connection, NULL); return MHD_YES; @@ -1958,8 +2038,7 @@ MHD_connection_handle_write (struct MHD_Connection *connection) FPRINTF (stderr, "Sent 100 continue response: `%.*s'\n", ret, - &HTTP_100_CONTINUE - [connection->continue_message_write_offset]); + &HTTP_100_CONTINUE[connection->continue_message_write_offset]); #endif connection->continue_message_write_offset += ret; break; @@ -2007,7 +2086,8 @@ MHD_connection_handle_write (struct MHD_Connection *connection) return MHD_YES; #if HAVE_MESSAGES MHD_DLOG (connection->daemon, - "Failed to send data: %s\n", STRERROR (errno)); + "Failed to send data: %s\n", + STRERROR (errno)); #endif CONNECTION_CLOSE_ERROR (connection, NULL); return MHD_YES; @@ -2015,7 +2095,7 @@ MHD_connection_handle_write (struct MHD_Connection *connection) connection->response_write_position += ret; if (connection->response_write_position == connection->response->total_size) - connection->state = MHD_CONNECTION_FOOTERS_SENT; /* have no footers... */ + connection->state = MHD_CONNECTION_FOOTERS_SENT; /* have no footers */ break; case MHD_CONNECTION_NORMAL_BODY_UNREADY: EXTRA_CHECK (0); @@ -2050,7 +2130,8 @@ MHD_connection_handle_write (struct MHD_Connection *connection) break; default: EXTRA_CHECK (0); - CONNECTION_CLOSE_ERROR (connection, "Internal error\n"); + CONNECTION_CLOSE_ERROR (connection, + "Internal error\n"); return MHD_YES; } break; @@ -2121,15 +2202,16 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) struct MHD_Daemon *daemon = connection->daemon; unsigned int timeout; const char *end; - int rend; char *line; connection->in_idle = MHD_YES; while (1) { #if DEBUG_STATES - MHD_DLOG (daemon, "%s: state: %s\n", - __FUNCTION__, MHD_state_to_string (connection->state)); + MHD_DLOG (daemon, + "%s: state: %s\n", + __FUNCTION__, + MHD_state_to_string (connection->state)); #endif switch (connection->state) { @@ -2241,15 +2323,15 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) } break; case MHD_CONNECTION_CONTINUE_SENT: - if (connection->read_buffer_offset != 0) + if (0 != connection->read_buffer_offset) { process_request_body (connection); /* loop call */ if (MHD_CONNECTION_CLOSED == connection->state) continue; } - if ((connection->remaining_upload_size == 0) || + if ((0 == connection->remaining_upload_size) || ((connection->remaining_upload_size == MHD_SIZE_UNKNOWN) && - (connection->read_buffer_offset == 0) && + (0 == connection->read_buffer_offset) && (MHD_YES == connection->read_closed))) { if ((MHD_YES == connection->have_chunked_upload) && @@ -2262,7 +2344,7 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) break; case MHD_CONNECTION_BODY_RECEIVED: line = get_next_header_line (connection); - if (line == NULL) + if (NULL == line) { if (connection->state != MHD_CONNECTION_BODY_RECEIVED) continue; @@ -2274,7 +2356,7 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) } break; } - if (strlen (line) == 0) + if (0 == strlen (line)) { connection->state = MHD_CONNECTION_FOOTERS_RECEIVED; continue; @@ -2290,7 +2372,7 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) continue; case MHD_CONNECTION_FOOTER_PART_RECEIVED: line = get_next_header_line (connection); - if (line == NULL) + if (NULL == line) { if (connection->state != MHD_CONNECTION_FOOTER_PART_RECEIVED) continue; @@ -2305,7 +2387,7 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) if (MHD_NO == process_broken_line (connection, line, MHD_FOOTER_KIND)) continue; - if (strlen (line) == 0) + if (0 == strlen (line)) { connection->state = MHD_CONNECTION_FOOTERS_RECEIVED; continue; @@ -2315,7 +2397,7 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) call_connection_handler (connection); /* "final" call */ if (connection->state == MHD_CONNECTION_CLOSED) continue; - if (connection->response == NULL) + if (NULL == connection->response) break; /* try again next time */ if (MHD_NO == build_header_response (connection)) { @@ -2412,44 +2494,29 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) end = MHD_get_response_header (connection->response, MHD_HTTP_HEADER_CONNECTION); - rend = ( (MHD_YES == connection->read_closed) || - ( (end != NULL) && (0 == strcasecmp (end, "close")) ) ); MHD_destroy_response (connection->response); connection->response = NULL; - if (daemon->notify_completed != NULL) + if (NULL != daemon->notify_completed) daemon->notify_completed (daemon->notify_completed_cls, connection, &connection->client_context, MHD_REQUEST_TERMINATED_COMPLETED_OK); - connection->client_aware = MHD_NO; end = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONNECTION); - connection->client_context = NULL; - connection->continue_message_write_offset = 0; - connection->responseCode = 0; - connection->headers_received = NULL; - connection->headers_received_tail = NULL; - connection->response_write_position = 0; - connection->have_chunked_upload = MHD_NO; - connection->method = NULL; - connection->url = NULL; - connection->write_buffer = NULL; - connection->write_buffer_size = 0; - connection->write_buffer_send_offset = 0; - connection->write_buffer_append_offset = 0; - if ( (rend) || ((end != NULL) && (0 == strcasecmp (end, "close"))) ) + if ( (MHD_YES == connection->read_closed) || + ((NULL != end) && (0 == strcasecmp (end, "close"))) ) { connection->read_closed = MHD_YES; connection->read_buffer_offset = 0; } if (((MHD_YES == connection->read_closed) && (0 == connection->read_buffer_offset)) || - (connection->version == NULL) || - (0 != strcasecmp (MHD_HTTP_VERSION_1_1, connection->version))) + (MHD_NO == keepalive_possible (connection))) { - /* http 1.0, version-less requests cannot be pipelined */ - MHD_connection_close (connection, MHD_REQUEST_TERMINATED_COMPLETED_OK); + /* have to close for some reason */ + MHD_connection_close (connection, + MHD_REQUEST_TERMINATED_COMPLETED_OK); MHD_pool_destroy (connection->pool); connection->pool = NULL; connection->read_buffer = NULL; @@ -2458,6 +2525,7 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) } else { + /* can try to keep-alive */ connection->version = NULL; connection->state = MHD_CONNECTION_INIT; connection->read_buffer @@ -2465,6 +2533,20 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) connection->read_buffer, connection->read_buffer_size); } + connection->client_aware = MHD_NO; + connection->client_context = NULL; + connection->continue_message_write_offset = 0; + connection->responseCode = 0; + connection->headers_received = NULL; + connection->headers_received_tail = NULL; + connection->response_write_position = 0; + connection->have_chunked_upload = MHD_NO; + connection->method = NULL; + connection->url = NULL; + connection->write_buffer = NULL; + connection->write_buffer_size = 0; + connection->write_buffer_send_offset = 0; + connection->write_buffer_append_offset = 0; continue; case MHD_CONNECTION_CLOSED: cleanup_connection (connection); @@ -2476,7 +2558,7 @@ MHD_connection_handle_idle (struct MHD_Connection *connection) break; } timeout = connection->connection_timeout; - if ( (timeout != 0) && + if ( (0 != timeout) && (timeout <= (MHD_monotonic_time() - connection->last_activity)) ) { MHD_connection_close (connection, MHD_REQUEST_TERMINATED_TIMEOUT_REACHED);