libmicrohttpd2

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

commit 7971df739ffdf324abc87a7886b901375644807a
parent 88529410e76a5aec3e7b9354d7990eedc7908063
Author: Christian Grothoff <grothoff@gnunet.org>
Date:   Tue, 13 May 2025 12:29:15 +0200

add json_echo example

Diffstat:
Mconfigure.ac | 3+++
Msrc/examples2/.gitignore | 4++--
Msrc/examples2/Makefile.am | 11+++++++++++
Msrc/examples2/demo.c | 18++++++++++++------
Asrc/examples2/json_echo.c | 354+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 382 insertions(+), 8 deletions(-)

diff --git a/configure.ac b/configure.ac @@ -3231,6 +3231,9 @@ AM_CONDITIONAL([MHD_HAVE_TLS_PLUGIN], [[test "x$have_tlsplugin" = xyes]]) AC_CHECK_HEADERS([zlib.h],[have_zlib=yes],[have_zlib=no], [AC_INCLUDES_DEFAULT]) AM_CONDITIONAL([HAVE_ZLIB], [[test "x$have_zlib" = xyes]]) +AC_CHECK_HEADERS([jansson.h],[have_json=yes],[have_json=no], [AC_INCLUDES_DEFAULT]) +AM_CONDITIONAL([HAVE_JANSSON], [[test "x$have_json" = xyes]]) + # Check for generic functions MHD_CHECK_FUNC([random], [ diff --git a/src/examples2/.gitignore b/src/examples2/.gitignore @@ -4,4 +4,5 @@ /*_example? /*example_*[a-z0-9_][a-z0-9_][a-z0-9_] !*.c -!*.h -\ No newline at end of file +!*.hjson_echo +json_echo diff --git a/src/examples2/Makefile.am b/src/examples2/Makefile.am @@ -32,6 +32,10 @@ if HAVE_POSIX_THREADS noinst_PROGRAMS += demo endif +if HAVE_JANSSON + noinst_PROGRAMS += json_echo +endif + if MHD_SUPPORT_AUTH_BASIC noinst_PROGRAMS += minimal_auth_basic endif @@ -42,3 +46,10 @@ endif demo_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS) demo_LDADD = $(LDADD) $(PTHREAD_LIBS) + + +json_echo_SOURCES = \ + json_echo.c +json_echo_LDADD = \ + $(top_builddir)/src/mhd2/libmicrohttpd2.la \ + -ljansson diff --git a/src/examples2/demo.c b/src/examples2/demo.c @@ -1279,17 +1279,23 @@ main (int argc, { struct MHD_Daemon *d; unsigned int port; + char dummy; if ( (argc != 2) || - (1 != sscanf (argv[1], "%u", &port)) || + (1 != sscanf (argv[1], + "%u%d", + &port, + &dummy)) || (UINT16_MAX < port) ) { - fprintf (stderr, - "Usage: %s PORT\n", argv[0]); - if (argc != 2) - port = 8080; - else + if (2 == argc) + { + fprintf (stderr, + "Usage: %s PORT\n", + argv[0]); return 1; + } + port = 8080; } #if ! defined(_WIN32) || defined(__CYGWIN__) ignore_sigpipe (); diff --git a/src/examples2/json_echo.c b/src/examples2/json_echo.c @@ -0,0 +1,354 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2025 Christian Grothoff (and other contributing authors) + + This library 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. + + This library 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. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +/** + * @file json_echo.c + * @brief example for processing POST requests with JSON uploads, echos the JSON back to the client + * @author Christian Grothoff + */ +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <assert.h> +#define MHD_APP_SOCKET_CNTX_TYPE struct AppSockContext +#include <microhttpd2.h> +#include <jansson.h> + +/** + * Bad request page. + */ +#define BAD_REQUEST_PAGE \ + "<html><head><title>Illegal request</title></head><body>Go away.</body></html>" + +/** + * Invalid JSON page. + */ +#define FILE_NOT_FOUND_PAGE \ + "<html><head><title>Not found</title></head><body>Go away.</body></html>" + +/** + * We keep the sockets we are waiting on in a DLL. + */ +struct AppSockContext +{ + struct AppSockContext *next; + struct AppSockContext *prev; + struct MHD_EventUpdateContext *ecb_cntx; + MHD_Socket fd; +}; + + +/** + * Current read set. + */ +static fd_set rs; + +/** + * Current write set. + */ +static fd_set ws; + +/** + * Current error set. + */ +static fd_set es; + +/** + * Maximum FD in any set. + */ +static int max_fd; + +/** + * Head of our internal list of sockets to select() on. + */ +static struct AppSockContext *head; + +/** + * Generates 404. + */ +static struct MHD_Response *file_not_found_response; + +/** + * Generates 400. + */ +static struct MHD_Response *bad_request_response; + + +static const struct MHD_UploadAction * +handle_upload (void *upload_cls, + struct MHD_Request *request, + size_t content_data_size, + void *content_data) +{ + json_t *j; + json_error_t err; + char *s; + struct MHD_Response *response; + + j = json_loadb (content_data, + content_data_size, + 0, + &err); + if (NULL == j) + return MHD_upload_action_from_response (request, + bad_request_response); + s = json_dumps (j, + JSON_INDENT (2)); + json_decref (j); + response = MHD_response_from_buffer (MHD_HTTP_STATUS_OK, + strlen (s), + s, + &free, + s); + return MHD_upload_action_from_response (request, + response); +} + + +static const struct MHD_Action * +handle_request (void *cls, + struct MHD_Request *request, + const struct MHD_String *path, + enum MHD_HTTP_Method method, + uint_fast64_t upload_size) +{ + if (method != MHD_HTTP_METHOD_POST) + return MHD_action_from_response (request, + file_not_found_response); + if (upload_size > 16 * 1024 * 1024) + return MHD_action_abort_request (request); + return MHD_action_process_upload_full (request, + upload_size, + &handle_upload, + NULL); +} + + +/* This is the function MHD will call when the external event + loop needs to change how it watches out for changes to + some socket's state */ +static MHD_APP_SOCKET_CNTX_TYPE * +sock_reg_update_cb ( + void *cls, + MHD_Socket fd, + enum MHD_FdState watch_for, + MHD_APP_SOCKET_CNTX_TYPE *app_cntx, + struct MHD_EventUpdateContext *ecb_cntx) +{ + /* Note: This code only works on UNIX where MHD_Socket is an "int". */ + if (fd >= FD_SETSIZE) + return NULL; /* not allowed by select() */ + if (MHD_FD_STATE_NONE == watch_for) + { + /* Remove from DLL */ + if (app_cntx == head) + head = app_cntx->next; + if (NULL != app_cntx->prev) + app_cntx->prev->next = app_cntx->next; + if (NULL != app_cntx->next) + app_cntx->next->prev = app_cntx->prev; + free (app_cntx); + return NULL; + } + if (NULL == app_cntx) + { + /* First time, allocate data structure to keep + the socket and MHD's context */ + app_cntx = malloc (sizeof (MHD_APP_SOCKET_CNTX_TYPE)); + if (NULL == app_cntx) + return NULL; /* closes connection */ + /* prepend to DLL */ + app_cntx->prev = NULL; + app_cntx->next = head; + head->prev = app_cntx; + head = app_cntx; + app_cntx->fd = fd; + } + else + { + /* socket must not change */ + assert (fd == app_cntx->fd); + } + /* MHD could change its associated context, so always update */ + app_cntx->ecb_cntx = ecb_cntx; + /* Since we are level-triggered and thus called by MHD in every + iteration, we simply build the event sets for select() + here directly. */ + if (watch_for & MHD_FD_STATE_RECV) + FD_SET (fd, + &rs); + if (watch_for & MHD_FD_STATE_SEND) + FD_SET (fd, + &ws); + if (watch_for & MHD_FD_STATE_EXCEPT) + FD_SET (fd, + &es); + if (fd > max_fd) + max_fd = fd; + return app_cntx; +} + + +/** + * Mark the given response as HTML for the browser. + * + * @param response response to mark + */ +static void +mark_as_html (struct MHD_Response *response) +{ + if (NULL == response) + return; + (void) MHD_response_add_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + "text/html"); +} + + +/** + * Call with the port number as the only argument. + * Terminates when reading from stdin or on signals, such as CTRL-C. + */ +int +main (int argc, + char *const *argv) +{ + struct MHD_Daemon *d; + struct timeval tv; + struct timeval *tvp; + fd_set rs; + fd_set ws; + fd_set es; + MHD_Socket max; + uint64_t mhd_timeout; + unsigned int port; + char dummy; + + if ( (argc != 2) || + (1 != sscanf (argv[1], + "%u%c", + &port, + &dummy)) || + (UINT16_MAX < port) ) + { + if (2 == argc) + { + fprintf (stderr, + "Usage: %s PORT\n", + argv[0]); + return 1; + } + port = 8080; + } + file_not_found_response = + MHD_response_from_buffer_static ( + MHD_HTTP_STATUS_NOT_FOUND, + strlen (FILE_NOT_FOUND_PAGE), + FILE_NOT_FOUND_PAGE); + mark_as_html (file_not_found_response); + if (MHD_SC_OK != + MHD_response_set_option (file_not_found_response, + &MHD_R_OPTION_REUSABLE (MHD_YES))) + return 1; + bad_request_response = + MHD_response_from_buffer_static ( + MHD_HTTP_STATUS_BAD_REQUEST, + strlen (BAD_REQUEST_PAGE), + BAD_REQUEST_PAGE); + mark_as_html (bad_request_response); + if (MHD_SC_OK != + MHD_response_set_option (bad_request_response, + &MHD_R_OPTION_REUSABLE (MHD_YES))) + return 1; + + d = MHD_daemon_create (&handle_request, + NULL); + if (NULL == d) + return 1; + if (MHD_SC_OK != + MHD_DAEMON_SET_OPTIONS ( + d, + MHD_D_OPTION_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL ( + &sock_reg_update_cb, + NULL), + MHD_D_OPTION_DEFAULT_TIMEOUT (120 /* seconds */), + MHD_D_OPTION_CONN_MEMORY_LIMIT (256 * 1024), + MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO, + (uint_least16_t) port))) + return 1; + if (MHD_SC_OK != + MHD_daemon_start (d)) + { + MHD_daemon_destroy (d); + return 1; + } + while (1) + { + struct timeval ts; + struct AppSockContext *pos; + uint_fast64_t next_wait; + + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + FD_SET (STDIN_FILENO, + &rs); + max_fd = STDIN_FILENO; + /* This will cause MHD to call the #sock_reg_update_cb() */ + MHD_daemon_process_reg_events (d, + &next_wait); + ts.tv_sec = next_wait / 1000000; + ts.tv_usec = next_wait % 1000000; + /* Real applications may do nicer error handling here */ + (void) select (max_fd + 1, + &rs, + &ws, + &es, + &ts); + if (FD_ISSET (STDIN_FILENO, + &rs)) + break; /* exit on input on stdin */ + + /* Now we need to tell MHD which events were triggered */ + for (pos = head; NULL != pos; pos = pos->next) + { + enum MHD_FdState current_state = MHD_FD_STATE_NONE; + + if (FD_ISSET (pos->fd, + &rs)) + current_state |= MHD_FD_STATE_RECV; + if (FD_ISSET (pos->fd, + &ws)) + current_state |= MHD_FD_STATE_SEND; + if (FD_ISSET (pos->fd, + &es)) + current_state |= MHD_FD_STATE_EXCEPT; + if (MHD_FD_STATE_NONE == current_state) + continue; /* not triggered */ + MHD_daemon_event_update (d, + pos->ecb_cntx, + current_state); + } + } + MHD_daemon_destroy (d); + MHD_response_destroy (file_not_found_response); + MHD_response_destroy (bad_request_response); + return 0; +}