libmicrohttpd2

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

commit aba1c8bd98795cb1eec679b762228524733e954f
parent 7031d8be9f4f8f21e63b5fe7193a9c1597e84d11
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat, 15 Nov 2025 23:14:41 +0100

add tests for HTTP2 over TLS

Diffstat:
Msrc/tests/client_server/Makefile.am | 5+++++
Msrc/tests/client_server/libtest.h | 3++-
Msrc/tests/client_server/libtest_convenience_client_request.c | 37++++++++++++++++++++++++++++++++-----
Msrc/tests/client_server/test_tls.c | 36++++++++++++++++++++++++------------
Asrc/tests/client_server/test_tls2.c | 366+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 429 insertions(+), 18 deletions(-)

diff --git a/src/tests/client_server/Makefile.am b/src/tests/client_server/Makefile.am @@ -40,6 +40,11 @@ endif if MHD_SUPPORT_HTTP2 check_PROGRAMS += \ test_http2 + +if MHD_SUPPORT_HTTPS +check_PROGRAMS += \ + test_tls2 +endif endif TESTS = $(check_PROGRAMS) diff --git a/src/tests/client_server/libtest.h b/src/tests/client_server/libtest.h @@ -154,7 +154,8 @@ struct MHDT_Phase bool check_server_cert; /** - * HTTP version to use. 0 = HTTP/1.x, 2 = HTTP/2, 3 = HTTP/3. + * HTTP version to use. 0 = any (negotiated), + * 1 = HTTP/1.x, 2 = HTTP/2, 3 = HTTP/3. */ unsigned int http_version; diff --git a/src/tests/client_server/libtest_convenience_client_request.c b/src/tests/client_server/libtest_convenience_client_request.c @@ -371,21 +371,48 @@ setup_curl (const struct MHDT_PhaseContext *pc) return NULL; switch (p->http_version) { - case 0: /* unset == HTTP/1.x! */ - case 1: + case 0: /* unset == any */ break; - case 2: /* HTTP/2 */ + case 1: if (CURLE_OK != curl_easy_setopt (c, CURLOPT_HTTP_VERSION, - CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE)) + CURL_HTTP_VERSION_1_1)) { curl_easy_cleanup (c); fprintf (stderr, - "HTTP/2 not supported by curl?\n"); + "HTTP/1 not supported by curl?\n"); return NULL; } break; + case 2: /* HTTP/2 */ + if (p->use_tls) + { + if (CURLE_OK != + curl_easy_setopt (c, + CURLOPT_HTTP_VERSION, + CURL_HTTP_VERSION_2TLS)) + { + curl_easy_cleanup (c); + fprintf (stderr, + "HTTP/2 not supported by curl?\n"); + return NULL; + } + } + else + { + 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; diff --git a/src/tests/client_server/test_tls.c b/src/tests/client_server/test_tls.c @@ -210,7 +210,8 @@ main (int argc, char *argv[]) .client_cb = &MHDT_client_get_root, .client_cb_cls = "Hello world", .timeout_ms = 2500, - .use_tls = true + .use_tls = true, + .http_version = 1, }, { .label = "GET with sendfile", @@ -219,7 +220,8 @@ main (int argc, char *argv[]) .client_cb = &MHDT_client_get_root, .client_cb_cls = "Hello world", .timeout_ms = 2500, - .use_tls = true + .use_tls = true, + .http_version = 1, }, { .label = "client PUT with content-length", @@ -228,7 +230,8 @@ main (int argc, char *argv[]) .client_cb = &MHDT_client_put_data, .client_cb_cls = "simple-upload-value", .timeout_ms = 2500, - .use_tls = true + .use_tls = true, + .http_version = 1, }, { .label = "client PUT with 2 chunks", @@ -237,7 +240,8 @@ main (int argc, char *argv[]) .client_cb = &MHDT_client_chunk_data, .client_cb_cls = "chunky-upload-value", .timeout_ms = 2500, - .use_tls = true + .use_tls = true, + .http_version = 1, }, { .label = "client request with custom header", @@ -246,7 +250,8 @@ main (int argc, char *argv[]) .client_cb = &MHDT_client_set_header, .client_cb_cls = "C-Header:testvalue", .timeout_ms = 2500, - .use_tls = true + .use_tls = true, + .http_version = 1, }, { .label = "server response with custom header", @@ -255,7 +260,8 @@ main (int argc, char *argv[]) .client_cb = &MHDT_client_expect_header, .client_cb_cls = "X-Header:testvalue", .timeout_ms = 2500, - .use_tls = true + .use_tls = true, + .http_version = 1, }, { .label = "URL with query parameters 1", @@ -265,7 +271,8 @@ main (int argc, char *argv[]) .client_cb_cls = "?a=b&c", .timeout_ms = 5000, .num_clients = 4, - .use_tls = true + .use_tls = true, + .http_version = 1, }, { .label = "URL with query parameters 2", @@ -275,7 +282,8 @@ main (int argc, char *argv[]) .client_cb_cls = "?c&a=b", .timeout_ms = 5000, .num_clients = 1, - .use_tls = true + .use_tls = true, + .http_version = 1, }, { .label = "URL with query parameters 3", @@ -285,7 +293,8 @@ main (int argc, char *argv[]) .client_cb_cls = "?c&a=", .timeout_ms = 5000, .num_clients = 1, - .use_tls = true + .use_tls = true, + .http_version = 1, }, { .label = "URL with query parameters 4", @@ -295,7 +304,8 @@ main (int argc, char *argv[]) .client_cb_cls = "?a=", .timeout_ms = 5000, .num_clients = 1, - .use_tls = true + .use_tls = true, + .http_version = 1, }, { .label = "URL with query parameters 5", @@ -305,7 +315,8 @@ main (int argc, char *argv[]) .client_cb_cls = "?a=b", .timeout_ms = 5000, .num_clients = 1, - .use_tls = true + .use_tls = true, + .http_version = 1, }, { .label = "chunked response get", @@ -314,7 +325,8 @@ main (int argc, char *argv[]) .client_cb = &MHDT_client_get_root, .client_cb_cls = "Hello world", .timeout_ms = 2500, - .use_tls = true + .use_tls = true, + .http_version = 1, }, // TODO: chunked download { diff --git a/src/tests/client_server/test_tls2.c b/src/tests/client_server/test_tls2.c @@ -0,0 +1,366 @@ +/* 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 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_tls.c + * @brief test with client against TLS 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 () + }; +#ifdef MHD_SUPPORT_EPOLL + 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 () + }; +#endif /* MHD_SUPPORT_EPOLL */ + 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_tls, + .server_setup_cls = thread1select, + .server_runner = &MHDT_server_run_minimal, + }, + { + .label = "multi-threaded select", + .server_setup = &MHDT_server_setup_tls, + .server_setup_cls = thread2select, + .server_runner = &MHDT_server_run_minimal, + }, +#if MHD_SUPPORT_GNUTLS + { + .label = "multi-threaded select, forcing GnuTLS", + .server_setup = &MHDT_server_setup_gnutls, + .server_setup_cls = thread2select, + .server_runner = &MHDT_server_run_minimal, + }, +#endif +#endif +#ifdef MHD_SUPPORT_POLL + { + .label = "single threaded poll", + .server_setup = &MHDT_server_setup_tls, + .server_setup_cls = thread1poll, + .server_runner = &MHDT_server_run_minimal, + }, + { + .label = "multi-threaded poll", + .server_setup = &MHDT_server_setup_tls, + .server_setup_cls = thread2poll, + .server_runner = &MHDT_server_run_minimal, + }, +#ifdef MHD_SUPPORT_GNUTLS + { + .label = "multi-threaded poll, forcing GnuTLS", + .server_setup = &MHDT_server_setup_gnutls, + .server_setup_cls = thread2poll, + .server_runner = &MHDT_server_run_minimal, + }, +#endif +#endif +#ifdef MHD_SUPPORT_EPOLL + { + .label = "single threaded epoll", + .server_setup = &MHDT_server_setup_tls, + .server_setup_cls = thread1epoll, + .server_runner = &MHDT_server_run_minimal, + }, + { + .label = "multi-threaded epoll", + .server_setup = &MHDT_server_setup_tls, + .server_setup_cls = thread2epoll, + .server_runner = &MHDT_server_run_minimal, + }, +#ifdef MHD_SUPPORT_GNUTLS + { + .label = "multi-threaded epoll, forcing GnuTLS", + .server_setup = &MHDT_server_setup_gnutls, + .server_setup_cls = thread2epoll, + .server_runner = &MHDT_server_run_minimal, + }, +#endif +#endif + { + .label = "auto-selected mode, single threaded", + .server_setup = &MHDT_server_setup_tls, + .server_setup_cls = thread1auto, + .server_runner = &MHDT_server_run_minimal, + }, +#ifdef MHD_SUPPORT_GNUTLS + { + .label = "auto-selected mode, single threaded, forcing GnuTLS", + .server_setup = &MHDT_server_setup_gnutls, + .server_setup_cls = thread1auto, + .server_runner = &MHDT_server_run_minimal, + }, +#endif +#ifdef MHD_SUPPORT_OPENSSL + { + .label = "auto-selected mode, single threaded, forcing OpenSSL", + .server_setup = &MHDT_server_setup_openssl, + .server_setup_cls = thread1auto, + .server_runner = &MHDT_server_run_minimal, + }, +#endif +#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_tls, + .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, + .use_tls = true, + .http_version = 2, + }, +#if 0 + { + .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, + .use_tls = true, + .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, + .use_tls = true, + .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, + .use_tls = true, + .http_version = 2, + }, +#endif + { + .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, + .use_tls = true, + .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, + .use_tls = true, + .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, + .use_tls = true, + .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, + .use_tls = true, + .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, + .use_tls = true, + .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, + .use_tls = true, + .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, + .use_tls = true, + .http_version = 2, + }, +#if 0 + { + .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, + .use_tls = true, + .http_version = 2, + }, +#endif + // TODO: chunked download + { + .label = NULL, + }, + }; + unsigned int i; + int ret = 0; + + (void) argc; /* Unused. Silence compiler warning. */ + (void) argv; /* Unused. Silence compiler warning. */ + + for (i = 0; NULL != configs[i].server_setup; i++) + { + fprintf (stderr, + "Running TLS 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); + break; + } + } + return ret; +}