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
46static 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
59static char *put_buffer;
60
61struct CBC
62{
63  char *buf;
64  size_t pos;
65  size_t size;
66};
67
68static size_t
69putBuffer (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
82static size_t
83copyBuffer (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
94static int
95ahc_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
141static int
142testInternalPut ()
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, &copyBuffer);
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
199static int
200testMultithreadedPut ()
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, &copyBuffer);
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
260static int
261testMultithreadedPoolPut ()
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, &copyBuffer);
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
322static int
323testExternalPut ()
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, &copyBuffer);
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
458int
459main (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