summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--ChangeLog11
-rw-r--r--configure.ac22
-rw-r--r--doc/libmicrohttpd.texi4
-rw-r--r--src/include/microhttpd.h12
-rw-r--r--src/microhttpd/Makefile.am1
-rw-r--r--src/microhttpd/connection.c468
-rw-r--r--src/microhttpd/connection_https.c2
-rw-r--r--src/microhttpd/connection_https.h14
-rw-r--r--src/microhttpd/daemon.c14
-rw-r--r--src/microhttpd/internal.h14
-rw-r--r--src/microhttpd/mhd_send.c637
-rw-r--r--src/microhttpd/mhd_send.h97
-rw-r--r--src/microhttpd/mhd_sockets.c88
-rw-r--r--src/microhttpd/mhd_sockets.h38
-rw-r--r--src/microhttpd/response.c44
-rw-r--r--src/microhttpd/test_upgrade.c2
-rw-r--r--src/microhttpd/test_upgrade_large.c2
18 files changed, 1045 insertions, 426 deletions
diff --git a/.gitignore b/.gitignore
index ff6afe00..88f8ab32 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,7 @@ config.rpath
/config.log
/app.info
/debug
+/build-aux
/exclude
/autom4te.cache
/scripts
diff --git a/ChangeLog b/ChangeLog
index 92161c6f..afedf50c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+Fri 02 Aug 2019 00:00:00 PM UTC
+ Fixes and optimizations for the setsockopt handling:
+ * Added: MHD_UPGRADE_ACTION_CORK_ON and MHD_UPGRADE_ACTION_CORK_OFF
+ to enum MHD_UpgradeAction (turn corking on/off on the underlying
+ socket).
+ * Use calls and flags native to the system for corking and
+ other operations, tested with performance improvements on
+ FreeBSD, Debian Linux, NetBSD, and cygwin. In particular,
+ this adds selective usage of MSG_MORE, NODELAY, TCP_NOPUSH,
+ TCP_CORK. -ng0
+
Thu 01 Aug 2019 01:23:36 PM CEST
Releasing libmicrohttpd 0.9.66. -CG
diff --git a/configure.ac b/configure.ac
index 5292798a..1a0870b7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -642,6 +642,28 @@ AM_CONDITIONAL(USE_MS_LIB_TOOL, [test "x$ac_cv_use_ms_lib_tool" = "xyes"])
MHD_CHECK_SOCKET_SHUTDOWN_TRIGGER([AC_DEFINE([HAVE_LISTEN_SHUTDOWN],[1],[can use shutdown on listen sockets])])
AM_CONDITIONAL([HAVE_LISTEN_SHUTDOWN], [test "x$mhd_cv_host_shtdwn_trgr_select" = "xyes"])
+# SENDMSG. Should we check for SCM_RIGHTS instead?
+# https://lists.x.org/archives/xorg-devel/2013-November/038687.html
+AC_MSG_CHECKING([whether sendmsg is available])
+AC_SEARCH_LIBS(sendmsg, socket, AC_DEFINE([HAVE_SENDMSG],1,[Define if your platform supports sendmsg]))
+AC_MSG_CHECKING([whether writev is available])
+AC_CHECK_FUNCS([writev])
+
+# check MSG_MORE defined
+AC_MSG_CHECKING([whether MSG_MORE is defined])
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+#include <sys/types.h>
+#include <sys/socket.h>
+]],[[return MSG_MORE;]]
+ )],
+ [
+ AC_MSG_RESULT(yes)
+ AC_DEFINE(HAVE_MSG_MORE, [1], [have MSG_MORE])
+ ],
+ [
+ AC_MSG_RESULT(no)
+ ])
+
# set GCC options
# use '-fno-strict-aliasing', but only if the compiler
# and linker can take it
diff --git a/doc/libmicrohttpd.texi b/doc/libmicrohttpd.texi
index 4424337f..d9d53dd7 100644
--- a/doc/libmicrohttpd.texi
+++ b/doc/libmicrohttpd.texi
@@ -2329,6 +2329,10 @@ Set of actions to be performed on upgraded connections. Passed as an argument t
@table @code
@item MHD_UPGRADE_ACTION_CLOSE
Closes the connection. Must be called once the application is done with the client. Takes no additional arguments.
+@item MHD_UPGRADE_ACTION_CORK_ON
+Enable corking on the underlying socket.
+@item MHD_UPGRADE_ACTION_CORK_OFF
+Disable corking on the underlying socket.
@end table
@end deftp
diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h
index c2fc90a4..00288696 100644
--- a/src/include/microhttpd.h
+++ b/src/include/microhttpd.h
@@ -3132,7 +3132,17 @@ enum MHD_UpgradeAction
*
* Takes no extra arguments.
*/
- MHD_UPGRADE_ACTION_CLOSE = 0
+ MHD_UPGRADE_ACTION_CLOSE = 0,
+
+ /**
+ * Enable CORKing on the underlying socket.
+ */
+ MHD_UPGRADE_ACTION_CORK_ON = 1,
+
+ /**
+ * Disable CORKing on the underlying socket.
+ */
+ MHD_UPGRADE_ACTION_CORK_OFF = 2
};
diff --git a/src/microhttpd/Makefile.am b/src/microhttpd/Makefile.am
index 8bc60879..597a2d56 100644
--- a/src/microhttpd/Makefile.am
+++ b/src/microhttpd/Makefile.am
@@ -62,6 +62,7 @@ libmicrohttpd_la_SOURCES = \
mhd_limits.h mhd_byteorder.h \
sysfdsetsize.c sysfdsetsize.h \
mhd_str.c mhd_str.h \
+ mhd_send.h mhd_send.c \
mhd_assert.h \
mhd_sockets.c mhd_sockets.h \
mhd_itc.c mhd_itc.h mhd_itc_types.h \
diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c
index 6f33dbc1..7b630b77 100644
--- a/src/microhttpd/connection.c
+++ b/src/microhttpd/connection.c
@@ -53,7 +53,7 @@
/* For FreeBSD version identification */
#include <sys/param.h>
#endif /* HAVE_SYS_PARAM_H */
-
+#include "mhd_send.h"
/**
* Message to transmit when http 1.1 request is received
@@ -278,187 +278,6 @@ send_param_adapter (struct MHD_Connection *connection,
}
-#if defined(_MHD_HAVE_SENDFILE)
-/**
- * Function for sending responses backed by file FD.
- *
- * @param connection the MHD connection structure
- * @return actual number of bytes sent
- */
-static ssize_t
-sendfile_adapter (struct MHD_Connection *connection)
-{
- ssize_t ret;
- const int file_fd = connection->response->fd;
- uint64_t left;
- uint64_t offsetu64;
-#ifndef HAVE_SENDFILE64
- const uint64_t max_off_t = (uint64_t)OFF_T_MAX;
-#else /* HAVE_SENDFILE64 */
- const uint64_t max_off_t = (uint64_t)OFF64_T_MAX;
-#endif /* HAVE_SENDFILE64 */
-#ifdef MHD_LINUX_SOLARIS_SENDFILE
-#ifndef HAVE_SENDFILE64
- off_t offset;
-#else /* HAVE_SENDFILE64 */
- off64_t offset;
-#endif /* HAVE_SENDFILE64 */
-#endif /* MHD_LINUX_SOLARIS_SENDFILE */
-#ifdef HAVE_FREEBSD_SENDFILE
- off_t sent_bytes;
- int flags = 0;
-#endif
-#ifdef HAVE_DARWIN_SENDFILE
- off_t len;
-#endif /* HAVE_DARWIN_SENDFILE */
- const bool used_thr_p_c = (0 != (connection->daemon->options & MHD_USE_THREAD_PER_CONNECTION));
- const size_t chunk_size = used_thr_p_c ? MHD_SENFILE_CHUNK_THR_P_C_ : MHD_SENFILE_CHUNK_;
- size_t send_size = 0;
- mhd_assert (MHD_resp_sender_sendfile == connection->resp_sender);
-
- offsetu64 = connection->response_write_position + connection->response->fd_off;
- left = connection->response->total_size - connection->response_write_position;
- /* Do not allow system to stick sending on single fast connection:
- * use 128KiB chunks (2MiB for thread-per-connection). */
- send_size = (left > chunk_size) ? chunk_size : (size_t) left;
- if (max_off_t < offsetu64)
- { /* Retry to send with standard 'send()'. */
- connection->resp_sender = MHD_resp_sender_std;
- return MHD_ERR_AGAIN_;
- }
-#ifdef MHD_LINUX_SOLARIS_SENDFILE
-#ifndef HAVE_SENDFILE64
- offset = (off_t) offsetu64;
- ret = sendfile (connection->socket_fd,
- file_fd,
- &offset,
- send_size);
-#else /* HAVE_SENDFILE64 */
- offset = (off64_t) offsetu64;
- ret = sendfile64 (connection->socket_fd,
- file_fd,
- &offset,
- send_size);
-#endif /* HAVE_SENDFILE64 */
- if (0 > ret)
- {
- const int err = MHD_socket_get_error_();
- if (MHD_SCKT_ERR_IS_EAGAIN_(err))
- {
-#ifdef EPOLL_SUPPORT
- /* EAGAIN --- no longer write-ready */
- connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY;
-#endif /* EPOLL_SUPPORT */
- return MHD_ERR_AGAIN_;
- }
- if (MHD_SCKT_ERR_IS_EINTR_ (err))
- return MHD_ERR_AGAIN_;
-#ifdef HAVE_LINUX_SENDFILE
- if (MHD_SCKT_ERR_IS_(err,
- MHD_SCKT_EBADF_))
- return MHD_ERR_BADF_;
- /* sendfile() failed with EINVAL if mmap()-like operations are not
- supported for FD or other 'unusual' errors occurred, so we should try
- to fall back to 'SEND'; see also this thread for info on
- odd libc/Linux behavior with sendfile:
- http://lists.gnu.org/archive/html/libmicrohttpd/2011-02/msg00015.html */
- connection->resp_sender = MHD_resp_sender_std;
- return MHD_ERR_AGAIN_;
-#else /* HAVE_SOLARIS_SENDFILE */
- if ( (EAFNOSUPPORT == err) ||
- (EINVAL == err) ||
- (EOPNOTSUPP == err) )
- { /* Retry with standard file reader. */
- connection->resp_sender = MHD_resp_sender_std;
- return MHD_ERR_AGAIN_;
- }
- if ( (ENOTCONN == err) ||
- (EPIPE == err) )
- {
- return MHD_ERR_CONNRESET_;
- }
- return MHD_ERR_BADF_; /* Fail hard */
-#endif /* HAVE_SOLARIS_SENDFILE */
- }
-#ifdef EPOLL_SUPPORT
- else if (send_size > (size_t)ret)
- connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY;
-#endif /* EPOLL_SUPPORT */
-#elif defined(HAVE_FREEBSD_SENDFILE)
-#ifdef SF_FLAGS
- flags = used_thr_p_c ?
- freebsd_sendfile_flags_thd_p_c_ : freebsd_sendfile_flags_;
-#endif /* SF_FLAGS */
- if (0 != sendfile (file_fd,
- connection->socket_fd,
- (off_t) offsetu64,
- send_size,
- NULL,
- &sent_bytes,
- flags))
- {
- const int err = MHD_socket_get_error_();
- if (MHD_SCKT_ERR_IS_EAGAIN_(err) ||
- MHD_SCKT_ERR_IS_EINTR_(err) ||
- EBUSY == err)
- {
- mhd_assert (SSIZE_MAX >= sent_bytes);
- if (0 != sent_bytes)
- return (ssize_t)sent_bytes;
-
- return MHD_ERR_AGAIN_;
- }
- /* Some unrecoverable error. Possibly file FD is not suitable
- * for sendfile(). Retry with standard send(). */
- connection->resp_sender = MHD_resp_sender_std;
- return MHD_ERR_AGAIN_;
- }
- mhd_assert (0 < sent_bytes);
- mhd_assert (SSIZE_MAX >= sent_bytes);
- ret = (ssize_t)sent_bytes;
-#elif defined(HAVE_DARWIN_SENDFILE)
- len = (off_t)send_size; /* chunk always fit */
- if (0 != sendfile (file_fd,
- connection->socket_fd,
- (off_t) offsetu64,
- &len,
- NULL,
- 0))
- {
- const int err = MHD_socket_get_error_();
- if (MHD_SCKT_ERR_IS_EAGAIN_(err) ||
- MHD_SCKT_ERR_IS_EINTR_(err))
- {
- mhd_assert (0 <= len);
- mhd_assert (SSIZE_MAX >= len);
- mhd_assert (send_size >= (size_t)len);
- if (0 != len)
- return (ssize_t)len;
-
- return MHD_ERR_AGAIN_;
- }
- if (ENOTCONN == err ||
- EPIPE == err)
- return MHD_ERR_CONNRESET_;
- if (ENOTSUP == err ||
- EOPNOTSUPP == err)
- { /* This file FD is not suitable for sendfile().
- * Retry with standard send(). */
- connection->resp_sender = MHD_resp_sender_std;
- return MHD_ERR_AGAIN_;
- }
- return MHD_ERR_BADF_; /* Return hard error. */
- }
- mhd_assert (0 <= len);
- mhd_assert (SSIZE_MAX >= len);
- mhd_assert (send_size >= (size_t)len);
- ret = (ssize_t)len;
-#endif /* HAVE_FREEBSD_SENDFILE */
- return ret;
-}
-#endif /* _MHD_HAVE_SENDFILE */
-
-
/**
* Check whether is possible to force push socket buffer content as
* partial packet.
@@ -501,89 +320,6 @@ _MHD_static_inline bool
socket_start_extra_buffering (struct MHD_Connection *connection)
{
mhd_assert(NULL != connection);
-#if defined(TCP_NODELAY)
- if (connection->sk_tcp_nodelay_on)
- {
- const MHD_SCKT_OPT_BOOL_ off_val = 0;
- /* Enable Nagle's algorithm */
- /* TCP_NODELAY may interfere with TCP_NOPUSH */
- if (0 == setsockopt (connection->socket_fd,
- IPPROTO_TCP,
- TCP_NODELAY,
- (const void *) &off_val,
- sizeof (off_val)))
- {
- connection->sk_tcp_nodelay_on = false;
- }
- }
-#endif /* TCP_NODELAY */
-
-#if defined(MHD_TCP_CORK_NOPUSH)
- if (!connection->sk_tcp_cork_nopush_on)
- {
- const MHD_SCKT_OPT_BOOL_ on_val = 1;
- /* Buffer data before sending (TCP_CORK) or
- * Send only full frames (TCP_NOPUSH) */
- if (0 == setsockopt (connection->socket_fd,
- IPPROTO_TCP,
- MHD_TCP_CORK_NOPUSH,
- (const void *) &on_val,
- sizeof (on_val)))
- {
- connection->sk_tcp_cork_nopush_on = true;
- }
- }
-#endif /* MHD_TCP_CORK_NOPUSH */
-
-#if defined(TCP_NODELAY)
- return connection->sk_tcp_cork_nopush_on && !connection->sk_tcp_nodelay_on;
-#else /* ! TCP_NODELAY */
- return connection->sk_tcp_cork_nopush_on;
-#endif /* ! TCP_NODELAY */
-}
-
-
-/**
- * Activate no buffering mode (no delay sending) on connection socket.
- *
- * @param connection connection to be processed
- * @return true on success, false otherwise
- */
-_MHD_static_inline bool
-socket_start_no_buffering (struct MHD_Connection *connection)
-{
-#if defined(MHD_TCP_CORK_NOPUSH)
- if (connection->sk_tcp_cork_nopush_on)
- {
- const MHD_SCKT_OPT_BOOL_ off_val = 0;
- /* Disable extra buffering */
- if (0 == setsockopt (connection->socket_fd,
- IPPROTO_TCP,
- MHD_TCP_CORK_NOPUSH,
- (const void *) &off_val,
- sizeof (off_val)))
- {
- connection->sk_tcp_cork_nopush_on = false;
- }
- }
-#endif /* MHD_TCP_CORK_NOPUSH */
-
-#if defined(TCP_NODELAY)
- if (!connection->sk_tcp_nodelay_on)
- {
- const MHD_SCKT_OPT_BOOL_ on_val = 1;
- /* Enable sending without delay */
- if (0 == setsockopt (connection->socket_fd,
- IPPROTO_TCP,
- TCP_NODELAY,
- (const void *) &on_val,
- sizeof (on_val)))
- {
- connection->sk_tcp_nodelay_on = true;
- }
- }
-#endif /* TCP_NODELAY */
- return connection->sk_tcp_nodelay_on && !connection->sk_tcp_cork_nopush_on;
}
@@ -601,21 +337,7 @@ socket_start_no_buffering_flush (struct MHD_Connection *connection)
#if defined(TCP_NOPUSH) && !defined(TCP_CORK)
const int dummy = 0;
#endif /* !TCP_CORK */
-#if defined(TCP_CORK) || (defined(__FreeBSD__) && __FreeBSD__+0 >= 9)
- const MHD_SCKT_OPT_BOOL_ off_val = 0;
- /* Switching off TCP_CORK flush buffer even
- * if TCP_CORK was not enabled */
- if (0 == setsockopt (connection->socket_fd,
- IPPROTO_TCP,
- MHD_TCP_CORK_NOPUSH,
- (const void *) &off_val,
- sizeof (off_val)))
- {
- connection->sk_tcp_cork_nopush_on = false;
- }
-#endif /* MHD_TCP_CORK_NOPUSH */
- res = socket_start_no_buffering (connection);
#if defined(__FreeBSD__) && __FreeBSD__+0 >= 9
/* FreeBSD do not need zero-send for flushing starting from version 9 */
#elif defined(TCP_NOPUSH) && !defined(TCP_CORK)
@@ -631,51 +353,6 @@ socket_start_no_buffering_flush (struct MHD_Connection *connection)
/**
- * Activate normal buffering mode on connection socket.
- *
- * @param connection connection to be processed
- * @return true on success, false otherwise
- */
-_MHD_static_inline bool
-socket_start_normal_buffering (struct MHD_Connection *connection)
-{
- mhd_assert(NULL != connection);
-#if defined(MHD_TCP_CORK_NOPUSH)
- if (connection->sk_tcp_cork_nopush_on)
- {
- const MHD_SCKT_OPT_BOOL_ off_val = 0;
- /* Disable extra buffering */
- if (0 == setsockopt (connection->socket_fd,
- IPPROTO_TCP,
- MHD_TCP_CORK_NOPUSH,
- (const void *) &off_val,
- sizeof (off_val)))
- {
- connection->sk_tcp_cork_nopush_on = false;
- }
- }
-#endif /* MHD_TCP_CORK_NOPUSH */
-
-#if defined(TCP_NODELAY)
- if (connection->sk_tcp_nodelay_on)
- {
- const MHD_SCKT_OPT_BOOL_ off_val = 0;
- /* Enable Nagle's algorithm */
- if (0 == setsockopt (connection->socket_fd,
- IPPROTO_TCP,
- TCP_NODELAY,
- (const void *) &off_val,
- sizeof (off_val)))
- {
- connection->sk_tcp_nodelay_on = false;
- }
- }
-#endif /* TCP_NODELAY */
- return !connection->sk_tcp_nodelay_on && !connection->sk_tcp_cork_nopush_on;
-}
-
-
-/**
* Get all of the headers from the request.
*
* @param connection connection to get values from
@@ -3315,11 +2992,12 @@ MHD_connection_handle_write (struct MHD_Connection *connection)
case MHD_CONNECTION_HEADERS_PROCESSED:
return;
case MHD_CONNECTION_CONTINUE_SENDING:
- ret = connection->send_cls (connection,
- &HTTP_100_CONTINUE
- [connection->continue_message_write_offset],
- MHD_STATICSTR_LEN_ (HTTP_100_CONTINUE) -
- connection->continue_message_write_offset);
+ ret = MHD_send_on_connection_ (connection,
+ &HTTP_100_CONTINUE
+ [connection->continue_message_write_offset],
+ MHD_STATICSTR_LEN_ (HTTP_100_CONTINUE) -
+ connection->continue_message_write_offset,
+ MHD_SSO_NO_CORK);
if (ret < 0)
{
if (MHD_ERR_AGAIN_ == ret)
@@ -3349,26 +3027,55 @@ MHD_connection_handle_write (struct MHD_Connection *connection)
mhd_assert (0);
return;
case MHD_CONNECTION_HEADERS_SENDING:
- ret = connection->send_cls (connection,
- &connection->write_buffer
- [connection->write_buffer_send_offset],
- connection->write_buffer_append_offset -
- connection->write_buffer_send_offset);
- if (ret < 0)
- {
- if (MHD_ERR_AGAIN_ == ret)
+ {
+ const size_t wb_ready = connection->write_buffer_append_offset -
+ connection->write_buffer_send_offset;
+
+ /* if the response body is not available, we use MHD_send_on_connection_() */
+ if (NULL != connection->response->crc)
+ {
+ ret = MHD_send_on_connection_ (connection,
+ &connection->write_buffer
+ [connection->write_buffer_send_offset],
+ wb_ready,
+ MHD_SSO_MAY_CORK);
+ }
+ else
+ {
+ ret = MHD_send_on_connection2_ (connection,
+ &connection->write_buffer
+ [connection->write_buffer_send_offset],
+ wb_ready,
+ connection->response->data,
+ connection->response->data_buffer_size);
+ }
+
+ if (ret < 0)
+ {
+ if (MHD_ERR_AGAIN_ == ret)
+ return;
+ CONNECTION_CLOSE_ERROR (connection,
+ _("Connection was closed while sending response headers.\n"));
return;
- CONNECTION_CLOSE_ERROR (connection,
- _("Connection was closed while sending response headers.\n"));
+ }
+ if (ret > wb_ready)
+ {
+ mhd_assert (NULL == connection->repsonse->crc);
+ /* We sent not just header data but also some response data,
+ update both offsets! */
+ connection->write_buffer_send_offset += wb_ready;
+ ret -= wb_ready;
+ connection->response_write_position += ret;
+ }
+ else
+ connection->write_buffer_send_offset += ret;
+ MHD_update_last_activity_ (connection);
+ if (MHD_CONNECTION_HEADERS_SENDING != connection->state)
return;
- }
- connection->write_buffer_send_offset += ret;
- MHD_update_last_activity_ (connection);
- if (MHD_CONNECTION_HEADERS_SENDING != connection->state)
+ check_write_done (connection,
+ MHD_CONNECTION_HEADERS_SENT);
return;
- check_write_done (connection,
- MHD_CONNECTION_HEADERS_SENT);
- return;
+ }
case MHD_CONNECTION_HEADERS_SENT:
return;
case MHD_CONNECTION_NORMAL_BODY_READY:
@@ -3390,7 +3097,7 @@ MHD_connection_handle_write (struct MHD_Connection *connection)
#if defined(_MHD_HAVE_SENDFILE)
if (MHD_resp_sender_sendfile == connection->resp_sender)
{
- ret = sendfile_adapter (connection);
+ ret = MHD_send_sendfile_ (connection);
}
else
#else /* ! _MHD_HAVE_SENDFILE */
@@ -3401,11 +3108,12 @@ MHD_connection_handle_write (struct MHD_Connection *connection)
- response->data_start;
if (data_write_offset > (uint64_t)SIZE_MAX)
MHD_PANIC (_("Data offset exceeds limit"));
- ret = connection->send_cls (connection,
- &response->data
- [(size_t)data_write_offset],
- response->data_size -
- (size_t)data_write_offset);
+ ret = MHD_send_on_connection_ (connection,
+ &response->data
+ [(size_t)data_write_offset],
+ response->data_size -
+ (size_t)data_write_offset,
+ MHD_SSO_NO_CORK);
#if DEBUG_SEND_DATA
if (ret > 0)
fprintf (stderr,
@@ -3444,11 +3152,12 @@ MHD_connection_handle_write (struct MHD_Connection *connection)
mhd_assert (0);
return;
case MHD_CONNECTION_CHUNKED_BODY_READY:
- ret = connection->send_cls (connection,
- &connection->write_buffer
- [connection->write_buffer_send_offset],
- connection->write_buffer_append_offset -
- connection->write_buffer_send_offset);
+ ret = MHD_send_on_connection_ (connection,
+ &connection->write_buffer
+ [connection->write_buffer_send_offset],
+ connection->write_buffer_append_offset -
+ connection->write_buffer_send_offset,
+ MHD_SSO_NO_CORK);
if (ret < 0)
{
if (MHD_ERR_AGAIN_ == ret)
@@ -3472,11 +3181,12 @@ MHD_connection_handle_write (struct MHD_Connection *connection)
mhd_assert (0);
return;
case MHD_CONNECTION_FOOTERS_SENDING:
- ret = connection->send_cls (connection,
- &connection->write_buffer
- [connection->write_buffer_send_offset],
- connection->write_buffer_append_offset -
- connection->write_buffer_send_offset);
+ ret = MHD_send_on_connection_ (connection,
+ &connection->write_buffer
+ [connection->write_buffer_send_offset],
+ connection->write_buffer_append_offset -
+ connection->write_buffer_send_offset,
+ MHD_SSO_HDR_CORK);
if (ret < 0)
{
if (MHD_ERR_AGAIN_ == ret)
@@ -3723,11 +3433,6 @@ MHD_connection_handle_idle (struct MHD_Connection *connection)
if (need_100_continue (connection))
{
connection->state = MHD_CONNECTION_CONTINUE_SENDING;
- if (socket_flush_possible (connection))
- socket_start_extra_buffering (connection);
- else
- socket_start_no_buffering (connection);
-
break;
}
if ( (NULL != connection->response) &&
@@ -3752,9 +3457,7 @@ MHD_connection_handle_idle (struct MHD_Connection *connection)
{
connection->state = MHD_CONNECTION_CONTINUE_SENT;
if (socket_flush_possible (connection))
- socket_start_no_buffering_flush (connection);
- else
- socket_start_normal_buffering (connection);
+ socket_start_no_buffering_flush (connection); /* REMOVE: Dead */
continue;
}
@@ -3855,11 +3558,6 @@ MHD_connection_handle_idle (struct MHD_Connection *connection)
continue;
}
connection->state = MHD_CONNECTION_HEADERS_SENDING;
- if (socket_flush_possible (connection))
- socket_start_extra_buffering (connection);
- else
- socket_start_no_buffering (connection);
-
break;
case MHD_CONNECTION_HEADERS_SENDING:
/* no default action */
@@ -3867,12 +3565,11 @@ MHD_connection_handle_idle (struct MHD_Connection *connection)
case MHD_CONNECTION_HEADERS_SENT:
/* Some clients may take some actions right after header receive */
if (socket_flush_possible (connection))
- socket_start_no_buffering_flush (connection);
+ socket_start_no_buffering_flush (connection); /* REMOVE: Dead */
#ifdef UPGRADE_SUPPORT
if (NULL != connection->response->upgrade_handler)
{
- socket_start_normal_buffering (connection);
connection->state = MHD_CONNECTION_UPGRADE;
/* This connection is "upgraded". Pass socket to application. */
if (MHD_YES !=
@@ -3894,10 +3591,6 @@ MHD_connection_handle_idle (struct MHD_Connection *connection)
continue;
}
#endif /* UPGRADE_SUPPORT */
- if (socket_flush_possible (connection))
- socket_start_extra_buffering (connection);
- else
- socket_start_normal_buffering (connection);
if (connection->have_chunked_upload)
connection->state = MHD_CONNECTION_CHUNKED_BODY_UNREADY;
@@ -3929,8 +3622,7 @@ MHD_connection_handle_idle (struct MHD_Connection *connection)
#endif
connection->state = MHD_CONNECTION_NORMAL_BODY_READY;
/* Buffering for flushable socket was already enabled*/
- if (socket_flush_possible (connection))
- socket_start_no_buffering (connection);
+
break;
}
/* mutex was already unlocked by "try_ready_normal_body */
@@ -3963,8 +3655,7 @@ MHD_connection_handle_idle (struct MHD_Connection *connection)
#endif
connection->state = MHD_CONNECTION_CHUNKED_BODY_READY;
/* Buffering for flushable socket was already enabled */
- if (socket_flush_possible (connection))
- socket_start_no_buffering (connection);
+
continue;
}
/* mutex was already unlocked by try_ready_chunked_body */
@@ -3998,9 +3689,7 @@ MHD_connection_handle_idle (struct MHD_Connection *connection)
continue;
}
if (socket_flush_possible (connection))
- socket_start_no_buffering_flush (connection);
- else
- socket_start_normal_buffering (connection);
+ socket_start_no_buffering_flush (connection); /* REMOVE: Dead */
MHD_destroy_response (connection->response);
connection->response = NULL;
@@ -4028,8 +3717,7 @@ MHD_connection_handle_idle (struct MHD_Connection *connection)
else
{
/* can try to keep-alive */
- if (socket_flush_possible (connection))
- socket_start_normal_buffering (connection);
+
connection->version = NULL;
connection->state = MHD_CONNECTION_INIT;
connection->last = NULL;
diff --git a/src/microhttpd/connection_https.c b/src/microhttpd/connection_https.c
index 5efced33..8202329b 100644
--- a/src/microhttpd/connection_https.c
+++ b/src/microhttpd/connection_https.c
@@ -98,7 +98,7 @@ recv_tls_adapter (struct MHD_Connection *connection,
* @return positive value for number of bytes actually sent or
* negative value for error number MHD_ERR_xxx_
*/
-static ssize_t
+ssize_t
send_tls_adapter (struct MHD_Connection *connection,
const void *other,
size_t i)
diff --git a/src/microhttpd/connection_https.h b/src/microhttpd/connection_https.h
index 1c12ea9f..e91a84d3 100644
--- a/src/microhttpd/connection_https.h
+++ b/src/microhttpd/connection_https.h
@@ -60,6 +60,20 @@ MHD_run_tls_handshake_ (struct MHD_Connection *connection);
*/
bool
MHD_tls_connection_shutdown (struct MHD_Connection *connection);
+
+/**
+ * Callback for writing data to the socket.
+ *
+ * @param connection the MHD connection structure
+ * @param other data to write
+ * @param i number of bytes to write
+ * @return positive value for number of bytes actually sent or
+ * negative value for error number MHD_ERR_xxx_
+ */
+ssize_t
+send_tls_adapter (struct MHD_Connection *connection,
+ const void *other,
+ size_t i);
#endif /* HTTPS_SUPPORT */
#endif
diff --git a/src/microhttpd/daemon.c b/src/microhttpd/daemon.c
index a8fc98c6..fb4abd5d 100644
--- a/src/microhttpd/daemon.c
+++ b/src/microhttpd/daemon.c
@@ -3229,6 +3229,19 @@ MHD_accept_connection (struct MHD_Daemon *daemon)
}
return MHD_NO;
}
+#if defined(MHD_TCP_CORK_NOPUSH) || defined(HAVE_MSG_MORE)
+ /* We will use TCP_CORK or TCP_NOPUSH or MSG_MORE to control
+ transmission, disable Nagle's algorithm (always) */
+ if (0 != MHD_socket_set_nodelay_ (s,
+ true))
+ {
+#ifdef HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ _("Failed to disable TCP Nagle on socket: %s\n"),
+ MHD_socket_last_strerr_());
+ }
+#endif
+#endif
#if !defined(USE_ACCEPT4) || !defined(HAVE_SOCK_NONBLOCK)
if (! MHD_socket_nonblocking_ (s))
{
@@ -6228,7 +6241,6 @@ MHD_start_daemon_va (unsigned int flags,
}
}
#endif /* HAVE_GETSOCKNAME */
-
if ( (MHD_INVALID_SOCKET != listen_fd) &&
(! MHD_socket_nonblocking_ (listen_fd)) )
{
diff --git a/src/microhttpd/internal.h b/src/microhttpd/internal.h
index 1f5aeaf3..ac43d819 100644
--- a/src/microhttpd/internal.h
+++ b/src/microhttpd/internal.h
@@ -871,19 +871,13 @@ struct MHD_Connection
/**
* true if #socket_fd is non-blocking, false otherwise.
*/
- bool sk_nonblck;
+ bool sk_nonblck; // FIXME: hopefully dead?
/**
- * Indicate whether connection socket has TCP_NODELAY turned on / Nagle’s algorithm turned off.
- * TCP_NODELAY should not be turned on when TCP_CORK/TCP_NOPUSH is turned off.
+ * Indicate whether connection socket has TCP_CORK / Nagle’s algorithm turned on/off
+ * on this socket.
*/
- bool sk_tcp_nodelay_on;
-
- /**
- * Indicate whether connection socket has TCP_CORK/TCP_NOPUSH turned on.
- * TCP_CORK/TCP_NOPUSH should not be turned on when TCP_NODELAY is turned off.
- */
- bool sk_tcp_cork_nopush_on;
+ bool sk_cork_on;
/**
* Has this socket been closed for reading (i.e. other side closed
diff --git a/src/microhttpd/mhd_send.c b/src/microhttpd/mhd_send.c
new file mode 100644
index 00000000..928e92cb
--- /dev/null
+++ b/src/microhttpd/mhd_send.c
@@ -0,0 +1,637 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2019 ng0 <ng0@n0.is>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+ */
+
+/**
+ * @file microhttpd/mhd_send.c
+ * @brief Implementation of send() wrappers.
+ * @author ng0 (N. Gillmann)
+ * @author Christian Grothoff
+ * @author Evgeny Grin
+ */
+
+/* Worth considering for future improvements and additions:
+ * NetBSD has no sendfile or sendfile64. The way to work
+ * with this seems to be to mmap the file and write(2) as
+ * large a chunk as possible to the socket. Alternatively,
+ * use madvise(..., MADV_SEQUENTIAL). */
+
+/* Functions to be used in: send_param_adapter, MHD_send_
+ * and every place where sendfile(), sendfile64(), setsockopt()
+ * are used. */
+
+#include "mhd_send.h"
+
+/**
+ * Handle setsockopt calls.
+ *
+ * @param connection the MHD_Connection structure
+ * @param want_cork cork state, boolean
+ */
+static void
+pre_cork_setsockopt (struct MHD_Connection *connection,
+ bool want_cork)
+{
+#if HAVE_MSG_MORE
+ /* We use the MSG_MORE option for corking, no need for extra syscalls! */
+#elif defined(MHD_TCP_CORK_NOPUSH)
+ int ret;
+
+ /* If sk_cork_on is already what we pass in, return. */
+ if (connection->sk_cork_on == want_cork)
+ {
+ /* nothing to do, success! */
+ return;
+ }
+ if (! want_cork)
+ return; /* nothing to do *pre* syscall! */
+ ret = MHD_socket_cork_ (connection->socket_fd,
+ true);
+ if (0 == ret)
+ {
+ connection->sk_cork_on = true;
+ return;
+ }
+ switch (errno)
+ {
+ case ENOTSOCK:
+ /* FIXME: Could be we are talking to a pipe, maybe remember this
+ and avoid all setsockopt() in the future? */
+ break;
+ case EBADF:
+ /* FIXME: should we die hard here? */
+ break;
+ case EINVAL:
+ /* FIXME: optlen invalid, should at least log this, maybe die */
+ break;
+ case EFAULT:
+ /* wopsie, should at leats log this, FIXME: maybe die */
+#ifdef HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ _("The addresss pointed to by optval is not a valid part of the process address space: %s\n"),
+ MHD_socket_last_strerr_());
+#endif
+ break;
+ case ENOPROTOOPT:
+ /* optlen unknown, should at least log this */
+#ifdef HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ _("The option is unknown: %s\n"),
+ MHD_socket_last_strerr_());
+#endif
+ break;
+ default:
+ /* any others? man page does not list more... */
+ break;
+ }
+#else
+ /* CORK/NOPUSH/MSG_MORE do not exist on this platform,
+ so we must toggle Naggle's algorithm on/off instead
+ (otherwise we keep it always off) */
+ if (connection->sk_cork_on == want_cork)
+ {
+ /* nothing to do, success! */
+ return;
+ }
+ if ( (want_cork) &&
+ (0 == MHD_socket_set_nodelay_ (connection->socket_fd,
+ false)) )
+ connection->sk_cork_on = true;
+#endif
+}
+
+
+/**
+ * Handle setsockopt calls.
+ *
+ * @param connection the MHD_Connection structure
+ * @param want_cork cork state, boolean
+ */
+static void
+post_cork_setsockopt (struct MHD_Connection *connection,
+ bool want_cork)
+{
+#if HAVE_MSG_MORE
+ /* We use the MSG_MORE option for corking, no need for extra syscalls! */
+#elif defined(MHD_TCP_CORK_NOPUSH)
+ int ret;
+
+ /* If sk_cork_on is already what we pass in, return. */
+ if (connection->sk_cork_on == want_cork)
+ {
+ /* nothing to do, success! */
+ return;
+ }
+ if (want_cork)
+ return; /* nothing to do *post* syscall (in fact, we should never
+ get here, as sk_cork_on should have succeeded in the
+ pre-syscall) */
+ ret = MHD_socket_cork_ (connection->socket_fd,
+ false);
+ if (0 == ret)
+ {
+ connection->sk_cork_on = false;
+ return;
+ }
+ switch (errno)
+ {
+ case ENOTSOCK:
+ /* FIXME: Could be we are talking to a pipe, maybe remember this
+ and avoid all setsockopt() in the future? */
+ break;
+ case EBADF:
+ /* FIXME: should we die hard here? */
+ break;
+ case EINVAL:
+ /* FIXME: optlen invalid, should at least log this, maybe die */
+ break;
+ case EFAULT:
+ /* wopsie, should at leats log this, FIXME: maybe die */
+#ifdef HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ _("The addresss pointed to by optval is not a valid part of the process address space: %s\n"),
+ MHD_socket_last_strerr_());
+#endif
+ break;
+ case ENOPROTOOPT:
+ /* optlen unknown, should at least log this */
+#ifdef HAVE_MESSAGES
+ MHD_DLOG (daemon,
+ _("The option is unknown: %s\n"),
+ MHD_socket_last_strerr_());
+#endif
+ break;
+ default:
+ /* any others? man page does not list more... */
+ break;
+ }
+#else
+ /* CORK/NOPUSH/MSG_MORE do not exist on this platform,
+ so we must toggle Naggle's algorithm on/off instead
+ (otherwise we keep it always off) */
+ if (connection->sk_cork_on == want_cork)
+ {
+ /* nothing to do, success! */
+ return;
+ }
+ if ( (! want_cork) &&
+ (0 == MHD_socket_set_nodelay_ (connection->socket_fd,
+ true)) )
+ connection->sk_cork_on = false;
+#endif
+}
+
+
+/**
+ * Send buffer on connection, and remember the current state of
+ * the socket options; only call setsockopt when absolutely
+ * necessary.
+ *
+ * @param connection the MHD_Connection structure
+ * @param buffer content of the buffer to send
+ * @param buffer_size the size of the buffer (in bytes)
+ * @param options the #MHD_SendSocketOptions enum,
+ * #MHD_SSO_NO_CORK: definitely no corking (use NODELAY, or explicitly disable cork),
+ * #MHD_SSO_MAY_CORK: should enable corking (use MSG_MORE, or explicitly enable cork),
+ * #MHD_SSO_HDR_CORK: consider tcpi_snd_mss and consider not corking for the header
+ * part if the size of the header is close to the MSS.
+ * Only used if we are NOT doing 100 Continue and are still sending the
+ * header (provided in full as the buffer to #MHD_send_on_connection_ or as
+ * the header to #MHD_send_on_connection2_).
+ * @return sum of the number of bytes sent from both buffers or
+ * -1 on error
+ */
+ssize_t
+MHD_send_on_connection_ (struct MHD_Connection *connection,
+ const char *buffer,
+ size_t buffer_size,
+ enum MHD_SendSocketOptions options)
+{
+ bool want_cork;
+ MHD_socket s = connection->socket_fd;
+ ssize_t ret;
+
+ /* error handling from send_param_adapter() */
+ if ((MHD_INVALID_SOCKET == s) || (MHD_CONNECTION_CLOSED == connection->state))
+ {
+ return MHD_ERR_NOTCONN_;
+ }
+
+ /* from send_param_adapter() */
+ if (buffer_size > MHD_SCKT_SEND_MAX_SIZE_)
+ buffer_size = MHD_SCKT_SEND_MAX_SIZE_; /* return value limit */
+
+ /* Get socket options, change/set options if necessary. */
+ switch (options)
+ {
+ /* No corking */
+ case MHD_SSO_NO_CORK:
+ want_cork = false;
+ break;
+ /* Do corking, consider MSG_MORE instead if available. */
+ case MHD_SSO_MAY_CORK:
+ want_cork = true;
+ break;
+ /* Cork the header. */
+ case MHD_SSO_HDR_CORK:
+ want_cork = (buffer_size <= 1024);
+ break;
+ }
+
+#ifdef HTTPS_SUPPORT
+ if (0 != (connection->daemon->options & MHD_USE_TLS))
+ {
+ bool have_cork = connection->sk_cork_on;
+
+ if (want_cork && ! have_cork)
+ {
+ gnutls_record_cork (connection->tls_session);
+ connection->sk_cork_on = true;
+ }
+ if (buffer_size > SSIZE_MAX)
+ buffer_size = SSIZE_MAX;
+ ret = gnutls_record_send (connection->tls_session,
+ buffer,
+ buffer_size);
+ if ( (GNUTLS_E_AGAIN == ret) ||
+ (GNUTLS_E_INTERRUPTED == ret) )
+ {
+#ifdef EPOLL_SUPPORT
+ if (GNUTLS_E_AGAIN == ret)
+ connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY;
+#endif
+ return MHD_ERR_AGAIN_;
+ }
+ if (ret < 0)
+ {
+ /* Likely 'GNUTLS_E_INVALID_SESSION' (client communication
+ disrupted); interpret as a hard error */
+ return MHD_ERR_NOTCONN_;
+ }
+#ifdef EPOLL_SUPPORT
+ /* Unlike non-TLS connections, do not reset "write-ready" if
+ * sent amount smaller than provided amount, as TLS
+ * connections may break data into smaller parts for sending. */
+#endif /* EPOLL_SUPPORT */
+
+ if (! want_cork && have_cork)
+ {
+ (void) gnutls_record_uncork (connection->tls_session, 0);
+ connection->sk_cork_on = false;
+ }
+ }
+ else
+#endif /* HTTPS_SUPPORT */
+ {
+ /* plaintext transmission */
+ pre_cork_setsockopt (connection, want_cork);
+#if HAVE_MSG_MORE
+ ret = send (s,
+ buffer,
+ buffer_size,
+ MAYBE_MSG_NOSIGNAL | (want_cork ? MSG_MORE : 0));
+#else
+ ret = send (connection->socket_fd,
+ buffer,
+ buffer_size,
+ MAYBE_MSG_NOSIGNAL);
+#endif
+
+ if (0 > ret)
+ {
+ const int err = MHD_socket_get_error_ ();
+
+ if (MHD_SCKT_ERR_IS_EAGAIN_ (err))
+ {
+#if EPOLL_SUPPORT
+ /* EAGAIN, no longer write-ready */
+ connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY;
+#endif /* EPOLL_SUPPORT */
+ return MHD_ERR_AGAIN_;
+ }
+ if (MHD_SCKT_ERR_IS_EINTR_ (err))
+ return MHD_ERR_AGAIN_;
+ if (MHD_SCKT_ERR_IS_ (err, MHD_SCKT_ECONNRESET_))
+ return MHD_ERR_CONNRESET_;
+ /* Treat any other error as hard error. */
+ return MHD_ERR_NOTCONN_;
+ }
+#if EPOLL_SUPPORT
+ else if (buffer_size > (size_t) ret)
+ connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY;
+#endif /* EPOLL_SUPPORT */
+ if (ret == buffer_size)
+ post_cork_setsockopt (connection, want_cork);
+ }
+
+ return ret;
+}
+
+
+/**
+ * Send header followed by buffer on connection.
+ * Uses writev if possible to send both at once
+ * and returns the sum of the number of bytes sent from
+ * both buffers, or -1 on error;
+ * if writev is unavailable, this call MUST only send from 'header'
+ * (as we cannot handle the case that the first write
+ * succeeds and the 2nd fails!).
+ *
+ * @param connection the MHD_Connection structure
+ * @param header content of header to send
+ * @param header_size the size of the header (in bytes)
+ * @param buffer content of the buffer to send
+ * @param buffer_size the size of the buffer (in bytes)
+ * @return sum of the number of bytes sent from both buffers or
+ * -1 on error
+ */
+ssize_t
+MHD_send_on_connection2_ (struct MHD_Connection *connection,
+ const char *header,
+ size_t header_size,
+ const char *buffer,
+ size_t buffer_size)
+{
+#ifdef HTTPS_SUPPORT
+ if (0 != (connection->daemon->options & MHD_USE_TLS))
+ return MHD_send_on_connection_ (connection,
+ header,
+ header_size,
+ MHD_SSO_HDR_CORK);
+#endif
+#if defined(HAVE_SENDMSG) || defined(HAVE_WRITEV)
+ MHD_socket s = connection->socket_fd;
+ ssize_t ret;
+ struct iovec vector[2];
+
+ /* Since we generally give the fully answer, we do not want
+ corking to happen */
+ pre_cork_setsockopt (connection, false);
+
+ vector[0].iov_base = (void *) header;
+ vector[0].iov_len = header_size;
+ vector[1].iov_base = (void *) buffer;
+ vector[1].iov_len = buffer_size;
+
+#if HAVE_SENDMSG
+ {
+ struct msghdr msg;
+
+ memset(&msg, 0, sizeof(struct msghdr));
+ msg.msg_iov = vector;
+ msg.msg_iovlen = 2;
+
+ ret = sendmsg (s, &msg, MAYBE_MSG_NOSIGNAL);
+ }
+#elif HAVE_WRITEV
+ {
+ int iovcnt;
+
+ iovcnt = sizeof (vector) / sizeof (struct iovec);
+ ret = writev (s, vector, iovcnt);
+ }
+#endif
+
+ /* Only if we succeeded sending the full buffer, we need to make sure that
+ the OS flushes at the end */
+ if (ret == header_size + buffer_size)
+ post_cork_setsockopt (connection, false);
+
+ return ret;
+
+#else
+ return MHD_send_on_connection_ (connection,
+ header,
+ header_size,
+ MHD_SSO_HDR_CORK);
+#endif
+}
+
+/**
+ * sendfile() chuck size
+ */
+#define MHD_SENFILE_CHUNK_ (0x20000)
+
+/**
+ * sendfile() chuck size for thread-per-connection
+ */
+#define MHD_SENFILE_CHUNK_THR_P_C_ (0x200000)
+
+#ifdef HAVE_FREEBSD_SENDFILE
+#ifdef SF_FLAGS
+/**
+ * FreeBSD sendfile() flags
+ */
+static int freebsd_sendfile_flags_;
+
+/**
+ * FreeBSD sendfile() flags for thread-per-connection
+ */
+static int freebsd_sendfile_flags_thd_p_c_;
+#endif /* SF_FLAGS */
+
+#endif /* HAVE_FREEBSD_SENDFILE */
+
+#if defined(_MHD_HAVE_SENDFILE)
+/**
+ * Function for sending responses backed by file FD.
+ *
+ * @param connection the MHD connection structure
+ * @return actual number of bytes sent
+ */
+ssize_t
+MHD_send_sendfile_ (struct MHD_Connection *connection)
+{
+ ssize_t ret;
+ const int file_fd = connection->response->fd;
+ uint64_t left;
+ uint64_t offsetu64;
+#ifndef HAVE_SENDFILE64
+ const uint64_t max_off_t = (uint64_t)OFF_T_MAX;
+#else /* HAVE_SENDFILE64 */
+ const uint64_t max_off_t = (uint64_t)OFF64_T_MAX;
+#endif /* HAVE_SENDFILE64 */
+#ifdef MHD_LINUX_SOLARIS_SENDFILE
+#ifndef HAVE_SENDFILE64
+ off_t offset;
+#else /* HAVE_SENDFILE64 */
+ off64_t offset;
+#endif /* HAVE_SENDFILE64 */
+#endif /* MHD_LINUX_SOLARIS_SENDFILE */
+#ifdef HAVE_FREEBSD_SENDFILE
+ off_t sent_bytes;
+ int flags = 0;
+#endif
+#ifdef HAVE_DARWIN_SENDFILE
+ off_t len;
+#endif /* HAVE_DARWIN_SENDFILE */
+ const bool used_thr_p_c = (0 != (connection->daemon->options & MHD_USE_THREAD_PER_CONNECTION));
+ const size_t chunk_size = used_thr_p_c ? MHD_SENFILE_CHUNK_THR_P_C_ : MHD_SENFILE_CHUNK_;
+ size_t send_size = 0;
+ mhd_assert (MHD_resp_sender_sendfile == connection->resp_sender);
+
+ pre_cork_setsockopt (connection, false);
+
+ offsetu64 = connection->response_write_position + connection->response->fd_off;
+ left = connection->response->total_size - connection->response_write_position;
+ /* Do not allow system to stick sending on single fast connection:
+ * use 128KiB chunks (2MiB for thread-per-connection). */
+ send_size = (left > chunk_size) ? chunk_size : (size_t) left;
+ if (max_off_t < offsetu64)
+ { /* Retry to send with standard 'send()'. */
+ connection->resp_sender = MHD_resp_sender_std;
+ return MHD_ERR_AGAIN_;
+ }
+#ifdef MHD_LINUX_SOLARIS_SENDFILE
+#ifndef HAVE_SENDFILE64
+ offset = (off_t) offsetu64;
+ ret = sendfile (connection->socket_fd,
+ file_fd,
+ &offset,
+ send_size);
+#else /* HAVE_SENDFILE64 */
+ offset = (off64_t) offsetu64;
+ ret = sendfile64 (connection->socket_fd,
+ file_fd,
+ &offset,
+ send_size);
+#endif /* HAVE_SENDFILE64 */
+ if (0 > ret)
+ {
+ const int err = MHD_socket_get_error_();
+ if (MHD_SCKT_ERR_IS_EAGAIN_(err))
+ {
+#ifdef EPOLL_SUPPORT
+ /* EAGAIN --- no longer write-ready */
+ connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY;
+#endif /* EPOLL_SUPPORT */
+ return MHD_ERR_AGAIN_;
+ }
+ if (MHD_SCKT_ERR_IS_EINTR_ (err))
+ return MHD_ERR_AGAIN_;
+#ifdef HAVE_LINUX_SENDFILE
+ if (MHD_SCKT_ERR_IS_(err,
+ MHD_SCKT_EBADF_))
+ return MHD_ERR_BADF_;
+ /* sendfile() failed with EINVAL if mmap()-like operations are not
+ supported for FD or other 'unusual' errors occurred, so we should try
+ to fall back to 'SEND'; see also this thread for info on
+ odd libc/Linux behavior with sendfile:
+ http://lists.gnu.org/archive/html/libmicrohttpd/2011-02/msg00015.html */
+ connection->resp_sender = MHD_resp_sender_std;
+ return MHD_ERR_AGAIN_;
+#else /* HAVE_SOLARIS_SENDFILE */
+ if ( (EAFNOSUPPORT == err) ||
+ (EINVAL == err) ||
+ (EOPNOTSUPP == err) )
+ { /* Retry with standard file reader. */
+ connection->resp_sender = MHD_resp_sender_std;
+ return MHD_ERR_AGAIN_;
+ }
+ if ( (ENOTCONN == err) ||
+ (EPIPE == err) )
+ {
+ return MHD_ERR_CONNRESET_;
+ }
+ return MHD_ERR_BADF_; /* Fail hard */
+#endif /* HAVE_SOLARIS_SENDFILE */
+ }
+#ifdef EPOLL_SUPPORT
+ else if (send_size > (size_t)ret)
+ connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY;
+#endif /* EPOLL_SUPPORT */
+#elif defined(HAVE_FREEBSD_SENDFILE)
+#ifdef SF_FLAGS
+ flags = used_thr_p_c ?
+ freebsd_sendfile_flags_thd_p_c_ : freebsd_sendfile_flags_;
+#endif /* SF_FLAGS */
+ if (0 != sendfile (file_fd,
+ connection->socket_fd,
+ (off_t) offsetu64,
+ send_size,
+ NULL,
+ &sent_bytes,
+ flags))
+ {
+ const int err = MHD_socket_get_error_();
+ if (MHD_SCKT_ERR_IS_EAGAIN_(err) ||
+ MHD_SCKT_ERR_IS_EINTR_(err) ||
+ EBUSY == err)
+ {
+ mhd_assert (SSIZE_MAX >= sent_bytes);
+ if (0 != sent_bytes)
+ return (ssize_t)sent_bytes;
+
+ return MHD_ERR_AGAIN_;
+ }
+ /* Some unrecoverable error. Possibly file FD is not suitable
+ * for sendfile(). Retry with standard send(). */
+ connection->resp_sender = MHD_resp_sender_std;
+ return MHD_ERR_AGAIN_;
+ }
+ mhd_assert (0 < sent_bytes);
+ mhd_assert (SSIZE_MAX >= sent_bytes);
+ ret = (ssize_t)sent_bytes;
+#elif defined(HAVE_DARWIN_SENDFILE)
+ len = (off_t)send_size; /* chunk always fit */
+ if (0 != sendfile (file_fd,
+ connection->socket_fd,
+ (off_t) offsetu64,
+ &len,
+ NULL,
+ 0))
+ {
+ const int err = MHD_socket_get_error_();
+ if (MHD_SCKT_ERR_IS_EAGAIN_(err) ||
+ MHD_SCKT_ERR_IS_EINTR_(err))
+ {
+ mhd_assert (0 <= len);
+ mhd_assert (SSIZE_MAX >= len);
+ mhd_assert (send_size >= (size_t)len);
+ if (0 != len)
+ return (ssize_t)len;
+
+ return MHD_ERR_AGAIN_;
+ }
+ if (ENOTCONN == err ||
+ EPIPE == err)
+ return MHD_ERR_CONNRESET_;
+ if (ENOTSUP == err ||
+ EOPNOTSUPP == err)
+ { /* This file FD is not suitable for sendfile().
+ * Retry with standard send(). */
+ connection->resp_sender = MHD_resp_sender_std;
+ return MHD_ERR_AGAIN_;
+ }
+ return MHD_ERR_BADF_; /* Return hard error. */
+ }
+ mhd_assert (0 <= len);
+ mhd_assert (SSIZE_MAX >= len);
+ mhd_assert (send_size >= (size_t)len);
+ ret = (ssize_t)len;
+#endif /* HAVE_FREEBSD_SENDFILE */
+
+ /* Make sure we send the data without delay ONLY if we
+ provided the complete response (not on partial write) */
+ if (ret == left)
+ post_cork_setsockopt (connection, false);
+
+ return ret;
+}
+#endif /* _MHD_HAVE_SENDFILE */
diff --git a/src/microhttpd/mhd_send.h b/src/microhttpd/mhd_send.h
new file mode 100644
index 00000000..a766c8c0
--- /dev/null
+++ b/src/microhttpd/mhd_send.h
@@ -0,0 +1,97 @@
+/*
+ This file is part of libmicrohttpd
+ Copyright (C) 2019 ng0
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+*/
+
+/**
+ * @file mhd_send.h
+ * @brief Implementation of send() wrappers.
+ * @author ng0
+ */
+
+#ifndef MHD_SEND_H
+#define MHD_SEND_H
+
+#include "platform.h"
+#include "internal.h"
+#if defined(HAVE_STDBOOL_H)
+#include <stdbool.h>
+#endif /* HAVE_STDBOOL_H */
+#include <errno.h>
+#include "mhd_sockets.h"
+#include "connection.h"
+#include "connection_https.h"
+
+#ifdef MHD_LINUX_SOLARIS_SENDFILE
+#include <sys/sendfile.h>
+#endif /* MHD_LINUX_SOLARIS_SENDFILE */
+#if defined(HAVE_FREEBSD_SENDFILE) || defined(HAVE_DARWIN_SENDFILE)
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#endif /* HAVE_FREEBSD_SENDFILE || HAVE_DARWIN_SENDFILE */
+
+#ifdef HAVE_SYS_PARAM_H
+/* For FreeBSD version identification */
+#include <sys/param.h>
+#endif /* HAVE_SYS_PARAM_H */
+
+/**
+ * The enumeration of send socket options.
+ */
+enum MHD_SendSocketOptions
+{
+ /**
+ * definitely no corking (use NODELAY, or explicitly disable cork)
+ */
+ MHD_SSO_NO_CORK = 0,
+ /**
+ * should enable corking (use MSG_MORE, or explicitly enable cork)
+ */
+ MHD_SSO_MAY_CORK = 1,
+ /**
+ * consider tcpi_snd_mss and consider not corking for the header
+ * part if the size of the header is close to the MSS.
+ * Only used if we are NOT doing 100 Continue and are still
+ * sending the header (provided in full as the buffer to
+ * MHD_send_on_connection_ or as the header to
+ * MHD_send_on_connection2_).
+ */
+ MHD_SSO_HDR_CORK = 2
+};
+
+
+ssize_t
+MHD_send_on_connection_ (struct MHD_Connection *connection,
+ const char *buffer,
+ size_t buffer_size,
+ enum MHD_SendSocketOptions options);
+
+ssize_t
+MHD_send_on_connection2_ (struct MHD_Connection *connection,
+ const char *header,
+ size_t header_size,
+ const char *buffer,
+ size_t buffer_size);
+
+#if defined(_MHD_HAVE_SENDFILE)
+ssize_t
+MHD_send_sendfile_ (struct MHD_Connection *connection);
+#endif
+
+#endif /* MHD_SEND_H */
diff --git a/src/microhttpd/mhd_sockets.c b/src/microhttpd/mhd_sockets.c
index 04405945..b4e73480 100644
--- a/src/microhttpd/mhd_sockets.c
+++ b/src/microhttpd/mhd_sockets.c
@@ -462,6 +462,64 @@ MHD_socket_noninheritable_ (MHD_socket sock)
/**
+ * Disable Nagle's algorithm on @a sock. This is what we do by default for
+ * all TCP sockets in MHD, unless the platform does not support the MSG_MORE
+ * or MSG_CORK or MSG_NOPUSH options.
+ *
+ * @param sock socket to manipulate
+ * @param on value to use
+ * @return 0 on success
+ */
+int
+MHD_socket_set_nodelay_ (MHD_socket sock,
+ bool on)
+{
+#ifdef TCP_NODELAY
+ {
+ const MHD_SCKT_OPT_BOOL_ off_val = 0;
+ const MHD_SCKT_OPT_BOOL_ on_val = 1;
+ /* Disable Nagle's algorithm for normal buffering */
+ return setsockopt (sock,
+ IPPROTO_TCP,
+ TCP_NODELAY,
+ (const void *) (on) ? &on_val : &off_val,
+ sizeof (on_val));
+ }
+#else
+ (void) sock;
+ return 0;
+#endif /* TCP_NODELAY */
+}
+
+
+/**
+ * Enable/disable the cork option.
+ *
+ * @param sock socket to manipulate
+ * @param on set to true to enable CORK, false to disable
+ * @return non-zero if succeeded, zero otherwise
+ */
+int
+MHD_socket_cork_ (MHD_socket sock,
+ bool on)
+{
+#if defined(MHD_TCP_CORK_NOPUSH)
+ const MHD_SCKT_OPT_BOOL_ off_val = 0;
+ const MHD_SCKT_OPT_BOOL_ on_val = 1;
+
+ /* Disable extra buffering */
+ return (0 == setsockopt (sock,
+ IPPROTO_TCP,
+ MHD_TCP_CORK_NOPUSH,
+ (const void *) (on ? &on_val : &off_val),
+ sizeof (off_val)));
+#else
+ return 0;
+#endif /* MHD_TCP_CORK_NOPUSH */
+}
+
+
+/**
* Change socket buffering mode to default.
*
* @param sock socket to manipulate
@@ -470,29 +528,19 @@ MHD_socket_noninheritable_ (MHD_socket sock)
int
MHD_socket_buffering_reset_ (MHD_socket sock)
{
- int res = !0;
-#if defined(TCP_NODELAY) || defined(MHD_TCP_CORK_NOPUSH)
- const MHD_SCKT_OPT_BOOL_ off_val = 0;
#if defined(MHD_TCP_CORK_NOPUSH)
+ int res = ! MHD_socket_set_nodelay_ (sock,
+ true);
/* Disable extra buffering */
- res = (0 == setsockopt (sock,
- IPPROTO_TCP,
- MHD_TCP_CORK_NOPUSH,
- (const void *) &off_val,
- sizeof (off_val))) && res;
+ return MHD_socket_cork_ (sock,
+ false) && res;
+#elif defined(HAVE_MSG_MORE)
+ return ! MHD_socket_set_nodelay_ (sock,
+ true);
+#else
+ return ! MHD_socket_set_nodelay_ (sock,
+ false);
#endif /* MHD_TCP_CORK_NOPUSH */
-#if defined(TCP_NODELAY)
- /* Enable Nagle's algorithm for normal buffering */
- res = (0 == setsockopt (sock,
- IPPROTO_TCP,
- TCP_NODELAY,
- (const void *) &off_val,
- sizeof (off_val))) && res;
-#endif /* TCP_NODELAY */
-#else /* !TCP_NODELAY && !MHD_TCP_CORK_NOPUSH */
- (void) sock;
-#endif /* !TCP_NODELAY && !MHD_TCP_CORK_NOPUSH */
- return res;
}
diff --git a/src/microhttpd/mhd_sockets.h b/src/microhttpd/mhd_sockets.h
index a684d71d..08b01c20 100644
--- a/src/microhttpd/mhd_sockets.h
+++ b/src/microhttpd/mhd_sockets.h
@@ -35,6 +35,7 @@
#include "mhd_options.h"
#include <errno.h>
+#include <stdbool.h>
#if !defined(MHD_POSIX_SOCKETS) && !defined(MHD_WINSOCK_SOCKETS)
# if !defined(_WIN32) || defined(__CYGWIN__)
@@ -745,6 +746,19 @@ MHD_socket_nonblocking_ (MHD_socket sock);
/**
+ * Disable Nagle's algorithm on @a sock. This is what we do by default for
+ * all TCP sockets in MHD, unless the platform does not support the MSG_MORE
+ * or MSG_CORK or MSG_NOPUSH options.
+ *
+ * @param sock socket to manipulate
+ * @param on value to use
+ * @return 0 on success
+ */
+int
+MHD_socket_set_nodelay_ (MHD_socket sock,
+ bool on);
+
+/**
* Change socket options to be non-inheritable.
*
* @param sock socket to manipulate
@@ -756,6 +770,30 @@ MHD_socket_noninheritable_ (MHD_socket sock);
/**
+ * Enable/disable the cork option.
+ *
+ * TCP_NOPUSH has the same logic as MSG_MSG_MORE.
+ * The two are more or less equivalent by a source
+ * transformation (ie
+ * send(MSG_MORE) => "set TCP_NOPUSH + send() + clear TCP_NOPUSH".
+ * Both of them are really fairly "local", but TCP_NOPUSH has a
+ * _notion_ of persistency that is entirely lacking in MSG_MORE.
+ * ... with TCP_NOPUSH you basically have to know what your last
+ * write is, and clear the bit _before_ that write if you want
+ * to avoid bad latencies.
+ *
+ * See also: https://yarchive.net/comp/linux/sendfile.html
+ *
+ * @param sock socket to manipulate
+ * @param on set to true to enable CORK, false to disable
+ * @return non-zero if succeeded, zero otherwise
+ */
+int
+MHD_socket_cork_ (MHD_socket sock,
+ bool on);
+
+
+/**
* Change socket buffering mode to default.
*
* @param sock socket to manipulate
diff --git a/src/microhttpd/response.c b/src/microhttpd/response.c
index 3e9fb053..65ea7b09 100644
--- a/src/microhttpd/response.c
+++ b/src/microhttpd/response.c
@@ -604,9 +604,9 @@ MHD_create_response_from_fd_at_offset64 (uint64_t size,
response = MHD_create_response_from_callback (size,
MHD_FILE_READ_BLOCK_SIZE,
- &file_reader,
- NULL,
- &free_callback);
+ &file_reader,
+ NULL,
+ &free_callback);
if (NULL == response)
return NULL;
response->fd = fd;
@@ -825,6 +825,44 @@ MHD_upgrade_action (struct MHD_UpgradeResponseHandle *urh,
* be moved to cleanup list by MHD_resume_connection(). */
MHD_resume_connection (connection);
return MHD_YES;
+ case MHD_UPGRADE_ACTION_CORK_ON:
+ if (connection->sk_cork_on)
+ return MHD_YES;
+#ifdef HTTPS_SUPPORT
+ if (0 != (daemon->options & MHD_USE_TLS) )
+ {
+ gnutls_record_cork (connection->tls_session);
+ connection->sk_cork_on = true;
+ return MHD_YES;
+ }
+ else
+#else
+ {
+ if (0 ==
+ MHD_socket_cork_ (connection->socket_fd,
+ true))
+ connection->sk_cork_on = true;
+ }
+#endif
+ case MHD_UPGRADE_ACTION_CORK_OFF:
+ if (! connection->sk_cork_on)
+ return MHD_YES;
+#ifdef HTTPS_SUPPORT
+ if (0 != (daemon->options & MHD_USE_TLS) )
+ {
+ gnutls_record_uncork (connection->tls_session, 0);
+ connection->sk_cork_on = false;
+ return MHD_YES;
+ }
+ else
+#else
+ {
+ if (0 ==
+ MHD_socket_cork_ (connection->socket_fd,
+ false))
+ connection->sk_cork_on = false;
+ }
+#endif
default:
/* we don't understand this one */
return MHD_NO;
diff --git a/src/microhttpd/test_upgrade.c b/src/microhttpd/test_upgrade.c
index caf12e61..9135187c 100644
--- a/src/microhttpd/test_upgrade.c
+++ b/src/microhttpd/test_upgrade.c
@@ -685,6 +685,8 @@ run_usock (void *cls)
{
struct MHD_UpgradeResponseHandle *urh = cls;
+ MHD_upgrade_action (urh,
+ MHD_UPGRADE_ACTION_CORK_OFF);
send_all (usock,
"Hello");
recv_all (usock,
diff --git a/src/microhttpd/test_upgrade_large.c b/src/microhttpd/test_upgrade_large.c
index 7eabf82c..3131fdf4 100644
--- a/src/microhttpd/test_upgrade_large.c
+++ b/src/microhttpd/test_upgrade_large.c
@@ -704,6 +704,8 @@ run_usock (void *cls)
{
struct MHD_UpgradeResponseHandle *urh = cls;
+ MHD_upgrade_action (urh,
+ MHD_UPGRADE_ACTION_CORK_OFF);
send_all (usock,
LARGE_STRING);
recv_all (usock,