diff options
author | Christian Grothoff <christian@grothoff.org> | 2008-01-21 19:01:07 +0000 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2008-01-21 19:01:07 +0000 |
commit | 6c34675ab9161454957cf1b4caf94f9bb2e4ba70 (patch) | |
tree | fdd3c64318abd6f8c145f70c22dff45ceb059dd6 | |
parent | 425a9ca5ba6c855b00249f0918310e8aa0f94980 (diff) |
adding option
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | README | 7 | ||||
-rw-r--r-- | src/daemon/daemon.c | 43 | ||||
-rw-r--r-- | src/daemon/daemontest_get_chunked.c | 364 | ||||
-rw-r--r-- | src/daemon/internal.h | 6 | ||||
-rw-r--r-- | src/include/microhttpd.h | 14 |
6 files changed, 435 insertions, 3 deletions
@@ -1,3 +1,7 @@ +Mon Jan 21 11:59:46 MST 2008 + Added option to limit number of concurrent connections + accepted from the same IP address. -CG + Fri Jan 4 16:02:08 MST 2008 Fix to properly close connection if application signals problem handling the request. - AS @@ -54,6 +54,9 @@ For POST: ========= - add support to decode multipart/form-data with nesting (used for files) -- #1221, TEST +- http://en.wikipedia.org/wiki/HTTP_pipelining: + "Only idempotent requests should be pipelined, such as GET and HEAD requests." + We should make sure that we force a close after POSTs, PUTs and DELETEs. For SSL: ======== @@ -64,7 +67,9 @@ microhttpd.h: Missing Testcases: ================== - add testcases for http/1.1 pipelining (need - to figure out how to ensure curl pipelines) + to figure out how to ensure curl pipelines + -- and it seems libcurl has issues with pipelining, + see http://curl.haxx.se/mail/lib-2007-12/0248.html) - add testcases for resource limit enforcement - add testcases for client queuing early response, suppressing 100 CONTINUE diff --git a/src/daemon/daemon.c b/src/daemon/daemon.c index 93017ae3..e971c9b2 100644 --- a/src/daemon/daemon.c +++ b/src/daemon/daemon.c @@ -172,10 +172,12 @@ MHD_handle_connection (void *data) static int MHD_accept_connection (struct MHD_Daemon *daemon) { + struct MHD_Connection *pos; struct MHD_Connection *connection; struct sockaddr_in6 addr6; struct sockaddr *addr = (struct sockaddr *) &addr6; socklen_t addrlen; + unsigned int have; int s; #if OSX static int on = 1; @@ -202,7 +204,42 @@ MHD_accept_connection (struct MHD_Daemon *daemon) #if DEBUG_CONNECT MHD_DLOG (daemon, "Accepted connection on socket %d\n", s); #endif - if (daemon->max_connections == 0) + have = 0; + if ( (daemon->per_ip_connection_limit != 0) && + (daemon->max_connections > 0) ) + { + pos = daemon->connections; + while (pos != NULL) + { + if ( (pos->addr != NULL) && + (pos->addr_len == addrlen) ) + { + if (addrlen == sizeof(struct sockaddr_in)) + { + const struct sockaddr_in * a1 = (const struct sockaddr_in *) &addr; + const struct sockaddr_in * a2 = (const struct sockaddr_in *) pos->addr; + if (0 == memcmp(&a1->sin_addr, + &a2->sin_addr, + sizeof(struct in_addr))) + have++; + } + if (addrlen == sizeof(struct sockaddr_in6)) + { + const struct sockaddr_in6 * a1 = (const struct sockaddr_in6 *) &addr; + const struct sockaddr_in6 * a2 = (const struct sockaddr_in6 *) pos->addr; + if (0 == memcmp(&a1->sin6_addr, + &a2->sin6_addr, + sizeof(struct in6_addr))) + have++; + } + } + pos = pos->next; + } + } + + if ( (daemon->max_connections == 0) || + ( (daemon->per_ip_connection_limit != 0) && + (daemon->per_ip_connection_limit <= have) ) ) { /* above connection limit - reject */ #if HAVE_MESSAGES @@ -631,6 +668,10 @@ MHD_start_daemon (unsigned int options, va_arg (ap, MHD_RequestCompletedCallback); retVal->notify_completed_cls = va_arg (ap, void *); break; + case MHD_OPTION_PER_IP_CONNECTION_LIMIT: + retVal->per_ip_connection_limit + = va_arg (ap, unsigned int); + break; default: #if HAVE_MESSAGES fprintf (stderr, diff --git a/src/daemon/daemontest_get_chunked.c b/src/daemon/daemontest_get_chunked.c new file mode 100644 index 00000000..4b2739cf --- /dev/null +++ b/src/daemon/daemontest_get_chunked.c @@ -0,0 +1,364 @@ +/* + This file is part of libmicrohttpd + (C) 2007 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., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +/** + * @file daemontest_get_chunked.c + * @brief Testcase for libmicrohttpd GET operations with chunked content encoding + * TODO: + * - how to test that chunking was actually used? + * - use CURLOPT_HEADERFUNCTION to validate + * footer was sent + * @author Christian Grothoff + */ + +#include "config.h" +#include <curl/curl.h> +#include <microhttpd.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#ifndef WINDOWS +#include <unistd.h> +#endif + +struct CBC +{ + char *buf; + size_t pos; + size_t size; +}; + +static size_t +copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx) +{ + struct CBC *cbc = ctx; + + if (cbc->pos + size * nmemb > cbc->size) + return 0; /* overflow */ + memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb); + cbc->pos += size * nmemb; + return size * nmemb; +} + +/** + * MHD content reader callback that returns + * data in chunks. + */ +static int +crc (void *cls, size_t pos, char *buf, int max) +{ + struct MHD_Response **responseptr = cls; + + if (pos == 128 * 10) + { + MHD_add_response_header (*responseptr, "Footer", "working"); + return -1; /* end of stream */ + } + if (max < 128) + abort (); /* should not happen in this testcase... */ + memset (buf, 'A' + (pos / 128), 128); + return 128; +} + +/** + * Dummy function that does nothing. + */ +static void +crcf (void *ptr) +{ + free (ptr); +} + +static int +ahc_echo (void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, unsigned int *upload_data_size, void **ptr) +{ + static int aptr; + const char *me = cls; + struct MHD_Response *response; + struct MHD_Response **responseptr; + int ret; + + if (0 != strcmp (me, method)) + return MHD_NO; /* unexpected method */ + if (&aptr != *ptr) + { + /* do never respond on first call */ + *ptr = &aptr; + return MHD_YES; + } + responseptr = malloc (sizeof (struct MHD_Response *)); + response = MHD_create_response_from_callback (-1, + 1024, + &crc, responseptr, &crcf); + *responseptr = response; + ret = MHD_queue_response (connection, MHD_HTTP_OK, response); + MHD_destroy_response (response); + return ret; +} + +static int +validate (struct CBC cbc, int ebase) +{ + int i; + char buf[128]; + + if (cbc.pos != 128 * 10) + return ebase; + + for (i = 0; i < 10; i++) + { + memset (buf, 'A' + i, 128); + if (0 != memcmp (buf, &cbc.buf[i * 128], 128)) + { + fprintf (stderr, + "Got `%.*s'\nWant `%.*s'\n", + 128, buf, 128, &cbc.buf[i * 128]); + return ebase * 2; + } + } + return 0; +} + +static int +testInternalGet () +{ + struct MHD_Daemon *d; + CURL *c; + char buf[2048]; + struct CBC cbc; + CURLcode errornum; + + cbc.buf = buf; + cbc.size = 2048; + cbc.pos = 0; + d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG, + 1080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END); + if (d == NULL) + return 1; + c = curl_easy_init (); + curl_easy_setopt (c, CURLOPT_URL, "http://localhost:1080/hello_world"); + curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); + curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); + curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); + curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); + curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 15L); + curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + // NOTE: use of CONNECTTIMEOUT without also + // setting NOSIGNAL results in really weird + // crashes on my system! + curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); + if (CURLE_OK != (errornum = curl_easy_perform (c))) + { + fprintf (stderr, + "curl_easy_perform failed: `%s'\n", + curl_easy_strerror (errornum)); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 2; + } + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return validate (cbc, 4); +} + +static int +testMultithreadedGet () +{ + struct MHD_Daemon *d; + CURL *c; + char buf[2048]; + struct CBC cbc; + CURLcode errornum; + + cbc.buf = buf; + cbc.size = 2048; + cbc.pos = 0; + d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG, + 1081, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END); + if (d == NULL) + return 16; + c = curl_easy_init (); + curl_easy_setopt (c, CURLOPT_URL, "http://localhost:1081/hello_world"); + curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); + curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); + curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); + curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); + curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 15L); + // NOTE: use of CONNECTTIMEOUT without also + // setting NOSIGNAL results in really weird + // crashes on my system! + curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); + if (CURLE_OK != (errornum = curl_easy_perform (c))) + { + fprintf (stderr, + "curl_easy_perform failed: `%s'\n", + curl_easy_strerror (errornum)); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 32; + } + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return validate (cbc, 64); +} + + +static int +testExternalGet () +{ + struct MHD_Daemon *d; + CURL *c; + char buf[2048]; + struct CBC cbc; + CURLM *multi; + CURLMcode mret; + fd_set rs; + fd_set ws; + fd_set es; + int max; + int running; + struct CURLMsg *msg; + time_t start; + struct timeval tv; + + multi = NULL; + cbc.buf = buf; + cbc.size = 2048; + cbc.pos = 0; + d = MHD_start_daemon (MHD_USE_DEBUG, + 1082, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END); + if (d == NULL) + return 256; + c = curl_easy_init (); + curl_easy_setopt (c, CURLOPT_URL, "http://localhost:1082/hello_world"); + curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); + curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); + curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); + curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); + curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 5L); + // NOTE: use of CONNECTTIMEOUT without also + // setting NOSIGNAL results in really weird + // crashes on my system! + curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); + + + multi = curl_multi_init (); + if (multi == NULL) + { + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 512; + } + mret = curl_multi_add_handle (multi, c); + if (mret != CURLM_OK) + { + curl_multi_cleanup (multi); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 1024; + } + start = time (NULL); + while ((time (NULL) - start < 5) && (multi != NULL)) + { + max = 0; + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + curl_multi_perform (multi, &running); + mret = curl_multi_fdset (multi, &rs, &ws, &es, &max); + if (mret != CURLM_OK) + { + curl_multi_remove_handle (multi, c); + curl_multi_cleanup (multi); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 2048; + } + if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max)) + { + curl_multi_remove_handle (multi, c); + curl_multi_cleanup (multi); + curl_easy_cleanup (c); + MHD_stop_daemon (d); + return 4096; + } + tv.tv_sec = 0; + tv.tv_usec = 1000; + select (max + 1, &rs, &ws, &es, &tv); + curl_multi_perform (multi, &running); + if (running == 0) + { + msg = curl_multi_info_read (multi, &running); + if (msg == NULL) + break; + if (msg->msg == CURLMSG_DONE) + { + if (msg->data.result != CURLE_OK) + printf ("%s failed at %s:%d: `%s'\n", + "curl_multi_perform", + __FILE__, + __LINE__, curl_easy_strerror (msg->data.result)); + curl_multi_remove_handle (multi, c); + curl_multi_cleanup (multi); + curl_easy_cleanup (c); + c = NULL; + multi = NULL; + } + } + MHD_run (d); + } + if (multi != NULL) + { + curl_multi_remove_handle (multi, c); + curl_easy_cleanup (c); + curl_multi_cleanup (multi); + } + MHD_stop_daemon (d); + return validate (cbc, 8192); +} + + + +int +main (int argc, char *const *argv) +{ + unsigned int errorCount = 0; + + if (0 != curl_global_init (CURL_GLOBAL_WIN32)) + return 2; + errorCount += testInternalGet (); + if (0) + { + errorCount += testMultithreadedGet (); + errorCount += testExternalGet (); + } + if (errorCount != 0) + fprintf (stderr, "Error (code: %u)\n", errorCount); + curl_global_cleanup (); + return errorCount != 0; /* 0 == pass */ +} diff --git a/src/daemon/internal.h b/src/daemon/internal.h index 43599149..e22de32f 100644 --- a/src/daemon/internal.h +++ b/src/daemon/internal.h @@ -572,6 +572,12 @@ struct MHD_Daemon unsigned int connection_timeout; /** + * Maximum number of connections per IP, or 0 for + * unlimited. + */ + unsigned int per_ip_connection_limit; + + /** * Daemon's options. */ enum MHD_OPTION options; diff --git a/src/include/microhttpd.h b/src/include/microhttpd.h index b8dfc426..76e64e96 100644 --- a/src/include/microhttpd.h +++ b/src/include/microhttpd.h @@ -84,7 +84,7 @@ extern "C" /** * Current version of the library. */ -#define MHD_VERSION 0x00000200 +#define MHD_VERSION 0x00000201 /** * MHD-internal return codes. @@ -333,6 +333,18 @@ enum MHD_OPTION */ MHD_OPTION_NOTIFY_COMPLETED = 4, + /** + * Limit on the number of (concurrent) connections made to the + * server from the same IP address. Can be used to prevent one + * IP from taking over all of the allowed connections. If the + * same IP tries to establish more than the specified number of + * connections, they will be immediately rejected. The option + * should be followed by an "unsigned int". The default is + * zero, which means no limit on the number of connections + * from the same IP address. + */ + MHD_OPTION_PER_IP_CONNECTION_LIMIT = 5, + }; /** |