Home | History | Annotate | Download | only in examples
      1 /* Feel free to use this example code in any way
      2    you see fit (Public Domain) */
      3 
      4 /* needed for asprintf */
      5 #define _GNU_SOURCE
      6 
      7 #include <stdlib.h>
      8 #include <string.h>
      9 #include <stdio.h>
     10 #include <errno.h>
     11 #include <time.h>
     12 #include <microhttpd.h>
     13 
     14 #if defined _WIN32 && !defined(__MINGW64_VERSION_MAJOR)
     15 static int
     16 asprintf (char **resultp, const char *format, ...)
     17 {
     18   va_list argptr;
     19   char *result = NULL;
     20   int len = 0;
     21 
     22   if (format == NULL)
     23     return -1;
     24 
     25   va_start (argptr, format);
     26 
     27   len = _vscprintf ((char *) format, argptr);
     28   if (len >= 0)
     29   {
     30     len += 1;
     31     result = (char *) malloc (sizeof (char *) * len);
     32     if (result != NULL)
     33     {
     34       int len2 = _vscprintf ((char *) format, argptr);
     35       if (len2 != len - 1 || len2 <= 0)
     36       {
     37         free (result);
     38         result = NULL;
     39         len = -1;
     40       }
     41       else
     42       {
     43         len = len2;
     44         if (resultp)
     45           *resultp = result;
     46       }
     47     }
     48   }
     49   va_end (argptr);
     50   return len;
     51 }
     52 #endif
     53 
     54 /**
     55  * Invalid method page.
     56  */
     57 #define METHOD_ERROR "<html><head><title>Illegal request</title></head><body>Go away.</body></html>"
     58 
     59 /**
     60  * Invalid URL page.
     61  */
     62 #define NOT_FOUND_ERROR "<html><head><title>Not found</title></head><body>Go away.</body></html>"
     63 
     64 /**
     65  * Front page. (/)
     66  */
     67 #define MAIN_PAGE "<html><head><title>Welcome</title></head><body><form action=\"/2\" method=\"post\">What is your name? <input type=\"text\" name=\"v1\" value=\"%s\" /><input type=\"submit\" value=\"Next\" /></body></html>"
     68 
     69 /**
     70  * Second page. (/2)
     71  */
     72 #define SECOND_PAGE "<html><head><title>Tell me more</title></head><body><a href=\"/\">previous</a> <form action=\"/S\" method=\"post\">%s, what is your job? <input type=\"text\" name=\"v2\" value=\"%s\" /><input type=\"submit\" value=\"Next\" /></body></html>"
     73 
     74 /**
     75  * Second page (/S)
     76  */
     77 #define SUBMIT_PAGE "<html><head><title>Ready to submit?</title></head><body><form action=\"/F\" method=\"post\"><a href=\"/2\">previous </a> <input type=\"hidden\" name=\"DONE\" value=\"yes\" /><input type=\"submit\" value=\"Submit\" /></body></html>"
     78 
     79 /**
     80  * Last page.
     81  */
     82 #define LAST_PAGE "<html><head><title>Thank you</title></head><body>Thank you.</body></html>"
     83 
     84 /**
     85  * Name of our cookie.
     86  */
     87 #define COOKIE_NAME "session"
     88 
     89 
     90 /**
     91  * State we keep for each user/session/browser.
     92  */
     93 struct Session
     94 {
     95   /**
     96    * We keep all sessions in a linked list.
     97    */
     98   struct Session *next;
     99 
    100   /**
    101    * Unique ID for this session.
    102    */
    103   char sid[33];
    104 
    105   /**
    106    * Reference counter giving the number of connections
    107    * currently using this session.
    108    */
    109   unsigned int rc;
    110 
    111   /**
    112    * Time when this session was last active.
    113    */
    114   time_t start;
    115 
    116   /**
    117    * String submitted via form.
    118    */
    119   char value_1[64];
    120 
    121   /**
    122    * Another value submitted via form.
    123    */
    124   char value_2[64];
    125 
    126 };
    127 
    128 
    129 /**
    130  * Data kept per request.
    131  */
    132 struct Request
    133 {
    134 
    135   /**
    136    * Associated session.
    137    */
    138   struct Session *session;
    139 
    140   /**
    141    * Post processor handling form data (IF this is
    142    * a POST request).
    143    */
    144   struct MHD_PostProcessor *pp;
    145 
    146   /**
    147    * URL to serve in response to this POST (if this request
    148    * was a 'POST')
    149    */
    150   const char *post_url;
    151 
    152 };
    153 
    154 
    155 /**
    156  * Linked list of all active sessions.  Yes, O(n) but a
    157  * hash table would be overkill for a simple example...
    158  */
    159 static struct Session *sessions;
    160 
    161 
    162 
    163 
    164 /**
    165  * Return the session handle for this connection, or
    166  * create one if this is a new user.
    167  */
    168 static struct Session *
    169 get_session (struct MHD_Connection *connection)
    170 {
    171   struct Session *ret;
    172   const char *cookie;
    173 
    174   cookie = MHD_lookup_connection_value (connection,
    175 					MHD_COOKIE_KIND,
    176 					COOKIE_NAME);
    177   if (cookie != NULL)
    178     {
    179       /* find existing session */
    180       ret = sessions;
    181       while (NULL != ret)
    182 	{
    183 	  if (0 == strcmp (cookie, ret->sid))
    184 	    break;
    185 	  ret = ret->next;
    186 	}
    187       if (NULL != ret)
    188 	{
    189 	  ret->rc++;
    190 	  return ret;
    191 	}
    192     }
    193   /* create fresh session */
    194   ret = calloc (1, sizeof (struct Session));
    195   if (NULL == ret)
    196     {
    197       fprintf (stderr, "calloc error: %s\n", strerror (errno));
    198       return NULL;
    199     }
    200   /* not a super-secure way to generate a random session ID,
    201      but should do for a simple example... */
    202   snprintf (ret->sid,
    203 	    sizeof (ret->sid),
    204 	    "%X%X%X%X",
    205 	    (unsigned int) rand (),
    206 	    (unsigned int) rand (),
    207 	    (unsigned int) rand (),
    208 	    (unsigned int) rand ());
    209   ret->rc++;
    210   ret->start = time (NULL);
    211   ret->next = sessions;
    212   sessions = ret;
    213   return ret;
    214 }
    215 
    216 
    217 /**
    218  * Type of handler that generates a reply.
    219  *
    220  * @param cls content for the page (handler-specific)
    221  * @param mime mime type to use
    222  * @param session session information
    223  * @param connection connection to process
    224  * @param MHD_YES on success, MHD_NO on failure
    225  */
    226 typedef int (*PageHandler)(const void *cls,
    227 			   const char *mime,
    228 			   struct Session *session,
    229 			   struct MHD_Connection *connection);
    230 
    231 
    232 /**
    233  * Entry we generate for each page served.
    234  */
    235 struct Page
    236 {
    237   /**
    238    * Acceptable URL for this page.
    239    */
    240   const char *url;
    241 
    242   /**
    243    * Mime type to set for the page.
    244    */
    245   const char *mime;
    246 
    247   /**
    248    * Handler to call to generate response.
    249    */
    250   PageHandler handler;
    251 
    252   /**
    253    * Extra argument to handler.
    254    */
    255   const void *handler_cls;
    256 };
    257 
    258 
    259 /**
    260  * Add header to response to set a session cookie.
    261  *
    262  * @param session session to use
    263  * @param response response to modify
    264  */
    265 static void
    266 add_session_cookie (struct Session *session,
    267 		    struct MHD_Response *response)
    268 {
    269   char cstr[256];
    270   snprintf (cstr,
    271 	    sizeof (cstr),
    272 	    "%s=%s",
    273 	    COOKIE_NAME,
    274 	    session->sid);
    275   if (MHD_NO ==
    276       MHD_add_response_header (response,
    277 			       MHD_HTTP_HEADER_SET_COOKIE,
    278 			       cstr))
    279     {
    280       fprintf (stderr,
    281 	       "Failed to set session cookie header!\n");
    282     }
    283 }
    284 
    285 
    286 /**
    287  * Handler that returns a simple static HTTP page that
    288  * is passed in via 'cls'.
    289  *
    290  * @param cls a 'const char *' with the HTML webpage to return
    291  * @param mime mime type to use
    292  * @param session session handle
    293  * @param connection connection to use
    294  */
    295 static int
    296 serve_simple_form (const void *cls,
    297 		   const char *mime,
    298 		   struct Session *session,
    299 		   struct MHD_Connection *connection)
    300 {
    301   int ret;
    302   const char *form = cls;
    303   struct MHD_Response *response;
    304 
    305   /* return static form */
    306   response = MHD_create_response_from_buffer (strlen (form),
    307 					      (void *) form,
    308 					      MHD_RESPMEM_PERSISTENT);
    309   add_session_cookie (session, response);
    310   MHD_add_response_header (response,
    311 			   MHD_HTTP_HEADER_CONTENT_ENCODING,
    312 			   mime);
    313   ret = MHD_queue_response (connection,
    314 			    MHD_HTTP_OK,
    315 			    response);
    316   MHD_destroy_response (response);
    317   return ret;
    318 }
    319 
    320 
    321 /**
    322  * Handler that adds the 'v1' value to the given HTML code.
    323  *
    324  * @param cls a 'const char *' with the HTML webpage to return
    325  * @param mime mime type to use
    326  * @param session session handle
    327  * @param connection connection to use
    328  */
    329 static int
    330 fill_v1_form (const void *cls,
    331 	      const char *mime,
    332 	      struct Session *session,
    333 	      struct MHD_Connection *connection)
    334 {
    335   int ret;
    336   const char *form = cls;
    337   char *reply;
    338   struct MHD_Response *response;
    339 
    340   if (-1 == asprintf (&reply,
    341 		      form,
    342 		      session->value_1))
    343     {
    344       /* oops */
    345       return MHD_NO;
    346     }
    347   /* return static form */
    348   response = MHD_create_response_from_buffer (strlen (reply),
    349 					      (void *) reply,
    350 					      MHD_RESPMEM_MUST_FREE);
    351   add_session_cookie (session, response);
    352   MHD_add_response_header (response,
    353 			   MHD_HTTP_HEADER_CONTENT_ENCODING,
    354 			   mime);
    355   ret = MHD_queue_response (connection,
    356 			    MHD_HTTP_OK,
    357 			    response);
    358   MHD_destroy_response (response);
    359   return ret;
    360 }
    361 
    362 
    363 /**
    364  * Handler that adds the 'v1' and 'v2' values to the given HTML code.
    365  *
    366  * @param cls a 'const char *' with the HTML webpage to return
    367  * @param mime mime type to use
    368  * @param session session handle
    369  * @param connection connection to use
    370  */
    371 static int
    372 fill_v1_v2_form (const void *cls,
    373 		 const char *mime,
    374 		 struct Session *session,
    375 		 struct MHD_Connection *connection)
    376 {
    377   int ret;
    378   const char *form = cls;
    379   char *reply;
    380   struct MHD_Response *response;
    381 
    382   if (-1 == asprintf (&reply,
    383 		      form,
    384 		      session->value_1,
    385 		      session->value_2))
    386     {
    387       /* oops */
    388       return MHD_NO;
    389     }
    390   /* return static form */
    391   response = MHD_create_response_from_buffer (strlen (reply),
    392 					      (void *) reply,
    393 					      MHD_RESPMEM_MUST_FREE);
    394   add_session_cookie (session, response);
    395   MHD_add_response_header (response,
    396 			   MHD_HTTP_HEADER_CONTENT_ENCODING,
    397 			   mime);
    398   ret = MHD_queue_response (connection,
    399 			    MHD_HTTP_OK,
    400 			    response);
    401   MHD_destroy_response (response);
    402   return ret;
    403 }
    404 
    405 
    406 /**
    407  * Handler used to generate a 404 reply.
    408  *
    409  * @param cls a 'const char *' with the HTML webpage to return
    410  * @param mime mime type to use
    411  * @param session session handle
    412  * @param connection connection to use
    413  */
    414 static int
    415 not_found_page (const void *cls,
    416 		const char *mime,
    417 		struct Session *session,
    418 		struct MHD_Connection *connection)
    419 {
    420   int ret;
    421   struct MHD_Response *response;
    422 
    423   /* unsupported HTTP method */
    424   response = MHD_create_response_from_buffer (strlen (NOT_FOUND_ERROR),
    425 					      (void *) NOT_FOUND_ERROR,
    426 					      MHD_RESPMEM_PERSISTENT);
    427   ret = MHD_queue_response (connection,
    428 			    MHD_HTTP_NOT_FOUND,
    429 			    response);
    430   MHD_add_response_header (response,
    431 			   MHD_HTTP_HEADER_CONTENT_ENCODING,
    432 			   mime);
    433   MHD_destroy_response (response);
    434   return ret;
    435 }
    436 
    437 
    438 /**
    439  * List of all pages served by this HTTP server.
    440  */
    441 static struct Page pages[] =
    442   {
    443     { "/", "text/html",  &fill_v1_form, MAIN_PAGE },
    444     { "/2", "text/html", &fill_v1_v2_form, SECOND_PAGE },
    445     { "/S", "text/html", &serve_simple_form, SUBMIT_PAGE },
    446     { "/F", "text/html", &serve_simple_form, LAST_PAGE },
    447     { NULL, NULL, &not_found_page, NULL } /* 404 */
    448   };
    449 
    450 
    451 
    452 /**
    453  * Iterator over key-value pairs where the value
    454  * maybe made available in increments and/or may
    455  * not be zero-terminated.  Used for processing
    456  * POST data.
    457  *
    458  * @param cls user-specified closure
    459  * @param kind type of the value
    460  * @param key 0-terminated key for the value
    461  * @param filename name of the uploaded file, NULL if not known
    462  * @param content_type mime-type of the data, NULL if not known
    463  * @param transfer_encoding encoding of the data, NULL if not known
    464  * @param data pointer to size bytes of data at the
    465  *              specified offset
    466  * @param off offset of data in the overall value
    467  * @param size number of bytes in data available
    468  * @return MHD_YES to continue iterating,
    469  *         MHD_NO to abort the iteration
    470  */
    471 static int
    472 post_iterator (void *cls,
    473 	       enum MHD_ValueKind kind,
    474 	       const char *key,
    475 	       const char *filename,
    476 	       const char *content_type,
    477 	       const char *transfer_encoding,
    478 	       const char *data, uint64_t off, size_t size)
    479 {
    480   struct Request *request = cls;
    481   struct Session *session = request->session;
    482 
    483   if (0 == strcmp ("DONE", key))
    484     {
    485       fprintf (stdout,
    486 	       "Session `%s' submitted `%s', `%s'\n",
    487 	       session->sid,
    488 	       session->value_1,
    489 	       session->value_2);
    490       return MHD_YES;
    491     }
    492   if (0 == strcmp ("v1", key))
    493     {
    494       if (size + off > sizeof(session->value_1))
    495 	size = sizeof (session->value_1) - off;
    496       memcpy (&session->value_1[off],
    497 	      data,
    498 	      size);
    499       if (size + off < sizeof (session->value_1))
    500 	session->value_1[size+off] = '\0';
    501       return MHD_YES;
    502     }
    503   if (0 == strcmp ("v2", key))
    504     {
    505       if (size + off > sizeof(session->value_2))
    506 	size = sizeof (session->value_2) - off;
    507       memcpy (&session->value_2[off],
    508 	      data,
    509 	      size);
    510       if (size + off < sizeof (session->value_2))
    511 	session->value_2[size+off] = '\0';
    512       return MHD_YES;
    513     }
    514   fprintf (stderr, "Unsupported form value `%s'\n", key);
    515   return MHD_YES;
    516 }
    517 
    518 
    519 /**
    520  * Main MHD callback for handling requests.
    521  *
    522  *
    523  * @param cls argument given together with the function
    524  *        pointer when the handler was registered with MHD
    525  * @param connection handle to connection which is being processed
    526  * @param url the requested url
    527  * @param method the HTTP method used ("GET", "PUT", etc.)
    528  * @param version the HTTP version string (i.e. "HTTP/1.1")
    529  * @param upload_data the data being uploaded (excluding HEADERS,
    530  *        for a POST that fits into memory and that is encoded
    531  *        with a supported encoding, the POST data will NOT be
    532  *        given in upload_data and is instead available as
    533  *        part of MHD_get_connection_values; very large POST
    534  *        data *will* be made available incrementally in
    535  *        upload_data)
    536  * @param upload_data_size set initially to the size of the
    537  *        upload_data provided; the method must update this
    538  *        value to the number of bytes NOT processed;
    539  * @param ptr pointer that the callback can set to some
    540  *        address and that will be preserved by MHD for future
    541  *        calls for this request; since the access handler may
    542  *        be called many times (i.e., for a PUT/POST operation
    543  *        with plenty of upload data) this allows the application
    544  *        to easily associate some request-specific state.
    545  *        If necessary, this state can be cleaned up in the
    546  *        global "MHD_RequestCompleted" callback (which
    547  *        can be set with the MHD_OPTION_NOTIFY_COMPLETED).
    548  *        Initially, <tt>*con_cls</tt> will be NULL.
    549  * @return MHS_YES if the connection was handled successfully,
    550  *         MHS_NO if the socket must be closed due to a serios
    551  *         error while handling the request
    552  */
    553 static int
    554 create_response (void *cls,
    555 		 struct MHD_Connection *connection,
    556 		 const char *url,
    557 		 const char *method,
    558 		 const char *version,
    559 		 const char *upload_data,
    560 		 size_t *upload_data_size,
    561 		 void **ptr)
    562 {
    563   struct MHD_Response *response;
    564   struct Request *request;
    565   struct Session *session;
    566   int ret;
    567   unsigned int i;
    568 
    569   request = *ptr;
    570   if (NULL == request)
    571     {
    572       request = calloc (1, sizeof (struct Request));
    573       if (NULL == request)
    574 	{
    575 	  fprintf (stderr, "calloc error: %s\n", strerror (errno));
    576 	  return MHD_NO;
    577 	}
    578       *ptr = request;
    579       if (0 == strcmp (method, MHD_HTTP_METHOD_POST))
    580 	{
    581 	  request->pp = MHD_create_post_processor (connection, 1024,
    582 						   &post_iterator, request);
    583 	  if (NULL == request->pp)
    584 	    {
    585 	      fprintf (stderr, "Failed to setup post processor for `%s'\n",
    586 		       url);
    587 	      return MHD_NO; /* internal error */
    588 	    }
    589 	}
    590       return MHD_YES;
    591     }
    592   if (NULL == request->session)
    593     {
    594       request->session = get_session (connection);
    595       if (NULL == request->session)
    596 	{
    597 	  fprintf (stderr, "Failed to setup session for `%s'\n",
    598 		   url);
    599 	  return MHD_NO; /* internal error */
    600 	}
    601     }
    602   session = request->session;
    603   session->start = time (NULL);
    604   if (0 == strcmp (method, MHD_HTTP_METHOD_POST))
    605     {
    606       /* evaluate POST data */
    607       MHD_post_process (request->pp,
    608 			upload_data,
    609 			*upload_data_size);
    610       if (0 != *upload_data_size)
    611 	{
    612 	  *upload_data_size = 0;
    613 	  return MHD_YES;
    614 	}
    615       /* done with POST data, serve response */
    616       MHD_destroy_post_processor (request->pp);
    617       request->pp = NULL;
    618       method = MHD_HTTP_METHOD_GET; /* fake 'GET' */
    619       if (NULL != request->post_url)
    620 	url = request->post_url;
    621     }
    622 
    623   if ( (0 == strcmp (method, MHD_HTTP_METHOD_GET)) ||
    624        (0 == strcmp (method, MHD_HTTP_METHOD_HEAD)) )
    625     {
    626       /* find out which page to serve */
    627       i=0;
    628       while ( (pages[i].url != NULL) &&
    629 	      (0 != strcmp (pages[i].url, url)) )
    630 	i++;
    631       ret = pages[i].handler (pages[i].handler_cls,
    632 			      pages[i].mime,
    633 			      session, connection);
    634       if (ret != MHD_YES)
    635 	fprintf (stderr, "Failed to create page for `%s'\n",
    636 		 url);
    637       return ret;
    638     }
    639   /* unsupported HTTP method */
    640   response = MHD_create_response_from_buffer (strlen (METHOD_ERROR),
    641 					      (void *) METHOD_ERROR,
    642 					      MHD_RESPMEM_PERSISTENT);
    643   ret = MHD_queue_response (connection,
    644 			    MHD_HTTP_METHOD_NOT_ACCEPTABLE,
    645 			    response);
    646   MHD_destroy_response (response);
    647   return ret;
    648 }
    649 
    650 
    651 /**
    652  * Callback called upon completion of a request.
    653  * Decrements session reference counter.
    654  *
    655  * @param cls not used
    656  * @param connection connection that completed
    657  * @param con_cls session handle
    658  * @param toe status code
    659  */
    660 static void
    661 request_completed_callback (void *cls,
    662 			    struct MHD_Connection *connection,
    663 			    void **con_cls,
    664 			    enum MHD_RequestTerminationCode toe)
    665 {
    666   struct Request *request = *con_cls;
    667 
    668   if (NULL == request)
    669     return;
    670   if (NULL != request->session)
    671     request->session->rc--;
    672   if (NULL != request->pp)
    673     MHD_destroy_post_processor (request->pp);
    674   free (request);
    675 }
    676 
    677 
    678 /**
    679  * Clean up handles of sessions that have been idle for
    680  * too long.
    681  */
    682 static void
    683 expire_sessions ()
    684 {
    685   struct Session *pos;
    686   struct Session *prev;
    687   struct Session *next;
    688   time_t now;
    689 
    690   now = time (NULL);
    691   prev = NULL;
    692   pos = sessions;
    693   while (NULL != pos)
    694     {
    695       next = pos->next;
    696       if (now - pos->start > 60 * 60)
    697 	{
    698 	  /* expire sessions after 1h */
    699 	  if (NULL == prev)
    700 	    sessions = pos->next;
    701 	  else
    702 	    prev->next = next;
    703 	  free (pos);
    704 	}
    705       else
    706         prev = pos;
    707       pos = next;
    708     }
    709 }
    710 
    711 
    712 /**
    713  * Call with the port number as the only argument.
    714  * Never terminates (other than by signals, such as CTRL-C).
    715  */
    716 int
    717 main (int argc, char *const *argv)
    718 {
    719   struct MHD_Daemon *d;
    720   struct timeval tv;
    721   struct timeval *tvp;
    722   fd_set rs;
    723   fd_set ws;
    724   fd_set es;
    725   int max;
    726   MHD_UNSIGNED_LONG_LONG mhd_timeout;
    727 
    728   if (argc != 2)
    729     {
    730       printf ("%s PORT\n", argv[0]);
    731       return 1;
    732     }
    733   /* initialize PRNG */
    734   srand ((unsigned int) time (NULL));
    735   d = MHD_start_daemon (MHD_USE_DEBUG,
    736                         atoi (argv[1]),
    737                         NULL, NULL,
    738 			&create_response, NULL,
    739 			MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 15,
    740 			MHD_OPTION_NOTIFY_COMPLETED, &request_completed_callback, NULL,
    741 			MHD_OPTION_END);
    742   if (NULL == d)
    743     return 1;
    744   while (1)
    745     {
    746       expire_sessions ();
    747       max = 0;
    748       FD_ZERO (&rs);
    749       FD_ZERO (&ws);
    750       FD_ZERO (&es);
    751       if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max))
    752 	break; /* fatal internal error */
    753       if (MHD_get_timeout (d, &mhd_timeout) == MHD_YES)
    754 	{
    755 	  tv.tv_sec = mhd_timeout / 1000;
    756 	  tv.tv_usec = (mhd_timeout - (tv.tv_sec * 1000)) * 1000;
    757 	  tvp = &tv;
    758 	}
    759       else
    760 	tvp = NULL;
    761       if (-1 == select (max + 1, &rs, &ws, &es, tvp))
    762 	{
    763 	  if (EINTR != errno)
    764 	    fprintf (stderr,
    765 		     "Aborting due to error during select: %s\n",
    766 		     strerror (errno));
    767 	  break;
    768 	}
    769       MHD_run (d);
    770     }
    771   MHD_stop_daemon (d);
    772   return 0;
    773 }
    774 
    775