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
copyBuffer(void * ptr,size_t size,size_t nmemb,void * ctx)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
crc(void * cls,uint64_t pos,char * buf,size_t max)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
crcf(void * ptr)93 crcf (void *ptr)
94 {
95 free (ptr);
96 }
97
98 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 ** ptr)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
validate(struct CBC cbc,int ebase)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
testInternalGet()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
testMultithreadedGet()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
testMultithreadedPoolGet()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
testExternalGet()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
main(int argc,char * const * argv)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