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_post_loop.c
23 * @brief  Testcase for libmicrohttpd POST operations using URL-encoding
24 * @author Christian Grothoff (inspired by bug report #1296)
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#include <gauger.h>
35
36#ifndef WINDOWS
37#include <unistd.h>
38#endif
39
40#if defined(CPU_COUNT) && (CPU_COUNT+0) < 2
41#undef CPU_COUNT
42#endif
43#if !defined(CPU_COUNT)
44#define CPU_COUNT 2
45#endif
46
47#define POST_DATA "<?xml version='1.0' ?>\n<xml>\n<data-id>1</data-id>\n</xml>\n"
48
49#define LOOPCOUNT 1000
50
51static int oneone;
52
53struct CBC
54{
55  char *buf;
56  size_t pos;
57  size_t size;
58};
59
60static size_t
61copyBuffer (void *ptr, size_t size, size_t nmemb, void *ctx)
62{
63  struct CBC *cbc = ctx;
64
65  if (cbc->pos + size * nmemb > cbc->size)
66    return 0;                   /* overflow */
67  memcpy (&cbc->buf[cbc->pos], ptr, size * nmemb);
68  cbc->pos += size * nmemb;
69  return size * nmemb;
70}
71
72static int
73ahc_echo (void *cls,
74          struct MHD_Connection *connection,
75          const char *url,
76          const char *method,
77          const char *version,
78          const char *upload_data, size_t *upload_data_size,
79          void **mptr)
80{
81  static int marker;
82  struct MHD_Response *response;
83  int ret;
84
85  if (0 != strcmp ("POST", method))
86    {
87      printf ("METHOD: %s\n", method);
88      return MHD_NO;            /* unexpected method */
89    }
90  if ((*mptr != NULL) && (0 == *upload_data_size))
91    {
92      if (*mptr != &marker)
93        abort ();
94      response = MHD_create_response_from_buffer (2, "OK",
95						  MHD_RESPMEM_PERSISTENT);
96      ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
97      MHD_destroy_response (response);
98      *mptr = NULL;
99      return ret;
100    }
101  if (strlen (POST_DATA) != *upload_data_size)
102    return MHD_YES;
103  *upload_data_size = 0;
104  *mptr = &marker;
105  return MHD_YES;
106}
107
108
109static int
110testInternalPost ()
111{
112  struct MHD_Daemon *d;
113  CURL *c;
114  char buf[2048];
115  struct CBC cbc;
116  CURLcode errornum;
117  int i;
118  char url[1024];
119
120  cbc.buf = buf;
121  cbc.size = 2048;
122  d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
123                        1080, NULL, NULL, &ahc_echo, NULL, MHD_OPTION_END);
124  if (d == NULL)
125    return 1;
126  for (i = 0; i < LOOPCOUNT; i++)
127    {
128      if (99 == i % 100)
129        fprintf (stderr, ".");
130      c = curl_easy_init ();
131      cbc.pos = 0;
132      buf[0] = '\0';
133      sprintf (url, "http://127.0.0.1:1080/hw%d", i);
134      curl_easy_setopt (c, CURLOPT_URL, url);
135      curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
136      curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
137      curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA);
138      curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA));
139      curl_easy_setopt (c, CURLOPT_POST, 1L);
140      curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
141      curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
142      if (oneone)
143        curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
144      else
145        curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
146      curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
147      // NOTE: use of CONNECTTIMEOUT without also
148      //   setting NOSIGNAL results in really weird
149      //   crashes on my system!
150      curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
151      if (CURLE_OK != (errornum = curl_easy_perform (c)))
152        {
153          fprintf (stderr,
154                   "curl_easy_perform failed: `%s'\n",
155                   curl_easy_strerror (errornum));
156          curl_easy_cleanup (c);
157          MHD_stop_daemon (d);
158          return 2;
159        }
160      curl_easy_cleanup (c);
161      if ((buf[0] != 'O') || (buf[1] != 'K'))
162        {
163          MHD_stop_daemon (d);
164          return 4;
165        }
166    }
167  MHD_stop_daemon (d);
168  if (LOOPCOUNT >= 99)
169    fprintf (stderr, "\n");
170  return 0;
171}
172
173static int
174testMultithreadedPost ()
175{
176  struct MHD_Daemon *d;
177  CURL *c;
178  char buf[2048];
179  struct CBC cbc;
180  CURLcode errornum;
181  int i;
182  char url[1024];
183
184  cbc.buf = buf;
185  cbc.size = 2048;
186  d = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG,
187                        1081, NULL, NULL, &ahc_echo, NULL, MHD_OPTION_END);
188  if (d == NULL)
189    return 16;
190  for (i = 0; i < LOOPCOUNT; i++)
191    {
192      if (99 == i % 100)
193        fprintf (stderr, ".");
194      c = curl_easy_init ();
195      cbc.pos = 0;
196      buf[0] = '\0';
197      sprintf (url, "http://127.0.0.1:1081/hw%d", i);
198      curl_easy_setopt (c, CURLOPT_URL, url);
199      curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
200      curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
201      curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA);
202      curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA));
203      curl_easy_setopt (c, CURLOPT_POST, 1L);
204      curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
205      curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
206      if (oneone)
207        curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
208      else
209        curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
210      curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
211      // NOTE: use of CONNECTTIMEOUT without also
212      //   setting NOSIGNAL results in really weird
213      //   crashes on my system!
214      curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
215      if (CURLE_OK != (errornum = curl_easy_perform (c)))
216        {
217          fprintf (stderr,
218                   "curl_easy_perform failed: `%s'\n",
219                   curl_easy_strerror (errornum));
220          curl_easy_cleanup (c);
221          MHD_stop_daemon (d);
222          return 32;
223        }
224      curl_easy_cleanup (c);
225      if ((buf[0] != 'O') || (buf[1] != 'K'))
226        {
227          MHD_stop_daemon (d);
228          return 64;
229        }
230    }
231  MHD_stop_daemon (d);
232  if (LOOPCOUNT >= 99)
233    fprintf (stderr, "\n");
234  return 0;
235}
236
237static int
238testMultithreadedPoolPost ()
239{
240  struct MHD_Daemon *d;
241  CURL *c;
242  char buf[2048];
243  struct CBC cbc;
244  CURLcode errornum;
245  int i;
246  char url[1024];
247
248  cbc.buf = buf;
249  cbc.size = 2048;
250  d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
251                        1081, NULL, NULL, &ahc_echo, NULL,
252                        MHD_OPTION_THREAD_POOL_SIZE, CPU_COUNT, MHD_OPTION_END);
253  if (d == NULL)
254    return 16;
255  for (i = 0; i < LOOPCOUNT; i++)
256    {
257      if (99 == i % 100)
258        fprintf (stderr, ".");
259      c = curl_easy_init ();
260      cbc.pos = 0;
261      buf[0] = '\0';
262      sprintf (url, "http://127.0.0.1:1081/hw%d", i);
263      curl_easy_setopt (c, CURLOPT_URL, url);
264      curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
265      curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
266      curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA);
267      curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA));
268      curl_easy_setopt (c, CURLOPT_POST, 1L);
269      curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
270      curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
271      if (oneone)
272        curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
273      else
274        curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
275      curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
276      // NOTE: use of CONNECTTIMEOUT without also
277      //   setting NOSIGNAL results in really weird
278      //   crashes on my system!
279      curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
280      if (CURLE_OK != (errornum = curl_easy_perform (c)))
281        {
282          fprintf (stderr,
283                   "curl_easy_perform failed: `%s'\n",
284                   curl_easy_strerror (errornum));
285          curl_easy_cleanup (c);
286          MHD_stop_daemon (d);
287          return 32;
288        }
289      curl_easy_cleanup (c);
290      if ((buf[0] != 'O') || (buf[1] != 'K'))
291        {
292          MHD_stop_daemon (d);
293          return 64;
294        }
295    }
296  MHD_stop_daemon (d);
297  if (LOOPCOUNT >= 99)
298    fprintf (stderr, "\n");
299  return 0;
300}
301
302static int
303testExternalPost ()
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  unsigned long long timeout;
321  long ctimeout;
322  char url[1024];
323
324  multi = NULL;
325  cbc.buf = buf;
326  cbc.size = 2048;
327  cbc.pos = 0;
328  d = MHD_start_daemon (MHD_USE_DEBUG,
329                        1082, NULL, NULL, &ahc_echo, NULL, MHD_OPTION_END);
330  if (d == NULL)
331    return 256;
332  multi = curl_multi_init ();
333  if (multi == NULL)
334    {
335      MHD_stop_daemon (d);
336      return 512;
337    }
338  for (i = 0; i < LOOPCOUNT; i++)
339    {
340      if (99 == i % 100)
341	fprintf (stderr, ".");
342      c = curl_easy_init ();
343      cbc.pos = 0;
344      buf[0] = '\0';
345      sprintf (url, "http://127.0.0.1:1082/hw%d", i);
346      curl_easy_setopt (c, CURLOPT_URL, url);
347      curl_easy_setopt (c, CURLOPT_WRITEFUNCTION, &copyBuffer);
348      curl_easy_setopt (c, CURLOPT_WRITEDATA, &cbc);
349      curl_easy_setopt (c, CURLOPT_POSTFIELDS, POST_DATA);
350      curl_easy_setopt (c, CURLOPT_POSTFIELDSIZE, strlen (POST_DATA));
351      curl_easy_setopt (c, CURLOPT_POST, 1L);
352      curl_easy_setopt (c, CURLOPT_FAILONERROR, 1);
353      curl_easy_setopt (c, CURLOPT_TIMEOUT, 150L);
354      if (oneone)
355        curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
356      else
357        curl_easy_setopt (c, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
358      curl_easy_setopt (c, CURLOPT_CONNECTTIMEOUT, 150L);
359      // NOTE: use of CONNECTTIMEOUT without also
360      //   setting NOSIGNAL results in really weird
361      //   crashes on my system!
362      curl_easy_setopt (c, CURLOPT_NOSIGNAL, 1);
363      mret = curl_multi_add_handle (multi, c);
364      if (mret != CURLM_OK)
365        {
366          curl_multi_cleanup (multi);
367          curl_easy_cleanup (c);
368          MHD_stop_daemon (d);
369          return 1024;
370        }
371      start = time (NULL);
372      while ((time (NULL) - start < 5) && (multi != NULL))
373        {
374          max = 0;
375          FD_ZERO (&rs);
376          FD_ZERO (&ws);
377          FD_ZERO (&es);
378          while (CURLM_CALL_MULTI_PERFORM ==
379                 curl_multi_perform (multi, &running));
380          mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
381          if (mret != CURLM_OK)
382            {
383              curl_multi_remove_handle (multi, c);
384              curl_multi_cleanup (multi);
385              curl_easy_cleanup (c);
386              MHD_stop_daemon (d);
387              return 2048;
388            }
389          if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
390            {
391              curl_multi_remove_handle (multi, c);
392              curl_multi_cleanup (multi);
393              curl_easy_cleanup (c);
394              MHD_stop_daemon (d);
395              return 4096;
396            }
397          if (MHD_NO == MHD_get_timeout (d, &timeout))
398            timeout = 100;      /* 100ms == INFTY -- CURL bug... */
399          if ((CURLM_OK == curl_multi_timeout (multi, &ctimeout)) &&
400              (ctimeout < timeout) && (ctimeout >= 0))
401            timeout = ctimeout;
402	  if ( (c == NULL) || (running == 0) )
403	    timeout = 0; /* terminate quickly... */
404          tv.tv_sec = timeout / 1000;
405          tv.tv_usec = (timeout % 1000) * 1000;
406          if (-1 == select (max + 1, &rs, &ws, &es, &tv))
407	    {
408	      if (EINTR == errno)
409		continue;
410	      fprintf (stderr,
411		       "select failed: %s\n",
412		       strerror (errno));
413	      break;
414	    }
415          while (CURLM_CALL_MULTI_PERFORM ==
416                 curl_multi_perform (multi, &running));
417          if (running == 0)
418            {
419              msg = curl_multi_info_read (multi, &running);
420              if (msg == NULL)
421                break;
422              if (msg->msg == CURLMSG_DONE)
423                {
424                  if (msg->data.result != CURLE_OK)
425                    printf ("%s failed at %s:%d: `%s'\n",
426                            "curl_multi_perform",
427                            __FILE__,
428                            __LINE__, curl_easy_strerror (msg->data.result));
429                  curl_multi_remove_handle (multi, c);
430                  curl_easy_cleanup (c);
431                  c = NULL;
432                }
433            }
434          MHD_run (d);
435        }
436      if (c != NULL)
437        {
438          curl_multi_remove_handle (multi, c);
439          curl_easy_cleanup (c);
440        }
441      if ((buf[0] != 'O') || (buf[1] != 'K'))
442        {
443          curl_multi_cleanup (multi);
444          MHD_stop_daemon (d);
445          return 8192;
446        }
447    }
448  curl_multi_cleanup (multi);
449  MHD_stop_daemon (d);
450  if (LOOPCOUNT >= 99)
451    fprintf (stderr, "\n");
452  return 0;
453}
454
455
456/**
457 * Time this round was started.
458 */
459static unsigned long long start_time;
460
461
462/**
463 * Get the current timestamp
464 *
465 * @return current time in ms
466 */
467static unsigned long long
468now ()
469{
470  struct timeval tv;
471
472  gettimeofday (&tv, NULL);
473  return (((unsigned long long) tv.tv_sec * 1000LL) +
474	  ((unsigned long long) tv.tv_usec / 1000LL));
475}
476
477
478int
479main (int argc, char *const *argv)
480{
481  unsigned int errorCount = 0;
482
483  oneone = (NULL != strrchr (argv[0], (int) '/')) ?
484    (NULL != strstr (strrchr (argv[0], (int) '/'), "11")) : 0;
485  if (0 != curl_global_init (CURL_GLOBAL_WIN32))
486    return 2;
487  start_time = now();
488  errorCount += testInternalPost ();
489  fprintf (stderr,
490	   oneone ? "%s: Sequential POSTs (http/1.1) %f/s\n" : "%s: Sequential POSTs (http/1.0) %f/s\n",
491	   "internal select",
492	   (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0));
493  GAUGER ("internal select",
494	  oneone ? "Sequential POSTs (http/1.1)" : "Sequential POSTs (http/1.0)",
495	  (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0),
496	  "requests/s");
497  start_time = now();
498  errorCount += testMultithreadedPost ();
499  fprintf (stderr,
500	   oneone ? "%s: Sequential POSTs (http/1.1) %f/s\n" : "%s: Sequential POSTs (http/1.0) %f/s\n",
501	   "multithreaded post",
502	   (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0));
503  GAUGER ("Multithreaded select",
504	  oneone ? "Sequential POSTs (http/1.1)" : "Sequential POSTs (http/1.0)",
505	  (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0),
506	  "requests/s");
507  start_time = now();
508  errorCount += testMultithreadedPoolPost ();
509  fprintf (stderr,
510	   oneone ? "%s: Sequential POSTs (http/1.1) %f/s\n" : "%s: Sequential POSTs (http/1.0) %f/s\n",
511	   "thread with pool",
512	   (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0));
513  GAUGER ("thread with pool",
514	  oneone ? "Sequential POSTs (http/1.1)" : "Sequential POSTs (http/1.0)",
515	  (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0),
516	  "requests/s");
517  start_time = now();
518  errorCount += testExternalPost ();
519  fprintf (stderr,
520	   oneone ? "%s: Sequential POSTs (http/1.1) %f/s\n" : "%s: Sequential POSTs (http/1.0) %f/s\n",
521	   "external select",
522	   (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0));
523  GAUGER ("external select",
524	  oneone ? "Sequential POSTs (http/1.1)" : "Sequential POSTs (http/1.0)",
525	  (double) 1000 * LOOPCOUNT / (now() - start_time + 1.0),
526	  "requests/s");
527  if (errorCount != 0)
528    fprintf (stderr, "Error (code: %u)\n", errorCount);
529  curl_global_cleanup ();
530  return errorCount != 0;       /* 0 == pass */
531}
532