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
putBuffer(void * stream,size_t size,size_t nmemb,void * ptr)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
copyBuffer(void * ptr,size_t size,size_t nmemb,void * ctx)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
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)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
testInternalPut()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
testMultithreadedPut()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
testMultithreadedPoolPut()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
testExternalPut()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
main(int argc,char * const * argv)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