1 /* 2 This file is part of libmicrohttpd 3 Copyright (C) 2007, 2008 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 test_large_put.c 23 * @brief Testcase for libmicrohttpd PUT operations 24 * @author Christian Grothoff 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 35 #ifndef WINDOWS 36 #include <unistd.h> 37 #endif 38 39 #if defined(CPU_COUNT) && (CPU_COUNT+0) < 2 40 #undef CPU_COUNT 41 #endif 42 #if !defined(CPU_COUNT) 43 #define CPU_COUNT 2 44 #endif 45 46 static int oneone; 47 48 /** 49 * Do not make this much larger since we will hit the 50 * MHD default buffer limit and the test code is not 51 * written for incremental upload processing... 52 * (larger values will likely cause MHD to generate 53 * an internal server error -- which would be avoided 54 * by writing the putBuffer method in a more general 55 * fashion). 56 */ 57 #define PUT_SIZE (256 * 1024) 58 59 static char *put_buffer; 60 61 struct CBC 62 { 63 char *buf; 64 size_t pos; 65 size_t size; 66 }; 67 68 static size_t 69 putBuffer (void *stream, size_t size, size_t nmemb, void *ptr) 70 { 71 unsigned int *pos = ptr; 72 unsigned int wrt; 73 74 wrt = size * nmemb; 75 if (wrt > PUT_SIZE - (*pos)) 76 wrt = PUT_SIZE - (*pos); 77 memcpy (stream, &put_buffer[*pos], wrt); 78 (*pos) += wrt; 79 return wrt; 80 } 81 82 static size_t 83 copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx) 84 { 85 struct CBC *cbc = ctx; 86 87 if (cbc->pos + size * nmemb > cbc->size) 88 return 0; /* overflow */ 89 memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb); 90 cbc->pos += size * nmemb; 91 return size * nmemb; 92 } 93 94 static int 95 ahc_echo (void *cls, 96 struct MHD_Connection *connection, 97 const char *url, 98 const char *method, 99 const char *version, 100 const char *upload_data, size_t *upload_data_size, 101 void **unused) 102 { 103 int *done = cls; 104 struct MHD_Response *response; 105 int ret; 106 107 if (0 != strcmp ("PUT", method)) 108 return MHD_NO; /* unexpected method */ 109 if ((*done) == 0) 110 { 111 if (*upload_data_size != PUT_SIZE) 112 { 113 #if 0 114 fprintf (stderr, 115 "Waiting for more data (%u/%u)...\n", 116 *upload_data_size, PUT_SIZE); 117 #endif 118 return MHD_YES; /* not yet ready */ 119 } 120 if (0 == memcmp (upload_data, put_buffer, PUT_SIZE)) 121 { 122 *upload_data_size = 0; 123 } 124 else 125 { 126 printf ("Invalid upload data!\n"); 127 return MHD_NO; 128 } 129 *done = 1; 130 return MHD_YES; 131 } 132 response = MHD_create_response_from_buffer (strlen (url), 133 (void *) url, 134 MHD_RESPMEM_MUST_COPY); 135 ret = MHD_queue_response (connection, MHD_HTTP_OK, response); 136 MHD_destroy_response (response); 137 return ret; 138 } 139 140 141 static int 142 testInternalPut () 143 { 144 struct MHD_Daemon *d; 145 CURL *c; 146 struct CBC cbc; 147 unsigned int pos = 0; 148 int done_flag = 0; 149 CURLcode errornum; 150 char buf[2048]; 151 152 cbc.buf = buf; 153 cbc.size = 2048; 154 cbc.pos = 0; 155 d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG, 156 1080, 157 NULL, NULL, &ahc_echo, &done_flag, 158 MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (1024*1024), 159 MHD_OPTION_END); 160 if (d == NULL) 161 return 1; 162 c = curl_easy_init (); 163 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1080/hello_world"); 164 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 165 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 166 curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer); 167 curl_easy_setopt (c, CURLOPT_READDATA, &pos); 168 curl_easy_setopt (c, CURLOPT_UPLOAD, 1L); 169 curl_easy_setopt (c, CURLOPT_INFILESIZE, (long) PUT_SIZE); 170 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 171 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 172 if (oneone) 173 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 174 else 175 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 176 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 177 // NOTE: use of CONNECTTIMEOUT without also 178 // setting NOSIGNAL results in really weird 179 // crashes on my system! 180 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 181 if (CURLE_OK != (errornum = curl_easy_perform (c))) 182 { 183 fprintf (stderr, 184 "curl_easy_perform failed: `%s'\n", 185 curl_easy_strerror (errornum)); 186 curl_easy_cleanup (c); 187 MHD_stop_daemon (d); 188 return 2; 189 } 190 curl_easy_cleanup (c); 191 MHD_stop_daemon (d); 192 if (cbc.pos != strlen ("/hello_world")) 193 return 4; 194 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) 195 return 8; 196 return 0; 197 } 198 199 static int 200 testMultithreadedPut () 201 { 202 struct MHD_Daemon *d; 203 CURL *c; 204 struct CBC cbc; 205 unsigned int pos = 0; 206 int done_flag = 0; 207 CURLcode errornum; 208 char buf[2048]; 209 210 cbc.buf = buf; 211 cbc.size = 2048; 212 cbc.pos = 0; 213 d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG, 214 1081, 215 NULL, NULL, &ahc_echo, &done_flag, 216 MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (1024*1024), 217 MHD_OPTION_END); 218 if (d == NULL) 219 return 16; 220 c = curl_easy_init (); 221 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world"); 222 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 223 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 224 curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer); 225 curl_easy_setopt (c, CURLOPT_READDATA, &pos); 226 curl_easy_setopt (c, CURLOPT_UPLOAD, 1L); 227 curl_easy_setopt (c, CURLOPT_INFILESIZE, (long) PUT_SIZE); 228 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 229 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 230 if (oneone) 231 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 232 else 233 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 234 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 235 // NOTE: use of CONNECTTIMEOUT without also 236 // setting NOSIGNAL results in really weird 237 // crashes on my system! 238 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 239 if (CURLE_OK != (errornum = curl_easy_perform (c))) 240 { 241 fprintf (stderr, 242 "curl_easy_perform failed: `%s'\n", 243 curl_easy_strerror (errornum)); 244 curl_easy_cleanup (c); 245 MHD_stop_daemon (d); 246 return 32; 247 } 248 curl_easy_cleanup (c); 249 MHD_stop_daemon (d); 250 if (cbc.pos != strlen ("/hello_world")) 251 { 252 fprintf (stderr, "Got invalid response `%.*s'\n", (int)cbc.pos, cbc.buf); 253 return 64; 254 } 255 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) 256 return 128; 257 return 0; 258 } 259 260 static int 261 testMultithreadedPoolPut () 262 { 263 struct MHD_Daemon *d; 264 CURL *c; 265 struct CBC cbc; 266 unsigned int pos = 0; 267 int done_flag = 0; 268 CURLcode errornum; 269 char buf[2048]; 270 271 cbc.buf = buf; 272 cbc.size = 2048; 273 cbc.pos = 0; 274 d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG, 275 1081, 276 NULL, NULL, &ahc_echo, &done_flag, 277 MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT, 278 MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (1024*1024), 279 MHD_OPTION_END); 280 if (d == NULL) 281 return 16; 282 c = curl_easy_init (); 283 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world"); 284 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 285 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 286 curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer); 287 curl_easy_setopt (c, CURLOPT_READDATA, &pos); 288 curl_easy_setopt (c, CURLOPT_UPLOAD, 1L); 289 curl_easy_setopt (c, CURLOPT_INFILESIZE, (long) PUT_SIZE); 290 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 291 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 292 if (oneone) 293 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 294 else 295 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 296 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 297 // NOTE: use of CONNECTTIMEOUT without also 298 // setting NOSIGNAL results in really weird 299 // crashes on my system! 300 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 301 if (CURLE_OK != (errornum = curl_easy_perform (c))) 302 { 303 fprintf (stderr, 304 "curl_easy_perform failed: `%s'\n", 305 curl_easy_strerror (errornum)); 306 curl_easy_cleanup (c); 307 MHD_stop_daemon (d); 308 return 32; 309 } 310 curl_easy_cleanup (c); 311 MHD_stop_daemon (d); 312 if (cbc.pos != strlen ("/hello_world")) 313 { 314 fprintf (stderr, "Got invalid response `%.*s'\n", (int)cbc.pos, cbc.buf); 315 return 64; 316 } 317 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) 318 return 128; 319 return 0; 320 } 321 322 static int 323 testExternalPut () 324 { 325 struct MHD_Daemon *d; 326 CURL *c; 327 struct CBC cbc; 328 CURLM *multi; 329 CURLMcode mret; 330 fd_set rs; 331 fd_set ws; 332 fd_set es; 333 MHD_socket max; 334 int running; 335 struct CURLMsg *msg; 336 time_t start; 337 struct timeval tv; 338 unsigned int pos = 0; 339 int done_flag = 0; 340 char buf[2048]; 341 342 cbc.buf = buf; 343 cbc.size = 2048; 344 cbc.pos = 0; 345 multi = NULL; 346 d = MHD_start_daemon (MHD_USE_DEBUG, 347 1082, 348 NULL, NULL, &ahc_echo, &done_flag, 349 MHD_OPTION_CONNECTION_MEMORY_LIMIT, 350 (size_t) (PUT_SIZE * 4), MHD_OPTION_END); 351 if (d == NULL) 352 return 256; 353 c = curl_easy_init (); 354 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1082/hello_world"); 355 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 356 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 357 curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer); 358 curl_easy_setopt (c, CURLOPT_READDATA, &pos); 359 curl_easy_setopt (c, CURLOPT_UPLOAD, 1L); 360 curl_easy_setopt (c, CURLOPT_INFILESIZE, (long) PUT_SIZE); 361 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 362 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 363 if (oneone) 364 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 365 else 366 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 367 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 368 // NOTE: use of CONNECTTIMEOUT without also 369 // setting NOSIGNAL results in really weird 370 // crashes on my system! 371 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 372 373 374 multi = curl_multi_init (); 375 if (multi == NULL) 376 { 377 curl_easy_cleanup (c); 378 MHD_stop_daemon (d); 379 return 512; 380 } 381 mret = curl_multi_add_handle (multi, c); 382 if (mret != CURLM_OK) 383 { 384 curl_multi_cleanup (multi); 385 curl_easy_cleanup (c); 386 MHD_stop_daemon (d); 387 return 1024; 388 } 389 start = time (NULL); 390 while ((time (NULL) - start < 5) && (multi != NULL)) 391 { 392 max = 0; 393 FD_ZERO (&rs); 394 FD_ZERO (&ws); 395 FD_ZERO (&es); 396 curl_multi_perform (multi, &running); 397 mret = curl_multi_fdset (multi, &rs, &ws, &es, &max); 398 if (mret != CURLM_OK) 399 { 400 curl_multi_remove_handle (multi, c); 401 curl_multi_cleanup (multi); 402 curl_easy_cleanup (c); 403 MHD_stop_daemon (d); 404 return 2048; 405 } 406 if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max)) 407 { 408 curl_multi_remove_handle (multi, c); 409 curl_multi_cleanup (multi); 410 curl_easy_cleanup (c); 411 MHD_stop_daemon (d); 412 return 4096; 413 } 414 tv.tv_sec = 0; 415 tv.tv_usec = 1000; 416 select (max + 1, &rs, &ws, &es, &tv); 417 curl_multi_perform (multi, &running); 418 if (running == 0) 419 { 420 msg = curl_multi_info_read (multi, &running); 421 if (msg == NULL) 422 break; 423 if (msg->msg == CURLMSG_DONE) 424 { 425 if (msg->data.result != CURLE_OK) 426 printf ("%s failed at %s:%d: `%s'\n", 427 "curl_multi_perform", 428 __FILE__, 429 __LINE__, curl_easy_strerror (msg->data.result)); 430 curl_multi_remove_handle (multi, c); 431 curl_multi_cleanup (multi); 432 curl_easy_cleanup (c); 433 c = NULL; 434 multi = NULL; 435 } 436 } 437 MHD_run (d); 438 } 439 if (multi != NULL) 440 { 441 curl_multi_remove_handle (multi, c); 442 curl_easy_cleanup (c); 443 curl_multi_cleanup (multi); 444 } 445 MHD_stop_daemon (d); 446 if (cbc.pos != strlen ("/hello_world")) 447 { 448 fprintf (stderr, "Got invalid response `%.*s'\n", (int)cbc.pos, cbc.buf); 449 return 8192; 450 } 451 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) 452 return 16384; 453 return 0; 454 } 455 456 457 458 int 459 main (int argc, char *const *argv) 460 { 461 unsigned int errorCount = 0; 462 463 oneone = (NULL != strrchr (argv[0], (int) '/')) ? 464 (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0; 465 if (0 != curl_global_init (CURL_GLOBAL_WIN32)) 466 return 2; 467 put_buffer = malloc (PUT_SIZE); 468 if (NULL == put_buffer) return 1; 469 memset (put_buffer, 1, PUT_SIZE); 470 errorCount += testInternalPut (); 471 errorCount += testMultithreadedPut (); 472 errorCount += testMultithreadedPoolPut (); 473 errorCount += testExternalPut (); 474 free (put_buffer); 475 if (errorCount != 0) 476 fprintf (stderr, "Error (code: %u)\n", errorCount); 477 curl_global_cleanup (); 478 return errorCount != 0; /* 0 == pass */ 479 } 480