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