1 /*
2 This file is part of libmicrohttpd
3 Copyright (C) 2013, 2015 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 * @file test_quiesce.c
22 * @brief Testcase for libmicrohttpd quiescing
23 * @author Christian Grothoff
24 */
25
26 #include "MHD_config.h"
27 #include "platform.h"
28 #include "platform_interface.h"
29 #include <curl/curl.h>
30 #include <microhttpd.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <time.h>
34 #include <sys/types.h>
35 #include <pthread.h>
36
37 #ifndef WINDOWS
38 #include <unistd.h>
39 #include <sys/socket.h>
40 #endif
41
42 #if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
43 #undef CPU_COUNT
44 #endif
45 #if !defined(CPU_COUNT)
46 #define CPU_COUNT 2
47 #endif
48
49
50 struct CBC
51 {
52 char *buf;
53 size_t pos;
54 size_t size;
55 };
56
57
58 static size_t
copyBuffer(void * ptr,size_t size,size_t nmemb,void * ctx)59 copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
60 {
61 struct CBC *cbc = ctx;
62
63 if (cbc->pos + size * nmemb > cbc->size)
64 return 0; /* overflow */
65 memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
66 cbc->pos += size * nmemb;
67 return size * nmemb;
68 }
69
70
71 static int
ahc_echo(void * cls,struct MHD_Connection * connection,const char * url,const char * method,const char * version,const char * upload_data,size_t * upload_data_size,void ** unused)72 ahc_echo (void *cls,
73 struct MHD_Connection *connection,
74 const char *url,
75 const char *method,
76 const char *version,
77 const char *upload_data, size_t *upload_data_size,
78 void **unused)
79 {
80 static int ptr;
81 const char *me = cls;
82 struct MHD_Response *response;
83 int ret;
84
85 if (0 != strcmp (me, method))
86 return MHD_NO; /* unexpected method */
87 if (&ptr != *unused)
88 {
89 *unused = &ptr;
90 return MHD_YES;
91 }
92 *unused = NULL;
93 response = MHD_create_response_from_buffer (strlen (url),
94 (void *) url,
95 MHD_RESPMEM_MUST_COPY);
96 ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
97 MHD_destroy_response (response);
98 if (ret == MHD_NO)
99 abort ();
100 return ret;
101 }
102
103
104 static void
request_completed(void * cls,struct MHD_Connection * connection,void ** con_cls,enum MHD_RequestTerminationCode code)105 request_completed (void *cls, struct MHD_Connection *connection,
106 void **con_cls, enum MHD_RequestTerminationCode code)
107 {
108 int *done = (int *)cls;
109 *done = 1;
110 }
111
112
113 static void *
ServeOneRequest(void * param)114 ServeOneRequest(void *param)
115 {
116 struct MHD_Daemon *d;
117 fd_set rs;
118 fd_set ws;
119 fd_set es;
120 MHD_socket fd, max;
121 time_t start;
122 struct timeval tv;
123 int done = 0;
124
125 fd = (MHD_socket) (intptr_t) param;
126
127 d = MHD_start_daemon (MHD_USE_DEBUG,
128 1082, NULL, NULL, &ahc_echo, "GET",
129 MHD_OPTION_LISTEN_SOCKET, fd,
130 MHD_OPTION_NOTIFY_COMPLETED, &request_completed, &done,
131 MHD_OPTION_END);
132 if (d == NULL)
133 return "MHD_start_daemon() failed";
134
135 start = time (NULL);
136 while ((time (NULL) - start < 5) && done == 0)
137 {
138 max = 0;
139 FD_ZERO (&rs);
140 FD_ZERO (&ws);
141 FD_ZERO (&es);
142 if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
143 {
144 MHD_stop_daemon (d);
145 MHD_socket_close_(fd);
146 return "MHD_get_fdset() failed";
147 }
148 tv.tv_sec = 0;
149 tv.tv_usec = 1000;
150 MHD_SYS_select_ (max + 1, &rs, &ws, &es, &tv);
151 MHD_run (d);
152 }
153 MHD_stop_daemon (d);
154 MHD_socket_close_(fd);
155 return NULL;
156 }
157
158
159 static CURL *
setupCURL(void * cbc)160 setupCURL (void *cbc)
161 {
162 CURL *c;
163
164 c = curl_easy_init ();
165 curl_easy_setopt (c, CURLOPT_URL, "http://127.0.0.1:11080/hello_world");
166 curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, ©Buffer);
167 curl_easy_setopt (c, CURLOPT_WRITEDATA, cbc);
168 curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
169 curl_easy_setopt (c, CURLOPT_TIMEOUT_MS, 150L);
170 curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT_MS, 150L);
171 curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
172 /* NOTE: use of CONNECTTIMEOUT without also
173 setting NOSIGNAL results in really weird
174 crashes on my system!*/
175 curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
176
177 return c;
178 }
179
180
181 static int
testGet(int type,int pool_count,int poll_flag)182 testGet (int type, int pool_count, int poll_flag)
183 {
184 struct MHD_Daemon *d;
185 CURL *c;
186 char buf[2048];
187 struct CBC cbc;
188 CURLcode errornum;
189 MHD_socket fd;
190 pthread_t thrd;
191 const char *thrdRet;
192
193 cbc.buf = buf;
194 cbc.size = 2048;
195 cbc.pos = 0;
196 if (pool_count > 0) {
197 d = MHD_start_daemon (type | MHD_USE_DEBUG | MHD_USE_PIPE_FOR_SHUTDOWN | poll_flag,
198 11080, NULL, NULL, &ahc_echo, "GET",
199 MHD_OPTION_THREAD_POOL_SIZE, pool_count, MHD_OPTION_END);
200
201 } else {
202 d = MHD_start_daemon (type | MHD_USE_DEBUG | MHD_USE_PIPE_FOR_SHUTDOWN | poll_flag,
203 11080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
204 }
205 if (d == NULL)
206 return 1;
207
208 c = setupCURL(&cbc);
209
210 if (CURLE_OK != (errornum = curl_easy_perform (c)))
211 {
212 fprintf (stderr,
213 "curl_easy_perform failed: `%s'\n",
214 curl_easy_strerror (errornum));
215 curl_easy_cleanup (c);
216 MHD_stop_daemon (d);
217 return 2;
218 }
219
220 if (cbc.pos != strlen ("/hello_world")) {
221 curl_easy_cleanup (c);
222 MHD_stop_daemon (d);
223 return 4;
224 }
225 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world"))) {
226 curl_easy_cleanup (c);
227 MHD_stop_daemon (d);
228 return 8;
229 }
230
231 fd = MHD_quiesce_daemon (d);
232 if (0 != pthread_create(&thrd, NULL, &ServeOneRequest, (void*)(intptr_t) fd))
233 {
234 fprintf (stderr, "pthread_create failed\n");
235 curl_easy_cleanup (c);
236 MHD_stop_daemon (d);
237 return 16;
238 }
239
240 cbc.pos = 0;
241 if (CURLE_OK != (errornum = curl_easy_perform (c)))
242 {
243 fprintf (stderr,
244 "curl_easy_perform failed: `%s'\n",
245 curl_easy_strerror (errornum));
246 curl_easy_cleanup (c);
247 MHD_stop_daemon (d);
248 return 2;
249 }
250
251 if (0 != pthread_join(thrd, (void**)&thrdRet))
252 {
253 fprintf (stderr, "pthread_join failed\n");
254 curl_easy_cleanup (c);
255 MHD_stop_daemon (d);
256 return 16;
257 }
258 if (NULL != thrdRet)
259 {
260 fprintf (stderr, "ServeOneRequest() error: %s\n", thrdRet);
261 curl_easy_cleanup (c);
262 MHD_stop_daemon (d);
263 return 16;
264 }
265
266 if (cbc.pos != strlen ("/hello_world"))
267 {
268 fprintf(stderr, "%s\n", cbc.buf);
269 curl_easy_cleanup (c);
270 MHD_stop_daemon (d);
271 MHD_socket_close_(fd);
272 return 4;
273 }
274 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
275 {
276 fprintf(stderr, "%s\n", cbc.buf);
277 curl_easy_cleanup (c);
278 MHD_stop_daemon (d);
279 MHD_socket_close_(fd);
280 return 8;
281 }
282
283 /* at this point, the forked server quit, and the new
284 * server has quiesced, so new requests should fail
285 */
286 if (CURLE_OK == (errornum = curl_easy_perform (c)))
287 {
288 fprintf (stderr, "curl_easy_perform should fail\n");
289 curl_easy_cleanup (c);
290 MHD_stop_daemon (d);
291 MHD_socket_close_(fd);
292 return 2;
293 }
294 curl_easy_cleanup (c);
295 MHD_stop_daemon (d);
296 MHD_socket_close_(fd);
297
298 return 0;
299 }
300
301
302 static int
testExternalGet()303 testExternalGet ()
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 MHD_socket fd;
321
322 multi = NULL;
323 cbc.buf = buf;
324 cbc.size = 2048;
325 cbc.pos = 0;
326 d = MHD_start_daemon (MHD_USE_DEBUG,
327 11080, NULL, NULL, &ahc_echo, "GET", MHD_OPTION_END);
328 if (d == NULL)
329 return 256;
330 c = setupCURL(&cbc);
331
332 multi = curl_multi_init ();
333 if (multi == NULL)
334 {
335 curl_easy_cleanup (c);
336 MHD_stop_daemon (d);
337 return 512;
338 }
339 mret = curl_multi_add_handle (multi, c);
340 if (mret != CURLM_OK)
341 {
342 curl_multi_cleanup (multi);
343 curl_easy_cleanup (c);
344 MHD_stop_daemon (d);
345 return 1024;
346 }
347
348 for (i = 0; i < 2; i++) {
349 start = time (NULL);
350 while ((time (NULL) - start < 5) && (multi != NULL))
351 {
352 max = 0;
353 FD_ZERO (&rs);
354 FD_ZERO (&ws);
355 FD_ZERO (&es);
356 curl_multi_perform (multi, &running);
357 mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
358 if (mret != CURLM_OK)
359 {
360 curl_multi_remove_handle (multi, c);
361 curl_multi_cleanup (multi);
362 curl_easy_cleanup (c);
363 MHD_stop_daemon (d);
364 return 2048;
365 }
366 if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
367 {
368 curl_multi_remove_handle (multi, c);
369 curl_multi_cleanup (multi);
370 curl_easy_cleanup (c);
371 MHD_stop_daemon (d);
372 return 4096;
373 }
374 tv.tv_sec = 0;
375 tv.tv_usec = 1000;
376 select (max + 1, &rs, &ws, &es, &tv);
377 curl_multi_perform (multi, &running);
378 if (running == 0)
379 {
380 msg = curl_multi_info_read (multi, &running);
381 if (msg == NULL)
382 break;
383 if (msg->msg == CURLMSG_DONE)
384 {
385 if (i == 0 && msg->data.result != CURLE_OK)
386 printf ("%s failed at %s:%d: `%s'\n",
387 "curl_multi_perform",
388 __FILE__,
389 __LINE__, curl_easy_strerror (msg->data.result));
390 else if (i == 1 && msg->data.result == CURLE_OK)
391 printf ("%s should have failed at %s:%d\n",
392 "curl_multi_perform",
393 __FILE__,
394 __LINE__);
395 curl_multi_remove_handle (multi, c);
396 curl_multi_cleanup (multi);
397 curl_easy_cleanup (c);
398 c = NULL;
399 multi = NULL;
400 }
401 }
402 MHD_run (d);
403 }
404
405 if (i == 0) {
406 /* quiesce the daemon on the 1st iteration, so the 2nd should fail */
407 fd = MHD_quiesce_daemon(d);
408 if (MHD_INVALID_SOCKET == fd)
409 abort ();
410 MHD_socket_close_ (fd);
411 c = setupCURL (&cbc);
412 multi = curl_multi_init ();
413 mret = curl_multi_add_handle (multi, c);
414 }
415 }
416 if (multi != NULL)
417 {
418 curl_multi_remove_handle (multi, c);
419 curl_easy_cleanup (c);
420 curl_multi_cleanup (multi);
421 }
422 MHD_stop_daemon (d);
423 if (cbc.pos != strlen ("/hello_world"))
424 return 8192;
425 if (0 != strncmp ("/hello_world", cbc.buf, strlen ("/hello_world")))
426 return 16384;
427 return 0;
428 }
429
430
431 int
main(int argc,char * const * argv)432 main (int argc, char *const *argv)
433 {
434 unsigned int errorCount = 0;
435
436 if (0 != curl_global_init (CURL_GLOBAL_WIN32))
437 return 2;
438 errorCount += testGet (MHD_USE_SELECT_INTERNALLY, 0, 0);
439 errorCount += testGet (MHD_USE_THREAD_PER_CONNECTION, 0, 0);
440 errorCount += testGet (MHD_USE_SELECT_INTERNALLY, CPU_COUNT, 0);
441 errorCount += testExternalGet ();
442 if (MHD_YES == MHD_is_feature_supported(MHD_FEATURE_POLL))
443 {
444 errorCount += testGet(MHD_USE_SELECT_INTERNALLY, 0, MHD_USE_POLL);
445 errorCount += testGet (MHD_USE_THREAD_PER_CONNECTION, 0, MHD_USE_POLL);
446 errorCount += testGet (MHD_USE_SELECT_INTERNALLY, CPU_COUNT, MHD_USE_POLL);
447 }
448 if (MHD_YES == MHD_is_feature_supported(MHD_FEATURE_EPOLL))
449 {
450 errorCount += testGet (MHD_USE_SELECT_INTERNALLY, 0, MHD_USE_EPOLL_LINUX_ONLY);
451 errorCount += testGet (MHD_USE_SELECT_INTERNALLY, CPU_COUNT, MHD_USE_EPOLL_LINUX_ONLY);
452 }
453 if (errorCount != 0)
454 fprintf (stderr, "Error (code: %u)\n", errorCount);
455 curl_global_cleanup ();
456 return errorCount != 0; /* 0 == pass */
457 }
458