paivana

HTTP paywall reverse proxy
Log | Files | Refs | Submodules | README | LICENSE

upstream_mhd.c (11549B)


      1 /*
      2   This file is part of paivana tests.
      3   Copyright (C) 2026 Taler Systems SA
      4 
      5   Paivana is free software; you can redistribute it and/or
      6   modify it under the terms of the GNU General Public License
      7   as published by the Free Software Foundation; either version
      8   3, or (at your option) any later version.
      9 
     10   Paivana is distributed in the hope that it will be useful,
     11   but WITHOUT ANY WARRANTY; without even the implied warranty
     12   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See
     13   the GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with Paivana; see the file COPYING.  If not,
     17   write to the Free Software Foundation, Inc., 51 Franklin
     18   Street, Fifth Floor, Boston, MA 02110-1301, USA.
     19 */
     20 
     21 /**
     22  * @file upstream_mhd.c
     23  * @brief libmicrohttpd-based upstream test server for the
     24  *        paivana reverse-proxy test suite.  Implements a small
     25  *        set of canned endpoints (see README).
     26  */
     27 #include <errno.h>
     28 #include <signal.h>
     29 #include <stdio.h>
     30 #include <stdlib.h>
     31 #include <string.h>
     32 #include <time.h>
     33 #include <unistd.h>
     34 #include <sys/time.h>
     35 #include <microhttpd.h>
     36 
     37 #define SERVER_NAME "mhd"
     38 
     39 struct Ctx
     40 {
     41   char *body;
     42   size_t body_len;
     43   size_t body_cap;
     44 };
     45 
     46 
     47 static void
     48 ctx_free (struct Ctx *c)
     49 {
     50   free (c->body);
     51   free (c);
     52 }
     53 
     54 
     55 struct HdrBuf
     56 {
     57   char *data;
     58   size_t len;
     59   size_t cap;
     60 };
     61 
     62 
     63 static enum MHD_Result
     64 append_hdr (void *cls,
     65             enum MHD_ValueKind kind,
     66             const char *key,
     67             const char *value)
     68 {
     69   struct HdrBuf *hb = cls;
     70   size_t need = strlen (key) + strlen (value) + 4;
     71 
     72   (void) kind;
     73   if (hb->len + need > hb->cap)
     74   {
     75     size_t nc = hb->cap ? hb->cap * 2 : 1024;
     76     char *nb;
     77 
     78     while (nc < hb->len + need)
     79       nc *= 2;
     80     nb = realloc (hb->data, nc);
     81     if (NULL == nb)
     82       return MHD_NO;
     83     hb->data = nb;
     84     hb->cap = nc;
     85   }
     86   hb->len += (size_t) snprintf (hb->data + hb->len,
     87                                 hb->cap - hb->len,
     88                                 "%s: %s\n",
     89                                 key,
     90                                 value);
     91   return MHD_YES;
     92 }
     93 
     94 
     95 static struct MHD_Response *
     96 make_text_response (const char *s)
     97 {
     98   struct MHD_Response *r;
     99 
    100   r = MHD_create_response_from_buffer (strlen (s),
    101                                        (void *) s,
    102                                        MHD_RESPMEM_MUST_COPY);
    103   MHD_add_response_header (r,
    104                            MHD_HTTP_HEADER_CONTENT_TYPE,
    105                            "text/plain");
    106   MHD_add_response_header (r,
    107                            "X-Upstream",
    108                            SERVER_NAME);
    109   return r;
    110 }
    111 
    112 
    113 static enum MHD_Result
    114 reply_text (struct MHD_Connection *con,
    115             unsigned int status,
    116             const char *s)
    117 {
    118   struct MHD_Response *r = make_text_response (s);
    119   enum MHD_Result ret;
    120 
    121   ret = MHD_queue_response (con,
    122                             status,
    123                             r);
    124   MHD_destroy_response (r);
    125   return ret;
    126 }
    127 
    128 
    129 static enum MHD_Result
    130 reply_bytes (struct MHD_Connection *con,
    131              unsigned int status,
    132              const char *buf,
    133              size_t len)
    134 {
    135   struct MHD_Response *r;
    136   enum MHD_Result ret;
    137 
    138   r = MHD_create_response_from_buffer (len,
    139                                        (void *) buf,
    140                                        MHD_RESPMEM_MUST_COPY);
    141   MHD_add_response_header (r,
    142                            "X-Upstream",
    143                            SERVER_NAME);
    144   MHD_add_response_header (r,
    145                            MHD_HTTP_HEADER_CONTENT_TYPE,
    146                            "application/octet-stream");
    147   ret = MHD_queue_response (con,
    148                             status,
    149                             r);
    150   MHD_destroy_response (r);
    151   return ret;
    152 }
    153 
    154 
    155 static enum MHD_Result
    156 handler (void *cls,
    157          struct MHD_Connection *con,
    158          const char *url,
    159          const char *method,
    160          const char *version,
    161          const char *upload_data,
    162          size_t *upload_data_size,
    163          void **con_cls)
    164 {
    165   struct Ctx *ctx = *con_cls;
    166 
    167   (void) cls;
    168   (void) version;
    169   if (NULL == ctx)
    170   {
    171     ctx = calloc (1,
    172                   sizeof (*ctx));
    173     *con_cls = ctx;
    174     return MHD_YES;
    175   }
    176   if (0 != *upload_data_size)
    177   {
    178     if (ctx->body_len + *upload_data_size > ctx->body_cap)
    179     {
    180       size_t n = ctx->body_cap ? ctx->body_cap * 2 : 4096;
    181       char *nb;
    182 
    183       while (n < ctx->body_len + *upload_data_size)
    184         n *= 2;
    185       nb = realloc (ctx->body,
    186                     n);
    187       if (NULL == nb)
    188         return MHD_NO;
    189       ctx->body = nb;
    190       ctx->body_cap = n;
    191     }
    192     memcpy (ctx->body + ctx->body_len,
    193             upload_data,
    194             *upload_data_size);
    195     ctx->body_len += *upload_data_size;
    196     *upload_data_size = 0;
    197     return MHD_YES;
    198   }
    199 
    200   /* OPTIONS on anything */
    201   if (0 == strcmp (method,
    202                    MHD_HTTP_METHOD_OPTIONS))
    203   {
    204     struct MHD_Response *r = make_text_response ("");
    205     enum MHD_Result ret;
    206 
    207     MHD_add_response_header (r,
    208                              MHD_HTTP_HEADER_ALLOW,
    209                              "GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS");
    210     ret = MHD_queue_response (con,
    211                               MHD_HTTP_NO_CONTENT,
    212                               r);
    213     MHD_destroy_response (r);
    214     return ret;
    215   }
    216 
    217   /* GET /hello */
    218   if ( (0 == strcmp (method,
    219                      MHD_HTTP_METHOD_GET) ||
    220         0 == strcmp (method,
    221                      MHD_HTTP_METHOD_HEAD)) &&
    222        (0 == strcmp (url,
    223                      "/hello")) )
    224     return reply_text (con,
    225                        MHD_HTTP_OK,
    226                        "Hello from " SERVER_NAME "\n");
    227 
    228   /* GET /echo-headers — return all request headers in the body */
    229   if ( (0 == strcmp (method,
    230                      MHD_HTTP_METHOD_GET)) &&
    231        (0 == strcmp (url,
    232                      "/echo-headers")) )
    233   {
    234     struct HdrBuf hb = { 0 };
    235     enum MHD_Result ret;
    236 
    237     MHD_get_connection_values (con,
    238                                MHD_HEADER_KIND,
    239                                &append_hdr,
    240                                &hb);
    241     ret = reply_bytes (con,
    242                        MHD_HTTP_OK,
    243                        hb.data ? hb.data : "",
    244                        hb.len);
    245     free (hb.data);
    246     return ret;
    247   }
    248 
    249   /* GET /status/NNN */
    250   if ( (0 == strcmp (method,
    251                      MHD_HTTP_METHOD_GET)) &&
    252        (0 == strncmp (url,
    253                       "/status/",
    254                       strlen ("/status/"))) )
    255   {
    256     char msg[64];
    257     int s = atoi (url + 8);
    258 
    259     if (s < 100 || s > 599)
    260       s = 500;
    261     snprintf (msg,
    262               sizeof (msg),
    263               "status %d\n",
    264               s);
    265     return reply_text (con,
    266                        (unsigned int) s,
    267                        msg);
    268   }
    269 
    270   /* GET /large/NNN */
    271   if ( (0 == strcmp (method,
    272                      MHD_HTTP_METHOD_GET) ||
    273         0 == strcmp (method,
    274                      MHD_HTTP_METHOD_HEAD)) &&
    275        (0 == strncmp (url,
    276                       "/large/",
    277                       strlen ("/large/"))) )
    278   {
    279     long n = atol (url + 7);
    280     char *b;
    281     enum MHD_Result ret;
    282 
    283     if (n < 0)
    284       n = 0;
    285     if (n > 10 * 1024 * 1024)
    286       n = 10 * 1024 * 1024;
    287     b = malloc ((size_t) n);
    288     if (NULL == b && n > 0)
    289       return reply_text (con, 500, "oom\n");
    290     for (long i = 0; i < n; i++)
    291       b[i] = 'A' + (char) (i % 26);
    292     ret = reply_bytes (con,
    293                        MHD_HTTP_OK,
    294                        b,
    295                        (size_t) n);
    296     free (b);
    297     return ret;
    298   }
    299 
    300   /* GET /slow/NNN — sleep NNN ms then respond */
    301   if ( (0 == strcmp (method,
    302                      MHD_HTTP_METHOD_GET)) &&
    303        (0 == strncmp (url,
    304                       "/slow/",
    305                       strlen ("/slow/"))) )
    306   {
    307     int ms = atoi (url + 6);
    308     struct timespec ts;
    309 
    310     if (ms < 0)
    311       ms = 0;
    312     if (ms > 30000)
    313       ms = 30000;
    314     ts.tv_sec = ms / 1000;
    315     ts.tv_nsec = (ms % 1000) * 1000000L;
    316     nanosleep (&ts, NULL);
    317     return reply_text (con,
    318                        MHD_HTTP_OK,
    319                        "slept\n");
    320   }
    321 
    322   /* POST /echo — echoes body */
    323   if ( (0 == strcmp (method,
    324                      MHD_HTTP_METHOD_POST)) &&
    325        (0 == strcmp (url, "/echo")) )
    326     return reply_bytes (con,
    327                         MHD_HTTP_OK,
    328                         ctx->body ? ctx->body : "",
    329                         ctx->body_len);
    330 
    331   /* POST /upload — returns count */
    332   if ( (0 == strcmp (method,
    333                      MHD_HTTP_METHOD_POST)) &&
    334        (0 == strcmp (url,
    335                      "/upload")) )
    336   {
    337     char msg[64];
    338 
    339     snprintf (msg,
    340               sizeof (msg),
    341               "Received %zu bytes\n",
    342               ctx->body_len);
    343     return reply_text (con,
    344                        MHD_HTTP_OK,
    345                        msg);
    346   }
    347 
    348   /* PUT /put */
    349   if ( (0 == strcmp (method,
    350                      MHD_HTTP_METHOD_PUT)) &&
    351        (0 == strcmp (url,
    352                      "/put")) )
    353   {
    354     char msg[64];
    355 
    356     snprintf (msg,
    357               sizeof (msg),
    358               "PUT received %zu\n",
    359               ctx->body_len);
    360     return reply_text (con,
    361                        MHD_HTTP_OK,
    362                        msg);
    363   }
    364 
    365   /* PATCH /patch */
    366   if ( (0 == strcmp (method,
    367                      MHD_HTTP_METHOD_PATCH)) &&
    368        (0 == strcmp (url,
    369                      "/patch")) )
    370   {
    371     char msg[64];
    372 
    373     snprintf (msg,
    374               sizeof (msg),
    375               "PATCH received %zu\n",
    376               ctx->body_len);
    377     return reply_text (con,
    378                        MHD_HTTP_OK,
    379                        msg);
    380   }
    381 
    382   /* DELETE /item */
    383   if ( (0 == strcmp (method,
    384                      "DELETE")) &&
    385        (0 == strncmp (url,
    386                       "/item",
    387                       strlen ("/item"))) )
    388   {
    389     struct MHD_Response *r = make_text_response ("");
    390     enum MHD_Result ret = MHD_queue_response (con,
    391                                               MHD_HTTP_NO_CONTENT,
    392                                               r);
    393     MHD_destroy_response (r);
    394     return ret;
    395   }
    396 
    397   return reply_text (con,
    398                      MHD_HTTP_NOT_FOUND,
    399                      "not found\n");
    400 }
    401 
    402 
    403 static void
    404 completed_cb (void *cls,
    405               struct MHD_Connection *con,
    406               void **con_cls,
    407               enum MHD_RequestTerminationCode toe)
    408 {
    409   struct Ctx *ctx = *con_cls;
    410 
    411   (void) cls;
    412   (void) con;
    413   (void) toe;
    414   if (NULL != ctx)
    415     ctx_free (ctx);
    416   *con_cls = NULL;
    417 }
    418 
    419 
    420 static volatile sig_atomic_t run_flag = 1;
    421 
    422 static void
    423 on_int (int s)
    424 {
    425   (void) s;
    426   run_flag = 0;
    427 }
    428 
    429 
    430 int
    431 main (int argc, char **argv)
    432 {
    433   unsigned int port = 8401;
    434   struct MHD_Daemon *d;
    435 
    436   if (argc > 1)
    437     port = (unsigned int) atoi (argv[1]);
    438   signal (SIGINT,
    439           on_int);
    440   signal (SIGTERM,
    441           on_int);
    442   signal (SIGPIPE,
    443           SIG_IGN);
    444   d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD
    445                         | MHD_USE_DUAL_STACK,
    446                         port,
    447                         NULL, NULL,
    448                         &handler, NULL,
    449                         MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
    450                         MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 30,
    451                         MHD_OPTION_END);
    452   if (NULL == d)
    453   {
    454     fprintf (stderr,
    455              "MHD_start_daemon failed on port %u\n",
    456              port);
    457     return 1;
    458   }
    459   fprintf (stderr,
    460            "upstream_mhd listening on port %u\n",
    461            port);
    462   fflush (stderr);
    463   while (run_flag)
    464     sleep (1);
    465   MHD_stop_daemon (d);
    466   return 0;
    467 }