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_get_chunked.c 23 * @brief Testcase for libmicrohttpd GET operations with chunked content encoding 24 * TODO: 25 * - how to test that chunking was actually used? 26 * - use CURLOPT_HEADERFUNCTION to validate 27 * footer was sent 28 * @author Christian Grothoff 29 */ 30 31 #include "MHD_config.h" 32 #include "platform.h" 33 #include <curl/curl.h> 34 #include <microhttpd.h> 35 #include <stdlib.h> 36 #include <string.h> 37 #include <time.h> 38 39 #ifndef WINDOWS 40 #include <unistd.h> 41 #endif 42 43 #if defined(CPU_COUNT) && (CPU_COUNT+0) < 2 44 #undef CPU_COUNT 45 #endif 46 #if !defined(CPU_COUNT) 47 #define CPU_COUNT 2 48 #endif 49 50 struct CBC 51 { 52 char *buf; 53 size_t pos; 54 size_t size; 55 }; 56 57 static size_t 58 copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx) 59 { 60 struct CBC *cbc = ctx; 61 62 if (cbc->pos + size * nmemb > cbc->size) 63 return 0; /* overflow */ 64 memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb); 65 cbc->pos += size * nmemb; 66 return size * nmemb; 67 } 68 69 /** 70 * MHD content reader callback that returns 71 * data in chunks. 72 */ 73 static ssize_t 74 crc (void *cls, uint64_t pos, char *buf, size_t max) 75 { 76 struct MHD_Response **responseptr = cls; 77 78 if (pos == 128 * 10) 79 { 80 MHD_add_response_header (*responseptr, "Footer", "working"); 81 return MHD_CONTENT_READER_END_OF_STREAM; 82 } 83 if (max < 128) 84 abort (); /* should not happen in this testcase... */ 85 memset (buf, 'A' + (pos / 128), 128); 86 return 128; 87 } 88 89 /** 90 * Dummy function that does nothing. 91 */ 92 static void 93 crcf (void *ptr) 94 { 95 free (ptr); 96 } 97 98 static int 99 ahc_echo (void *cls, 100 struct MHD_Connection *connection, 101 const char *url, 102 const char *method, 103 const char *version, 104 const char *upload_data, size_t *upload_data_size, void **ptr) 105 { 106 static int aptr; 107 const char *me = cls; 108 struct MHD_Response *response; 109 struct MHD_Response **responseptr; 110 int ret; 111 112 if (0 != strcmp (me, method)) 113 return MHD_NO; /* unexpected method */ 114 if (&aptr != *ptr) 115 { 116 /* do never respond on first call */ 117 *ptr = &aptr; 118 return MHD_YES; 119 } 120 responseptr = malloc (sizeof (struct MHD_Response *)); 121 if (responseptr == NULL) 122 return MHD_NO; 123 response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN, 124 1024, 125 &crc, responseptr, &crcf); 126 *responseptr = response; 127 ret = MHD_queue_response (connection, MHD_HTTP_OK, response); 128 MHD_destroy_response (response); 129 return ret; 130 } 131 132 static int 133 validate (struct CBC cbc, int ebase) 134 { 135 int i; 136 char buf[128]; 137 138 if (cbc.pos != 128 * 10) 139 return ebase; 140 141 for (i = 0; i < 10; i++) 142 { 143 memset (buf, 'A' + i, 128); 144 if (0 != memcmp (buf, &cbc.buf[i * 128], 128)) 145 { 146 fprintf (stderr, 147 "Got `%.*s'\nWant `%.*s'\n", 148 128, buf, 128, &cbc.buf[i * 128]); 149 return ebase * 2; 150 } 151 } 152 return 0; 153 } 154 155 static int 156 testInternalGet () 157 { 158 struct MHD_Daemon *d; 159 CURL *c; 160 char buf[2048]; 161 struct CBC cbc; 162 CURLcode errornum; 163 164 cbc.buf = buf; 165 cbc.size = 2048; 166 cbc.pos = 0; 167 d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG, 168 1080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END); 169 if (d == NULL) 170 return 1; 171 c = curl_easy_init (); 172 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1080/hello_world"); 173 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 174 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 175 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 176 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 177 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 178 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 179 // NOTE: use of CONNECTTIMEOUT without also 180 // setting NOSIGNAL results in really weird 181 // crashes on my system! 182 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 183 if (CURLE_OK != (errornum = curl_easy_perform (c))) 184 { 185 fprintf (stderr, 186 "curl_easy_perform failed: `%s'\n", 187 curl_easy_strerror (errornum)); 188 curl_easy_cleanup (c); 189 MHD_stop_daemon (d); 190 return 2; 191 } 192 curl_easy_cleanup (c); 193 MHD_stop_daemon (d); 194 return validate (cbc, 4); 195 } 196 197 static int 198 testMultithreadedGet () 199 { 200 struct MHD_Daemon *d; 201 CURL *c; 202 char buf[2048]; 203 struct CBC cbc; 204 CURLcode errornum; 205 206 cbc.buf = buf; 207 cbc.size = 2048; 208 cbc.pos = 0; 209 d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG, 210 1081, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END); 211 if (d == NULL) 212 return 16; 213 c = curl_easy_init (); 214 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world"); 215 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 216 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 217 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 218 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 219 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 220 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 221 // NOTE: use of CONNECTTIMEOUT without also 222 // setting NOSIGNAL results in really weird 223 // crashes on my system! 224 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 225 if (CURLE_OK != (errornum = curl_easy_perform (c))) 226 { 227 fprintf (stderr, 228 "curl_easy_perform failed: `%s'\n", 229 curl_easy_strerror (errornum)); 230 curl_easy_cleanup (c); 231 MHD_stop_daemon (d); 232 return 32; 233 } 234 curl_easy_cleanup (c); 235 MHD_stop_daemon (d); 236 return validate (cbc, 64); 237 } 238 239 static int 240 testMultithreadedPoolGet () 241 { 242 struct MHD_Daemon *d; 243 CURL *c; 244 char buf[2048]; 245 struct CBC cbc; 246 CURLcode errornum; 247 248 cbc.buf = buf; 249 cbc.size = 2048; 250 cbc.pos = 0; 251 d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG, 252 1081, NULL, NULL, &ahc_echo, "GET", 253 MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT, MHD_OPTION_END); 254 if (d == NULL) 255 return 16; 256 c = curl_easy_init (); 257 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1081/hello_world"); 258 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 259 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 260 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 261 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 262 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 263 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 264 // NOTE: use of CONNECTTIMEOUT without also 265 // setting NOSIGNAL results in really weird 266 // crashes on my system! 267 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 268 if (CURLE_OK != (errornum = curl_easy_perform (c))) 269 { 270 fprintf (stderr, 271 "curl_easy_perform failed: `%s'\n", 272 curl_easy_strerror (errornum)); 273 curl_easy_cleanup (c); 274 MHD_stop_daemon (d); 275 return 32; 276 } 277 curl_easy_cleanup (c); 278 MHD_stop_daemon (d); 279 return validate (cbc, 64); 280 } 281 282 static int 283 testExternalGet () 284 { 285 struct MHD_Daemon *d; 286 CURL *c; 287 char buf[2048]; 288 struct CBC cbc; 289 CURLM *multi; 290 CURLMcode mret; 291 fd_set rs; 292 fd_set ws; 293 fd_set es; 294 MHD_socket max; 295 int running; 296 struct CURLMsg *msg; 297 time_t start; 298 struct timeval tv; 299 300 multi = NULL; 301 cbc.buf = buf; 302 cbc.size = 2048; 303 cbc.pos = 0; 304 d = MHD_start_daemon (MHD_USE_DEBUG, 305 1082, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END); 306 if (d == NULL) 307 return 256; 308 c = curl_easy_init (); 309 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:1082/hello_world"); 310 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 311 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 312 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1); 313 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 314 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 315 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 5L); 316 // NOTE: use of CONNECTTIMEOUT without also 317 // setting NOSIGNAL results in really weird 318 // crashes on my system! 319 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1); 320 321 322 multi = curl_multi_init (); 323 if (multi == NULL) 324 { 325 curl_easy_cleanup (c); 326 MHD_stop_daemon (d); 327 return 512; 328 } 329 mret = curl_multi_add_handle (multi, c); 330 if (mret != CURLM_OK) 331 { 332 curl_multi_cleanup (multi); 333 curl_easy_cleanup (c); 334 MHD_stop_daemon (d); 335 return 1024; 336 } 337 start = time (NULL); 338 while ((time (NULL) - start < 5) && (multi != NULL)) 339 { 340 max = 0; 341 FD_ZERO (&rs); 342 FD_ZERO (&ws); 343 FD_ZERO (&es); 344 curl_multi_perform (multi, &running); 345 mret = curl_multi_fdset (multi, &rs, &ws, &es, &max); 346 if (mret != CURLM_OK) 347 { 348 curl_multi_remove_handle (multi, c); 349 curl_multi_cleanup (multi); 350 curl_easy_cleanup (c); 351 MHD_stop_daemon (d); 352 return 2048; 353 } 354 if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max)) 355 { 356 curl_multi_remove_handle (multi, c); 357 curl_multi_cleanup (multi); 358 curl_easy_cleanup (c); 359 MHD_stop_daemon (d); 360 return 4096; 361 } 362 tv.tv_sec = 0; 363 tv.tv_usec = 1000; 364 select (max + 1, &rs, &ws, &es, &tv); 365 curl_multi_perform (multi, &running); 366 if (running == 0) 367 { 368 msg = curl_multi_info_read (multi, &running); 369 if (msg == NULL) 370 break; 371 if (msg->msg == CURLMSG_DONE) 372 { 373 if (msg->data.result != CURLE_OK) 374 printf ("%s failed at %s:%d: `%s'\n", 375 "curl_multi_perform", 376 __FILE__, 377 __LINE__, curl_easy_strerror (msg->data.result)); 378 curl_multi_remove_handle (multi, c); 379 curl_multi_cleanup (multi); 380 curl_easy_cleanup (c); 381 c = NULL; 382 multi = NULL; 383 } 384 } 385 MHD_run (d); 386 } 387 if (multi != NULL) 388 { 389 curl_multi_remove_handle (multi, c); 390 curl_easy_cleanup (c); 391 curl_multi_cleanup (multi); 392 } 393 MHD_stop_daemon (d); 394 return validate (cbc, 8192); 395 } 396 397 398 399 int 400 main (int argc, char *const *argv) 401 { 402 unsigned int errorCount = 0; 403 404 if (0 != curl_global_init (CURL_GLOBAL_WIN32)) 405 return 2; 406 errorCount += testInternalGet (); 407 errorCount += testMultithreadedGet (); 408 errorCount += testMultithreadedPoolGet (); 409 errorCount += testExternalGet (); 410 if (errorCount != 0) 411 fprintf (stderr, "Error (code: %u)\n", errorCount); 412 curl_global_cleanup (); 413 return errorCount != 0; /* 0 == pass */ 414 } 415