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