Home | History | Annotate | Download | only in testcurl
      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
     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
     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
     93 crcf (void *ptr)
     94 {
     95   free (ptr);
     96 }
     97 
     98 static int
     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
    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
    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, &copyBuffer);
    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
    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, &copyBuffer);
    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
    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, &copyBuffer);
    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
    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, &copyBuffer);
    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
    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