paivana

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

pipeline_client.c (9104B)


      1 /*
      2   This file is part of Paivana.
      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, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   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 pipeline_client.c
     23  * @brief HTTP/1.1 pipelining client: opens a single TCP connection
     24  *        to a host/port, sends N GET requests back-to-back *without*
     25  *        waiting for their responses, then reads N responses in
     26  *        order and prints each body (separated by "---").
     27  *
     28  * Used by the paivana test suite to verify that paivana correctly
     29  * handles pipelined requests on a single keep-alive connection.
     30  */
     31 #include <arpa/inet.h>
     32 #include <errno.h>
     33 #include <netdb.h>
     34 #include <netinet/in.h>
     35 #include <stdio.h>
     36 #include <stdlib.h>
     37 #include <string.h>
     38 #include <sys/socket.h>
     39 #include <sys/types.h>
     40 #include <unistd.h>
     41 
     42 #define BUF_GROW 8192
     43 
     44 static int
     45 connect_to (const char *host,
     46             const char *port)
     47 {
     48   struct addrinfo hints = { 0 };
     49   struct addrinfo *res = NULL;
     50   int rc;
     51 
     52   hints.ai_family = AF_UNSPEC;
     53   hints.ai_socktype = SOCK_STREAM;
     54   rc = getaddrinfo (host,
     55                     port,
     56                     &hints,
     57                     &res);
     58   if (0 != rc)
     59   {
     60     fprintf (stderr,
     61              "getaddrinfo(%s:%s): %s\n",
     62              host,
     63              port,
     64              gai_strerror (rc));
     65     return -1;
     66   }
     67   for (struct addrinfo *p = res; NULL != p; p = p->ai_next)
     68   {
     69     int fd = socket (p->ai_family,
     70                      p->ai_socktype,
     71                      p->ai_protocol);
     72     if (fd < 0)
     73       continue;
     74     if (0 == connect (fd,
     75                       p->ai_addr,
     76                       p->ai_addrlen))
     77     {
     78       freeaddrinfo (res);
     79       return fd;
     80     }
     81     close (fd);
     82   }
     83   freeaddrinfo (res);
     84   fprintf (stderr,
     85            "could not connect to %s:%s\n",
     86            host,
     87            port);
     88   return -1;
     89 }
     90 
     91 
     92 static int
     93 write_all (int fd,
     94            const char *buf,
     95            size_t len)
     96 {
     97   while (len > 0)
     98   {
     99     ssize_t w = write (fd,
    100                        buf,
    101                        len);
    102 
    103     if (w < 0)
    104     {
    105       if (EINTR == errno)
    106         continue;
    107       return -1;
    108     }
    109     buf += w;
    110     len -= (size_t) w;
    111   }
    112   return 0;
    113 }
    114 
    115 
    116 /**
    117  * Read exactly @a want bytes from @a fd into the tail of the buffer.
    118  * Grows the buffer as needed.  @a have is updated.
    119  */
    120 static int
    121 ensure_bytes (int fd,
    122               char **buf,
    123               size_t *cap,
    124               size_t *have,
    125               size_t want)
    126 {
    127   while (*have < want)
    128   {
    129     ssize_t r;
    130     if (*have + BUF_GROW > *cap)
    131     {
    132       size_t nc = *cap ? *cap * 2 : BUF_GROW;
    133       char *nb;
    134 
    135       while (nc < *have + BUF_GROW)
    136         nc *= 2;
    137       nb = realloc (*buf,
    138                     nc);
    139       if (NULL == nb)
    140         return -1;
    141       *buf = nb;
    142       *cap = nc;
    143     }
    144 
    145     r = read (fd,
    146               *buf + *have,
    147               *cap - *have);
    148     if (r < 0)
    149     {
    150       if (EINTR == errno)
    151         continue;
    152       return -1;
    153     }
    154     if (0 == r)
    155       return -1; /* EOF */
    156     *have += (size_t) r;
    157   }
    158   return 0;
    159 }
    160 
    161 
    162 /**
    163  * Find the end of the HTTP header block ("\r\n\r\n") starting at
    164  * some offset in buf.  Returns the absolute offset of the byte
    165  * just past the final "\r\n\r\n", or (size_t) -1 if not yet present.
    166  * Grows the buffer / reads more data as needed.
    167  */
    168 static size_t
    169 read_headers (int fd,
    170               char **buf,
    171               size_t *cap,
    172               size_t *have,
    173               size_t start)
    174 {
    175   while (1)
    176   {
    177     if (*have >= start + 4)
    178     {
    179       for (size_t i = start; i + 3 < *have; i++)
    180       {
    181         if ( ('\r' == (*buf)[i]) &&
    182              ('\n' == (*buf)[i + 1]) &&
    183              ('\r' == (*buf)[i + 2]) &&
    184              ('\n' == (*buf)[i + 3]) )
    185           return i + 4;
    186       }
    187     }
    188     if (ensure_bytes (fd,
    189                       buf,
    190                       cap,
    191                       have,
    192                       *have + 1))
    193       return (size_t) -1;
    194   }
    195 }
    196 
    197 
    198 /**
    199  * Extract Content-Length from a header block [start, hdr_end).
    200  * Returns -1 if not found.
    201  */
    202 static long long
    203 header_content_length (const char *buf,
    204                        size_t start,
    205                        size_t hdr_end)
    206 {
    207   const char *hdrs = buf + start;
    208   size_t len = hdr_end - start;
    209   const char *p = hdrs;
    210   const char *end = hdrs + len;
    211   const char *needle = "Content-Length:";
    212 
    213   while (p + strlen (needle) < end)
    214   {
    215     const char *line_end = memchr (p,
    216                                    '\n',
    217                                    end - p);
    218 
    219     if (NULL == line_end)
    220       break;
    221     if (0 == strncasecmp (p,
    222                           needle,
    223                           strlen (needle)))
    224     {
    225       const char *v = p + strlen (needle);
    226 
    227       while ( (v < line_end) &&
    228               (' ' == *v ||
    229                '\t' == *v) )
    230         v++;
    231       return strtoll (v,
    232                       NULL,
    233                       10);
    234     }
    235     p = line_end + 1;
    236   }
    237   return -1;
    238 }
    239 
    240 
    241 /**
    242  * Parse the status code out of "HTTP/1.1 <code> ..."
    243  */
    244 static int
    245 header_status (const char *buf,
    246                size_t start)
    247 {
    248   const char *p = buf + start;
    249   const char *sp = strchr (p,
    250                            ' ');
    251 
    252   if (NULL == sp)
    253     return -1;
    254   return atoi (sp + 1);
    255 }
    256 
    257 
    258 int
    259 main (int argc, char **argv)
    260 {
    261   const char *host = argv[1];
    262   const char *port = argv[2];
    263   int n_paths = argc - 3;
    264   int fd;
    265 
    266   if (argc < 4)
    267   {
    268     fprintf (stderr,
    269              "usage: %s <host> <port> <path> [<path>...]\n",
    270              argv[0]);
    271     return 2;
    272   }
    273 
    274   fd = connect_to (host,
    275                    port);
    276   if (fd < 0)
    277     return 1;
    278 
    279   /* Pipeline: send all requests back-to-back. */
    280   for (int i = 0; i < n_paths; i++)
    281   {
    282     char req[2048];
    283     const char *conn_hdr
    284       = (i == n_paths - 1)
    285       ? "close"
    286       : "keep-alive";
    287     int n = snprintf (req,
    288                       sizeof (req),
    289                       "GET %s HTTP/1.1\r\n"
    290                       "Host: %s:%s\r\n"
    291                       "User-Agent: paivana-pipeline-test\r\n"
    292                       "Connection: %s\r\n"
    293                       "\r\n",
    294                       argv[3 + i],
    295                       host,
    296                       port,
    297                       conn_hdr);
    298     if ( (n < 0) ||
    299          ((size_t) n >= sizeof (req)) )
    300     {
    301       fprintf (stderr,
    302                "request too long\n");
    303       close (fd);
    304       return 1;
    305     }
    306     if (0 != write_all (fd,
    307                         req,
    308                         (size_t) n))
    309     {
    310       fprintf (stderr,
    311                "write failed: %s\n",
    312                strerror (errno));
    313       close (fd);
    314       return 1;
    315     }
    316   }
    317 
    318   {
    319     /* Now read N responses in order, using Content-Length. */
    320     char *buf = NULL;
    321     size_t cap = 0;
    322     size_t have = 0;
    323     size_t pos = 0;
    324     int ret = 0;
    325 
    326     for (int i = 0; i < n_paths; i++)
    327     {
    328       size_t hdr_end = read_headers (fd,
    329                                      &buf,
    330                                      &cap,
    331                                      &have,
    332                                      pos);
    333       long status;
    334       long long cl;
    335       size_t body_end;
    336 
    337       if ((size_t) -1 == hdr_end)
    338       {
    339         fprintf (stderr,
    340                  "failed to read headers of response #%d\n",
    341                  i);
    342         ret = 1;
    343         break;
    344       }
    345       status = header_status (buf,
    346                               pos);
    347       cl = header_content_length (buf,
    348                                   pos,
    349                                   hdr_end);
    350       if (cl < 0)
    351       {
    352         fprintf (stderr,
    353                  "response #%d has no Content-Length (got status %d); "
    354                  "cannot safely parse pipelined response stream\n",
    355                  i,
    356                  (int) status);
    357         ret = 1;
    358         break;
    359       }
    360 
    361       body_end = hdr_end + (size_t) cl;
    362       if (0 != ensure_bytes (fd,
    363                              &buf,
    364                              &cap,
    365                              &have,
    366                              body_end))
    367       {
    368         fprintf (stderr,
    369                  "short body for response #%d (expected %lld bytes)\n",
    370                  i, cl);
    371         ret = 1;
    372         break;
    373       }
    374       printf ("--- response %d: status=%d len=%lld ---\n",
    375               i,
    376               (int) status,
    377               cl);
    378       fwrite (buf + hdr_end,
    379               1,
    380               (size_t) cl,
    381               stdout);
    382       if ( (cl > 0) &&
    383            (buf[body_end - 1] != '\n') )
    384         putchar ('\n');
    385       pos = body_end;
    386     }
    387     free (buf);
    388     close (fd);
    389     return ret;
    390   }
    391 }