test_timeout.c (11568B)
1 /* 2 This file is part of libmicrohttpd 3 Copyright (C) 2007 Christian Grothoff 4 Copyright (C) 2016-2022 Evgeny Grin (Karlson2k) 5 6 libmicrohttpd is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published 8 by the Free Software Foundation; either version 2, or (at your 9 option) any later version. 10 11 libmicrohttpd is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with libmicrohttpd; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 Boston, MA 02110-1301, USA. 20 */ 21 22 /** 23 * @file test_timeout.c 24 * @brief Testcase for libmicrohttpd PUT operations 25 * @author Matthias Wachs 26 * @author Karlson2k (Evgeny Grin) 27 */ 28 29 #include "MHD_config.h" 30 #include "platform.h" 31 #include <curl/curl.h> 32 #include <microhttpd.h> 33 #include <stdlib.h> 34 #include <string.h> 35 #ifdef HAVE_UNISTD_H 36 #include <unistd.h> 37 #endif /* HAVE_UNISTD_H */ 38 #ifdef HAVE_TIME_H 39 #include <time.h> 40 #endif /* HAVE_TIME_H */ 41 #include "mhd_has_in_name.h" 42 43 /* Turn any MHD_PANIC() or failing mhd_assert() reached from this 44 test into a marked, classifiable test error (TESTING.md, P5). */ 45 #include "mhd_panic_tripwire.h" 46 47 48 /** 49 * Pause execution for specified number of milliseconds. 50 * @param ms the number of milliseconds to sleep 51 */ 52 static void 53 _MHD_sleep (uint32_t ms) 54 { 55 #if defined(_WIN32) 56 Sleep (ms); 57 #elif defined(HAVE_NANOSLEEP) 58 struct timespec slp = {ms / 1000, (ms % 1000) * 1000000}; 59 struct timespec rmn; 60 int num_retries = 0; 61 while (0 != nanosleep (&slp, &rmn)) 62 { 63 if (num_retries++ > 8) 64 break; 65 slp = rmn; 66 } 67 #elif defined(HAVE_USLEEP) 68 uint64_t us = ms * 1000; 69 do 70 { 71 uint64_t this_sleep; 72 if (999999 < us) 73 this_sleep = 999999; 74 else 75 this_sleep = us; 76 /* Ignore return value as it could be void */ 77 usleep (this_sleep); 78 us -= this_sleep; 79 } while (us > 0); 80 #else 81 fprintf (stderr, "No sleep function available on this system.\n"); 82 #endif 83 } 84 85 86 static int oneone; 87 88 static int withTimeout = 0; 89 90 static int withoutTimeout = 0; 91 92 struct CBC 93 { 94 char *buf; 95 size_t pos; 96 size_t size; 97 }; 98 99 100 static void 101 termination_cb (void *cls, 102 struct MHD_Connection *connection, 103 void **req_cls, 104 enum MHD_RequestTerminationCode toe) 105 { 106 int *test = cls; 107 (void) connection; (void) req_cls; /* Unused. Silent compiler warning. */ 108 109 switch (toe) 110 { 111 case MHD_REQUEST_TERMINATED_COMPLETED_OK: 112 if (test == &withoutTimeout) 113 { 114 withoutTimeout = 1; 115 } 116 else 117 { 118 fprintf (stderr, "Connection completed without errors while " 119 "timeout is expected.\n"); 120 } 121 break; 122 case MHD_REQUEST_TERMINATED_WITH_ERROR: 123 fprintf (stderr, "Connection terminated with error.\n"); 124 exit (4); 125 break; 126 case MHD_REQUEST_TERMINATED_READ_ERROR: 127 fprintf (stderr, "Connection terminated with read error.\n"); 128 exit (4); 129 break; 130 case MHD_REQUEST_TERMINATED_TIMEOUT_REACHED: 131 if (test == &withTimeout) 132 { 133 withTimeout = 1; 134 } 135 else 136 { 137 fprintf (stderr, "Connection terminated with timeout while expected " 138 "to be successfully completed.\n"); 139 } 140 break; 141 case MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN: 142 fprintf (stderr, "Connection terminated by daemon shutdown.\n"); 143 exit (4); 144 break; 145 case MHD_REQUEST_TERMINATED_CLIENT_ABORT: 146 fprintf (stderr, "Connection terminated by client.\n"); 147 exit (4); 148 break; 149 } 150 } 151 152 153 static size_t 154 putBuffer (void *stream, size_t size, size_t nmemb, void *ptr) 155 { 156 size_t *pos = ptr; 157 size_t wrt; 158 159 wrt = size * nmemb; 160 if (wrt > 8 - (*pos)) 161 wrt = 8 - (*pos); 162 memcpy (stream, &("Hello123"[*pos]), wrt); 163 (*pos) += wrt; 164 return wrt; 165 } 166 167 168 static size_t 169 putBuffer_fail (void *stream, size_t size, size_t nmemb, void *ptr) 170 { 171 (void) stream; (void) size; (void) nmemb; (void) ptr; /* Unused. Silent compiler warning. */ 172 _MHD_sleep (100); /* Avoid busy-waiting */ 173 return 0; 174 } 175 176 177 static size_t 178 copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx) 179 { 180 struct CBC *cbc = ctx; 181 182 if (cbc->pos + size * nmemb > cbc->size) 183 return 0; /* overflow */ 184 memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb); 185 cbc->pos += size * nmemb; 186 return size * nmemb; 187 } 188 189 190 static enum MHD_Result 191 ahc_echo (void *cls, 192 struct MHD_Connection *connection, 193 const char *url, 194 const char *method, 195 const char *version, 196 const char *upload_data, size_t *upload_data_size, 197 void **req_cls) 198 { 199 int *done = cls; 200 struct MHD_Response *response; 201 enum MHD_Result ret; 202 (void) version; (void) req_cls; /* Unused. Silent compiler warning. */ 203 204 if (0 != strcmp (MHD_HTTP_METHOD_PUT, method)) 205 return MHD_NO; /* unexpected method */ 206 if ((*done) == 0) 207 { 208 if (*upload_data_size != 8) 209 return MHD_YES; /* not yet ready */ 210 if (0 == memcmp (upload_data, "Hello123", 8)) 211 { 212 *upload_data_size = 0; 213 } 214 else 215 { 216 printf ("Invalid upload data `%8s'!\n", upload_data); 217 return MHD_NO; 218 } 219 *done = 1; 220 return MHD_YES; 221 } 222 response = MHD_create_response_from_buffer_copy (strlen (url), 223 (const void *) url); 224 ret = MHD_queue_response (connection, MHD_HTTP_OK, response); 225 MHD_destroy_response (response); 226 return ret; 227 } 228 229 230 static unsigned int 231 testWithoutTimeout (void) 232 { 233 struct MHD_Daemon *d; 234 CURL *c; 235 char buf[2048]; 236 struct CBC cbc; 237 size_t pos = 0; 238 int done_flag = 0; 239 CURLcode errornum; 240 uint16_t port; 241 242 if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) 243 port = 0; 244 else 245 { 246 port = 1500; 247 if (oneone) 248 port += 5; 249 } 250 251 cbc.buf = buf; 252 cbc.size = 2048; 253 cbc.pos = 0; 254 d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG, 255 port, 256 NULL, NULL, &ahc_echo, &done_flag, 257 MHD_OPTION_CONNECTION_TIMEOUT, 2, 258 MHD_OPTION_NOTIFY_COMPLETED, &termination_cb, 259 &withoutTimeout, 260 MHD_OPTION_END); 261 if (d == NULL) 262 return 1; 263 if (0 == port) 264 { 265 const union MHD_DaemonInfo *dinfo; 266 dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); 267 if ((NULL == dinfo) || (0 == dinfo->port) ) 268 { 269 MHD_stop_daemon (d); return 32; 270 } 271 port = dinfo->port; 272 } 273 c = curl_easy_init (); 274 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/hello_world"); 275 curl_easy_setopt (c, CURLOPT_PORT, (long) port); 276 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 277 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 278 curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer); 279 curl_easy_setopt (c, CURLOPT_READDATA, &pos); 280 curl_easy_setopt (c, CURLOPT_UPLOAD, 1L); 281 curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L); 282 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L); 283 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 284 if (oneone) 285 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 286 else 287 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 288 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 289 /* NOTE: use of CONNECTTIMEOUT without also 290 * setting NOSIGNAL results in really weird 291 * crashes on my system! */ 292 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L); 293 withoutTimeout = 0; 294 if (CURLE_OK != (errornum = curl_easy_perform (c))) 295 { 296 fprintf (stderr, "curl_easy_perform failed: '%s'\n", 297 curl_easy_strerror (errornum)); 298 curl_easy_cleanup (c); 299 MHD_stop_daemon (d); 300 return 2; 301 } 302 curl_easy_cleanup (c); 303 MHD_stop_daemon (d); 304 if (0 == withoutTimeout) 305 { 306 fprintf (stderr, "Request wasn't processed successfully.\n"); 307 return 2; 308 } 309 if (cbc.pos != strlen ("/hello_world")) 310 return 4; 311 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) 312 return 8; 313 return 0; 314 } 315 316 317 static unsigned int 318 testWithTimeout (void) 319 { 320 struct MHD_Daemon *d; 321 CURL *c; 322 char buf[2048]; 323 struct CBC cbc; 324 int done_flag = 0; 325 CURLcode errornum; 326 uint16_t port; 327 328 if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT)) 329 port = 0; 330 else 331 { 332 port = 1501; 333 if (oneone) 334 port += 5; 335 } 336 337 cbc.buf = buf; 338 cbc.size = 2048; 339 cbc.pos = 0; 340 d = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG, 341 port, 342 NULL, NULL, &ahc_echo, &done_flag, 343 MHD_OPTION_CONNECTION_TIMEOUT, 2, 344 MHD_OPTION_NOTIFY_COMPLETED, &termination_cb, 345 &withTimeout, 346 MHD_OPTION_END); 347 if (d == NULL) 348 return 16; 349 if (0 == port) 350 { 351 const union MHD_DaemonInfo *dinfo; 352 dinfo = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT); 353 if ((NULL == dinfo) || (0 == dinfo->port) ) 354 { 355 MHD_stop_daemon (d); return 32; 356 } 357 port = dinfo->port; 358 } 359 c = curl_easy_init (); 360 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1/hello_world"); 361 curl_easy_setopt (c, CURLOPT_PORT, (long) port); 362 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer); 363 curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc); 364 curl_easy_setopt (c, CURLOPT_READFUNCTION, &putBuffer_fail); 365 curl_easy_setopt (c, CURLOPT_READDATA, &testWithTimeout); 366 curl_easy_setopt (c, CURLOPT_UPLOAD, 1L); 367 curl_easy_setopt (c, CURLOPT_INFILESIZE_LARGE, (curl_off_t) 8L); 368 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1L); 369 curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L); 370 if (oneone) 371 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 372 else 373 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); 374 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L); 375 /* NOTE: use of CONNECTTIMEOUT without also 376 * setting NOSIGNAL results in really weird 377 * crashes on my system! */ 378 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1L); 379 withTimeout = 0; 380 if (CURLE_OK != (errornum = curl_easy_perform (c))) 381 { 382 curl_easy_cleanup (c); 383 MHD_stop_daemon (d); 384 if ((errornum == CURLE_GOT_NOTHING) 385 || (CURLE_READ_ERROR == errornum)) 386 { 387 if (0 != withTimeout) 388 { 389 /* mhd had the timeout */ 390 return 0; 391 } 392 else 393 { 394 fprintf (stderr, "Timeout wasn't detected.\n"); 395 return 8; 396 } 397 } 398 else 399 { 400 fprintf (stderr, "libcurl reported error: %s (%u)\n", 401 curl_easy_strerror (errornum), 402 errornum); 403 return 32; 404 } 405 } 406 curl_easy_cleanup (c); 407 MHD_stop_daemon (d); 408 return 64; 409 } 410 411 412 int 413 main (int argc, char *const *argv) 414 { 415 unsigned int errorCount = 0; 416 (void) argc; /* Unused. Silent compiler warning. */ 417 418 if ((NULL == argv) || (0 == argv[0])) 419 return 99; 420 oneone = has_in_name (argv[0], "11"); 421 if (0 != curl_global_init (CURL_GLOBAL_WIN32)) 422 return 16; 423 errorCount += testWithoutTimeout (); 424 errorCount += testWithTimeout (); 425 if (errorCount != 0) 426 fprintf (stderr, 427 "Error during test execution (code: %u)\n", 428 errorCount); 429 curl_global_cleanup (); 430 return (0 == errorCount) ? 0 : 1; /* 0 == pass */ 431 }