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:
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;
+}