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 }