commit 8157744d45ee018ab325ff5a16be1212cd7e8f58
parent eaba29f5716951cffff9869f98db9b4726f1b154
Author: Christian Grothoff <christian@grothoff.org>
Date: Sun, 25 Jul 2010 13:17:10 +0000
systemd and sendfile support
Diffstat:
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