libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

json_echo.c (10293B)


      1 /*
      2      This file is part of libmicrohttpd
      3      Copyright (C) 2025 Christian Grothoff (and other contributing authors)
      4 
      5      This library is free software; you can redistribute it and/or
      6      modify it under the terms of the GNU Lesser General Public
      7      License as published by the Free Software Foundation; either
      8      version 2.1 of the License, or (at your option) any later version.
      9 
     10      This library is distributed in the hope that it will be useful,
     11      but WITHOUT ANY WARRANTY; without even the implied warranty of
     12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13      Lesser General Public License for more details.
     14 
     15      You should have received a copy of the GNU Lesser General Public
     16      License along with this library; if not, write to the Free Software
     17      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
     18 */
     19 /**
     20  * @file json_echo.c
     21  * @brief example for processing POST requests with JSON uploads, echos the JSON back to the client
     22  * @author Christian Grothoff
     23  */
     24 #include <stdlib.h>
     25 #include <string.h>
     26 #include <stdio.h>
     27 #include <errno.h>
     28 #include <time.h>
     29 #include <microhttpd.h>
     30 #include <jansson.h>
     31 
     32 /**
     33  * Bad request page.
     34  */
     35 #define BAD_REQUEST_ERROR \
     36         "<html><head><title>Illegal request</title></head><body>Go away.</body></html>"
     37 
     38 /**
     39  * Invalid JSON page.
     40  */
     41 #define NOT_FOUND_ERROR \
     42         "<html><head><title>Not found</title></head><body>Go away.</body></html>"
     43 
     44 
     45 /**
     46  * State we keep for each request.
     47  */
     48 struct Request
     49 {
     50 
     51   /**
     52    * Number of bytes received.
     53    */
     54   size_t off;
     55 
     56   /**
     57    * Size of @a buf.
     58    */
     59   size_t len;
     60 
     61   /**
     62    * Buffer for POST data.
     63    */
     64   void *buf;
     65 
     66 };
     67 
     68 
     69 /**
     70  * Handler used to generate a 404 reply.
     71  *
     72  * @param connection connection to use
     73  */
     74 static enum MHD_Result
     75 not_found_page (struct MHD_Connection *connection)
     76 {
     77   struct MHD_Response *response;
     78   enum MHD_Result ret;
     79 
     80   response =
     81     MHD_create_response_from_buffer_static (strlen (NOT_FOUND_ERROR),
     82                                             (const void *) NOT_FOUND_ERROR);
     83   if (NULL == response)
     84     return MHD_NO;
     85   ret = MHD_queue_response (connection,
     86                             MHD_HTTP_NOT_FOUND,
     87                             response);
     88   if (MHD_YES !=
     89       MHD_add_response_header (response,
     90                                MHD_HTTP_HEADER_CONTENT_ENCODING,
     91                                "text/html"))
     92   {
     93     fprintf (stderr,
     94              "Failed to set content encoding header!\n");
     95   }
     96   MHD_destroy_response (response);
     97   return ret;
     98 }
     99 
    100 
    101 /**
    102  * Handler used to generate a 400 reply.
    103  *
    104  * @param connection connection to use
    105  */
    106 static enum MHD_Result
    107 invalid_request (struct MHD_Connection *connection)
    108 {
    109   enum MHD_Result ret;
    110   struct MHD_Response *response;
    111 
    112   response =
    113     MHD_create_response_from_buffer_static (
    114       strlen (BAD_REQUEST_ERROR),
    115       (const void *) BAD_REQUEST_ERROR);
    116   if (NULL == response)
    117     return MHD_NO;
    118   ret = MHD_queue_response (connection,
    119                             MHD_HTTP_BAD_REQUEST,
    120                             response);
    121   if (MHD_YES !=
    122       MHD_add_response_header (response,
    123                                MHD_HTTP_HEADER_CONTENT_ENCODING,
    124                                "text/html"))
    125   {
    126     fprintf (stderr,
    127              "Failed to set content encoding header!\n");
    128   }
    129   MHD_destroy_response (response);
    130   return ret;
    131 }
    132 
    133 
    134 /**
    135  * Main MHD callback for handling requests.
    136  *
    137  * @param cls argument given together with the function
    138  *        pointer when the handler was registered with MHD
    139  * @param connection handle identifying the incoming connection
    140  * @param url the requested url
    141  * @param method the HTTP method used ("GET", "PUT", etc.)
    142  * @param version the HTTP version string (i.e. "HTTP/1.1")
    143  * @param upload_data the data being uploaded (excluding HEADERS,
    144  *        for a POST that fits into memory and that is encoded
    145  *        with a supported encoding, the POST data will NOT be
    146  *        given in upload_data and is instead available as
    147  *        part of MHD_get_connection_values; very large POST
    148  *        data *will* be made available incrementally in
    149  *        upload_data)
    150  * @param upload_data_size set initially to the size of the
    151  *        upload_data provided; the method must update this
    152  *        value to the number of bytes NOT processed;
    153  * @param req_cls pointer that the callback can set to some
    154  *        address and that will be preserved by MHD for future
    155  *        calls for this request; since the access handler may
    156  *        be called many times (i.e., for a PUT/POST operation
    157  *        with plenty of upload data) this allows the application
    158  *        to easily associate some request-specific state.
    159  *        If necessary, this state can be cleaned up in the
    160  *        global "MHD_RequestCompleted" callback (which
    161  *        can be set with the MHD_OPTION_NOTIFY_COMPLETED).
    162  *        Initially, <tt>*req_cls</tt> will be NULL.
    163  * @return MHS_YES if the connection was handled successfully,
    164  *         MHS_NO if the socket must be closed due to a serious
    165  *         error while handling the request
    166  */
    167 static enum MHD_Result
    168 create_response (void *cls,
    169                  struct MHD_Connection *connection,
    170                  const char *url,
    171                  const char *method,
    172                  const char *version,
    173                  const char *upload_data,
    174                  size_t *upload_data_size,
    175                  void **req_cls)
    176 {
    177   struct Request *request = *req_cls;
    178   struct MHD_Response *response;
    179   enum MHD_Result ret;
    180 
    181   (void) cls;               /* Unused. Silence compiler warning. */
    182   (void) version;           /* Unused. Silence compiler warning. */
    183 
    184   if (NULL == request)
    185   {
    186     const char *clen;
    187     char dummy;
    188     unsigned int len;
    189 
    190     request = calloc (1, sizeof (struct Request));
    191     if (NULL == request)
    192     {
    193       fprintf (stderr,
    194                "calloc error: %s\n",
    195                strerror (errno));
    196       return MHD_NO;
    197     }
    198     *req_cls = request;
    199     if (0 != strcmp (method,
    200                      MHD_HTTP_METHOD_POST))
    201     {
    202       return not_found_page (connection);
    203     }
    204     clen = MHD_lookup_connection_value (
    205       connection,
    206       MHD_HEADER_KIND,
    207       MHD_HTTP_HEADER_CONTENT_LENGTH);
    208     if (NULL == clen)
    209       return invalid_request (connection);
    210     if (1 != sscanf (clen,
    211                      "%u%c",
    212                      &len,
    213                      &dummy))
    214       return invalid_request (connection);
    215     request->len = len;
    216     request->buf = malloc (request->len);
    217     if (NULL == request->buf)
    218       return MHD_NO;
    219     return MHD_YES;
    220   }
    221   if (0 != *upload_data_size)
    222   {
    223     if (request->len < *upload_data_size + request->off)
    224     {
    225       fprintf (stderr,
    226                "Content-length header wrong, aborting\n");
    227       return MHD_NO;
    228     }
    229     memcpy (request->buf,
    230             upload_data,
    231             *upload_data_size);
    232     request->off += *upload_data_size;
    233     *upload_data_size = 0;
    234     return MHD_YES;
    235   }
    236   {
    237     json_t *j;
    238     json_error_t err;
    239     char *s;
    240 
    241     j = json_loadb (request->buf,
    242                     request->len,
    243                     0,
    244                     &err);
    245     if (NULL == j)
    246       return invalid_request (connection);
    247     s = json_dumps (j,
    248                     JSON_INDENT (2));
    249     json_decref (j);
    250     response =
    251       MHD_create_response_from_buffer (strlen (s),
    252                                        s,
    253                                        MHD_RESPMEM_MUST_FREE);
    254     ret = MHD_queue_response (connection,
    255                               MHD_HTTP_OK,
    256                               response);
    257     MHD_destroy_response (response);
    258     return ret;
    259   }
    260 }
    261 
    262 
    263 /**
    264  * Callback called upon completion of a request.
    265  * Decrements session reference counter.
    266  *
    267  * @param cls not used
    268  * @param connection connection that completed
    269  * @param req_cls session handle
    270  * @param toe status code
    271  */
    272 static void
    273 request_completed_callback (void *cls,
    274                             struct MHD_Connection *connection,
    275                             void **req_cls,
    276                             enum MHD_RequestTerminationCode toe)
    277 {
    278   struct Request *request = *req_cls;
    279   (void) cls;         /* Unused. Silence compiler warning. */
    280   (void) connection;  /* Unused. Silence compiler warning. */
    281   (void) toe;         /* Unused. Silence compiler warning. */
    282 
    283   if (NULL == request)
    284     return;
    285   if (NULL != request->buf)
    286     free (request->buf);
    287   free (request);
    288 }
    289 
    290 
    291 /**
    292  * Call with the port number as the only argument.
    293  * Never terminates (other than by signals, such as CTRL-C).
    294  */
    295 int
    296 main (int argc, char *const *argv)
    297 {
    298   struct MHD_Daemon *d;
    299   struct timeval tv;
    300   struct timeval *tvp;
    301   fd_set rs;
    302   fd_set ws;
    303   fd_set es;
    304   MHD_socket max;
    305   uint64_t mhd_timeout;
    306   int port;
    307 
    308   if (argc != 2)
    309   {
    310     printf ("%s PORT\n", argv[0]);
    311     return 1;
    312   }
    313   port = atoi (argv[1]);
    314   if ( (1 > port) || (port > 65535) )
    315   {
    316     fprintf (stderr,
    317              "Port must be a number between 1 and 65535.\n");
    318     return 1;
    319   }
    320   /* initialize PRNG */
    321   srand ((unsigned int) time (NULL));
    322   d = MHD_start_daemon (MHD_USE_ERROR_LOG,
    323                         (uint16_t) port,
    324                         NULL, NULL,
    325                         &create_response, NULL,
    326                         MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 15,
    327                         MHD_OPTION_NOTIFY_COMPLETED,
    328                         &request_completed_callback, NULL,
    329                         MHD_OPTION_APP_FD_SETSIZE, (int) FD_SETSIZE,
    330                         MHD_OPTION_END);
    331   if (NULL == d)
    332     return 1;
    333   while (1)
    334   {
    335     max = 0;
    336     FD_ZERO (&rs);
    337     FD_ZERO (&ws);
    338     FD_ZERO (&es);
    339     if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
    340       break; /* fatal internal error */
    341     if (MHD_get_timeout64 (d, &mhd_timeout) == MHD_YES)
    342     {
    343 #if ! defined(_WIN32) || defined(__CYGWIN__)
    344       tv.tv_sec = (time_t) (mhd_timeout / 1000LL);
    345 #else  /* Native W32 */
    346       tv.tv_sec = (long) (mhd_timeout / 1000LL);
    347 #endif /* Native W32 */
    348       tv.tv_usec = ((long) (mhd_timeout % 1000)) * 1000;
    349       tvp = &tv;
    350     }
    351     else
    352       tvp = NULL;
    353     if (-1 == select ((int) max + 1, &rs, &ws, &es, tvp))
    354     {
    355       if (EINTR != errno)
    356         abort ();
    357     }
    358     MHD_run (d);
    359   }
    360   MHD_stop_daemon (d);
    361   return 0;
    362 }