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 }