libmicrohttpd2

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

libtest.c (17420B)


      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) 2024 Christian Grothoff
      5 
      6   GNU libmicrohttpd is free software; you can redistribute it and/or
      7   modify it under the terms of the GNU Lesser General Public
      8   License as published by the Free Software Foundation; either
      9   version 2.1 of the License, or (at your option) any later version.
     10 
     11   GNU libmicrohttpd is distributed in the hope that it will be useful,
     12   but WITHOUT ANY WARRANTY; without even the implied warranty of
     13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     14   Lesser General Public License for more details.
     15 
     16   Alternatively, you can redistribute GNU libmicrohttpd and/or
     17   modify it under the terms of the GNU General Public License as
     18   published by the Free Software Foundation; either version 2 of
     19   the License, or (at your option) any later version, together
     20   with the eCos exception, as follows:
     21 
     22     As a special exception, if other files instantiate templates or
     23     use macros or inline functions from this file, or you compile this
     24     file and link it with other works to produce a work based on this
     25     file, this file does not by itself cause the resulting work to be
     26     covered by the GNU General Public License. However the source code
     27     for this file must still be made available in accordance with
     28     section (3) of the GNU General Public License v2.
     29 
     30     This exception does not invalidate any other reasons why a work
     31     based on this file might be covered by the GNU General Public
     32     License.
     33 
     34   You should have received copies of the GNU Lesser General Public
     35   License and the GNU General Public License along with this library;
     36   if not, see <https://www.gnu.org/licenses/>.
     37 */
     38 
     39 /**
     40  * @file libtest.c
     41  * @brief testing harness with clients against server
     42  * @author Christian Grothoff
     43  */
     44 #include "libtest.h"
     45 #include <pthread.h>
     46 #include <stdbool.h>
     47 #include <fcntl.h>
     48 #include <unistd.h>
     49 #include <errno.h>
     50 #include <sys/stat.h>
     51 #include <curl/curl.h>
     52 
     53 /**
     54  * A semaphore.
     55  */
     56 struct Semaphore
     57 {
     58   /**
     59    * Mutex for the semaphore.
     60    */
     61   pthread_mutex_t mutex;
     62 
     63   /**
     64    * Condition variable for the semaphore.
     65    */
     66   pthread_cond_t cv;
     67 
     68   /**
     69    * Counter of the semaphore.
     70    */
     71   unsigned int ctr;
     72 };
     73 
     74 
     75 /**
     76  * Check that @a cond is true, otherwise abort().
     77  *
     78  * @param cond condition to check
     79  * @param filename filename to log
     80  * @param line line number to log
     81  */
     82 static void
     83 test_check_ (bool cond,
     84              const char *filename,
     85              unsigned int line)
     86 {
     87   if (! cond)
     88   {
     89     fprintf (stderr,
     90              "Assertion failed at %s:%u\n",
     91              filename,
     92              line);
     93     abort ();
     94   }
     95 }
     96 
     97 
     98 /**
     99  * Checks that @a cond is true and otherwise aborts.
    100  *
    101  * @param cond condition to check
    102  */
    103 #define test_check(cond) \
    104         test_check_ (cond, __FILE__, __LINE__)
    105 
    106 
    107 /**
    108  * Initialize a semaphore @a sem with a value of @a val.
    109  *
    110  * @param[out] sem semaphore to initialize
    111  * @param val initial value of the semaphore
    112  */
    113 static void
    114 semaphore_create (struct Semaphore *sem,
    115                   unsigned int val)
    116 {
    117   test_check (0 ==
    118               pthread_mutex_init (&sem->mutex,
    119                                   NULL));
    120   test_check (0 ==
    121               pthread_cond_init (&sem->cv,
    122                                  NULL));
    123   sem->ctr = val;
    124 }
    125 
    126 
    127 /**
    128  * Decrement semaphore, blocks until this is possible.
    129  *
    130  * @param[in,out] sem semaphore to decrement
    131  */
    132 static void
    133 semaphore_down (struct Semaphore *sem)
    134 {
    135   test_check (0 == pthread_mutex_lock (&sem->mutex));
    136   while (0 == sem->ctr)
    137   {
    138     pthread_cond_wait (&sem->cv,
    139                        &sem->mutex);
    140   }
    141   sem->ctr--;
    142   test_check (0 == pthread_mutex_unlock (&sem->mutex));
    143 }
    144 
    145 
    146 /**
    147  * Increment semaphore, blocks until this is possible.
    148  *
    149  * @param[in,out] sem semaphore to decrement
    150  */
    151 static void
    152 semaphore_up (struct Semaphore *sem)
    153 {
    154   test_check (0 == pthread_mutex_lock (&sem->mutex));
    155   sem->ctr++;
    156   test_check (0 == pthread_mutex_unlock (&sem->mutex));
    157   pthread_cond_signal (&sem->cv);
    158 }
    159 
    160 
    161 /**
    162  * Release resources used by @a sem.
    163  *
    164  * @param[in] sem semaphore to release (except the memory itself)
    165  */
    166 static void
    167 semaphore_destroy (struct Semaphore *sem)
    168 {
    169   test_check (0 == pthread_cond_destroy (&sem->cv));
    170   test_check (0 == pthread_mutex_destroy (&sem->mutex));
    171 }
    172 
    173 
    174 /**
    175  * Context for the implementation of the HTTP server.
    176  */
    177 struct ServerContext
    178 {
    179   /**
    180    * Semaphore the client raises when it goes into the
    181    * next phase.
    182    */
    183   struct Semaphore client_sem;
    184 
    185   /**
    186    * Semaphore the server raises when it goes into the
    187    * next phase.
    188    */
    189   struct Semaphore server_sem;
    190 
    191   /**
    192    * Current phase of the server.
    193    */
    194   const struct MHDT_Phase *phase;
    195 
    196   /**
    197    * Main function to run the server.
    198    */
    199   MHDT_ServerRunner run_cb;
    200 
    201   /**
    202    * Closure for @e run_cb.
    203    */
    204   void *run_cb_cls;
    205 
    206   /**
    207    * The daemon we are running.
    208    */
    209   struct MHD_Daemon *d;
    210 
    211   /**
    212    * Signal for server termination.
    213    */
    214   int finsig;
    215 };
    216 
    217 
    218 /**
    219  * A client has requested the given url using the given method
    220  * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
    221  * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc).
    222  * If @a upload_size is not zero and response action is provided by this
    223  * callback, then upload will be discarded and the stream (the connection for
    224  * HTTP/1.1) will be closed after sending the response.
    225  *
    226  * @param cls argument given together with the function
    227  *        pointer when the handler was registered with MHD
    228  * @param request the request object
    229  * @param path the requested uri (without arguments after "?")
    230  * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
    231  *        #MHD_HTTP_METHOD_PUT, etc.)
    232  * @param upload_size the size of the message upload content payload,
    233  *                    #MHD_SIZE_UNKNOWN for chunked uploads (if the
    234  *                    final chunk has not been processed yet)
    235  * @return action how to proceed, NULL
    236  *         if the request must be aborted due to a serious
    237  *         error while handling the request (implies closure
    238  *         of underling data stream, for HTTP/1.1 it means
    239  *         socket closure).
    240  */
    241 static const struct MHD_Action *
    242 server_req_cb (void *cls,
    243                struct MHD_Request *MHD_RESTRICT request,
    244                const struct MHD_String *MHD_RESTRICT path,
    245                enum MHD_HTTP_Method method,
    246                uint_fast64_t upload_size)
    247 {
    248   struct ServerContext *sc = cls;
    249 
    250   if (NULL == sc->phase->label)
    251     return NULL;
    252   return sc->phase->server_cb (sc->phase->server_cb_cls,
    253                                request,
    254                                path,
    255                                method,
    256                                upload_size);
    257 }
    258 
    259 
    260 /**
    261  * Closure for run_single_client()
    262  */
    263 struct ClientContext
    264 {
    265   /**
    266    * Test phase to run.
    267    */
    268   const struct MHDT_Phase *phase;
    269 
    270   /**
    271    * Phase and client specific context.
    272    */
    273   struct MHDT_PhaseContext pc;
    274 
    275   /**
    276    * Pipe to use to signal that the thread has
    277    * finished.
    278    */
    279   int p2;
    280 
    281   /**
    282    * Set to true on success.
    283    */
    284   bool status;
    285 };
    286 
    287 
    288 /**
    289  * Runs the logic for a single client in a thread.
    290  *
    291  * @param cls a `struct ClientContext`
    292  * @return NULL
    293  */
    294 static void *
    295 run_single_client (void *cls)
    296 {
    297   struct ClientContext *cc = cls;
    298   const char *err;
    299 
    300   fprintf (stderr,
    301            "Client %u started in phase '%s'\n",
    302            cc->pc.client_id,
    303            cc->phase->label);
    304   err = cc->phase->client_cb (cc->phase->client_cb_cls,
    305                               &cc->pc);
    306   if (NULL != err)
    307   {
    308     fprintf (stderr,
    309              "Client %u failed in phase '%s': %s\n",
    310              cc->pc.client_id,
    311              cc->phase->label,
    312              err);
    313     /* This is a blocking write, thus must succeed */
    314     test_check (1 ==
    315                 write (cc->p2,
    316                        "e",
    317                        1));
    318     return NULL;
    319   }
    320   cc->status = true;
    321   /* This is a blocking write, thus must succeed */
    322   test_check (1 ==
    323               write (cc->p2,
    324                      "s",
    325                      1));
    326   fprintf (stderr,
    327            "Client %u finished in phase '%s'\n",
    328            cc->pc.client_id,
    329            cc->phase->label);
    330   return NULL;
    331 }
    332 
    333 
    334 /**
    335  * Creates a pipe with a non-blocking read end.
    336  *
    337  * @param p pipe to initialize
    338  */
    339 static void
    340 make_pipe (int p[2])
    341 {
    342   int flags;
    343 
    344   test_check (0 ==
    345               pipe (p));
    346   flags = fcntl (p[0],
    347                  F_GETFL);
    348   flags |= O_NONBLOCK;
    349   test_check (0 ==
    350               fcntl (p[0],
    351                      F_SETFL,
    352                      flags));
    353 }
    354 
    355 
    356 /**
    357  * Run client processes for the given test @a phase
    358  *
    359  * @param phase test phase to run
    360  * @param pc context to give to clients
    361  */
    362 static bool
    363 run_client_phase (const struct MHDT_Phase *phase,
    364                   const struct MHDT_PhaseContext *pc)
    365 {
    366   unsigned int num_clients
    367     = (0 == phase->num_clients)
    368     ? 1
    369     : phase->num_clients;
    370   unsigned int clients_left = 0;
    371   struct ClientContext cctxs[num_clients];
    372   pthread_t clients[num_clients];
    373   int p[2];
    374   unsigned int i;
    375   bool ret = true;
    376 
    377   make_pipe (p);
    378   fprintf (stderr,
    379            "Starting phase '%s'\n",
    380            phase->label);
    381   for (i = 0; i<num_clients; i++)
    382   {
    383     cctxs[i].phase = phase;
    384     cctxs[i].pc = *pc;
    385     cctxs[i].pc.client_id = i;
    386     cctxs[i].p2 = p[1];
    387     cctxs[i].status = false;
    388     if (0 !=
    389         pthread_create (&clients[i],
    390                         NULL,
    391                         &run_single_client,
    392                         &cctxs[i]))
    393       goto cleanup;
    394     clients_left++;
    395   }
    396 
    397   /* 0 for timeout_ms means no timeout, we deliberately
    398      underflow to MAX_UINT in this case... */
    399   for (i = phase->timeout_ms - 1; i>0; i--)
    400   {
    401     struct timespec ms = {
    402       .tv_nsec = 1000 * 1000
    403     };
    404     struct timespec rem;
    405     char c;
    406 
    407     if (0 != nanosleep (&ms,
    408                         &rem))
    409     {
    410       fprintf (stderr,
    411                "nanosleep() interrupted (%s), trying again\n",
    412                strerror (errno));
    413       i++;
    414     }
    415     /* This is a non-blocking read */
    416     while (1 == read (p[0],
    417                       &c,
    418                       1))
    419       clients_left--;
    420     if (0 == clients_left)
    421       break;
    422   }
    423   if (0 != clients_left)
    424   {
    425     fprintf (stderr,
    426              "Timeout (%u ms) in phase '%s': %u clients still running\n",
    427              phase->timeout_ms,
    428              phase->label,
    429              clients_left);
    430     exit (1);
    431   }
    432 cleanup:
    433   for (i = 0; i<num_clients; i++)
    434   {
    435     void *res;
    436 
    437     test_check (0 ==
    438                 pthread_join (clients[i],
    439                               &res));
    440     if (! cctxs[i].status)
    441       ret = false;
    442     curl_slist_free_all (cctxs[i].pc.hosts);
    443   }
    444   test_check (0 == close (p[0]));
    445   test_check (0 == close (p[1]));
    446   fprintf (stderr,
    447            "Finished phase '%s' with %s\n",
    448            phase->label,
    449            ret ? "success" : "FAILURE");
    450   return ret;
    451 }
    452 
    453 
    454 /**
    455  * Thread that switches the server to the next phase
    456  * as needed.
    457  *
    458  * @param cls a `struct ServerContext`
    459  * @return NULL
    460  */
    461 static void *
    462 server_phase_logic (void *cls)
    463 {
    464   struct ServerContext *ctx = cls;
    465   unsigned int i;
    466 
    467   for (i = 0; NULL != ctx->phase->label; i++)
    468   {
    469     fprintf (stderr,
    470              "Running server phase '%s'\n",
    471              ctx->phase->label);
    472     semaphore_down (&ctx->client_sem);
    473     ctx->phase++;
    474     semaphore_up (&ctx->server_sem);
    475   }
    476   fprintf (stderr,
    477            "Server terminating\n");
    478   return NULL;
    479 }
    480 
    481 
    482 /**
    483  * Thread that runs the MHD daemon.
    484  *
    485  * @param cls a `struct ServerContext`
    486  * @return NULL
    487  */
    488 static void *
    489 server_run_logic (void *cls)
    490 {
    491   struct ServerContext *ctx = cls;
    492 
    493   ctx->run_cb (ctx->run_cb_cls,
    494                ctx->finsig,
    495                ctx->d);
    496   return NULL;
    497 }
    498 
    499 
    500 int
    501 MHDT_test (MHDT_ServerSetup ss_cb,
    502            void *ss_cb_cls,
    503            MHDT_ServerRunner run_cb,
    504            void *run_cb_cls,
    505            struct MHDT_Phase *phases)
    506 {
    507   struct ServerContext ctx = {
    508     .run_cb = run_cb,
    509     .run_cb_cls = run_cb_cls,
    510     .phase = &phases[0]
    511   };
    512   struct MHD_Daemon *d;
    513   int res;
    514   const char *err;
    515   pthread_t server_phase_thr;
    516   pthread_t server_run_thr;
    517   struct MHDT_PhaseContext pc_https;
    518   struct MHDT_PhaseContext pc_http;
    519   char base_http_url[128];
    520   char base_https_url[128];
    521   unsigned int i;
    522   int p[2];
    523 
    524   make_pipe (p);
    525   semaphore_create (&ctx.server_sem,
    526                     0);
    527   semaphore_create (&ctx.client_sem,
    528                     0);
    529   d = MHD_daemon_create (&server_req_cb,
    530                          &ctx);
    531   if (NULL == d)
    532     exit (77);
    533   err = ss_cb (ss_cb_cls,
    534                d);
    535   if (NULL != err)
    536   {
    537     fprintf (stderr,
    538              "Failed to setup server: %s\n",
    539              err);
    540     return 1;
    541   }
    542   {
    543     enum MHD_StatusCode sc;
    544 
    545     sc = MHD_daemon_start (d);
    546     if (MHD_SC_OK != sc)
    547     {
    548 #ifdef FIXME_STATUS_CODE_TO_STRING_NOT_IMPLEMENTED
    549       fprintf (stderr,
    550                "Failed to start server: %s\n",
    551                MHD_status_code_to_string_lazy (sc));
    552 #else
    553       fprintf (stderr,
    554                "Failed to start server: %u\n",
    555                (unsigned int) sc);
    556 #endif
    557       MHD_daemon_destroy (d);
    558       return 1;
    559     }
    560   }
    561   {
    562     union MHD_DaemonInfoFixedData info;
    563     enum MHD_StatusCode sc;
    564 
    565     sc = MHD_daemon_get_info_fixed (
    566       d,
    567       MHD_DAEMON_INFO_FIXED_BIND_PORT,
    568       &info);
    569     test_check (MHD_SC_OK == sc);
    570     snprintf (base_http_url,
    571               sizeof (base_http_url),
    572               "http://localhost:%u/",
    573               (unsigned int) info.v_bind_port_uint16);
    574     snprintf (base_https_url,
    575               sizeof (base_https_url),
    576               "https://localhost:%u/",
    577               (unsigned int) info.v_bind_port_uint16);
    578     pc_http.base_url = base_http_url;
    579     pc_https.base_url = base_https_url;
    580   }
    581   if (0 != pthread_create (&server_phase_thr,
    582                            NULL,
    583                            &server_phase_logic,
    584                            &ctx))
    585   {
    586     fprintf (stderr,
    587              "Failed to start server phase thread: %s\n",
    588              strerror (errno));
    589     MHD_daemon_destroy (d);
    590     return 77;
    591   }
    592   ctx.finsig = p[0];
    593   ctx.d = d;
    594   if (0 != pthread_create (&server_run_thr,
    595                            NULL,
    596                            &server_run_logic,
    597                            &ctx))
    598   {
    599     fprintf (stderr,
    600              "Failed to start server run thread: %s\n",
    601              strerror (errno));
    602     MHD_daemon_destroy (d);
    603     return 77;
    604   }
    605   for (i = 0; NULL != phases[i].label; i++)
    606   {
    607     struct MHDT_Phase *pi = &phases[i];
    608     struct MHDT_PhaseContext *pc
    609       = pi->use_tls
    610       ? &pc_https
    611       : &pc_http;
    612     pc->phase = &phases[i];
    613     pc->hosts = NULL;
    614     fprintf (stderr,
    615              "Running test phase '%s'\n",
    616              pi->label);
    617     if (! run_client_phase (pi,
    618                             pc))
    619     {
    620       res = 1;
    621       goto cleanup;
    622     }
    623     pc->hosts = NULL;
    624     /* client is done with phase */
    625     semaphore_up (&ctx.client_sem);
    626     /* wait for server to have moved to new phase */
    627     semaphore_down (&ctx.server_sem);
    628   }
    629   res = 0;
    630 cleanup:
    631   /* stop thread that runs the actual server */
    632   {
    633     void *pres;
    634 
    635     test_check (1 ==
    636                 write (p[1],
    637                        "e",
    638                        1));
    639     test_check (0 ==
    640                 pthread_join (server_run_thr,
    641                               &pres));
    642   }
    643   {
    644     void *pres;
    645 
    646     /* Unblock the #server_phase_logic() even if we had
    647        an error */
    648     for (i = 0; NULL != phases[i].label; i++)
    649       semaphore_up (&ctx.client_sem);
    650     test_check (0 ==
    651                 pthread_join (server_phase_thr,
    652                               &pres));
    653   }
    654   MHD_daemon_destroy (d);
    655   semaphore_destroy (&ctx.client_sem);
    656   semaphore_destroy (&ctx.server_sem);
    657   test_check (0 == close (p[0]));
    658   test_check (0 == close (p[1]));
    659   return res;
    660 }
    661 
    662 
    663 char *
    664 MHDT_load_pem (const char *name)
    665 {
    666   char path[256];
    667   int fd;
    668   struct stat s;
    669   char *buf;
    670 
    671   snprintf (path,
    672             sizeof (path),
    673             "data/%s",
    674             name);
    675   fd = open (path,
    676              O_RDONLY);
    677   if (-1 == fd)
    678   {
    679     fprintf (stderr,
    680              "Failed to open %s: %s\n",
    681              path,
    682              strerror (errno));
    683     return NULL;
    684   }
    685   if (0 !=
    686       fstat (fd,
    687              &s))
    688   {
    689     fprintf (stderr,
    690              "Failed to fstat %s: %s\n",
    691              path,
    692              strerror (errno));
    693     (void) close (fd);
    694     return NULL;
    695   }
    696   if (((unsigned long long) s.st_size) >= (unsigned long long) SIZE_MAX)
    697   {
    698     fprintf (stderr,
    699              "File %s too large (%llu >= %llu bytes) to malloc()\n",
    700              path,
    701              (unsigned long long) s.st_size,
    702              (unsigned long long) SIZE_MAX);
    703     (void) close (fd);
    704     return NULL;
    705   }
    706   buf = malloc (((size_t) s.st_size + 1));
    707   if (NULL == buf)
    708   {
    709     fprintf (stderr,
    710              "Failed to malloc(): %s\n",
    711              strerror (errno));
    712     (void) close (fd);
    713     return NULL;
    714   }
    715   if (-1 ==
    716       read (fd, // FIXME: read() should be called in loop to handle partial reads
    717             buf,
    718             (size_t) s.st_size))
    719   {
    720     fprintf (stderr,
    721              "Failed to read %s: %s\n",
    722              path,
    723              strerror (errno));
    724     free (buf);
    725     (void) close (fd);
    726     return NULL;
    727   }
    728   (void) close (fd);
    729   buf[(size_t) s.st_size] = 0;
    730   return buf;
    731 }