1 /* 2 This file is part of libmicrohttpd 3 Copyright (C) 2007 Christian Grothoff 4 5 libmicrohttpd is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published 7 by the Free Software Foundation; either version 2, or (at your 8 option) any later version. 9 10 libmicrohttpd 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 GNU 13 General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with libmicrohttpd; see the file COPYING. If not, write to the 17 Free Software Foundation, Inc., 59 Temple Place - Suite 330, 18 Boston, MA 02111-1307, USA. 19 */ 20 21 /** 22 * @file daemontest_post_loop.c 23 * @brief Testcase for libmicrohttpd POST operations using URL-encoding 24 * @author Christian Grothoff (inspired by bug report #1296) 25 */ 26 27 #include "MHD_config.h" 28 #include "platform.h" 29 #include <curl/curl.h> 30 #include <microhttpd.h> 31 #include <stdlib.h> 32 #include <string.h> 33 #include <time.h> 34 #include <gauger.h> 35 36 #ifndef WINDOWS 37 #include <unistd.h> 38 #endif 39 40 #if defined(CPU_COUNT) && (CPU_COUNT+0) < 2 41 #undef CPU_COUNT 42 #endif 43 #if !defined(CPU_COUNT) 44 #define CPU_COUNT 2 45 #endif 46 47 #define POST_DATA "<?xml version='1.0' ?>\n<xml>\n<data-id>1</data-id>\n</xml>\n" 48 49 #define LOOPCOUNT 1000 50 51 static int oneone; 52 53 struct CBC 54 { 55 char *buf; 56 size_t pos; 57 size_t size; 58 }; 59 60 static size_t 61 copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx) 62 { 63 struct CBC *cbc = ctx; 64 65 if (cbc->pos + size * nmemb > cbc->size) 66 return 0; /* overflow */ 67 memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb); 68 cbc->pos += size * nmemb; 69 return size * nmemb; 70 } 71 72 static int 73 ahc_echo (void *cls, 74 struct MHD_Connection *connection, 75 const char *url, 76 const char *method, 77 const char *version, 78 const char *upload_data, size_t *upload_data_size, 79 void **mptr) 80 { 81 static int marker; 82 struct MHD_Response *response; 83 int ret; 84 85 if (0 != strcmp ("POST", method)) 86 { 87 printf ("METHOD: %s\n", method); 88 return MHD_NO; /* unexpected method */ 89 } 90 if ((*mptr != NULL) && (0 == *upload_data_size)) 91 { 92 if (*mptr != &marker) 93 abort (); 94 response = MHD_create_response_from_buffer (2, "OK", 95 MHD_RESPMEM_PERSISTENT); 96 ret = MHD_queue_response (connection, MHD_HTTP_OK, response); 97 MHD_destroy_response (response); 98 *mptr = NULL; 99 return ret; 100 } 101 if (strlen (POST_DATA) != *upload_data_size) 102 return MHD_YES; 103 *upload_data_size = 0; 104 *mptr = ▮ 105 return MHD_YES; 106 } 107 108 109 static int 110 testInternalPost () 111 { 112 struct MHD_Daemon *d; 113 CURL *c; 114 char buf[2048]; 115 struct CBC cbc; 116 CURLcode errornum; 117 int i; 118 char url[1024]; 119 120 cbc.buf = buf; 121 cbc.size = 2048; 122 d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG, 123 1080, NULL, NULL, &ahc_echo, NULL, MHD_OPTION_END); 124 if (d == NULL) 125 return 1; 126 for (i = 0; i < LOOPCOUNT; i++) 127 { 128 if (99 == i % 100) 129 fprintf (stderr, "."); 130 c = curl_easy_init (); 131 cbc.pos = 0; 132 buf[0] = '\0'; 133 sprintf (url, "http://127.0.0.1:1080/hw%d", i); 134 curl_easy_setopt (c, CURLOPT_URL, url); 135 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 136 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 137 curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA); 138 curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA)); 139 curl_easy_setopt (c, CURLOPT_POST, 1L); 140 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 141 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 142 if (oneone) 143 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 144 else 145 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 146 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 147 // NOTE: use of CONNECTTIMEOUT without also 148 // setting NOSIGNAL results in really weird 149 // crashes on my system! 150 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 151 if (CURLE_OK != (errornum = curl_easy_perform (c))) 152 { 153 fprintf (stderr, 154 "curl_easy_perform failed: `%s'\n", 155 curl_easy_strerror (errornum)); 156 curl_easy_cleanup (c); 157 MHD_stop_daemon (d); 158 return 2; 159 } 160 curl_easy_cleanup (c); 161 if ((buf[0] != 'O') || (buf[1] != 'K')) 162 { 163 MHD_stop_daemon (d); 164 return 4; 165 } 166 } 167 MHD_stop_daemon (d); 168 if (LOOPCOUNT >= 99) 169 fprintf (stderr, "\n"); 170 return 0; 171 } 172 173 static int 174 testMultithreadedPost () 175 { 176 struct MHD_Daemon *d; 177 CURL *c; 178 char buf[2048]; 179 struct CBC cbc; 180 CURLcode errornum; 181 int i; 182 char url[1024]; 183 184 cbc.buf = buf; 185 cbc.size = 2048; 186 d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG, 187 1081, NULL, NULL, &ahc_echo, NULL, MHD_OPTION_END); 188 if (d == NULL) 189 return 16; 190 for (i = 0; i < LOOPCOUNT; i++) 191 { 192 if (99 == i % 100) 193 fprintf (stderr, "."); 194 c = curl_easy_init (); 195 cbc.pos = 0; 196 buf[0] = '\0'; 197 sprintf (url, "http://127.0.0.1:1081/hw%d", i); 198 curl_easy_setopt (c, CURLOPT_URL, url); 199 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 200 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 201 curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA); 202 curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA)); 203 curl_easy_setopt (c, CURLOPT_POST, 1L); 204 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 205 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 206 if (oneone) 207 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 208 else 209 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 210 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 211 // NOTE: use of CONNECTTIMEOUT without also 212 // setting NOSIGNAL results in really weird 213 // crashes on my system! 214 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 215 if (CURLE_OK != (errornum = curl_easy_perform (c))) 216 { 217 fprintf (stderr, 218 "curl_easy_perform failed: `%s'\n", 219 curl_easy_strerror (errornum)); 220 curl_easy_cleanup (c); 221 MHD_stop_daemon (d); 222 return 32; 223 } 224 curl_easy_cleanup (c); 225 if ((buf[0] != 'O') || (buf[1] != 'K')) 226 { 227 MHD_stop_daemon (d); 228 return 64; 229 } 230 } 231 MHD_stop_daemon (d); 232 if (LOOPCOUNT >= 99) 233 fprintf (stderr, "\n"); 234 return 0; 235 } 236 237 static int 238 testMultithreadedPoolPost () 239 { 240 struct MHD_Daemon *d; 241 CURL *c; 242 char buf[2048]; 243 struct CBC cbc; 244 CURLcode errornum; 245 int i; 246 char url[1024]; 247 248 cbc.buf = buf; 249 cbc.size = 2048; 250 d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG, 251 1081, NULL, NULL, &ahc_echo, NULL, 252 MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT, MHD_OPTION_END); 253 if (d == NULL) 254 return 16; 255 for (i = 0; i < LOOPCOUNT; i++) 256 { 257 if (99 == i % 100) 258 fprintf (stderr, "."); 259 c = curl_easy_init (); 260 cbc.pos = 0; 261 buf[0] = '\0'; 262 sprintf (url, "http://127.0.0.1:1081/hw%d", i); 263 curl_easy_setopt (c, CURLOPT_URL, url); 264 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 265 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 266 curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA); 267 curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA)); 268 curl_easy_setopt (c, CURLOPT_POST, 1L); 269 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 270 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 271 if (oneone) 272 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 273 else 274 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 275 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 276 // NOTE: use of CONNECTTIMEOUT without also 277 // setting NOSIGNAL results in really weird 278 // crashes on my system! 279 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 280 if (CURLE_OK != (errornum = curl_easy_perform (c))) 281 { 282 fprintf (stderr, 283 "curl_easy_perform failed: `%s'\n", 284 curl_easy_strerror (errornum)); 285 curl_easy_cleanup (c); 286 MHD_stop_daemon (d); 287 return 32; 288 } 289 curl_easy_cleanup (c); 290 if ((buf[0] != 'O') || (buf[1] != 'K')) 291 { 292 MHD_stop_daemon (d); 293 return 64; 294 } 295 } 296 MHD_stop_daemon (d); 297 if (LOOPCOUNT >= 99) 298 fprintf (stderr, "\n"); 299 return 0; 300 } 301 302 static int 303 testExternalPost () 304 { 305 struct MHD_Daemon *d; 306 CURL *c; 307 char buf[2048]; 308 struct CBC cbc; 309 CURLM *multi; 310 CURLMcode mret; 311 fd_set rs; 312 fd_set ws; 313 fd_set es; 314 MHD_socket max; 315 int running; 316 struct CURLMsg *msg; 317 time_t start; 318 struct timeval tv; 319 int i; 320 unsigned long long timeout; 321 long ctimeout; 322 char url[1024]; 323 324 multi = NULL; 325 cbc.buf = buf; 326 cbc.size = 2048; 327 cbc.pos = 0; 328 d = MHD_start_daemon (MHD_USE_DEBUG, 329 1082, NULL, NULL, &ahc_echo, NULL, MHD_OPTION_END); 330 if (d == NULL) 331 return 256; 332 multi = curl_multi_init (); 333 if (multi == NULL) 334 { 335 MHD_stop_daemon (d); 336 return 512; 337 } 338 for (i = 0; i < LOOPCOUNT; i++) 339 { 340 if (99 == i % 100) 341 fprintf (stderr, "."); 342 c = curl_easy_init (); 343 cbc.pos = 0; 344 buf[0] = '\0'; 345 sprintf (url, "http://127.0.0.1:1082/hw%d", i); 346 curl_easy_setopt (c, CURLOPT_URL, url); 347 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 348 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 349 curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA); 350 curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA)); 351 curl_easy_setopt (c, CURLOPT_POST, 1L); 352 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 353 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 354 if (oneone) 355 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 356 else 357 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 358 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 359 // NOTE: use of CONNECTTIMEOUT without also 360 // setting NOSIGNAL results in really weird 361 // crashes on my system! 362 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 363 mret = curl_multi_add_handle (multi, c); 364 if (mret != CURLM_OK) 365 { 366 curl_multi_cleanup (multi); 367 curl_easy_cleanup (c); 368 MHD_stop_daemon (d); 369 return 1024; 370 } 371 start = time (NULL); 372 while ((time (NULL) - start < 5) && (multi != NULL)) 373 { 374 max = 0; 375 FD_ZERO (&rs); 376 FD_ZERO (&ws); 377 FD_ZERO (&es); 378 while (CURLM_CALL_MULTI_PERFORM == 379 curl_multi_perform (multi, &running)); 380 mret = curl_multi_fdset (multi, &rs, &ws, &es, &max); 381 if (mret != CURLM_OK) 382 { 383 curl_multi_remove_handle (multi, c); 384 curl_multi_cleanup (multi); 385 curl_easy_cleanup (c); 386 MHD_stop_daemon (d); 387 return 2048; 388 } 389 if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max)) 390 { 391 curl_multi_remove_handle (multi, c); 392 curl_multi_cleanup (multi); 393 curl_easy_cleanup (c); 394 MHD_stop_daemon (d); 395 return 4096; 396 } 397 if (MHD_NO == MHD_get_timeout (d, &timeout)) 398 timeout = 100; /* 100ms == INFTY -- CURL bug... */ 399 if ((CURLM_OK == curl_multi_timeout (multi, &ctimeout)) && 400 (ctimeout < timeout) && (ctimeout >= 0)) 401 timeout = ctimeout; 402 if ( (c == NULL) || (running == 0) ) 403 timeout = 0; /* terminate quickly... */ 404 tv.tv_sec = timeout / 1000; 405 tv.tv_usec = (timeout % 1000) * 1000; 406 if (-1 == select (max + 1, &rs, &ws, &es, &tv)) 407 { 408 if (EINTR == errno) 409 continue; 410 fprintf (stderr, 411 "select failed: %s\n", 412 strerror (errno)); 413 break; 414 } 415 while (CURLM_CALL_MULTI_PERFORM == 416 curl_multi_perform (multi, &running)); 417 if (running == 0) 418 { 419 msg = curl_multi_info_read (multi, &running); 420 if (msg == NULL) 421 break; 422 if (msg->msg == CURLMSG_DONE) 423 { 424 if (msg->data.result != CURLE_OK) 425 printf ("%s failed at %s:%d: `%s'\n", 426 "curl_multi_perform", 427 __FILE__, 428 __LINE__, curl_easy_strerror (msg->data.result)); 429 curl_multi_remove_handle (multi, c); 430 curl_easy_cleanup (c); 431 c = NULL; 432 } 433 } 434 MHD_run (d); 435 } 436 if (c != NULL) 437 { 438 curl_multi_remove_handle (multi, c); 439 curl_easy_cleanup (c); 440 } 441 if ((buf[0] != 'O') || (buf[1] != 'K')) 442 { 443 curl_multi_cleanup (multi); 444 MHD_stop_daemon (d); 445 return 8192; 446 } 447 } 448 curl_multi_cleanup (multi); 449 MHD_stop_daemon (d); 450 if (LOOPCOUNT >= 99) 451 fprintf (stderr, "\n"); 452 return 0; 453 } 454 455 456 /** 457 * Time this round was started. 458 */ 459 static unsigned long long start_time; 460 461 462 /** 463 * Get the current timestamp 464 * 465 * @return current time in ms 466 */ 467 static unsigned long long 468 now () 469 { 470 struct timeval tv; 471 472 gettimeofday (&tv, NULL); 473 return (((unsigned long long) tv.tv_sec * 1000LL) + 474 ((unsigned long long) tv.tv_usec / 1000LL)); 475 } 476 477 478 int 479 main (int argc, char *const *argv) 480 { 481 unsigned int errorCount = 0; 482 483 oneone = (NULL != strrchr (argv[0], (int) '/')) ? 484 (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0; 485 if (0 != curl_global_init (CURL_GLOBAL_WIN32)) 486 return 2; 487 start_time = now(); 488 errorCount += testInternalPost (); 489 fprintf (stderr, 490 oneone ? "%s: Sequential POSTs (http/1.1) %f/s\n" : "%s: Sequential POSTs (http/1.0) %f/s\n", 491 "internal select", 492 (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0)); 493 GAUGER ("internal select", 494 oneone ? "Sequential POSTs (http/1.1)" : "Sequential POSTs (http/1.0)", 495 (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0), 496 "requests/s"); 497 start_time = now(); 498 errorCount += testMultithreadedPost (); 499 fprintf (stderr, 500 oneone ? "%s: Sequential POSTs (http/1.1) %f/s\n" : "%s: Sequential POSTs (http/1.0) %f/s\n", 501 "multithreaded post", 502 (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0)); 503 GAUGER ("Multithreaded select", 504 oneone ? "Sequential POSTs (http/1.1)" : "Sequential POSTs (http/1.0)", 505 (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0), 506 "requests/s"); 507 start_time = now(); 508 errorCount += testMultithreadedPoolPost (); 509 fprintf (stderr, 510 oneone ? "%s: Sequential POSTs (http/1.1) %f/s\n" : "%s: Sequential POSTs (http/1.0) %f/s\n", 511 "thread with pool", 512 (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0)); 513 GAUGER ("thread with pool", 514 oneone ? "Sequential POSTs (http/1.1)" : "Sequential POSTs (http/1.0)", 515 (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0), 516 "requests/s"); 517 start_time = now(); 518 errorCount += testExternalPost (); 519 fprintf (stderr, 520 oneone ? "%s: Sequential POSTs (http/1.1) %f/s\n" : "%s: Sequential POSTs (http/1.0) %f/s\n", 521 "external select", 522 (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0)); 523 GAUGER ("external select", 524 oneone ? "Sequential POSTs (http/1.1)" : "Sequential POSTs (http/1.0)", 525 (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0), 526 "requests/s"); 527 if (errorCount != 0) 528 fprintf (stderr, "Error (code: %u)\n", errorCount); 529 curl_global_cleanup (); 530 return errorCount != 0; /* 0 == pass */ 531 } 532