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