libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

commit 44f40ec132d377aad0c99c32728eefae742e609b
parent c803eebd067b3e025cfa0da21712caf642ab73bc
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat, 15 Nov 2025 15:01:25 +0100

start work to support HTTP/2 in test suite

Diffstat:
Msrc/tests/client_server/Makefile.am | 6++++++
Msrc/tests/client_server/libtest.h | 5+++++
Msrc/tests/client_server/libtest_convenience_client_request.c | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/tests/client_server/libtest_convenience_server_reply.c | 42++++++++++++++++++++++--------------------
Asrc/tests/client_server/test_http2.c | 317+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 403 insertions(+), 30 deletions(-)

diff --git a/src/tests/client_server/Makefile.am b/src/tests/client_server/Makefile.am @@ -37,6 +37,11 @@ check_PROGRAMS += \ test_cert_tls endif +if MHD_SUPPORT_HTTP2 +check_PROGRAMS += \ + test_http2 +endif + TESTS = $(check_PROGRAMS) noinst_LTLIBRARIES = \ @@ -46,6 +51,7 @@ libmhdt_la_SOURCES = \ libtest.c libtest.h \ libtest_convenience.c \ libtest_convenience_client_request.c \ + libtest_convenience_client2_request.c \ libtest_convenience_server_reply.c # TODO: fix out-of-tree 'make check' diff --git a/src/tests/client_server/libtest.h b/src/tests/client_server/libtest.h @@ -154,6 +154,11 @@ struct MHDT_Phase bool check_server_cert; /** + * HTTP version to use. 0 = HTTP/1.x, 2 = HTTP/2, 3 = HTTP/3. + */ + unsigned int http_version; + + /** * Client certificate to present to the server, NULL for none. */ const char *client_cert; diff --git a/src/tests/client_server/libtest_convenience_client_request.c b/src/tests/client_server/libtest_convenience_client_request.c @@ -354,6 +354,49 @@ set_url (CURL *c, } +/** + * Create a curl handle for the given @a pc. + * + * @param pc current phase context with options to use + * @return NULL on error + */ +static CURL * +setup_curl (const struct MHDT_PhaseContext *pc) +{ + struct MHDT_Phase *p = pc->phase; + CURL *c; + + c = curl_easy_init (); + if (NULL == c) + return NULL; + switch (p->http_version) + { + case 0: /* unset == HTTP/1.x! */ + case 1: + break; + case 2: /* HTTP/2 */ + if (CURLE_OK != + curl_easy_setopt (c, + CURLOPT_HTTP_VERSION, + CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE)) + { + curl_easy_cleanup (c); + fprintf (stderr, + "HTTP/2 not supported by curl?\n"); + return NULL; + } + break; + case 3: + abort (); // not yet supported + break; + default: + abort (); + break; + } + return c; +} + + const char * MHDT_client_get_host (const void *cls, struct MHDT_PhaseContext *pc) @@ -379,7 +422,7 @@ MHDT_client_get_host (const void *cls, "https://%s%s", host, colon); - c = curl_easy_init (); + c = setup_curl (pc); if (NULL == c) return "Failed to initialize Curl handle"; err = set_url (c, @@ -405,7 +448,7 @@ MHDT_client_get_root ( const char *err; DECLARE_WB (text); - c = curl_easy_init (); + c = setup_curl (pc); if (NULL == c) return "Failed to initialize Curl handle"; err = set_url (c, @@ -442,7 +485,7 @@ MHDT_client_get_with_query ( args, alen); u[alen + blen] = '\0'; - c = curl_easy_init (); + c = setup_curl (pc); if (NULL == c) return "Failed to initialize Curl handle"; err = set_url (c, @@ -469,7 +512,7 @@ MHDT_client_set_header ( CURLcode res; struct curl_slist *slist; - c = curl_easy_init (); + c = setup_curl (pc); if (NULL == c) return "Failed to initialize Curl handle"; err = set_url (c, @@ -523,7 +566,7 @@ MHDT_client_expect_header (const void *cls, hlen); key[colon - hdr] = '\0'; value = &key[colon - hdr + 1]; - c = curl_easy_init (); + c = setup_curl (pc); if (NULL == c) return "Failed to initialize Curl handle"; err = set_url (c, @@ -642,7 +685,7 @@ MHDT_client_put_data ( }; CURL *c; - c = curl_easy_init (); + c = setup_curl (pc); if (NULL == c) return "Failed to initialize Curl handle"; err = set_url (c, @@ -704,7 +747,7 @@ MHDT_client_chunk_data ( }; CURL *c; - c = curl_easy_init (); + c = setup_curl (pc); if (NULL == c) return "Failed to initialize Curl handle"; err = set_url (c, @@ -763,7 +806,7 @@ MHDT_client_do_post ( pi->wants[i].satisfied = false; } } - c = curl_easy_init (); + c = setup_curl (pc); if (NULL == c) return "Failed to initialize Curl handle"; err = set_url (c, @@ -858,7 +901,7 @@ send_basic_auth (const char *cred, user = strndup (cred, pass - cred); pass++; - c = curl_easy_init (); + c = setup_curl (pc); if (NULL == c) { free (user); @@ -969,7 +1012,7 @@ send_digest_auth (const char *cred, user = strndup (cred, pass - cred); pass++; - c = curl_easy_init (); + c = setup_curl (pc); if (NULL == c) { free (user); diff --git a/src/tests/client_server/libtest_convenience_server_reply.c b/src/tests/client_server/libtest_convenience_server_reply.c @@ -187,7 +187,7 @@ MHDT_server_reply_check_query ( tok = strtok (NULL, "&")) { const char *end; - const struct MHD_StringNullable *sn; + struct MHD_StringNullable sn; const char *val; end = strchr (tok, '='); @@ -208,10 +208,11 @@ MHDT_server_reply_check_query ( tok, alen); arg[alen] = '\0'; - sn = MHD_request_get_value (request, - MHD_VK_URI_QUERY_PARAM, - arg); - if (NULL == sn) + if (MHD_NO == + MHD_request_get_value (request, + MHD_VK_URI_QUERY_PARAM, + arg, + &sn)) { fprintf (stderr, "NULL returned for query key %s\n", @@ -220,18 +221,18 @@ MHDT_server_reply_check_query ( } if (NULL == val) { - if (NULL != sn->cstr) + if (NULL != sn.cstr) { fprintf (stderr, "NULL expected for value for query key %s, got %s\n", arg, - sn->cstr); + sn.cstr); return MHD_action_abort_request (request); } } else { - if (NULL == sn->cstr) + if (NULL == sn.cstr) { fprintf (stderr, "%s expected for value for query key %s, got NULL\n", @@ -240,13 +241,13 @@ MHDT_server_reply_check_query ( return MHD_action_abort_request (request); } if (0 != strcmp (val, - sn->cstr)) + sn.cstr)) { fprintf (stderr, "%s expected for value for query key %s, got %s\n", val, arg, - sn->cstr); + sn.cstr); return MHD_action_abort_request (request); } } @@ -272,7 +273,7 @@ MHDT_server_reply_check_header ( size_t wlen = strlen (want) + 1; char key[wlen]; const char *colon = strchr (want, ':'); - const struct MHD_StringNullable *have; + struct MHD_StringNullable have; const char *value; (void) path; (void) method; (void) upload_size; /* Unused */ @@ -289,10 +290,11 @@ MHDT_server_reply_check_header ( { value = NULL; } - have = MHD_request_get_value (request, - MHD_VK_HEADER, - key); - if (NULL == have) + if (MHD_NO == + MHD_request_get_value (request, + MHD_VK_HEADER, + key, + &have)) { fprintf (stderr, "Missing client header `%s'\n", @@ -301,32 +303,32 @@ MHDT_server_reply_check_header ( } if (NULL == value) { - if (NULL != have->cstr) + if (NULL != have.cstr) { fprintf (stderr, "Have unexpected client header `%s': `%s'\n", key, - have->cstr); + have.cstr); return MHD_action_abort_request (request); } } else { - if (NULL == have->cstr) + if (NULL == have.cstr) { fprintf (stderr, "Missing value for client header `%s'\n", want); return MHD_action_abort_request (request); } - if (0 != strcmp (have->cstr, + if (0 != strcmp (have.cstr, value)) { fprintf (stderr, "Client HTTP header `%s' was expected to be `%s' but is `%s'\n", key, value, - have->cstr); + have.cstr); return MHD_action_abort_request (request); } } diff --git a/src/tests/client_server/test_http2.c b/src/tests/client_server/test_http2.c @@ -0,0 +1,317 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ +/* + This file is part of GNU libmicrohttpd. + Copyright (C) 2016, 2024, 2025 Christian Grothoff & Evgeny Grin (Karlson2k) + + GNU libmicrohttpd 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. + + GNU 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 + Lesser General Public License for more details. + + Alternatively, you can redistribute GNU libmicrohttpd and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version, together + with the eCos exception, as follows: + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile this + file and link it with other works to produce a work based on this + file, this file does not by itself cause the resulting work to be + covered by the GNU General Public License. However the source code + for this file must still be made available in accordance with + section (3) of the GNU General Public License v2. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + + You should have received copies of the GNU Lesser General Public + License and the GNU General Public License along with this library; + if not, see <https://www.gnu.org/licenses/>. +*/ + +/** + * @file test_http2.c + * @brief test with client against server + * @author Christian Grothoff + */ +#include "libtest.h" + + +int +main (int argc, char *argv[]) +{ + struct MHD_DaemonOptionAndValue thread1select[] = { + MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_SELECT), + MHD_D_OPTION_WM_WORKER_THREADS (1), + MHD_D_OPTION_TERMINATE () + }; + struct MHD_DaemonOptionAndValue thread2select[] = { + MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_SELECT), + MHD_D_OPTION_WM_WORKER_THREADS (2), + MHD_D_OPTION_TERMINATE () + }; + struct MHD_DaemonOptionAndValue thread1poll[] = { + MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_POLL), + MHD_D_OPTION_WM_WORKER_THREADS (1), + MHD_D_OPTION_TERMINATE () + }; + struct MHD_DaemonOptionAndValue thread2poll[] = { + MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_POLL), + MHD_D_OPTION_WM_WORKER_THREADS (1), + MHD_D_OPTION_TERMINATE () + }; + struct MHD_DaemonOptionAndValue thread1epoll[] = { + MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_EPOLL), + MHD_D_OPTION_WM_WORKER_THREADS (1), + MHD_D_OPTION_TERMINATE () + }; + struct MHD_DaemonOptionAndValue thread2epoll[] = { + MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_EPOLL), + MHD_D_OPTION_WM_WORKER_THREADS (1), + MHD_D_OPTION_TERMINATE () + }; + struct MHD_DaemonOptionAndValue thread1auto[] = { + MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_AUTO), + MHD_D_OPTION_WM_WORKER_THREADS (1), + MHD_D_OPTION_TERMINATE () + }; + struct MHD_DaemonOptionAndValue external0auto[] = { + MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_AUTO), + MHD_D_OPTION_WM_EXTERNAL_PERIODIC (), + MHD_D_OPTION_TERMINATE () + }; + struct ServerType + { + const char *label; + MHDT_ServerSetup server_setup; + void *server_setup_cls; + MHDT_ServerRunner server_runner; + void *server_runner_cls; + } configs[] = { +#ifdef MHD_SUPPORT_SELECT + { + .label = "single threaded select", + .server_setup = &MHDT_server_setup_minimal, + .server_setup_cls = thread1select, + .server_runner = &MHDT_server_run_minimal, + }, + { + .label = "multi-threaded select", + .server_setup = &MHDT_server_setup_minimal, + .server_setup_cls = thread2select, + .server_runner = &MHDT_server_run_minimal, + }, +#endif +#ifdef MHD_SUPPORT_POLL + { + .label = "single threaded poll", + .server_setup = &MHDT_server_setup_minimal, + .server_setup_cls = thread1poll, + .server_runner = &MHDT_server_run_minimal, + }, + { + .label = "multi-threaded poll", + .server_setup = &MHDT_server_setup_minimal, + .server_setup_cls = thread2poll, + .server_runner = &MHDT_server_run_minimal, + }, +#endif +#if MHD_SUPPORT_EPOLL + { + .label = "single threaded epoll", + .server_setup = &MHDT_server_setup_minimal, + .server_setup_cls = thread1epoll, + .server_runner = &MHDT_server_run_minimal, + }, + { + .label = "multi-threaded epoll", + .server_setup = &MHDT_server_setup_minimal, + .server_setup_cls = thread2epoll, + .server_runner = &MHDT_server_run_minimal, + }, +#endif + { + .label = "auto-selected mode, single threaded", + .server_setup = &MHDT_server_setup_minimal, + .server_setup_cls = thread1auto, + .server_runner = &MHDT_server_run_minimal, + }, +#ifdef MHD_SUPPORT_EPOLL + { + .label = "external events loop mode, no internal threads", + .server_setup = &MHDT_server_setup_external, + .server_setup_cls = NULL, + .server_runner = &MHDT_server_run_external, + }, +#endif /* MHD_SUPPORT_EPOLL */ +#if 1 + /* FIXME: remove once MHD_daemon_process_blocking + has been implemented */ + { + .label = "END" + }, +#endif + { + .label = "auto-selected external event loop mode, no threads", + .server_setup = &MHDT_server_setup_minimal, + .server_setup_cls = external0auto, + .server_runner = &MHDT_server_run_blocking, + }, + { + .label = "END" + } + }; + struct MHDT_Phase phases[] = { + { + .label = "simple get", + .server_cb = &MHDT_server_reply_text, + .server_cb_cls = (void *) "Hello world", + .client_cb = &MHDT_client_get_root, + .client_cb_cls = "Hello world", + .timeout_ms = 2500, + .http_version = 2, + }, + { + .label = "GET with sendfile", + .server_cb = &MHDT_server_reply_file, + .server_cb_cls = (void *) "Hello world", + .client_cb = &MHDT_client_get_root, + .client_cb_cls = "Hello world", + .timeout_ms = 2500, + .http_version = 2, + }, + { + .label = "client PUT with content-length", + .server_cb = &MHDT_server_reply_check_upload, + .server_cb_cls = (void *) "simple-upload-value", + .client_cb = &MHDT_client_put_data, + .client_cb_cls = "simple-upload-value", + .timeout_ms = 2500, + .http_version = 2, + }, + { + .label = "client PUT with 2 chunks", + .server_cb = &MHDT_server_reply_check_upload, + .server_cb_cls = (void *) "chunky-upload-value", + .client_cb = &MHDT_client_chunk_data, + .client_cb_cls = "chunky-upload-value", + .timeout_ms = 2500, + .http_version = 2, + }, + { + .label = "client request with custom header", + .server_cb = &MHDT_server_reply_check_header, + .server_cb_cls = (void *) "C-Header:testvalue", + .client_cb = &MHDT_client_set_header, + .client_cb_cls = "C-Header:testvalue", + .timeout_ms = 2500, + .http_version = 2, + }, + { + .label = "server response with custom header", + .server_cb = &MHDT_server_reply_with_header, + .server_cb_cls = (void *) "X-Header:testvalue", + .client_cb = &MHDT_client_expect_header, + .client_cb_cls = "X-Header:testvalue", + .timeout_ms = 2500, + .http_version = 2, + }, + { + .label = "URL with query parameters 1", + .server_cb = &MHDT_server_reply_check_query, + .server_cb_cls = (void *) "a=b&c", + .client_cb = &MHDT_client_get_with_query, + .client_cb_cls = "?a=b&c", + .timeout_ms = 5000, + .num_clients = 4, + .http_version = 2, + }, + { + .label = "URL with query parameters 2", + .server_cb = &MHDT_server_reply_check_query, + .server_cb_cls = (void *) "a=b&c", /* a => b, c => NULL */ + .client_cb = &MHDT_client_get_with_query, + .client_cb_cls = "?c&a=b", + .timeout_ms = 5000, + .num_clients = 1, + .http_version = 2, + }, + { + .label = "URL with query parameters 3", + .server_cb = &MHDT_server_reply_check_query, + .server_cb_cls = (void *) "a=&c", /* a => "", c => NULL */ + .client_cb = &MHDT_client_get_with_query, + .client_cb_cls = "?c&a=", + .timeout_ms = 5000, + .num_clients = 1, + .http_version = 2, + }, + { + .label = "URL with query parameters 4", + .server_cb = &MHDT_server_reply_check_query, + .server_cb_cls = (void *) "a=", /* a => "" */ + .client_cb = &MHDT_client_get_with_query, + .client_cb_cls = "?a=", + .timeout_ms = 5000, + .num_clients = 1, + .http_version = 2, + }, + { + .label = "URL with query parameters 5", + .server_cb = &MHDT_server_reply_check_query, + .server_cb_cls = (void *) "a=b", /* a => "b" */ + .client_cb = &MHDT_client_get_with_query, + .client_cb_cls = "?a=b", + .timeout_ms = 5000, + .num_clients = 1, + .http_version = 2, + }, + { + .label = "chunked response get", + .server_cb = &MHDT_server_reply_chunked_text, + .server_cb_cls = (void *) "Hello world", + .client_cb = &MHDT_client_get_root, + .client_cb_cls = "Hello world", + .timeout_ms = 2500, + .http_version = 2, + }, + // TODO: chunked download + { + .label = NULL, + }, + }; + unsigned int i; + + (void) argc; /* Unused. Silence compiler warning. */ + (void) argv; /* Unused. Silence compiler warning. */ + + for (i = 0; NULL != configs[i].server_setup; i++) + { + int ret; + + fprintf (stderr, + "Running tests with server setup '%s'\n", + configs[i].label); + ret = MHDT_test (configs[i].server_setup, + configs[i].server_setup_cls, + configs[i].server_runner, + configs[i].server_runner_cls, + phases); + if (0 != ret) + { + fprintf (stderr, + "Test failed with server of type '%s' (%u)\n", + configs[i].label, + i); + return ret; + } + } + return 0; +}