libmicrohttpd2

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

json_echo.c (11560B)


      1 /* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
      2 /*
      3   This file is part of GNU libmicrohttpd.
      4   Copyright (C) 2025 Christian Grothoff, Evgeny Grin (and other
      5   contributing authors)
      6 
      7   GNU libmicrohttpd is free software; you can redistribute it and/or
      8   modify it under the terms of the GNU Lesser General Public
      9   License as published by the Free Software Foundation; either
     10   version 2.1 of the License, or (at your option) any later version.
     11 
     12   GNU libmicrohttpd is distributed in the hope that it will be useful,
     13   but WITHOUT ANY WARRANTY; without even the implied warranty of
     14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     15   Lesser General Public License for more details.
     16 
     17   Alternatively, you can redistribute GNU libmicrohttpd and/or
     18   modify it under the terms of the GNU General Public License as
     19   published by the Free Software Foundation; either version 2 of
     20   the License, or (at your option) any later version, together
     21   with the eCos exception, as follows:
     22 
     23     As a special exception, if other files instantiate templates or
     24     use macros or inline functions from this file, or you compile this
     25     file and link it with other works to produce a work based on this
     26     file, this file does not by itself cause the resulting work to be
     27     covered by the GNU General Public License. However the source code
     28     for this file must still be made available in accordance with
     29     section (3) of the GNU General Public License v2.
     30 
     31     This exception does not invalidate any other reasons why a work
     32     based on this file might be covered by the GNU General Public
     33     License.
     34 
     35   You should have received copies of the GNU Lesser General Public
     36   License and the GNU General Public License along with this library;
     37   if not, see <https://www.gnu.org/licenses/>.
     38 */
     39 /**
     40  * @file json_echo.c
     41  * @brief example for processing POST requests with JSON uploads, echos the JSON back to the client
     42  * @author Christian Grothoff
     43  * @author Karlson2k (Evgeny Grin)
     44  */
     45 #include <sys/types.h>
     46 #include <stdint.h>
     47 #include <stdlib.h>
     48 #include <string.h>
     49 #include <stdio.h>
     50 #include <errno.h>
     51 #if ! defined(_WIN32) || defined (__CYGWIN__)
     52 #  include <sys/select.h>
     53 #  include <unistd.h>
     54 #else
     55 #  include <conio.h>
     56 #endif
     57 #include <assert.h>
     58 struct AppSockContext; /* Forward declaration */
     59 #define MHD_APP_SOCKET_CNTX_TYPE struct AppSockContext
     60 #include <microhttpd2.h>
     61 #include <jansson.h>
     62 
     63 /**
     64  * Bad request page.
     65  */
     66 #define BAD_REQUEST_PAGE \
     67         "<html><head><title>Illegal request</title></head><body>Go away.</body></html>"
     68 
     69 /**
     70  * Invalid JSON page.
     71  */
     72 #define FILE_NOT_FOUND_PAGE \
     73         "<html><head><title>Not found</title></head><body>Go away.</body></html>"
     74 
     75 /**
     76  * We keep the sockets we are waiting on in a DLL.
     77  */
     78 struct AppSockContext
     79 {
     80   struct AppSockContext *next;
     81   struct AppSockContext *prev;
     82   struct MHD_EventUpdateContext *ecb_cntx;
     83   MHD_Socket fd;
     84 };
     85 
     86 
     87 /**
     88  * Current read set.
     89  */
     90 static fd_set rs;
     91 
     92 /**
     93  * Current write set.
     94  */
     95 static fd_set ws;
     96 
     97 /**
     98  * Current error set.
     99  */
    100 static fd_set es;
    101 
    102 /**
    103  * Maximum FD in any set.
    104  */
    105 static MHD_Socket max_fd = 0;
    106 
    107 /**
    108  * Head of our internal list of sockets to select() on.
    109  */
    110 static struct AppSockContext *head;
    111 
    112 /**
    113  * Generates 404.
    114  */
    115 static struct MHD_Response *file_not_found_response;
    116 
    117 /**
    118  * Generates 400.
    119  */
    120 static struct MHD_Response *bad_request_response;
    121 
    122 
    123 static const struct MHD_UploadAction *
    124 handle_upload (void *upload_cls,
    125                struct MHD_Request *request,
    126                size_t content_data_size,
    127                void *content_data)
    128 {
    129   json_t *j;
    130   json_error_t err;
    131   char *s;
    132   struct MHD_Response *response;
    133   (void) upload_cls; /* Unused. Mute compiler warning. */
    134 
    135   j = json_loadb ((char *) content_data,
    136                   content_data_size,
    137                   0,
    138                   &err);
    139   if (NULL == j)
    140     return MHD_upload_action_from_response (request,
    141                                             bad_request_response);
    142   s = json_dumps (j,
    143                   JSON_INDENT (2));
    144   json_decref (j);
    145   response = MHD_response_from_buffer (MHD_HTTP_STATUS_OK,
    146                                        strlen (s),
    147                                        s,
    148                                        &free,
    149                                        s);
    150   return MHD_upload_action_from_response (request,
    151                                           response);
    152 }
    153 
    154 
    155 static const struct MHD_Action *
    156 handle_request (void *cls,
    157                 struct MHD_Request *request,
    158                 const struct MHD_String *path,
    159                 enum MHD_HTTP_Method method,
    160                 uint_fast64_t upload_size)
    161 {
    162   (void) cls;  /* Unused. Mute compiler warning. */
    163   (void) path; /* Unused. Mute compiler warning. */
    164 
    165   if (method != MHD_HTTP_METHOD_POST)
    166     return MHD_action_from_response (request,
    167                                      file_not_found_response);
    168   if (upload_size > 16 * 1024 * 1024)
    169     return MHD_action_abort_request (request);
    170   return MHD_action_process_upload_full (request,
    171                                          upload_size,
    172                                          &handle_upload,
    173                                          NULL);
    174 }
    175 
    176 
    177 /* This is the function MHD will call when the external event
    178    loop needs to change how it watches out for changes to
    179    some socket's state */
    180 static MHD_APP_SOCKET_CNTX_TYPE *
    181 sock_reg_update_cb (
    182   void *cls,
    183   MHD_Socket fd,
    184   enum MHD_FdState watch_for,
    185   MHD_APP_SOCKET_CNTX_TYPE *app_cntx,
    186   struct MHD_EventUpdateContext *ecb_cntx)
    187 {
    188   (void) cls; /* Unused. Mute compiler warning. */
    189 #ifdef MHD_SOCKETS_KIND_POSIX
    190   /* The value is limited by MHD_D_OPTION_FD_NUMBER_LIMIT() */
    191   assert (fd < FD_SETSIZE);
    192 #endif /* MHD_SOCKETS_KIND_POSIX */
    193   if (MHD_FD_STATE_NONE == watch_for)
    194   {
    195     /* Remove from DLL */
    196     if (app_cntx == head)
    197       head = app_cntx->next;
    198     if (NULL != app_cntx->prev)
    199       app_cntx->prev->next = app_cntx->next;
    200     if (NULL != app_cntx->next)
    201       app_cntx->next->prev = app_cntx->prev;
    202     free (app_cntx);
    203     return NULL;
    204   }
    205   if (NULL == app_cntx)
    206   {
    207     /* First time, allocate data structure to keep
    208        the socket and MHD's context */
    209     app_cntx =
    210       (MHD_APP_SOCKET_CNTX_TYPE *) malloc (sizeof (MHD_APP_SOCKET_CNTX_TYPE));
    211     if (NULL == app_cntx)
    212       return NULL; /* closes connection */
    213     /* prepend to DLL */
    214     app_cntx->prev = NULL;
    215     app_cntx->next = head;
    216     if (NULL != head)
    217       head->prev = app_cntx;
    218     head = app_cntx;
    219     app_cntx->fd = fd;
    220   }
    221   else
    222   {
    223     /* socket must not change */
    224     assert (fd == app_cntx->fd);
    225   }
    226   /* MHD could change its associated context, so always update */
    227   app_cntx->ecb_cntx = ecb_cntx;
    228   /* Since we are called by MHD in every iteration, we simply build
    229      the event sets for select() here directly. */
    230   if (watch_for & MHD_FD_STATE_RECV)
    231     FD_SET (fd,
    232             &rs);
    233   if (watch_for & MHD_FD_STATE_SEND)
    234     FD_SET (fd,
    235             &ws);
    236   if (watch_for & MHD_FD_STATE_EXCEPT)
    237     FD_SET (fd,
    238             &es);
    239   if (fd > max_fd)
    240     max_fd = fd;
    241   return app_cntx;
    242 }
    243 
    244 
    245 /**
    246  * Mark the given response as HTML for the browser.
    247  *
    248  * @param response response to mark
    249  */
    250 static void
    251 mark_as_html (struct MHD_Response *response)
    252 {
    253   if (NULL == response)
    254     return;
    255   (void) MHD_response_add_header (response,
    256                                   MHD_HTTP_HEADER_CONTENT_TYPE,
    257                                   "text/html");
    258 }
    259 
    260 
    261 /**
    262  * Call with the port number as the only argument.
    263  * Terminates when reading from stdin or on signals, such as CTRL-C.
    264  */
    265 int
    266 main (int argc,
    267       char *const *argv)
    268 {
    269   struct MHD_Daemon *d;
    270   unsigned int port;
    271   char dummy;
    272 
    273   if ( (argc != 2) ||
    274        (1 != sscanf (argv[1],
    275                      "%u%c",
    276                      &port,
    277                      &dummy)) ||
    278        (UINT16_MAX < port) )
    279   {
    280     if (2 == argc)
    281     {
    282       fprintf (stderr,
    283                "Usage: %s PORT\n",
    284                argv[0]);
    285       return 1;
    286     }
    287     port = 8080;
    288   }
    289   file_not_found_response =
    290     MHD_response_from_buffer_static (
    291       MHD_HTTP_STATUS_NOT_FOUND,
    292       strlen (FILE_NOT_FOUND_PAGE),
    293       FILE_NOT_FOUND_PAGE);
    294   mark_as_html (file_not_found_response);
    295   if (MHD_SC_OK !=
    296       MHD_response_set_option (file_not_found_response,
    297                                &MHD_R_OPTION_REUSABLE (MHD_YES)))
    298     return 1;
    299   bad_request_response =
    300     MHD_response_from_buffer_static (
    301       MHD_HTTP_STATUS_BAD_REQUEST,
    302       strlen (BAD_REQUEST_PAGE),
    303       BAD_REQUEST_PAGE);
    304   mark_as_html (bad_request_response);
    305   if (MHD_SC_OK !=
    306       MHD_response_set_option (bad_request_response,
    307                                &MHD_R_OPTION_REUSABLE (MHD_YES)))
    308     return 1;
    309 
    310   d = MHD_daemon_create (&handle_request,
    311                          NULL);
    312   if (NULL == d)
    313     return 1;
    314   if (MHD_SC_OK !=
    315       MHD_DAEMON_SET_OPTIONS (
    316         d,
    317         MHD_D_OPTION_REREGISTER_ALL (MHD_YES),
    318         MHD_D_OPTION_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL (
    319           &sock_reg_update_cb,
    320           NULL),
    321         MHD_D_OPTION_FD_NUMBER_LIMIT (FD_SETSIZE),
    322         MHD_D_OPTION_DEFAULT_TIMEOUT (120 /* seconds */),
    323         MHD_D_OPTION_CONN_MEMORY_LIMIT (256 * 1024),
    324         MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO,
    325                                 (uint_least16_t) port)))
    326     return 1;
    327   if (MHD_SC_OK !=
    328       MHD_daemon_start (d))
    329   {
    330     MHD_daemon_destroy (d);
    331     return 1;
    332   }
    333   while (1)
    334   {
    335     struct timeval ts;
    336     struct AppSockContext *pos;
    337     uint_fast64_t next_wait;
    338 
    339     FD_ZERO (&rs);
    340     FD_ZERO (&ws);
    341     FD_ZERO (&es);
    342 #ifdef MHD_SOCKETS_KIND_POSIX
    343     FD_SET (STDIN_FILENO,
    344             &rs);
    345     max_fd = STDIN_FILENO;
    346 #endif /* MHD_SOCKETS_KIND_POSIX */
    347 
    348     /* This will cause MHD to call the #sock_reg_update_cb() */
    349     MHD_daemon_process_reg_events (d,
    350                                    &next_wait);
    351 #ifdef MHD_SOCKETS_KIND_POSIX
    352     ts.tv_sec = (time_t) (next_wait / 1000000);
    353 #else  /* W32 */
    354     /* W32 cannot monitor "stdin" with select().
    355        Use poor man replacement. */
    356     if (300000u < next_wait)
    357       next_wait = 300000u;
    358     ts.tv_sec = (long) (next_wait / 1000000);
    359 #endif /* W32 */
    360     ts.tv_usec = (long) (next_wait % 1000000);
    361     /* Real applications may do nicer error handling here */
    362     (void) select ((int) max_fd + 1,
    363                    &rs,
    364                    &ws,
    365                    &es,
    366                    &ts);
    367 #ifdef MHD_SOCKETS_KIND_POSIX
    368     if (FD_ISSET (STDIN_FILENO,
    369                   &rs))
    370       break; /* exit on input on stdin */
    371 #else  /* W32 */
    372     if (0 != _kbhit ())
    373       break; /* exit on console input */
    374 #endif /* W32 */
    375 
    376     /* Now we need to tell MHD which events were triggered */
    377     for (pos = head; NULL != pos; pos = pos->next)
    378     {
    379       enum MHD_FdState current_state = MHD_FD_STATE_NONE;
    380 
    381       if (FD_ISSET (pos->fd,
    382                     &rs))
    383         current_state =
    384           (enum MHD_FdState) (current_state | MHD_FD_STATE_RECV);
    385       if (FD_ISSET (pos->fd,
    386                     &ws))
    387         current_state =
    388           (enum MHD_FdState) (current_state | MHD_FD_STATE_SEND);
    389       if (FD_ISSET (pos->fd,
    390                     &es))
    391         current_state =
    392           (enum MHD_FdState) (current_state | MHD_FD_STATE_EXCEPT);
    393       MHD_daemon_event_update (d,
    394                                pos->ecb_cntx,
    395                                current_state);
    396     }
    397   }
    398   MHD_daemon_destroy (d);
    399   MHD_response_destroy (file_not_found_response);
    400   MHD_response_destroy (bad_request_response);
    401   return 0;
    402 }