libmicrohttpd

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

commit 39bc8f5c76f844e56a04d5993ccfee9f2b41d17e
parent 1f2539eed6da8dcf7f4f1ede8ab14c278f9b0c5e
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun,  4 Sep 2016 15:05:41 +0000

factor out shared test logic, test with external select as well

Diffstat:
Msrc/microhttpd/Makefile.am | 2++
Msrc/microhttpd/test_upgrade.c | 380++++++++-----------------------------------------------------------------------
Asrc/microhttpd/test_upgrade_common.c | 451+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/microhttpd/test_upgrade_ssl.c | 383++++++++-----------------------------------------------------------------------
4 files changed, 524 insertions(+), 692 deletions(-)

diff --git a/src/microhttpd/Makefile.am b/src/microhttpd/Makefile.am @@ -8,6 +8,8 @@ AM_CFLAGS = $(HIDDEN_VISIBILITY_CFLAGS) lib_LTLIBRARIES = \ libmicrohttpd.la +EXTRA_DIST = test_upgrade_common.c + noinst_DATA = MOSTLYCLEANFILES = diff --git a/src/microhttpd/test_upgrade.c b/src/microhttpd/test_upgrade.c @@ -4,7 +4,7 @@ libmicrohttpd is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published - by the Free Software Foundation; either version 2, or (at your + by the Free Software Foundation; either version 3, or (at your option) any later version. libmicrohttpd is distributed in the hope that it will be useful, but @@ -39,337 +39,18 @@ #include <netinet/in.h> #include <netinet/ip.h> #include "mhd_sockets.h" +#include "test_upgrade_common.c" /** - * Thread we use to run the interaction with the upgraded socket. - */ -static pthread_t pt; - -/** - * Will be set to the upgraded socket. - */ -static MHD_socket usock; - -/** - * Thread we use to run the interaction with the upgraded socket. - */ -static pthread_t pt_client; - - -/** - * Change itc FD options to be non-blocking. - * - * @param fd the FD to manipulate - * @return non-zero if succeeded, zero otherwise - */ -static void -make_blocking (MHD_socket fd) -{ - int flags; - - flags = fcntl (fd, F_GETFL); - if (-1 == flags) - return; - if ((flags & ~O_NONBLOCK) != flags) - fcntl (fd, F_SETFL, flags & ~O_NONBLOCK); -} - - -static void -send_all (MHD_socket sock, - const char *text) -{ - size_t len = strlen (text); - ssize_t ret; - - make_blocking (sock); - for (size_t off = 0; off < len; off += ret) - { - ret = write (sock, - &text[off], - len - off); - if (-1 == ret) - { - if (EAGAIN == errno) - { - ret = 0; - continue; - } - abort (); - } - } -} - - -/** - * Read character-by-character until we - * get '\r\n\r\n'. - */ -static void -recv_hdr (MHD_socket sock) -{ - unsigned int i; - char next; - char c; - ssize_t ret; - - make_blocking (sock); - next = '\r'; - i = 0; - while (i < 4) - { - ret = read (sock, - &c, - 1); - if (-1 == ret) - { - if (EAGAIN == errno) - { - ret = 0; - continue; - } - abort (); - } - if (0 == ret) - continue; - if (c == next) - { - i++; - if (next == '\r') - next = '\n'; - else - next = '\r'; - continue; - } - if (c == '\r') - { - i = 1; - next = '\n'; - continue; - } - i = 0; - next = '\r'; - } -} - - -static void -recv_all (MHD_socket sock, - const char *text) -{ - size_t len = strlen (text); - char buf[len]; - ssize_t ret; - - make_blocking (sock); - for (size_t off = 0; off < len; off += ret) - { - ret = read (sock, - &buf[off], - len - off); - if (-1 == ret) - { - if (EAGAIN == errno) - { - ret = 0; - continue; - } - abort (); - } - } - if (0 != strncmp (text, buf, len)) - abort(); -} - - -/** - * Main function for the thread that runs the interaction with - * the upgraded socket. - * - * @param cls the handle for the upgrade - */ -static void * -run_usock (void *cls) -{ - struct MHD_UpgradeResponseHandle *urh = cls; - - send_all (usock, - "Hello"); - recv_all (usock, - "World"); - send_all (usock, - "Finished"); - MHD_upgrade_action (urh, - MHD_UPGRADE_ACTION_CLOSE); - return NULL; -} - - -/** - * Main function for the thread that runs the client-side of the - * interaction with the upgraded socket. - * - * @param cls the client socket - */ -static void * -run_usock_client (void *cls) -{ - MHD_socket *sock = cls; - - send_all (*sock, - "GET / HTTP/1.1\r\nConnection: Upgrade\r\n\r\n"); - recv_hdr (*sock); - recv_all (*sock, - "Hello"); - send_all (*sock, - "World"); - recv_all (*sock, - "Finished"); - MHD_socket_close_ (*sock); - return NULL; -} - - -/** - * Function called after a protocol "upgrade" response was sent - * successfully and the socket should now be controlled by some - * protocol other than HTTP. - * - * Any data received on the socket will be made available in - * 'data_in'. The function should update 'data_in_size' to - * reflect the number of bytes consumed from 'data_in' (the remaining - * bytes will be made available in the next call to the handler). - * - * Any data that should be transmitted on the socket should be - * stored in 'data_out'. '*data_out_size' is initially set to - * the available buffer space in 'data_out'. It should be set to - * the number of bytes stored in 'data_out' (which can be zero). - * - * The return value is a BITMASK that indicates how the function - * intends to interact with the event loop. It can request to be - * notified for reading, writing, request to UNCORK the send buffer - * (which MHD is allowed to ignore, if it is not possible to uncork on - * the local platform), to wait for the 'external' select loop to - * trigger another round. It is also possible to specify "no events" - * to terminate the connection; in this case, the - * #MHD_RequestCompletedCallback will be called and all resources of - * the connection will be released. + * Test upgrading a connection. * - * Except when in 'thread-per-connection' mode, implementations - * of this function should never block (as it will still be called - * from within the main event loop). - * - * @param cls closure, whatever was given to #MHD_create_response_for_upgrade(). - * @param connection original HTTP connection handle, - * giving the function a last chance - * to inspect the original HTTP request - * @param con_cls last value left in `*con_cls` in the `MHD_AccessHandlerCallback` - * @param extra_in if we happened to have read bytes after the - * HTTP header already (because the client sent - * more than the HTTP header of the request before - * we sent the upgrade response), - * these are the extra bytes already read from @a sock - * by MHD. The application should treat these as if - * it had read them from @a sock. - * @param extra_in_size number of bytes in @a extra_in - * @param sock socket to use for bi-directional communication - * with the client. For HTTPS, this may not be a socket - * that is directly connected to the client and thus certain - * operations (TCP-specific setsockopt(), getsockopt(), etc.) - * may not work as expected (as the socket could be from a - * socketpair() or a TCP-loopback) - * @param urh argument for #MHD_upgrade_action()s on this @a connection. - * Applications must eventually use this function to perform the - * close() action on the @a sock. - */ -static void -upgrade_cb (void *cls, - struct MHD_Connection *connection, - void *con_cls, - const char *extra_in, - size_t extra_in_size, - MHD_socket sock, - struct MHD_UpgradeResponseHandle *urh) -{ - usock = sock; - if (0 != extra_in_size) - abort (); - pthread_create (&pt, - NULL, - &run_usock, - urh); -} - - -/** - * A client has requested the given url using the given method - * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, - * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback - * must call MHD callbacks to provide content to give back to the - * client and return an HTTP status code (i.e. #MHD_HTTP_OK, - * #MHD_HTTP_NOT_FOUND, etc.). - * - * @param cls argument given together with the function - * pointer when the handler was registered with MHD - * @param url the requested url - * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, - * #MHD_HTTP_METHOD_PUT, etc.) - * @param version the HTTP version string (i.e. - * #MHD_HTTP_VERSION_1_1) - * @param upload_data the data being uploaded (excluding HEADERS, - * for a POST that fits into memory and that is encoded - * with a supported encoding, the POST data will NOT be - * given in upload_data and is instead available as - * part of #MHD_get_connection_values; very large POST - * data *will* be made available incrementally in - * @a upload_data) - * @param upload_data_size set initially to the size of the - * @a upload_data provided; the method must update this - * value to the number of bytes NOT processed; - * @param con_cls pointer that the callback can set to some - * address and that will be preserved by MHD for future - * calls for this request; since the access handler may - * be called many times (i.e., for a PUT/POST operation - * with plenty of upload data) this allows the application - * to easily associate some request-specific state. - * If necessary, this state can be cleaned up in the - * global #MHD_RequestCompletedCallback (which - * can be set with the #MHD_OPTION_NOTIFY_COMPLETED). - * Initially, `*con_cls` will be NULL. - * @return #MHD_YES if the connection was handled successfully, - * #MHD_NO if the socket must be closed due to a serios - * error while handling the request + * @param flags which event loop style should be tested + * @param pool size of the thread pool, 0 to disable */ static int -ahc_upgrade (void *cls, - struct MHD_Connection *connection, - const char *url, - const char *method, - const char *version, - const char *upload_data, - size_t *upload_data_size, - void **con_cls) -{ - struct MHD_Response *resp; - int ret; - - resp = MHD_create_response_for_upgrade (&upgrade_cb, - NULL); - MHD_add_response_header (resp, - MHD_HTTP_HEADER_UPGRADE, - "Hello World Protocol"); - ret = MHD_queue_response (connection, - MHD_HTTP_SWITCHING_PROTOCOLS, - resp); - MHD_destroy_response (resp); - return ret; -} - - -static int -test_upgrade_internal (int flags, - unsigned int pool) +test_upgrade (int flags, + unsigned int pool) { struct MHD_Daemon *d; MHD_socket sock; @@ -399,7 +80,9 @@ test_upgrade_internal (int flags, NULL, &run_usock_client, &sock); - + if (0 == (flags & (MHD_USE_SELECT_INTERNALLY | + MHD_USE_THREAD_PER_CONNECTION)) ) + run_mhd_loop (d, flags); pthread_join (pt_client, NULL); pthread_join (pt, @@ -415,27 +98,36 @@ main (int argc, { int error_count = 0; - error_count += test_upgrade_internal (MHD_USE_THREAD_PER_CONNECTION, - 0); - error_count += test_upgrade_internal (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_POLL, - 0); - error_count += test_upgrade_internal (MHD_USE_SELECT_INTERNALLY, - 0); - error_count += test_upgrade_internal (MHD_USE_SELECT_INTERNALLY, - 2); + /* try external select */ + error_count += test_upgrade (0, + 0); + + /* Test thread-per-connection */ + error_count += test_upgrade (MHD_USE_THREAD_PER_CONNECTION, + 0); + error_count += test_upgrade (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_POLL, + 0); + + /* Test different event loops, with and without thread pool */ + error_count += test_upgrade (MHD_USE_SELECT_INTERNALLY, + 0); + error_count += test_upgrade (MHD_USE_SELECT_INTERNALLY, + 2); #ifdef HAVE_POLL - error_count += test_upgrade_internal (MHD_USE_POLL_INTERNALLY, - 0); - error_count += test_upgrade_internal (MHD_USE_POLL_INTERNALLY, - 2); + error_count += test_upgrade (MHD_USE_POLL_INTERNALLY, + 0); + error_count += test_upgrade (MHD_USE_POLL_INTERNALLY, + 2); #endif #ifdef EPOLL_SUPPORT - error_count += test_upgrade_internal (MHD_USE_EPOLL_INTERNALLY, - 0); - error_count += test_upgrade_internal (MHD_USE_EPOLL_INTERNALLY, - 2); + error_count += test_upgrade (MHD_USE_EPOLL_INTERNALLY, + 0); + error_count += test_upgrade (MHD_USE_EPOLL_INTERNALLY, + 2); #endif - if (error_count != 0) + + /* report result */ + if (0 != error_count) fprintf (stderr, "Error (code: %u)\n", error_count); diff --git a/src/microhttpd/test_upgrade_common.c b/src/microhttpd/test_upgrade_common.c @@ -0,0 +1,451 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2016 Christian Grothoff + + libmicrohttpd is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 2, or (at your + option) any later version. + + libmicrohttpd 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with libmicrohttpd; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/** + * @file test_upgrade_common.c + * @brief Shared logic for testcases for libmicrohttpd upgrading a connection + * @author Christian Grothoff + */ + + +/** + * Thread we use to run the interaction with the upgraded socket. + */ +static pthread_t pt; + +/** + * Will be set to the upgraded socket. + */ +static MHD_socket usock; + +/** + * Thread we use to run the interaction with the upgraded socket. + */ +static pthread_t pt_client; + +/** + * Flag set to 1 once the test is finished. + */ +static int done; + +/** + * Change itc FD options to be non-blocking. + * + * @param fd the FD to manipulate + * @return non-zero if succeeded, zero otherwise + */ +static void +make_blocking (MHD_socket fd) +{ + int flags; + + flags = fcntl (fd, F_GETFL); + if (-1 == flags) + return; + if ((flags & ~O_NONBLOCK) != flags) + fcntl (fd, F_SETFL, flags & ~O_NONBLOCK); +} + + +static void +send_all (MHD_socket sock, + const char *text) +{ + size_t len = strlen (text); + ssize_t ret; + + make_blocking (sock); + for (size_t off = 0; off < len; off += ret) + { + ret = write (sock, + &text[off], + len - off); + if (-1 == ret) + { + if (EAGAIN == errno) + { + ret = 0; + continue; + } + abort (); + } + } +} + + +/** + * Read character-by-character until we + * get '\r\n\r\n'. + */ +static void +recv_hdr (MHD_socket sock) +{ + unsigned int i; + char next; + char c; + ssize_t ret; + + make_blocking (sock); + next = '\r'; + i = 0; + while (i < 4) + { + ret = read (sock, + &c, + 1); + if (-1 == ret) + { + if (EAGAIN == errno) + { + ret = 0; + continue; + } + abort (); + } + if (0 == ret) + continue; + if (c == next) + { + i++; + if (next == '\r') + next = '\n'; + else + next = '\r'; + continue; + } + if (c == '\r') + { + i = 1; + next = '\n'; + continue; + } + i = 0; + next = '\r'; + } +} + + +static void +recv_all (MHD_socket sock, + const char *text) +{ + size_t len = strlen (text); + char buf[len]; + ssize_t ret; + + make_blocking (sock); + for (size_t off = 0; off < len; off += ret) + { + ret = read (sock, + &buf[off], + len - off); + if (-1 == ret) + { + if (EAGAIN == errno) + { + ret = 0; + continue; + } + abort (); + } + } + if (0 != strncmp (text, buf, len)) + abort(); +} + + +/** + * Main function for the thread that runs the interaction with + * the upgraded socket. + * + * @param cls the handle for the upgrade + */ +static void * +run_usock (void *cls) +{ + struct MHD_UpgradeResponseHandle *urh = cls; + + send_all (usock, + "Hello"); + recv_all (usock, + "World"); + send_all (usock, + "Finished"); + MHD_upgrade_action (urh, + MHD_UPGRADE_ACTION_CLOSE); + return NULL; +} + + +/** + * Main function for the thread that runs the client-side of the + * interaction with the upgraded socket. + * + * @param cls the client socket + */ +static void * +run_usock_client (void *cls) +{ + MHD_socket *sock = cls; + + send_all (*sock, + "GET / HTTP/1.1\r\nConnection: Upgrade\r\n\r\n"); + recv_hdr (*sock); + recv_all (*sock, + "Hello"); + send_all (*sock, + "World"); + recv_all (*sock, + "Finished"); + MHD_socket_close_ (*sock); + done = 1; + return NULL; +} + + +/** + * Function called after a protocol "upgrade" response was sent + * successfully and the socket should now be controlled by some + * protocol other than HTTP. + * + * Any data received on the socket will be made available in + * 'data_in'. The function should update 'data_in_size' to + * reflect the number of bytes consumed from 'data_in' (the remaining + * bytes will be made available in the next call to the handler). + * + * Any data that should be transmitted on the socket should be + * stored in 'data_out'. '*data_out_size' is initially set to + * the available buffer space in 'data_out'. It should be set to + * the number of bytes stored in 'data_out' (which can be zero). + * + * The return value is a BITMASK that indicates how the function + * intends to interact with the event loop. It can request to be + * notified for reading, writing, request to UNCORK the send buffer + * (which MHD is allowed to ignore, if it is not possible to uncork on + * the local platform), to wait for the 'external' select loop to + * trigger another round. It is also possible to specify "no events" + * to terminate the connection; in this case, the + * #MHD_RequestCompletedCallback will be called and all resources of + * the connection will be released. + * + * Except when in 'thread-per-connection' mode, implementations + * of this function should never block (as it will still be called + * from within the main event loop). + * + * @param cls closure, whatever was given to #MHD_create_response_for_upgrade(). + * @param connection original HTTP connection handle, + * giving the function a last chance + * to inspect the original HTTP request + * @param con_cls last value left in `*con_cls` in the `MHD_AccessHandlerCallback` + * @param extra_in if we happened to have read bytes after the + * HTTP header already (because the client sent + * more than the HTTP header of the request before + * we sent the upgrade response), + * these are the extra bytes already read from @a sock + * by MHD. The application should treat these as if + * it had read them from @a sock. + * @param extra_in_size number of bytes in @a extra_in + * @param sock socket to use for bi-directional communication + * with the client. For HTTPS, this may not be a socket + * that is directly connected to the client and thus certain + * operations (TCP-specific setsockopt(), getsockopt(), etc.) + * may not work as expected (as the socket could be from a + * socketpair() or a TCP-loopback) + * @param urh argument for #MHD_upgrade_action()s on this @a connection. + * Applications must eventually use this function to perform the + * close() action on the @a sock. + */ +static void +upgrade_cb (void *cls, + struct MHD_Connection *connection, + void *con_cls, + const char *extra_in, + size_t extra_in_size, + MHD_socket sock, + struct MHD_UpgradeResponseHandle *urh) +{ + usock = sock; + if (0 != extra_in_size) + abort (); + pthread_create (&pt, + NULL, + &run_usock, + urh); +} + + +/** + * A client has requested the given url using the given method + * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, + * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback + * must call MHD callbacks to provide content to give back to the + * client and return an HTTP status code (i.e. #MHD_HTTP_OK, + * #MHD_HTTP_NOT_FOUND, etc.). + * + * @param cls argument given together with the function + * pointer when the handler was registered with MHD + * @param url the requested url + * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, + * #MHD_HTTP_METHOD_PUT, etc.) + * @param version the HTTP version string (i.e. + * #MHD_HTTP_VERSION_1_1) + * @param upload_data the data being uploaded (excluding HEADERS, + * for a POST that fits into memory and that is encoded + * with a supported encoding, the POST data will NOT be + * given in upload_data and is instead available as + * part of #MHD_get_connection_values; very large POST + * data *will* be made available incrementally in + * @a upload_data) + * @param upload_data_size set initially to the size of the + * @a upload_data provided; the method must update this + * value to the number of bytes NOT processed; + * @param con_cls pointer that the callback can set to some + * address and that will be preserved by MHD for future + * calls for this request; since the access handler may + * be called many times (i.e., for a PUT/POST operation + * with plenty of upload data) this allows the application + * to easily associate some request-specific state. + * If necessary, this state can be cleaned up in the + * global #MHD_RequestCompletedCallback (which + * can be set with the #MHD_OPTION_NOTIFY_COMPLETED). + * Initially, `*con_cls` will be NULL. + * @return #MHD_YES if the connection was handled successfully, + * #MHD_NO if the socket must be closed due to a serios + * error while handling the request + */ +static int +ahc_upgrade (void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, + size_t *upload_data_size, + void **con_cls) +{ + struct MHD_Response *resp; + int ret; + + resp = MHD_create_response_for_upgrade (&upgrade_cb, + NULL); + MHD_add_response_header (resp, + MHD_HTTP_HEADER_UPGRADE, + "Hello World Protocol"); + ret = MHD_queue_response (connection, + MHD_HTTP_SWITCHING_PROTOCOLS, + resp); + MHD_destroy_response (resp); + return ret; +} + + +/** + * Run the MHD external event loop using select. + * + * @param daemon daemon to run it for + * @param flags the flags the daemon was started with + */ +static void +run_mhd_select_loop (struct MHD_Daemon *daemon) +{ + fd_set rs; + fd_set ws; + fd_set es; + MHD_socket max_fd; + MHD_UNSIGNED_LONG_LONG to; + struct timeval tv; + + while (! done) + { + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + max_fd = -1; + to = 1000; + + if (MHD_YES != + MHD_get_fdset (daemon, + &rs, + &ws, + &es, + &max_fd)) + abort (); + MHD_get_timeout (daemon, + &to); + tv.tv_sec = to / 1000; + tv.tv_usec = 1000 * (to % 1000); + select (max_fd + 1, + &rs, + &ws, + &es, + &tv); + MHD_run_from_select (daemon, + &rs, + &ws, + &es); + } +} + + +/** + * Run the MHD external event loop using select. + * + * @param daemon daemon to run it for + * @param flags the flags the daemon was started with + */ +static void +run_mhd_poll_loop (struct MHD_Daemon *daemon) +{ + abort (); // not implemented +} + + +/** + * Run the MHD external event loop using select. + * + * @param daemon daemon to run it for + * @param flags the flags the daemon was started with + */ +static void +run_mhd_epoll_loop (struct MHD_Daemon *daemon) +{ + abort (); // not implemented +} + + +/** + * Run the MHD external event loop using select. + * + * @param daemon daemon to run it for + * @param flags the flags the daemon was started with + */ +static void +run_mhd_loop (struct MHD_Daemon *daemon, + int flags) +{ + if (0 != (flags & MHD_USE_POLL)) + run_mhd_poll_loop (daemon); +#if EPOLL_SUPPORT + else if (0 != (flags & MHD_USE_EPOLL)) + run_mhd_epoll_loop (daemon); +#endif + else + run_mhd_select_loop (daemon); +} diff --git a/src/microhttpd/test_upgrade_ssl.c b/src/microhttpd/test_upgrade_ssl.c @@ -4,7 +4,7 @@ libmicrohttpd is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published - by the Free Software Foundation; either version 2, or (at your + by the Free Software Foundation; either version 3, or (at your option) any later version. libmicrohttpd is distributed in the hope that it will be useful, but @@ -40,26 +40,12 @@ #include <netinet/ip.h> #include <pthread.h> #include "mhd_sockets.h" +#include "test_upgrade_common.c" #include "../testcurl/https/tls_test_keys.h" /** - * Thread we use to run the interaction with the upgraded socket. - */ -static pthread_t pt; - -/** - * Will be set to the upgraded socket. - */ -static MHD_socket usock; - -/** - * Thread we use to run the interaction with the upgraded socket. - */ -static pthread_t pt_client; - -/** * Fork child that connects via OpenSSL to our @a port. Allows us to * talk to our port over a socket in @a sp without having to worry * about TLS. @@ -109,322 +95,14 @@ openssl_connect (int *sock, /** - * Change itc FD options to be non-blocking. - * - * @param fd the FD to manipulate - * @return non-zero if succeeded, zero otherwise - */ -static void -make_blocking (MHD_socket fd) -{ - int flags; - - flags = fcntl (fd, F_GETFL); - if (-1 == flags) - return; - if ((flags & ~O_NONBLOCK) != flags) - fcntl (fd, F_SETFL, flags & ~O_NONBLOCK); -} - - -static void -send_all (MHD_socket sock, - const char *text) -{ - size_t len = strlen (text); - ssize_t ret; - - make_blocking (sock); - for (size_t off = 0; off < len; off += ret) - { - ret = write (sock, - &text[off], - len - off); - if (-1 == ret) - { - if (EAGAIN == errno) - { - ret = 0; - continue; - } - abort (); - } - } -} - - -/** - * Read character-by-character until we - * get '\r\n\r\n'. - */ -static void -recv_hdr (MHD_socket sock) -{ - unsigned int i; - char next; - char c; - ssize_t ret; - - make_blocking (sock); - next = '\r'; - i = 0; - while (i < 4) - { - ret = read (sock, - &c, - 1); - if (0 == ret) - abort (); /* this is fatal */ - if (-1 == ret) - { - if (EAGAIN == errno) - { - ret = 0; - continue; - } - abort (); - } - if (0 == ret) - continue; - if (c == next) - { - i++; - if (next == '\r') - next = '\n'; - else - next = '\r'; - continue; - } - if (c == '\r') - { - i = 1; - next = '\n'; - continue; - } - i = 0; - next = '\r'; - } -} - - -static void -recv_all (MHD_socket sock, - const char *text) -{ - size_t len = strlen (text); - char buf[len]; - ssize_t ret; - - make_blocking (sock); - for (size_t off = 0; off < len; off += ret) - { - ret = read (sock, - &buf[off], - len - off); - if (0 == ret) - abort (); /* this is fatal */ - if (-1 == ret) - { - if (EAGAIN == errno) - { - ret = 0; - continue; - } - abort (); - } - } - if (0 != strncmp (text, buf, len)) - abort(); -} - - -/** - * Main function for the thread that runs the interaction with - * the upgraded socket. - * - * @param cls the handle for the upgrade - */ -static void * -run_usock (void *cls) -{ - struct MHD_UpgradeResponseHandle *urh = cls; - - send_all (usock, - "Hello"); - recv_all (usock, - "World"); - send_all (usock, - "Finished"); - MHD_upgrade_action (urh, - MHD_UPGRADE_ACTION_CLOSE); - return NULL; -} - - -/** - * Main function for the thread that runs the client-side of the - * interaction with the upgraded socket. - * - * @param cls the client socket - */ -static void * -run_usock_client (void *cls) -{ - MHD_socket *sock = cls; - - send_all (*sock, - "GET / HTTP/1.1\r\nConnection: Upgrade\r\n\r\n"); - recv_hdr (*sock); - recv_all (*sock, - "Hello"); - send_all (*sock, - "World"); - recv_all (*sock, - "Finished"); - MHD_socket_close_ (*sock); - return NULL; -} - - -/** - * Function called after a protocol "upgrade" response was sent - * successfully and the socket should now be controlled by some - * protocol other than HTTP. - * - * Any data received on the socket will be made available in - * 'data_in'. The function should update 'data_in_size' to - * reflect the number of bytes consumed from 'data_in' (the remaining - * bytes will be made available in the next call to the handler). - * - * Any data that should be transmitted on the socket should be - * stored in 'data_out'. '*data_out_size' is initially set to - * the available buffer space in 'data_out'. It should be set to - * the number of bytes stored in 'data_out' (which can be zero). - * - * The return value is a BITMASK that indicates how the function - * intends to interact with the event loop. It can request to be - * notified for reading, writing, request to UNCORK the send buffer - * (which MHD is allowed to ignore, if it is not possible to uncork on - * the local platform), to wait for the 'external' select loop to - * trigger another round. It is also possible to specify "no events" - * to terminate the connection; in this case, the - * #MHD_RequestCompletedCallback will be called and all resources of - * the connection will be released. - * - * Except when in 'thread-per-connection' mode, implementations - * of this function should never block (as it will still be called - * from within the main event loop). - * - * @param cls closure, whatever was given to #MHD_create_response_for_upgrade(). - * @param connection original HTTP connection handle, - * giving the function a last chance - * to inspect the original HTTP request - * @param con_cls last value left in `*con_cls` in the `MHD_AccessHandlerCallback` - * @param extra_in if we happened to have read bytes after the - * HTTP header already (because the client sent - * more than the HTTP header of the request before - * we sent the upgrade response), - * these are the extra bytes already read from @a sock - * by MHD. The application should treat these as if - * it had read them from @a sock. - * @param extra_in_size number of bytes in @a extra_in - * @param sock socket to use for bi-directional communication - * with the client. For HTTPS, this may not be a socket - * that is directly connected to the client and thus certain - * operations (TCP-specific setsockopt(), getsockopt(), etc.) - * may not work as expected (as the socket could be from a - * socketpair() or a TCP-loopback) - * @param urh argument for #MHD_upgrade_action()s on this @a connection. - * Applications must eventually use this function to perform the - * close() action on the @a sock. - */ -static void -upgrade_cb (void *cls, - struct MHD_Connection *connection, - void *con_cls, - const char *extra_in, - size_t extra_in_size, - MHD_socket sock, - struct MHD_UpgradeResponseHandle *urh) -{ - usock = sock; - if (0 != extra_in_size) - abort (); - pthread_create (&pt, - NULL, - &run_usock, - urh); -} - - -/** - * A client has requested the given url using the given method - * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, - * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback - * must call MHD callbacks to provide content to give back to the - * client and return an HTTP status code (i.e. #MHD_HTTP_OK, - * #MHD_HTTP_NOT_FOUND, etc.). + * Test upgrading a connection. * - * @param cls argument given together with the function - * pointer when the handler was registered with MHD - * @param url the requested url - * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, - * #MHD_HTTP_METHOD_PUT, etc.) - * @param version the HTTP version string (i.e. - * #MHD_HTTP_VERSION_1_1) - * @param upload_data the data being uploaded (excluding HEADERS, - * for a POST that fits into memory and that is encoded - * with a supported encoding, the POST data will NOT be - * given in upload_data and is instead available as - * part of #MHD_get_connection_values; very large POST - * data *will* be made available incrementally in - * @a upload_data) - * @param upload_data_size set initially to the size of the - * @a upload_data provided; the method must update this - * value to the number of bytes NOT processed; - * @param con_cls pointer that the callback can set to some - * address and that will be preserved by MHD for future - * calls for this request; since the access handler may - * be called many times (i.e., for a PUT/POST operation - * with plenty of upload data) this allows the application - * to easily associate some request-specific state. - * If necessary, this state can be cleaned up in the - * global #MHD_RequestCompletedCallback (which - * can be set with the #MHD_OPTION_NOTIFY_COMPLETED). - * Initially, `*con_cls` will be NULL. - * @return #MHD_YES if the connection was handled successfully, - * #MHD_NO if the socket must be closed due to a serios - * error while handling the request + * @param flags which event loop style should be tested + * @param pool size of the thread pool, 0 to disable */ static int -ahc_upgrade (void *cls, - struct MHD_Connection *connection, - const char *url, - const char *method, - const char *version, - const char *upload_data, - size_t *upload_data_size, - void **con_cls) -{ - struct MHD_Response *resp; - int ret; - - resp = MHD_create_response_for_upgrade (&upgrade_cb, - NULL); - MHD_add_response_header (resp, - MHD_HTTP_HEADER_UPGRADE, - "Hello World Protocol"); - ret = MHD_queue_response (connection, - MHD_HTTP_SWITCHING_PROTOCOLS, - resp); - MHD_destroy_response (resp); - return ret; -} - - -static int -test_upgrade_internal (int flags, - unsigned int pool) +test_upgrade (int flags, + unsigned int pool) { struct MHD_Daemon *d; MHD_socket sock; @@ -452,9 +130,14 @@ test_upgrade_internal (int flags, NULL, &run_usock_client, &sock); - + if (0 == (flags & (MHD_USE_SELECT_INTERNALLY | + MHD_USE_THREAD_PER_CONNECTION)) ) + run_mhd_loop (d, flags); pthread_join (pt_client, NULL); + if (0 == (flags & (MHD_USE_SELECT_INTERNALLY | + MHD_USE_THREAD_PER_CONNECTION)) ) + run_mhd_loop (d, flags); pthread_join (pt, NULL); waitpid (pid, @@ -474,30 +157,34 @@ main (int argc, if (0 != system ("openssl version 1> /dev/null")) return 77; /* openssl not available, can't run the test */ + /* try external select */ + error_count += test_upgrade (0, + 0); + /* Test thread-per-connection */ - error_count += test_upgrade_internal (MHD_USE_THREAD_PER_CONNECTION, - 0); - error_count += test_upgrade_internal (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_POLL, - 0); + error_count += test_upgrade (MHD_USE_THREAD_PER_CONNECTION, + 0); + error_count += test_upgrade (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_POLL, + 0); /* Test different event loops, with and without thread pool */ - error_count += test_upgrade_internal (MHD_USE_SELECT_INTERNALLY, - 0); - error_count += test_upgrade_internal (MHD_USE_SELECT_INTERNALLY, - 2); + error_count += test_upgrade (MHD_USE_SELECT_INTERNALLY, + 0); + error_count += test_upgrade (MHD_USE_SELECT_INTERNALLY, + 2); #ifdef HAVE_POLL - error_count += test_upgrade_internal (MHD_USE_POLL_INTERNALLY, - 0); - error_count += test_upgrade_internal (MHD_USE_POLL_INTERNALLY, - 2); + error_count += test_upgrade (MHD_USE_POLL_INTERNALLY, + 0); + error_count += test_upgrade (MHD_USE_POLL_INTERNALLY, + 2); #endif #ifdef EPOLL_SUPPORT - error_count += test_upgrade_internal (MHD_USE_EPOLL_INTERNALLY | - MHD_USE_TLS_EPOLL_UPGRADE, - 0); - error_count += test_upgrade_internal (MHD_USE_EPOLL_INTERNALLY | - MHD_USE_TLS_EPOLL_UPGRADE, - 2); + error_count += test_upgrade (MHD_USE_EPOLL_INTERNALLY | + MHD_USE_TLS_EPOLL_UPGRADE, + 0); + error_count += test_upgrade (MHD_USE_EPOLL_INTERNALLY | + MHD_USE_TLS_EPOLL_UPGRADE, + 2); #endif /* report result */