--- libmicrohttpd-0.9.16/doc/chapters/sessions.inc.orig 1970-01-01 01:00:00.000000000 +0100 +++ libmicrohttpd-0.9.16/doc/chapters/sessions.inc 2011-11-06 06:41:40.346690352 +0100 @@ -0,0 +1,71 @@ +This chapter discusses how one should manage sessions, that is, share state between multiple +HTTP requests from the same user. We use a simple example where the user submits multiple +forms and the server is supposed to accumulate state from all of these forms. Naturally, as +this is a network protocol, our session mechanism must support having many users with +many concurrent sessions at the same time. + +In order to track users, we use a simple session cookie. A session cookie expires when the +user closes the browser. Changing from session cookies to persistent cookies only requires +adding an expiration time to the cookie. The server creates a fresh session cookie whenever +a request without a cookie is received, or if the supplied session cookie is not known to +the server. + +@heading Looking up the cookie + +Since MHD parses the HTTP cookie header for us, looking up an existing cookie +is straightforward: + +@verbatim +FIXME. +@end verbatim + +Here, FIXME is the name we chose for our session cookie. + + +@heading Setting the cookie header + +MHD requires the user to provide the full cookie format string in order to set +cookies. In order to generate a unique cookie, our example creates a random +64-character text string to be used as the value of the cookie: + +@verbatim +FIXME. +@end verbatim + +Given this cookie value, we can then set the cookie header in our HTTP response +as follows: + +@verbatim +FIXME. +@end verbatim + + +@heading Remark: Session expiration + +It is of course possible that clients stop their interaction with the +server at any time. In order to avoid using too much storage, the +server must thus discard inactive sessions at some point. Our example +implements this by discarding inactive sessions after a certain amount +of time. Alternatively, the implementation may limit the total number +of active sessions. Which bounds are used for idle sessions or the +total number of sessions obviously depends largely on the type of +the application and available server resources. + +@heading Example code + +A sample application implementing a website with multiple +forms (which are dynamically created using values from previous +POST requests from the same session) is available +as the example @code{sessions.c}. + +Note that the example uses a simple, $O(n)$ linked list traversal to +look up sessions and to expire old sessions. Using a hash table and a +heap would be more appropriate if a large number of concurrent +sessions is expected. + +@heading Remarks + +Naturally, it is quite conceivable to store session data in a database +instead of in memory. Still, having mechanisms to expire data +associated with long-time idle sessions (where the business process +has still not finished) is likely a good idea. --- libmicrohttpd-0.9.16/doc/examples/sessions.c.orig 1970-01-01 01:00:00.000000000 +0100 +++ libmicrohttpd-0.9.16/doc/examples/sessions.c 2011-11-06 06:41:35.430023520 +0100 @@ -0,0 +1,748 @@ +/* + This file is part of libmicrohttpd + (C) 2011 Christian Grothoff (and other contributing authors) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +/** + * @file post_example.c + * @brief example for processing POST requests using libmicrohttpd + * @author Christian Grothoff + */ + +/* needed for asprintf */ +#define _GNU_SOURCE + + +#include +#include +#include +#include +#include +#include + +/** + * Invalid method page. + */ +#define METHOD_ERROR "Illegal requestGo away." + +/** + * Invalid URL page. + */ +#define NOT_FOUND_ERROR "Not foundGo away." + +/** + * Front page. (/) + */ +#define MAIN_PAGE "Welcome
What is your name? " + +/** + * Second page. (/2) + */ +#define SECOND_PAGE "Tell me moreprevious %s, what is your job? " + +/** + * Second page (/S) + */ +#define SUBMIT_PAGE "Ready to submit?previous " + +/** + * Last page. + */ +#define LAST_PAGE "Thank youThank you." + +/** + * Name of our cookie. + */ +#define COOKIE_NAME "session" + + +/** + * State we keep for each user/session/browser. + */ +struct Session +{ + /** + * We keep all sessions in a linked list. + */ + struct Session *next; + + /** + * Unique ID for this session. + */ + char sid[33]; + + /** + * Reference counter giving the number of connections + * currently using this session. + */ + unsigned int rc; + + /** + * Time when this session was last active. + */ + time_t start; + + /** + * String submitted via form. + */ + char value_1[64]; + + /** + * Another value submitted via form. + */ + char value_2[64]; + +}; + + +/** + * Data kept per request. + */ +struct Request +{ + + /** + * Associated session. + */ + struct Session *session; + + /** + * Post processor handling form data (IF this is + * a POST request). + */ + struct MHD_PostProcessor *pp; + + /** + * URL to serve in response to this POST (if this request + * was a 'POST') + */ + const char *post_url; + +}; + + +/** + * Linked list of all active sessions. Yes, O(n) but a + * hash table would be overkill for a simple example... + */ +static struct Session *sessions; + + + + +/** + * Return the session handle for this connection, or + * create one if this is a new user. + */ +static struct Session * +get_session (struct MHD_Connection *connection) +{ + struct Session *ret; + const char *cookie; + + cookie = MHD_lookup_connection_value (connection, + MHD_COOKIE_KIND, + COOKIE_NAME); + if (cookie != NULL) + { + /* find existing session */ + ret = sessions; + while (NULL != ret) + { + if (0 == strcmp (cookie, ret->sid)) + break; + ret = ret->next; + } + if (NULL != ret) + { + ret->rc++; + return ret; + } + } + /* create fresh session */ + ret = calloc (1, sizeof (struct Session)); + if (NULL == ret) + { + fprintf (stderr, "calloc error: %s\n", strerror (errno)); + return NULL; + } + /* not a super-secure way to generate a random session ID, + but should do for a simple example... */ + snprintf (ret->sid, + sizeof (ret->sid), + "%X%X%X%X", + (unsigned int) random (), + (unsigned int) random (), + (unsigned int) random (), + (unsigned int) random ()); + ret->rc++; + ret->start = time (NULL); + ret->next = sessions; + sessions = ret; + return ret; +} + + +/** + * Type of handler that generates a reply. + * + * @param cls content for the page (handler-specific) + * @param mime mime type to use + * @param session session information + * @param connection connection to process + * @param MHD_YES on success, MHD_NO on failure + */ +typedef int (*PageHandler)(const void *cls, + const char *mime, + struct Session *session, + struct MHD_Connection *connection); + + +/** + * Entry we generate for each page served. + */ +struct Page +{ + /** + * Acceptable URL for this page. + */ + const char *url; + + /** + * Mime type to set for the page. + */ + const char *mime; + + /** + * Handler to call to generate response. + */ + PageHandler handler; + + /** + * Extra argument to handler. + */ + const void *handler_cls; +}; + + +/** + * Add header to response to set a session cookie. + * + * @param session session to use + * @param response response to modify + */ +static void +add_session_cookie (struct Session *session, + struct MHD_Response *response) +{ + char cstr[256]; + snprintf (cstr, + sizeof (cstr), + "%s=%s", + COOKIE_NAME, + session->sid); + if (MHD_NO == + MHD_add_response_header (response, + MHD_HTTP_HEADER_SET_COOKIE, + cstr)) + { + fprintf (stderr, + "Failed to set session cookie header!\n"); + } +} + + +/** + * Handler that returns a simple static HTTP page that + * is passed in via 'cls'. + * + * @param cls a 'const char *' with the HTML webpage to return + * @param mime mime type to use + * @param session session handle + * @param connection connection to use + */ +static int +serve_simple_form (const void *cls, + const char *mime, + struct Session *session, + struct MHD_Connection *connection) +{ + int ret; + const char *form = cls; + struct MHD_Response *response; + + /* return static form */ + response = MHD_create_response_from_buffer (strlen (form), + (void *) form, + MHD_RESPMEM_PERSISTENT); + add_session_cookie (session, response); + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_ENCODING, + mime); + ret = MHD_queue_response (connection, + MHD_HTTP_OK, + response); + MHD_destroy_response (response); + return ret; +} + + +/** + * Handler that adds the 'v1' value to the given HTML code. + * + * @param cls a 'const char *' with the HTML webpage to return + * @param mime mime type to use + * @param session session handle + * @param connection connection to use + */ +static int +fill_v1_form (const void *cls, + const char *mime, + struct Session *session, + struct MHD_Connection *connection) +{ + int ret; + const char *form = cls; + char *reply; + struct MHD_Response *response; + + if (-1 == asprintf (&reply, + form, + session->value_1)) + { + /* oops */ + return MHD_NO; + } + /* return static form */ + response = MHD_create_response_from_buffer (strlen (reply), + (void *) reply, + MHD_RESPMEM_MUST_FREE); + add_session_cookie (session, response); + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_ENCODING, + mime); + ret = MHD_queue_response (connection, + MHD_HTTP_OK, + response); + MHD_destroy_response (response); + return ret; +} + + +/** + * Handler that adds the 'v1' and 'v2' values to the given HTML code. + * + * @param cls a 'const char *' with the HTML webpage to return + * @param mime mime type to use + * @param session session handle + * @param connection connection to use + */ +static int +fill_v1_v2_form (const void *cls, + const char *mime, + struct Session *session, + struct MHD_Connection *connection) +{ + int ret; + const char *form = cls; + char *reply; + struct MHD_Response *response; + + if (-1 == asprintf (&reply, + form, + session->value_1, + session->value_2)) + { + /* oops */ + return MHD_NO; + } + /* return static form */ + response = MHD_create_response_from_buffer (strlen (reply), + (void *) reply, + MHD_RESPMEM_MUST_FREE); + add_session_cookie (session, response); + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_ENCODING, + mime); + ret = MHD_queue_response (connection, + MHD_HTTP_OK, + response); + MHD_destroy_response (response); + return ret; +} + + +/** + * Handler used to generate a 404 reply. + * + * @param cls a 'const char *' with the HTML webpage to return + * @param mime mime type to use + * @param session session handle + * @param connection connection to use + */ +static int +not_found_page (const void *cls, + const char *mime, + struct Session *session, + struct MHD_Connection *connection) +{ + int ret; + struct MHD_Response *response; + + /* unsupported HTTP method */ + response = MHD_create_response_from_buffer (strlen (NOT_FOUND_ERROR), + (void *) NOT_FOUND_ERROR, + MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response (connection, + MHD_HTTP_NOT_FOUND, + response); + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_ENCODING, + mime); + MHD_destroy_response (response); + return ret; +} + + +/** + * List of all pages served by this HTTP server. + */ +static struct Page pages[] = + { + { "/", "text/html", &fill_v1_form, MAIN_PAGE }, + { "/2", "text/html", &fill_v1_v2_form, SECOND_PAGE }, + { "/S", "text/html", &serve_simple_form, SUBMIT_PAGE }, + { "/F", "text/html", &serve_simple_form, LAST_PAGE }, + { NULL, NULL, ¬_found_page, NULL } /* 404 */ + }; + + + +/** + * Iterator over key-value pairs where the value + * maybe made available in increments and/or may + * not be zero-terminated. Used for processing + * POST data. + * + * @param cls user-specified closure + * @param kind type of the value + * @param key 0-terminated key for the value + * @param filename name of the uploaded file, NULL if not known + * @param content_type mime-type of the data, NULL if not known + * @param transfer_encoding encoding of the data, NULL if not known + * @param data pointer to size bytes of data at the + * specified offset + * @param off offset of data in the overall value + * @param size number of bytes in data available + * @return MHD_YES to continue iterating, + * MHD_NO to abort the iteration + */ +static int +post_iterator (void *cls, + enum MHD_ValueKind kind, + const char *key, + const char *filename, + const char *content_type, + const char *transfer_encoding, + const char *data, uint64_t off, size_t size) +{ + struct Request *request = cls; + struct Session *session = request->session; + + if (0 == strcmp ("DONE", key)) + { + fprintf (stdout, + "Session `%s' submitted `%s', `%s'\n", + session->sid, + session->value_1, + session->value_2); + return MHD_YES; + } + if (0 == strcmp ("v1", key)) + { + if (size + off > sizeof(session->value_1)) + size = sizeof (session->value_1) - off; + memcpy (&session->value_1[off], + data, + size); + if (size + off < sizeof (session->value_1)) + session->value_1[size+off] = '\0'; + return MHD_YES; + } + if (0 == strcmp ("v2", key)) + { + if (size + off > sizeof(session->value_2)) + size = sizeof (session->value_2) - off; + memcpy (&session->value_2[off], + data, + size); + if (size + off < sizeof (session->value_2)) + session->value_2[size+off] = '\0'; + return MHD_YES; + } + fprintf (stderr, "Unsupported form value `%s'\n", key); + return MHD_YES; +} + + +/** + * Main MHD callback for handling requests. + * + * + * @param cls argument given together with the function + * pointer when the handler was registered with MHD + * @param url the requested url + * @param method the HTTP method used ("GET", "PUT", etc.) + * @param version the HTTP version string (i.e. "HTTP/1.1") + * @param upload_data the data being uploaded (excluding HEADERS, + * for a POST that fits into memory and that is encoded + * with a supported encoding, the POST data will NOT be + * given in upload_data and is instead available as + * part of MHD_get_connection_values; very large POST + * data *will* be made available incrementally in + * upload_data) + * @param upload_data_size set initially to the size of the + * upload_data provided; the method must update this + * value to the number of bytes NOT processed; + * @param con_cls pointer that the callback can set to some + * address and that will be preserved by MHD for future + * calls for this request; since the access handler may + * be called many times (i.e., for a PUT/POST operation + * with plenty of upload data) this allows the application + * to easily associate some request-specific state. + * If necessary, this state can be cleaned up in the + * global "MHD_RequestCompleted" callback (which + * can be set with the MHD_OPTION_NOTIFY_COMPLETED). + * Initially, *con_cls will be NULL. + * @return MHS_YES if the connection was handled successfully, + * MHS_NO if the socket must be closed due to a serios + * error while handling the request + */ +static int +create_response (void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, + size_t *upload_data_size, + void **ptr) +{ + struct MHD_Response *response; + struct Request *request; + struct Session *session; + int ret; + unsigned int i; + + request = *ptr; + if (NULL == request) + { + request = calloc (1, sizeof (struct Request)); + if (NULL == request) + { + fprintf (stderr, "calloc error: %s\n", strerror (errno)); + return MHD_NO; + } + *ptr = request; + if (0 == strcmp (method, MHD_HTTP_METHOD_POST)) + { + request->pp = MHD_create_post_processor (connection, 1024, + &post_iterator, request); + if (NULL == request->pp) + { + fprintf (stderr, "Failed to setup post processor for `%s'\n", + url); + return MHD_NO; /* internal error */ + } + } + return MHD_YES; + } + if (NULL == request->session) + { + request->session = get_session (connection); + if (NULL == request->session) + { + fprintf (stderr, "Failed to setup session for `%s'\n", + url); + return MHD_NO; /* internal error */ + } + } + session = request->session; + session->start = time (NULL); + if (0 == strcmp (method, MHD_HTTP_METHOD_POST)) + { + /* evaluate POST data */ + MHD_post_process (request->pp, + upload_data, + *upload_data_size); + if (0 != *upload_data_size) + { + *upload_data_size = 0; + return MHD_YES; + } + /* done with POST data, serve response */ + MHD_destroy_post_processor (request->pp); + request->pp = NULL; + method = MHD_HTTP_METHOD_GET; /* fake 'GET' */ + if (NULL != request->post_url) + url = request->post_url; + } + + if ( (0 == strcmp (method, MHD_HTTP_METHOD_GET)) || + (0 == strcmp (method, MHD_HTTP_METHOD_HEAD)) ) + { + /* find out which page to serve */ + i=0; + while ( (pages[i].url != NULL) && + (0 != strcmp (pages[i].url, url)) ) + i++; + ret = pages[i].handler (pages[i].handler_cls, + pages[i].mime, + session, connection); + if (ret != MHD_YES) + fprintf (stderr, "Failed to create page for `%s'\n", + url); + return ret; + } + /* unsupported HTTP method */ + response = MHD_create_response_from_buffer (strlen (METHOD_ERROR), + (void *) METHOD_ERROR, + MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response (connection, + MHD_HTTP_METHOD_NOT_ACCEPTABLE, + response); + MHD_destroy_response (response); + return ret; +} + + +/** + * Callback called upon completion of a request. + * Decrements session reference counter. + * + * @param cls not used + * @param connection connection that completed + * @param con_cls session handle + * @param toe status code + */ +static void +request_completed_callback (void *cls, + struct MHD_Connection *connection, + void **con_cls, + enum MHD_RequestTerminationCode toe) +{ + struct Request *request = *con_cls; + + if (NULL == request) + return; + if (NULL != request->session) + request->session->rc--; + if (NULL != request->pp) + MHD_destroy_post_processor (request->pp); + free (request); +} + + +/** + * Clean up handles of sessions that have been idle for + * too long. + */ +static void +expire_sessions () +{ + struct Session *pos; + struct Session *prev; + struct Session *next; + time_t now; + + now = time (NULL); + prev = NULL; + pos = sessions; + while (NULL != pos) + { + next = pos->next; + if (now - pos->start > 60 * 60) + { + /* expire sessions after 1h */ + if (NULL == prev) + sessions = pos->next; + else + prev->next = next; + free (pos); + } + else + prev = pos; + pos = next; + } +} + + +/** + * Call with the port number as the only argument. + * Never terminates (other than by signals, such as CTRL-C). + */ +int +main (int argc, char *const *argv) +{ + struct MHD_Daemon *d; + struct timeval tv; + struct timeval *tvp; + fd_set rs; + fd_set ws; + fd_set es; + int max; + unsigned MHD_LONG_LONG mhd_timeout; + + if (argc != 2) + { + printf ("%s PORT\n", argv[0]); + return 1; + } + /* initialize PRNG */ + srandom ((unsigned int) time (NULL)); + d = MHD_start_daemon (MHD_USE_DEBUG, + atoi (argv[1]), + NULL, NULL, + &create_response, NULL, + MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 15, + MHD_OPTION_NOTIFY_COMPLETED, &request_completed_callback, NULL, + MHD_OPTION_END); + if (NULL == d) + return 1; + while (1) + { + expire_sessions (); + max = 0; + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max)) + break; /* fatal internal error */ + if (MHD_get_timeout (d, &mhd_timeout) == MHD_YES) + { + tv.tv_sec = mhd_timeout / 1000; + tv.tv_usec = (mhd_timeout - (tv.tv_sec * 1000)) * 1000; + tvp = &tv; + } + else + tvp = NULL; + select (max + 1, &rs, &ws, &es, tvp); + MHD_run (d); + } + MHD_stop_daemon (d); + return 0; +} +