libmicrohttpd

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

commit 8157744d45ee018ab325ff5a16be1212cd7e8f58
parent eaba29f5716951cffff9869f98db9b4726f1b154
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun, 25 Jul 2010 13:17:10 +0000

systemd and sendfile support

Diffstat:
MChangeLog | 5+++++
MREADME | 1+
Mdoc/microhttpd.texi | 35+++++++++++++++++++++++++++++++++++
Msrc/daemon/connection.c | 35++++++++++++-----------------------
Msrc/daemon/daemon.c | 191+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/daemon/internal.h | 21+++++++++++++--------
Msrc/daemon/response.c | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/include/microhttpd.h | 24++++++++++++++++++++++--
8 files changed, 265 insertions(+), 111 deletions(-)

diff --git a/ChangeLog b/ChangeLog @@ -1,3 +1,8 @@ +Sun Jul 25 14:57:47 CEST 2010 + Adding support for sendfile on Linux. Adding support + for systemd-style passing of an existing listen socket + as an option. -CG + Sun Jul 25 11:10:45 CEST 2010 Changed code to use external libgnutls code instead of the "fork". Minor API changes for setting TLS options. -CG diff --git a/README b/README @@ -95,6 +95,7 @@ Functions not covered by "make check": - MHD_del_response_header - MHD_get_response_headers - MHD_tls_connection_close +- MHD_create_response_from_fd & sendfile support (!) Missing documentation: diff --git a/doc/microhttpd.texi b/doc/microhttpd.texi @@ -342,23 +342,32 @@ MHD_OPTION_URI_LOG_CALLBACK. Finally, @code{uri} will be the 0-terminated URI of the request. @item MHD_OPTION_HTTPS_MEM_KEY +@cindex SSL +@cindex TLS Memory pointer to the private key to be used by the HTTPS daemon. This option should be followed by an "const char*" argument. This should be used in conjunction with 'MHD_OPTION_HTTPS_MEM_CERT'. @item MHD_OPTION_HTTPS_MEM_CERT +@cindex SSL +@cindex TLS Memory pointer to the certificate to be used by the HTTPS daemon. This option should be followed by an "const char*" argument. This should be used in conjunction with 'MHD_OPTION_HTTPS_MEM_KEY'. @item MHD_OPTION_CRED_TYPE +@cindex SSL +@cindex TLS Daemon credentials type. Either certificate or anonymous, this option should be followed by one of the values listed in "enum MHD_GNUTLS_CredentialsType". @item MHD_OPTION_HTTPS_PRIORITIES +@cindex SSL +@cindex TLS +@cindex cipher SSL/TLS protocol version and ciphers. This option must be followwed by an "const char *" argument specifying the SSL/TLS protocol versions and ciphers that @@ -366,6 +375,13 @@ are acceptable for the application. The string is passed unchanged to gnutls_priority_init. If this option is not specified, ``NORMAL'' is used. +@item MHD_OPTION_LISTEN_SOCKET +@cindex systemd +Listen socket to use. Pass a listen socket for MHD to use +(systemd-style). If this option is used, MHD will not open its own +listen socket(s). The argument passed must be of type "int" and refer +to an existing socket that has been bound to a port and is listening. + @item MHD_OPTION_EXTERNAL_LOGGER @cindex logging Use the given function for logging error messages. @@ -381,6 +397,7 @@ flag being set and the MHD_USE_DEBUG flag being set, even if this argument is used. @item MHD_OPTION_THREAD_POOL_SIZE +@cindex performance Number (unsigned int) of threads in thread pool. Enable thread pooling by setting this value to to something greater than 1. Currently, thread model must be @@ -1134,6 +1151,24 @@ Return @mynull{} on error (i.e. invalid arguments, out of memory). @end deftypefun + +@deftypefun {struct MHD_Response *} MHD_create_response_from_fd (uint64_t size, int fd) +Create a response object. The response object can be extended with +header information and then it can be used any number of times. + +@table @var +@item size +size of the data portion of the response, @code{-1} for unknown; + +@item fd +file descriptor referring to a file on disk with the data; will be +closed when response is destroyed +@end table + +Return @mynull{} on error (i.e. invalid arguments, out of memory). +@end deftypefun + + @deftypefun {struct MHD_Response *} MHD_create_response_from_data (size_t size, void *data, int must_free, int must_copy) Create a response object. The response object can be extended with header information and then it can be used any number of times. diff --git a/src/daemon/connection.c b/src/daemon/connection.c @@ -333,6 +333,11 @@ try_ready_normal_body (struct MHD_Connection *connection) (response->data_size + response->data_start > connection->response_write_position) ) return MHD_YES; /* response already ready */ +#if LINUX + if ( (response->fd != -1) && + (0 == (connection->daemon->options & MHD_USE_SSL)) ) + return MHD_YES; /* will use sendfile */ +#endif ret = response->crc (response->crc_cls, connection->response_write_position, response->data, @@ -1795,29 +1800,13 @@ MHD_connection_handle_write (struct MHD_Connection *connection) connection->state = MHD_CONNECTION_NORMAL_BODY_UNREADY; break; } -#if HTTPS_SUPPORT - if (connection->daemon->options & MHD_USE_SSL) - { - ret = gnutls_record_send (connection->tls_session, - &connection->response->data - [connection-> - response_write_position - - response->data_start], - response->data_size - - (connection->response_write_position - - response->data_start)); - } - else -#endif - { - ret = connection->send_cls (connection, - &response->data - [connection->response_write_position - - response->data_start], - response->data_size - - (connection->response_write_position - - response->data_start)); - } + ret = connection->send_cls (connection, + &response->data + [connection->response_write_position + - response->data_start], + response->data_size - + (connection->response_write_position + - response->data_start)); #if DEBUG_SEND_DATA if (ret > 0) FPRINTF (stderr, diff --git a/src/daemon/daemon.c b/src/daemon/daemon.c @@ -40,6 +40,10 @@ #include <poll.h> #endif +#ifdef LINUX +#include <sys/sendfile.h> +#endif + /** * Default connection limit. */ @@ -603,8 +607,7 @@ recv_param_adapter (struct MHD_Connection *connection, void *other, size_t i) return -1; if (0 != (connection->daemon->options & MHD_USE_SSL)) return RECV (connection->socket_fd, other, i, MSG_NOSIGNAL); - else - return RECV (connection->socket_fd, other, i, MSG_NOSIGNAL | MSG_DONTWAIT); + return RECV (connection->socket_fd, other, i, MSG_NOSIGNAL | MSG_DONTWAIT); } /** @@ -619,12 +622,27 @@ static ssize_t send_param_adapter (struct MHD_Connection *connection, const void *other, size_t i) { +#if LINUX + int fd; + off_t offset; +#endif if (connection->socket_fd == -1) return -1; if (0 != (connection->daemon->options & MHD_USE_SSL)) return SEND (connection->socket_fd, other, i, MSG_NOSIGNAL); - else - return SEND (connection->socket_fd, other, i, MSG_NOSIGNAL | MSG_DONTWAIT); +#if LINUX + if ( (NULL != connection->response) && + (-1 != (fd = connection->response->fd)) ) + { + /* can use sendfile */ + offset = (off_t) connection->response_write_position; + return sendfile (connection->socket_fd, + fd, + &offset, + i); + } +#endif + return SEND (connection->socket_fd, other, i, MSG_NOSIGNAL | MSG_DONTWAIT); } @@ -1310,7 +1328,7 @@ parse_options_va (struct MHD_Daemon *daemon, opt); #endif break; - case MHD_OPTION_CRED_TYPE: + case MHD_OPTION_HTTPS_CRED_TYPE: daemon->cred_type = va_arg (ap, gnutls_credentials_type_t); break; case MHD_OPTION_HTTPS_PRIORITIES: @@ -1328,6 +1346,9 @@ parse_options_va (struct MHD_Daemon *daemon, return MHD_NO; break; #endif + case MHD_OPTION_LISTEN_SOCKET: + daemon->socket_fd = va_arg (ap, int); + break; case MHD_OPTION_EXTERNAL_LOGGER: #if HAVE_MESSAGES daemon->custom_error_log = @@ -1367,7 +1388,8 @@ parse_options_va (struct MHD_Daemon *daemon, return MHD_NO; break; /* all options taking 'int' or 'enum' */ - case MHD_OPTION_CRED_TYPE: + case MHD_OPTION_HTTPS_CRED_TYPE: + case MHD_OPTION_LISTEN_SOCKET: if (MHD_YES != parse_options (daemon, servaddr, opt, @@ -1471,6 +1493,7 @@ MHD_start_daemon_va (unsigned int options, "NORMAL", NULL); #endif + retVal->socket_fd = -1; retVal->options = (enum MHD_OPTION)options; retVal->port = port; retVal->apc = apc; @@ -1547,105 +1570,117 @@ MHD_start_daemon_va (unsigned int options, return NULL; } #endif - if ((options & MHD_USE_IPv6) != 0) + if (retVal->socket_fd == -1) + { + if ((options & MHD_USE_IPv6) != 0) #if HAVE_INET6 - socket_fd = SOCKET (PF_INET6, SOCK_STREAM, 0); + socket_fd = SOCKET (PF_INET6, SOCK_STREAM, 0); #else - { + { #if HAVE_MESSAGES - fprintf (stderr, "AF_INET6 not supported\n"); + fprintf (stderr, "AF_INET6 not supported\n"); #endif - free (retVal); - return NULL; - } -#endif - else - socket_fd = SOCKET (PF_INET, SOCK_STREAM, 0); - if (socket_fd == -1) - { -#if HAVE_MESSAGES - if ((options & MHD_USE_DEBUG) != 0) - FPRINTF (stderr, "Call to socket failed: %s\n", STRERROR (errno)); + free (retVal); + return NULL; + } #endif - free (retVal); - return NULL; - } -#ifndef WINDOWS - if ( (socket_fd >= FD_SETSIZE) && - (0 == (options & MHD_USE_POLL)) ) - { + else + socket_fd = SOCKET (PF_INET, SOCK_STREAM, 0); + if (socket_fd == -1) + { #if HAVE_MESSAGES - if ((options & MHD_USE_DEBUG) != 0) - FPRINTF (stderr, - "Socket descriptor larger than FD_SETSIZE: %d > %d\n", - socket_fd, - FD_SETSIZE); + if ((options & MHD_USE_DEBUG) != 0) + FPRINTF (stderr, "Call to socket failed: %s\n", STRERROR (errno)); #endif - CLOSE (socket_fd); - free (retVal); - return NULL; - } -#endif - if ((SETSOCKOPT (socket_fd, - SOL_SOCKET, - SO_REUSEADDR, - &on, sizeof (on)) < 0) && ((options & MHD_USE_DEBUG) != 0)) - { + free (retVal); + return NULL; + } + if ((SETSOCKOPT (socket_fd, + SOL_SOCKET, + SO_REUSEADDR, + &on, sizeof (on)) < 0) && ((options & MHD_USE_DEBUG) != 0)) + { #if HAVE_MESSAGES - FPRINTF (stderr, "setsockopt failed: %s\n", STRERROR (errno)); + FPRINTF (stderr, "setsockopt failed: %s\n", STRERROR (errno)); #endif - } - - /* check for user supplied sockaddr */ + } + + /* check for user supplied sockaddr */ #if HAVE_INET6 - if ((options & MHD_USE_IPv6) != 0) - addrlen = sizeof (struct sockaddr_in6); - else + if ((options & MHD_USE_IPv6) != 0) + addrlen = sizeof (struct sockaddr_in6); + else #endif - addrlen = sizeof (struct sockaddr_in); - if (NULL == servaddr) - { + addrlen = sizeof (struct sockaddr_in); + if (NULL == servaddr) + { #if HAVE_INET6 + if ((options & MHD_USE_IPv6) != 0) + { + memset (&servaddr6, 0, sizeof (struct sockaddr_in6)); + servaddr6.sin6_family = AF_INET6; + servaddr6.sin6_port = htons (port); + servaddr = (struct sockaddr *) &servaddr6; + } + else +#endif + { + memset (&servaddr4, 0, sizeof (struct sockaddr_in)); + servaddr4.sin_family = AF_INET; + servaddr4.sin_port = htons (port); + servaddr = (struct sockaddr *) &servaddr4; + } + } + retVal->socket_fd = socket_fd; + if ((options & MHD_USE_IPv6) != 0) - { - memset (&servaddr6, 0, sizeof (struct sockaddr_in6)); - servaddr6.sin6_family = AF_INET6; - servaddr6.sin6_port = htons (port); - servaddr = (struct sockaddr *) &servaddr6; - } - else + { + const int on = 1; + setsockopt (socket_fd, + IPPROTO_IPV6, IPV6_V6ONLY, + &on, sizeof (on)); + } + if (BIND (socket_fd, servaddr, addrlen) == -1) + { +#if HAVE_MESSAGES + if ((options & MHD_USE_DEBUG) != 0) + FPRINTF (stderr, + "Failed to bind to port %u: %s\n", port, STRERROR (errno)); #endif - { - memset (&servaddr4, 0, sizeof (struct sockaddr_in)); - servaddr4.sin_family = AF_INET; - servaddr4.sin_port = htons (port); - servaddr = (struct sockaddr *) &servaddr4; - } - } - retVal->socket_fd = socket_fd; - if (BIND (socket_fd, servaddr, addrlen) == -1) - { + CLOSE (socket_fd); + free (retVal); + return NULL; + } + + if (LISTEN (socket_fd, 20) < 0) + { #if HAVE_MESSAGES - if ((options & MHD_USE_DEBUG) != 0) - FPRINTF (stderr, - "Failed to bind to port %u: %s\n", port, STRERROR (errno)); + if ((options & MHD_USE_DEBUG) != 0) + FPRINTF (stderr, + "Failed to listen for connections: %s\n", STRERROR (errno)); #endif - CLOSE (socket_fd); - free (retVal); - return NULL; + CLOSE (socket_fd); + free (retVal); + return NULL; + } } - if (LISTEN (socket_fd, 20) < 0) +#ifndef WINDOWS + if ( (socket_fd >= FD_SETSIZE) && + (0 == (options & MHD_USE_POLL)) ) { #if HAVE_MESSAGES if ((options & MHD_USE_DEBUG) != 0) FPRINTF (stderr, - "Failed to listen for connections: %s\n", STRERROR (errno)); + "Socket descriptor larger than FD_SETSIZE: %d > %d\n", + socket_fd, + FD_SETSIZE); #endif CLOSE (socket_fd); free (retVal); return NULL; } +#endif if (0 != pthread_mutex_init (&retVal->per_ip_connection_mutex, NULL)) { diff --git a/src/daemon/internal.h b/src/daemon/internal.h @@ -186,15 +186,15 @@ struct MHD_Response pthread_mutex_t mutex; /** - * Reference count for this response. Free - * once the counter hits zero. + * Set to MHD_SIZE_UNKNOWN if size is not known. */ - unsigned int reference_count; + uint64_t total_size; /** - * Set to MHD_SIZE_UNKNOWN if size is not known. + * At what offset in the stream is the + * beginning of data located? */ - uint64_t total_size; + uint64_t data_start; /** * Size of data. @@ -207,10 +207,15 @@ struct MHD_Response size_t data_buffer_size; /** - * At what offset in the stream is the - * beginning of data located? + * Reference count for this response. Free + * once the counter hits zero. */ - uint64_t data_start; + unsigned int reference_count; + + /** + * File-descriptor if this response is FD-backed. + */ + int fd; }; diff --git a/src/daemon/response.c b/src/daemon/response.c @@ -189,6 +189,7 @@ MHD_create_response_from_callback (uint64_t size, if (retVal == NULL) return NULL; memset (retVal, 0, sizeof (struct MHD_Response)); + retVal->fd = -1; retVal->data = (void *) &retVal[1]; retVal->data_buffer_size = block_size; if (pthread_mutex_init (&retVal->mutex, NULL) != 0) @@ -204,6 +205,68 @@ MHD_create_response_from_callback (uint64_t size, return retVal; } + +/** + * Given a file descriptor, read data from the file + * to generate the response. + * + * @param cls pointer to the file descriptor + * @param pos offset in the file to access + * @param buf where to write the data + * @param max number of bytes to write at most + * @return number of bytes written + */ +static int +file_reader (void *cls, uint64_t pos, char *buf, int max) +{ + int *fd = cls; + + (void) lseek (*fd, pos, SEEK_SET); + return read (*fd, buf, max); +} + + +/** + * Destroy file reader context. Closes the file + * descriptor. + * + * @param cls pointer to file descriptor + */ +static void +free_callback (void *cls) +{ + int *fd = cls; + close (*fd); + *fd = -1; +} + + +/** + * Create a response object. The response object can be extended with + * header information and then be used any number of times. + * + * @param size size of the data portion of the response + * @param fd file descriptor referring to a file on disk with the data + * @return NULL on error (i.e. invalid arguments, out of memory) + */ +struct MHD_Response *MHD_create_response_from_fd (size_t size, + int fd) +{ + struct MHD_Response *ret; + + ret = MHD_create_response_from_callback (size, + 4 * 1024, + &file_reader, + NULL, + &free_callback); + if (ret == NULL) + return NULL; + ret->fd = fd; + ret->crc_cls = &ret->fd; + return ret; +} + + /** * Create a response object. The response object can be extended with * header information and then be used any number of times. @@ -229,6 +292,7 @@ MHD_create_response_from_data (size_t size, if (retVal == NULL) return NULL; memset (retVal, 0, sizeof (struct MHD_Response)); + retVal->fd = -1; if (pthread_mutex_init (&retVal->mutex, NULL) != 0) { free (retVal); diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h @@ -415,13 +415,21 @@ enum MHD_OPTION * Followed by an argument of type * "gnutls_credentials_type_t". */ - MHD_OPTION_CRED_TYPE = 10, + MHD_OPTION_HTTPS_CRED_TYPE = 10, /** * Memory pointer to a "const char*" specifying the * cipher algorithm (default: "NORMAL"). */ - MHD_OPTION_HTTPS_PRIORITIES = 12, + MHD_OPTION_HTTPS_PRIORITIES = 11, + + /** + * Pass a listen socket for MHD to use (systemd-style). If this + * option is used, MHD will not open its own listen socket(s). The + * argument passed must be of type "int" and refer to an + * existing socket that has been bound to a port and is listening. + */ + MHD_OPTION_LISTEN_SOCKET = 12, /** * Use the given function for logging error messages. @@ -1074,6 +1082,18 @@ struct MHD_Response *MHD_create_response_from_data (size_t size, int must_free, int must_copy); + +/** + * Create a response object. The response object can be extended with + * header information and then be used any number of times. + * + * @param size size of the data portion of the response + * @param fd file descriptor referring to a file on disk with the data; will be closed when response is destroyed + * @return NULL on error (i.e. invalid arguments, out of memory) + */ +struct MHD_Response *MHD_create_response_from_fd (size_t size, + int fd); + /** * Destroy a response object and associated resources. Note that * libmicrohttpd may keep some of the resources around if the response