libmicrohttpd

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

json_echo.c (10299B)


      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   unsigned int i;
    181 
    182   (void) cls;               /* Unused. Silence compiler warning. */
    183   (void) version;           /* Unused. Silence compiler warning. */
    184 
    185   if (NULL == request)
    186   {
    187     const char *clen;
    188     char dummy;
    189     unsigned int len;
    190 
    191     request = calloc (1, sizeof (struct Request));
    192     if (NULL == request)
    193     {
    194       fprintf (stderr,
    195                "calloc error: %s\n",
    196                strerror (errno));
    197       return MHD_NO;
    198     }
    199     *req_cls = request;
    200     if (0 != strcmp (method,
    201                      MHD_HTTP_METHOD_POST))
    202     {
    203       return not_found_page (connection);
    204     }
    205     clen = MHD_lookup_connection_value (
    206       connection,
    207       MHD_HEADER_KIND,
    208       MHD_HTTP_HEADER_CONTENT_LENGTH);
    209     if (NULL == clen)
    210       return invalid_request (connection);
    211     if (1 != sscanf (clen,
    212                      "%u%c",
    213                      &len,
    214                      &dummy))
    215       return invalid_request (connection);
    216     request->len = len;
    217     request->buf = malloc (request->len);
    218     if (NULL == request->buf)
    219       return MHD_NO;
    220     return MHD_YES;
    221   }
    222   if (0 != *upload_data_size)
    223   {
    224     if (request->len < *upload_data_size + request->off)
    225     {
    226       fprintf (stderr,
    227                "Content-length header wrong, aborting\n");
    228       return MHD_NO;
    229     }
    230     memcpy (request->buf,
    231             upload_data,
    232             *upload_data_size);
    233     request->off += *upload_data_size;
    234     *upload_data_size = 0;
    235     return MHD_YES;
    236   }
    237   {
    238     json_t *j;
    239     json_error_t err;
    240     char *s;
    241 
    242     j = json_loadb (request->buf,
    243                     request->len,
    244                     0,
    245                     &err);
    246     if (NULL == j)
    247       return invalid_request (connection);
    248     s = json_dumps (j,
    249                     JSON_INDENT (2));
    250     json_decref (j);
    251     response =
    252       MHD_create_response_from_buffer (strlen (s),
    253                                        s,
    254                                        MHD_RESPMEM_MUST_FREE);
    255     ret = MHD_queue_response (connection,
    256                               MHD_HTTP_OK,
    257                               response);
    258     MHD_destroy_response (response);
    259     return ret;
    260   }
    261 }
    262 
    263 
    264 /**
    265  * Callback called upon completion of a request.
    266  * Decrements session reference counter.
    267  *
    268  * @param cls not used
    269  * @param connection connection that completed
    270  * @param req_cls session handle
    271  * @param toe status code
    272  */
    273 static void
    274 request_completed_callback (void *cls,
    275                             struct MHD_Connection *connection,
    276                             void **req_cls,
    277                             enum MHD_RequestTerminationCode toe)
    278 {
    279   struct Request *request = *req_cls;
    280   (void) cls;         /* Unused. Silence compiler warning. */
    281   (void) connection;  /* Unused. Silence compiler warning. */
    282   (void) toe;         /* Unused. Silence compiler warning. */
    283 
    284   if (NULL == request)
    285     return;
    286   if (NULL != request->buf)
    287     free (request->buf);
    288   free (request);
    289 }
    290 
    291 
    292 /**
    293  * Call with the port number as the only argument.
    294  * Never terminates (other than by signals, such as CTRL-C).
    295  */
    296 int
    297 main (int argc, char *const *argv)
    298 {
    299   struct MHD_Daemon *d;
    300   struct timeval tv;
    301   struct timeval *tvp;
    302   fd_set rs;
    303   fd_set ws;
    304   fd_set es;
    305   MHD_socket max;
    306   uint64_t mhd_timeout;
    307   int port;
    308 
    309   if (argc != 2)
    310   {
    311     printf ("%s PORT\n", argv[0]);
    312     return 1;
    313   }
    314   port = atoi (argv[1]);
    315   if ( (1 > port) || (port > 65535) )
    316   {
    317     fprintf (stderr,
    318              "Port must be a number between 1 and 65535.\n");
    319     return 1;
    320   }
    321   /* initialize PRNG */
    322   srand ((unsigned int) time (NULL));
    323   d = MHD_start_daemon (MHD_USE_ERROR_LOG,
    324                         (uint16_t) port,
    325                         NULL, NULL,
    326                         &create_response, NULL,
    327                         MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 15,
    328                         MHD_OPTION_NOTIFY_COMPLETED,
    329                         &request_completed_callback, NULL,
    330                         MHD_OPTION_APP_FD_SETSIZE, (int) FD_SETSIZE,
    331                         MHD_OPTION_END);
    332   if (NULL == d)
    333     return 1;
    334   while (1)
    335   {
    336     max = 0;
    337     FD_ZERO (&rs);
    338     FD_ZERO (&ws);
    339     FD_ZERO (&es);
    340     if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
    341       break; /* fatal internal error */
    342     if (MHD_get_timeout64 (d, &mhd_timeout) == MHD_YES)
    343     {
    344 #if ! defined(_WIN32) || defined(__CYGWIN__)
    345       tv.tv_sec = (time_t) (mhd_timeout / 1000LL);
    346 #else  /* Native W32 */
    347       tv.tv_sec = (long) (mhd_timeout / 1000LL);
    348 #endif /* Native W32 */
    349       tv.tv_usec = ((long) (mhd_timeout % 1000)) * 1000;
    350       tvp = &tv;
    351     }
    352     else
    353       tvp = NULL;
    354     if (-1 == select ((int) max + 1, &rs, &ws, &es, tvp))
    355     {
    356       if (EINTR != errno)
    357         abort ();
    358     }
    359     MHD_run (d);
    360   }
    361   MHD_stop_daemon (d);
    362   return 0;
    363 }