libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

commit c5969a735ab86f8c461b9188853e7bf4dd42d588
parent 53c25f932f5bd45c26af675b4c89d71eb99a9b8c
Author: Christian Grothoff <christian@grothoff.org>
Date:   Tue, 12 Aug 2008 19:49:42 +0000

tutorial

Diffstat:
MAUTHORS | 1+
Adoc/basicauthentication.inc | 208+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/bibliography.inc | 30++++++++++++++++++++++++++++++
Adoc/examples/basicauthentication.c | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/examples/hellobrowser.c | 34++++++++++++++++++++++++++++++++++
Adoc/examples/logging.c | 39+++++++++++++++++++++++++++++++++++++++
Adoc/examples/responseheaders.c | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/examples/simplepost.c | 157+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/exploringrequests.inc | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/hellobrowser.inc | 201+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/introduction.inc | 19+++++++++++++++++++
Adoc/processingpost.inc | 225+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/responseheaders.inc | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/tutorial.texi | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
14 files changed, 1554 insertions(+), 0 deletions(-)

diff --git a/AUTHORS b/AUTHORS @@ -13,3 +13,4 @@ Sagie Amir Documentation contributions also came from: Marco Maggi <marco.maggi-ipsu@poste.it> +Sebastian Gerhardt <sebgerhardt@gmx.net> diff --git a/doc/basicauthentication.inc b/doc/basicauthentication.inc @@ -0,0 +1,208 @@ +With the small exception of IP address based access control, +requests from all connecting clients where served equally until now. +This chapter discusses a first method of client's authentication and +its limits. + +A very simple approach feasible with the means already discussed would +be to expect the password in the @emph{URI} string before granting access to +the secured areas. The password could be separated from the actual resource identifier +by a certain character, thus the request line might look like +@verbatim +GET /picture.png?mypassword +@end verbatim +@noindent + +In a situation, where the client is customized enough and the connection occurs +through secured lines (e.g., a embedded device directly attached to another via wire), +this can be a reasonable choice. + +But when it is assumed that the user connecting does so with an ordinary Internet browser, +this implementation brings some problems about. For example, the URI including the password +stays in the address field or at least in the history of the browser for anybody near enough to see. +It will also be inconvenient to add the password manually to any new URI when the browser does +not know how to compose this automatically. + +At least the convenience issue can be addressed by employing the simplest built-in password +facilities of HTTP compliant browsers, hence we want to start there. It will however turn out +to have still severe weaknesses in terms of security which need consideration. + +Before we will start implementing @emph{Basic Authentication} as described in @emph{RFC 2617}, +we should finally abandon the bad practice of responding every request the first time our callback +is called for a given connection. This is becoming more important now because the client and +the server will have to talk in a more bi-directional way than before to + +But how can we tell whether the callback has been called before for the particular connection? +Initially, the pointer this parameter references is set by @emph{MHD} in the callback. But it will +also be "remembered" on the next call (for the same connection). +Thus, we will generate no response until the parameter is non-null---implying the callback was +called before at least once. We do not need to share information between different calls of the callback, +so we can set the parameter to any adress that is assured to be not null. The pointer to the +@code{connection} structure will be pointing to a legal adress, so we take this. + +Not even the headers will be looked at on the first iteration. + +@verbatim +int AnswerToConnection(void *cls, struct MHD_Connection *connection, + const char *url, const char *method, const char *version, + const char *upload_data, unsigned int *upload_data_size, void **con_cls) +{ + if (0 != strcmp(method, "GET")) return MHD_NO; + if(*con_cls==NULL) {*con_cls=connection; return MHD_YES;} + + ... + /* else respond accordingly */ + ... +} +@end verbatim +@noindent + +Note how we lop off the connection on the first condition, but return asking for more on +the other one with @code{MHD_YES}. +With the framework improved, we can proceed to implement the actual authentication process. + +@heading Request for authentication + +Let us assume we had only files not intended to be handed out without the correct username/password, +so every "GET" request will be challenged. +@emph{RFC 2617} describes how the server shall ask for authentication by adding a +@emph{WWW-Authenticate} response header with the name of the @emph{realm} protected. + +We let an extra function function do this. +@verbatim +int AskForAuthentication(struct MHD_Connection *connection, const char *realm) +{ + int ret; + struct MHD_Response *response; + char *headervalue; + const char *strbase = "Basic realm="; + + response = MHD_create_response_from_data(0, NULL, MHD_NO, MHD_NO); + if (!response) return MHD_NO; + + headervalue = malloc( strlen(strbase) + strlen(realm) + 1); + if (!headervalue) return MHD_NO; + + strcpy(headervalue, strbase); + strcat(headervalue, realm); + + ret = MHD_add_response_header(response, "WWW-Authenticate", headervalue); + free(headervalue); + if (!ret) {MHD_destroy_response (response); return MHD_NO;} + + ret = MHD_queue_response (connection, MHD_HTTP_UNAUTHORIZED, response); + + MHD_destroy_response (response); + + return ret; +} +@end verbatim +@noindent + +@code{#define} the realm name according to your own taste, e.g. "Maintenance" or "Area51" but +it will need to have extra quotes. + +But the client may send the authentication right away, so it would be wrong to ask for +it without checking the request's header--where the authentication is expected to be found. + +@heading Authentication in detail +Checking @emph{RFC 2617} again, we find that the client will pack the username and password, by +whatever means he might have obtained them, in a line separated by a colon---and then encodes +them to @emph{Base64}. The actual implementation of this encoding are not within the scope of +this tutorial although a working function is included in the complete source file of the example. + +An unencoded word describing the authentication method (here "Basic") will precede the code +and the resulting line is the value of a request header of the type "Authorization". + +This header line thus is of interest to the function checking a connection for a given username/password: +@verbatim +int IsAuthenticated(struct MHD_Connection *connection, + const char *username, const char *password) +{ + const char *headervalue; + ... + + headervalue = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, "Authorization"); + + if(headervalue == NULL) return 0; +@end verbatim +@noindent + +where, firstly, the authentication method will be checked. +@verbatim +const char *strbase = "Basic "; +... +if (strncmp(headervalue, strbase, strlen(strbase))!=0) return 0; +@end verbatim +@noindent + +Of course, we could decode the passed credentials in the next step and compare them directly to +the given strings. But as this would involve string parsing, which is more complicated then string +composing, it is done the other way around---the clear text credentials will be encoded to @emph{Base64} +and then compared against the headerline. The authentication method string will be left out here as +it has been checked already at this point. +@verbatim + char *expected_b64, *expected; + int authenticated; + + ... + strcpy(expected, username); + strcat(expected, ":"); + strcat(expected, password); + + expected_b64 = StringToBase64(expected); + if(expected_b64 == NULL) return 0; + + strcpy(expected, strbase); + + authenticated = (strcmp(headervalue+strlen(strbase), expected_b64) == 0); + + free(expected_b64); + + return authenticated; +} +@end verbatim +@noindent + +These two functions---together with a response function in case of positive authentication doing little +new---allow the rest of the callback function to be rather short. +@verbatim + if (!IsAuthenticated(connection, USER, PASSWORD)) + return AskForAuthentication(connection, REALM); + + return SecretPage(connection); +} +@end verbatim +@noindent + +See the @code{examples} directory for the complete example file. + +@heading Remarks +For a proper server, the conditional statements leading to a return of @code{MHD_NO} should yield a +response with a more precise status code instead of silently closing the connection. For example, +failures of memory allocation are best reported as @emph{internal server error} and unexpected +authentication methods as @emph{400 bad request}. + +@heading Exercises +@itemize @bullet +@item +Make the server respond to wrong credentials (but else correct requests) with the recommended +@emph{401 unauthorized} status code. If the client still does not authenticate correctly within the +same connection, close it and store the client's IP address for a certain time. (It is OK to check for +expiration not until the main thread wakes up again on the next connection.) If the client fails +authenticating three times during this period, add it to another list whose entries the +@code{AcceptPolicyCallback} function denies connection (temporally). + +@item +With the network utility @emph{netcat} connect and log the response of a "GET" request as you +did in the exercise of the first example, this time to a file. Now stop the server and let @emph{netcat} +listen on the same port the server used to listen on and have it fake being the proper server by giving +the file's content as the response (e.g. @code{cat log | nc -l -p 8888}). Pretending to think your were +connecting to the actual server, browse to the eavesdropper and give the correct credentials. + +Copy and paste the encoded string you see in netcat's output to some of the Base64 decode tools available online +and see how both the user's name and password could be completely restored. + +@end itemize + + diff --git a/doc/bibliography.inc b/doc/bibliography.inc @@ -0,0 +1,30 @@ +@itemize @bullet +@heading API reference +@item +The @emph{GNU libmicrohttpd} manual by Christian Grothoff 2008 +@uref{http://gnunet.org/libmicrohttpd/microhttpd.html} + +@heading Requests for comments +All referenced RFCs can be found on the website of @emph{The Internet Engineering Task Force} +@uref{http://www.ietf.org/} + +@item +@emph{RFC 2616}: Fielding, R., Gettys, J., Mogul, J., Frystyk, H., and T. Berners-Lee, +"Hypertext Transfer Protocol -- HTTP/1.1", RFC 2016, January 1997. + +@item +@emph{RFC 2617}: Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., +Luotonen, A., and L. Stewart, "HTTP Authentication: Basic and Digest Access Authentication", RFC 2617, June 1999. + + +@heading Recommended readings +@item +A well--structured @emph{HTML} reference can be found on +@uref{http://www.echoecho.com/html.htm} + +For those readers understanding German or French, there is an excellent document both for learning +@emph{HTML} and for reference, whose English version unfortunately has been discontinued. +@uref{http://de.selfhtml.org/} and @uref{http://fr.selfhtml.org/} + + +@end itemize diff --git a/doc/examples/basicauthentication.c b/doc/examples/basicauthentication.c @@ -0,0 +1,152 @@ +#include <microhttpd.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> + +#define PORT 8888 + +#define REALM "\"Maintenance\"" +#define USER "a legitimate user" +#define PASSWORD "and his password" + + +char* StringToBase64(const char *message); + + +int AskForAuthentication(struct MHD_Connection *connection, const char *realm) +{ + int ret; + struct MHD_Response *response; + char *headervalue; + const char *strbase = "Basic realm="; + + response = MHD_create_response_from_data(0, NULL, MHD_NO, MHD_NO); + if (!response) return MHD_NO; + + headervalue = malloc( strlen(strbase) + strlen(realm) + 1); + if (!headervalue) return MHD_NO; + + strcpy(headervalue, strbase); + strcat(headervalue, realm); + + ret = MHD_add_response_header(response, "WWW-Authenticate", headervalue); + free(headervalue); + if (!ret) {MHD_destroy_response (response); return MHD_NO;} + + ret = MHD_queue_response (connection, MHD_HTTP_UNAUTHORIZED, response); + + MHD_destroy_response (response); + + return ret; +} + +int IsAuthenticated(struct MHD_Connection *connection, const char *username, + const char *password) +{ + const char *headervalue; + char *expected_b64, *expected; + const char *strbase = "Basic "; + int authenticated; + + headervalue = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, "Authorization"); + if(headervalue == NULL) return 0; + if (strncmp(headervalue, strbase, strlen(strbase))!=0) return 0; + + expected = malloc(strlen(username) + 1 + strlen(password) + 1); + if(expected == NULL) return 0; + + strcpy(expected, username); + strcat(expected, ":"); + strcat(expected, password); + + expected_b64 = StringToBase64(expected); + if(expected_b64 == NULL) return 0; + + strcpy(expected, strbase); + + authenticated = (strcmp(headervalue+strlen(strbase), expected_b64) == 0); + + free(expected_b64); + + return authenticated; +} + + +int SecretPage(struct MHD_Connection *connection) +{ + int ret; + struct MHD_Response *response; + const char *page = "<html><body>A secret.</body></html>"; + + response = MHD_create_response_from_data(strlen(page), (void*)page, MHD_NO, MHD_NO); + if (!response) return MHD_NO; + + ret = MHD_queue_response (connection, MHD_HTTP_OK, response); + + MHD_destroy_response (response); + + return ret; +} + + +int AnswerToConnection(void *cls, struct MHD_Connection *connection, + const char *url, const char *method, const char *version, + const char *upload_data, unsigned int *upload_data_size, void **con_cls) +{ + if (0 != strcmp(method, "GET")) return MHD_NO; + if(*con_cls==NULL) {*con_cls=connection; return MHD_YES;} + + if (!IsAuthenticated(connection, USER, PASSWORD)) + return AskForAuthentication(connection, REALM); + + return SecretPage(connection); +} + + +int main () +{ + struct MHD_Daemon *daemon; + + daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, PORT, NULL, NULL, + &AnswerToConnection, NULL, MHD_OPTION_END); + + if (daemon == NULL) return 1; + + getchar(); + + MHD_stop_daemon(daemon); + return 0; +} + + +char* StringToBase64(const char *message) +{ + const char *lookup = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + unsigned long l; + int i; + char *tmp; + size_t length = strlen(message); + + tmp = malloc(length*2); + if (tmp==NULL) return tmp; + tmp[0]=0; + + for(i=0; i<length; i+=3) + { + l = ( ((unsigned long)message[i])<<16 ) | + (((i+1) < length) ? (((unsigned long)message[i+1])<<8 ) : 0 ) | + (((i+2) < length) ? ( (unsigned long)message[i+2] ) : 0 ); + + + strncat(tmp, &lookup[(l>>18) & 0x3F], 1); + strncat(tmp, &lookup[(l>>12) & 0x3F], 1); + + if (i+1 < length) strncat(tmp, &lookup[(l>> 6) & 0x3F], 1); + if (i+2 < length) strncat(tmp, &lookup[(l ) & 0x3F], 1); + } + + if (length%3) strncat(tmp, "===", 3-length%3) ; + + return tmp; +} diff --git a/doc/examples/hellobrowser.c b/doc/examples/hellobrowser.c @@ -0,0 +1,34 @@ +#include <microhttpd.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#define PORT 8888 + +int AnswerToConnection(void *cls, struct MHD_Connection *connection, const char *url, + const char *method, const char *version, const char *upload_data, + unsigned int *upload_data_size, void **con_cls) +{ + const char *page = "<html><body>Hello, browser!</body></html>"; + struct MHD_Response *response; + int ret; + + response = MHD_create_response_from_data (strlen (page), (void*) page, MHD_NO, MHD_NO); + ret = MHD_queue_response (connection, MHD_HTTP_OK, response); + MHD_destroy_response (response); + return ret; +} + +int main () +{ + struct MHD_Daemon *daemon; + + daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, PORT, NULL, NULL, + &AnswerToConnection, NULL, MHD_OPTION_END); + if (daemon == NULL) return 1; + + getchar(); + + MHD_stop_daemon(daemon); + return 0; +} diff --git a/doc/examples/logging.c b/doc/examples/logging.c @@ -0,0 +1,39 @@ +#include <microhttpd.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#define PORT 8888 + + +int PrintOutKey(void *cls, enum MHD_ValueKind kind, const char *key, const char *value) +{ + printf("%s = %s\n", key, value); + return MHD_YES; +} + +int AnswerToConnection(void *cls, struct MHD_Connection *connection, const char *url, + const char *method, const char *version, const char *upload_data, + unsigned int *upload_data_size, void **con_cls) +{ + + printf("New request %s for %s using version %s\n", method, url, version); + + MHD_get_connection_values(connection, MHD_HEADER_KIND, PrintOutKey, NULL); + + return MHD_NO; +} + +int main () +{ + struct MHD_Daemon *daemon; + + daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, PORT, NULL, NULL, + &AnswerToConnection, NULL, MHD_OPTION_END); + if (daemon == NULL) return 1; + + getchar(); + + MHD_stop_daemon(daemon); + return 0; +} diff --git a/doc/examples/responseheaders.c b/doc/examples/responseheaders.c @@ -0,0 +1,94 @@ +#include <microhttpd.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <time.h> + +#define PORT 8888 +#define FILENAME "picture.png" +#define MIMETYPE "image/png" + + +long GetFileSize(const char *filename) +{ + FILE *fp; + + fp = fopen(filename, "rb"); + if (fp) + { + long size; + + if ( (0!=fseek(fp, 0, SEEK_END)) || (-1==(size=ftell(fp))) ) + size = 0; + + fclose(fp); + return size; + } else return 0; +} + + +int AnswerToConnection(void *cls, struct MHD_Connection *connection, const char *url, + const char *method, const char *version, const char *upload_data, + unsigned int *upload_data_size, void **con_cls) +{ + unsigned char *buffer; + struct MHD_Response *response; + long size; + FILE *fp; + int ret=0; + + if (0 != strcmp(method, "GET")) return MHD_NO; + + size = GetFileSize(FILENAME); + if (size != 0) + { + fp = fopen(FILENAME, "rb"); + if (fp) + { + buffer = malloc(size); + if (buffer) + if (size == fread(buffer, 1, size, fp)) ret=1; + } + + fclose(fp); + } + + if (!ret) + { + const char *errorstr = "<html><body>An internal server error has occured!\ + </body></html>"; + + if (buffer) free(buffer); + response = MHD_create_response_from_data(strlen(errorstr), (void*)errorstr, + MHD_NO, MHD_NO); + + ret = MHD_queue_response (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response); + MHD_destroy_response (response); + return MHD_YES; + } + + response = MHD_create_response_from_data(size, (void*)buffer, MHD_YES, MHD_NO); + + MHD_add_response_header(response, "Content-Type", MIMETYPE); + + ret = MHD_queue_response (connection, MHD_HTTP_OK, response); + MHD_destroy_response (response); + return ret; +} + + +int main () +{ + struct MHD_Daemon *daemon; + + daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, PORT, NULL, NULL, + &AnswerToConnection, NULL, MHD_OPTION_END); + + if (daemon == NULL) return 1; + + getchar(); + + MHD_stop_daemon(daemon); + return 0; +} + diff --git a/doc/examples/simplepost.c b/doc/examples/simplepost.c @@ -0,0 +1,157 @@ +#include <microhttpd.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#define PORT 8888 +#define POSTBUFFERSIZE 512 +#define MAXNAMESIZE 20 +#define MAXANSWERSIZE 512 + +#define GET 0 +#define POST 1 + +struct ConnectionInfoStruct +{ + int connectiontype; + char *answerstring; + struct MHD_PostProcessor *postprocessor; +}; + +const char* askpage="<html><body>\ + What's your name, Sir?<br>\ + <form action=\"/namepost\" method=\"post\">\ + <input name=\"name\" type=\"text\"\ + <input type=\"submit\" value=\" Send \"></form>\ + </body></html>"; + +const char* greatingpage="<html><body><h1>Welcome, %s!</center></h1></body></html>"; + +const char* errorpage="<html><body>This doesn't seem to be right.</body></html>"; + + +int SendPage(struct MHD_Connection *connection, const char* page) +{ + int ret; + struct MHD_Response *response; + + + response = MHD_create_response_from_data(strlen(page), (void*)page, MHD_NO, MHD_NO); + if (!response) return MHD_NO; + + ret = MHD_queue_response(connection, MHD_HTTP_OK, response); + + MHD_destroy_response(response); + + return ret; +} + + +int IteratePost(void *coninfo_cls, enum MHD_ValueKind kind, const char *key, + const char *filename, const char *content_type, + const char *transfer_encoding, const char *data, size_t off, size_t size) +{ + struct ConnectionInfoStruct *con_info = (struct ConnectionInfoStruct*)(coninfo_cls); + + + if (0 == strcmp(key, "name")) + { + if ((size>0) && (size<=MAXNAMESIZE)) + { + char *answerstring; + answerstring = malloc(MAXANSWERSIZE); + if (!answerstring) return MHD_NO; + + snprintf(answerstring, MAXANSWERSIZE, greatingpage, data); + con_info->answerstring = answerstring; + } else con_info->answerstring=NULL; + + return MHD_NO; + } + + return MHD_YES; +} + +void RequestCompleted(void *cls, struct MHD_Connection *connection, void **con_cls, + enum MHD_RequestTerminationCode toe) +{ + struct ConnectionInfoStruct *con_info = (struct ConnectionInfoStruct*)(*con_cls); + + + if (NULL == con_info) return; + + if (con_info->connectiontype == POST) + { + MHD_destroy_post_processor(con_info->postprocessor); + if (con_info->answerstring) free(con_info->answerstring); + } + + free(con_info); +} + + +int AnswerToConnection(void *cls, struct MHD_Connection *connection, const char *url, + const char *method, const char *version, const char *upload_data, + unsigned int *upload_data_size, void **con_cls) +{ + if(*con_cls==NULL) + { + struct ConnectionInfoStruct *con_info; + + con_info = malloc(sizeof(struct ConnectionInfoStruct)); + if (NULL == con_info) return MHD_NO; + + if (0 == strcmp(method, "POST")) + { + con_info->postprocessor = MHD_create_post_processor(connection, POSTBUFFERSIZE, + IteratePost, (void*)con_info); + + if (NULL == con_info->postprocessor) + { + free(con_info); + return MHD_NO; + } + + con_info->connectiontype = POST; + } else con_info->connectiontype = GET; + + *con_cls = (void*)con_info; + return MHD_YES; + } + + if (0 == strcmp(method, "GET")) + { + return SendPage(connection, askpage); + } + + if (0 == strcmp(method, "POST")) + { + struct ConnectionInfoStruct *con_info = *con_cls; + + if (*upload_data_size != 0) + { + MHD_post_process(con_info->postprocessor, upload_data, *upload_data_size); + *upload_data_size = 0; + return MHD_YES; + } else return SendPage(connection, con_info->answerstring); + } + + return SendPage(connection, errorpage); +} + +int main () +{ + struct MHD_Daemon *daemon; + + + daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, PORT, NULL, NULL, + &AnswerToConnection, NULL, MHD_OPTION_NOTIFY_COMPLETED, + RequestCompleted, NULL, MHD_OPTION_END); + + if (NULL == daemon) return 1; + + getchar(); + + MHD_stop_daemon(daemon); + return 0; +} diff --git a/doc/exploringrequests.inc b/doc/exploringrequests.inc @@ -0,0 +1,104 @@ +This chapter will deal with the information which the client sends to the +server at every request. We are going to examine the most useful fields of such an request +and print them out in a readable manner. This could be useful for logging facilities. + +The starting point is the @emph{hellobrowser} program with the former response removed. + +This time, we just want to collect information in the callback function, thus we will +just return MHD_NO after we have probed the request. This way, the connection is closed +without much ado by the server. + +@verbatim +int AnswerToConnection(void *cls, struct MHD_Connection *connection, + const char *url, const char *method, const char *version, + const char *upload_data, unsigned int *upload_data_size, void **con_cls) +{ + ... + return MHD_NO; +} +@end verbatim +@noindent +The ellipsis marks the position where the following instructions shall be inserted. + + +We begin with the most obvious information available to the server, the request line. You should +already have noted that a request consists of a command (or "method") and a URI (e.g. a filename). +It also contains a string for the version of the protocol which can be found in @code{version}. +To call it a "new request" is justified because we return only @code{MHD_NO}, thus ensuring the +function will not be called again for this connection. +@verbatim +printf("New request %s for %s using version %s\n", method, url, version); +@end verbatim +@noindent + +The rest of the information is a bit more hidden. Nevertheless, there is lot of it sent from common +Internet browsers. It is stored in "key-name" pairs and we want to list what we find in the header. +As there is no mandatory set of keys a client has to send, each key--name pair is printed out one by +one until there are no more left. We do this by writing a separate function which will be called for +each pair just like the above function is called for each HTTP request. +It can then print out the content of this pair. +@verbatim +int PrintOutKey(void *cls, enum MHD_ValueKind kind, const char *key, + const char *value) +{ + printf("%s = %s\n", key, value); + return MHD_YES; +} +@end verbatim +@noindent + +To start the iteration process that calls our new function for every key, the line +@verbatim +MHD_get_connection_values(connection, MHD_HEADER_KIND, PrintOutKey, NULL); +@end verbatim +@noindent +needs to be inserted in the connection callback function too. The second parameter tells the function +that we are only interested in keys from the general HTTP header of the request. Our iterating +function @code{PrintOutKey} does not rely on any additional information to fulfill its duties +so the last parameter can be NULL. + +All in all, this constitutes the complete @code{logger.c} program for this chapter which can be +found in the @code{examples} section. + +Connecting with any modern Internet browser should yield a handful of keys. You should try to +interpret them with the aid of @emph{RFC 2616}. +Especially worth mentioning is the host key which is often used to serve several different websites +hosted under one single IP address but reachable by different domain names. + +@heading Conclusion +The introduced capabilities to itemize the content of a simple GET request---especially the +URI---should already allow the server to satisfy clients' requests for small specific resources +(e.g. files) or even induce alteration of how the server operates. However, the latter is not +recommended as the GET method (including its header data) is by convention considered a "SAFE" +operation, which should not change the server's state in a significant way, but temporally actions +like searching for a passed string is fine. + +Of course, no transmission can occur while the return value is still set to @code{MHD_NO} in the +callback function. + +@heading Exercises +@itemize @bullet +@item +By parsing the @code{url} string and delivering responses accordingly, implement a small server for +"virtual" files. When asked for @code{/index.htm@{l@}}, let the response consist of a HTML page +containing a link to @code{/another.html} page which is also to be created "on the fly" in case of +being requested. If neither of these two pages are requested, @code{MHD_HTTP_NOT_FOUND} shall be +returned accompanied by an informative message. + +@item +A very interesting information has still been ignored by our logger---the client's IP address. +Implement a callback function +@verbatim +int OnClientConnect(void *cls, + const struct sockaddr *addr,socklen_t addrlen) +@end verbatim +@noindent +that prints out the IP address in an appropriate format. You might want to use the posix function +@code{inet_ntoa} but bear in mind that @code{addr} is actually just a structure containing other +substructures and is @emph{not} the variable this function expects. +Make sure to return @code{MHD_YES} so that the library knows the client is allowed to connect +(and to request). If one wanted to limit access basing on IP addresses, this would be the place +to do it. The address of your function will then be passed as the third parameter of the +@code{MHD_start_daemon} call. + +@end itemize diff --git a/doc/hellobrowser.inc b/doc/hellobrowser.inc @@ -0,0 +1,201 @@ +The most basic task for a HTTP server is to deliver a static text message to any client connecting to it. +Given that this is also very easy to implement, it is an excellent problem to start with. + +For now, the particular filename the client asks for shall have no effect on the message that will +be returned. In addition, the server shall end the connection after the message has been sent so that +the client will know there is nothing more to expect. + +The C program @code{hellobrowser.c}, which is to be found in the examples section, does just that. +If you are very eager, you can compile and start it right away but it is advisable to type the +lines in by yourself as they will be discussed and explained in detail. + +After the unexciting includes and the definition of the port which our server should listen on +@verbatim +#include <microhttpd.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +@end verbatim +@noindent +the desired behaviour of our server when HTTP request arrive have to be implemented. We already have +agreed that it should not care about the particular details of the request, such as who is requesting +what. The server will respond merely with the same small HTML page to every request. + +The function we are going to write now will be called by @emph{GNU libmicrohttpd} every time an +appropriate request comes in. While the name of this callback function is arbitrary, its parameter +list has to follow a certain layout. So please, ignore the lot of parameters for now, they will be +explained at the point they are needed. We have to use only one of them, +@code{struct MHD_Connection *connection}, for the minimalistic functionality we want to archive at the moment. + +This parameter is set by the @emph{libmicrohttpd} daemon and holds the necessary information to +relate the call with a certain connection. Keep in mind that a server might have to satisfy hundreds +of concurrent connections and we have to make sure that the correct data is sent to the destined +client. Therefore, this variable is a means to refer to a particular connection if we ask the +daemon to sent the reply. + +Talking about the reply, it is defined as a string right after the function header +@verbatim +int AnswerToConnection(void *cls, struct MHD_Connection *connection, + const char *url, const char *method, const char *version, + const char *upload_data, unsigned int *upload_data_size, void **con_cls) +{ + const char *page = "<html><body>Hello, browser!</body></html>"; +@end verbatim +@noindent +HTTP is a rather strict protocol and the client would certainly consider it "inappropriate" if we +just sent the answer string "as is". Instead, it has to be wrapped in certain layers, called headers, +of additional information. Luckily, most of the work in this area is done by the library for us---we +just have to ask. Our reply string packed in the necessary layers will be called a "response". +To obtain such a response we hand our data (the reply--string) and its size over to the +@code{MHD_create_response_from_data} function. The last two parameters basically tell @emph{MHD} +that we do not want it to dispose the message data for us when it has been sent and there also needs +no internal copy to be done because the @emph{constant} string won't change anyway. + +@verbatim + struct MHD_Response *response; + int ret; + + response = MHD_create_response_from_data(strlen(page), + (void*)page, MHD_NO, MHD_NO); +@end verbatim +@noindent +Now that the the response has been laced up, it is ready for delivery and can be queued for sending. +This is done by passing it to another @emph{GNU libmicrohttpd} function. As all our work was done in +the scope of one function, the recipient is without doubt the one associated with the +local variable @code{connection} and consequently this variable is given to the queue function. +Every HTTP response is accompanied by a status code, here "OK", so that the client knows +this response is the intended result of his request and not due to some error or malfunction. + +Finally, the packet is destroyed and the return value from the queue returned, +already being set at this point to either MHD_YES or MHD_NO in case of success or failure. + +@verbatim + ret = MHD_queue_response (connection, MHD_HTTP_OK, response); + MHD_destroy_response (response); + return ret; +} +@end verbatim +@noindent +With the primary task of our server implemented, we can start the actual server daemon which will listen +on @code{PORT} for connections. This is done in the main function. +@verbatim +int main () +{ + struct MHD_Daemon *d; + + d = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, PORT, NULL, NULL, + &AnswerToConnection, NULL, MHD_OPTION_END); + if (d == NULL) return 1; +@end verbatim +@noindent +The first parameter is one of three possible modes of operation. Here we want the daemon to run in +a separate thread and to manage all incoming connections in the same thread. This means that while +producing the response for one connection, the other connections will be put on hold. In this +chapter, where the reply is already known and therefore the request is served quickly, this poses no problem. + +We will allow all clients to connect regardless of their name or location, therefore we do not check +them on connection and set the forth and fifth parameter to NULL. + +Parameter six is the address of the function we want to be called whenever a new connection has been +established. Our @code{AnswerToConnection} knows best what the client wants and needs no additional +information (which could be passed via the next parameter) so the next parameter is NULL. Likewise, +we do not need to pass extra options to the daemon so we just write the MHD_OPTION_END as the last parameter. + +As the server daemon runs in the background in its own thread, the execution flow in our main +function will contine right after the call. Because of this, we must delay the execution flow in the +main thread or else the program will terminate prematurely. We let it pause in a processing-time +friendly manner by waiting for the enter key to be pressed. In the end, we stop the daemon so it can +do its cleanup tasks. +@verbatim + getchar(); + + MHD_stop_daemon(d); + return 0; +} +@end verbatim +@noindent +The first example is now complete. + +Compile it with +@verbatim +cc hellobrowser.c -o hellobrowser -I$PATH_TO_LIBMHD_INCLUDES + -L$PATH_TO_LIBMHD_INCLUDES -static -lmicrohttpd -pthread +@end verbatim +with the two paths set accordingly and run it. + +Now open your favorite Internet browser and go to the address @code{localhost:8888}, provided that +is the port you chose. If everything works as expected, the browser will present the message of the +static HTML page it got from our minimal server. + +@heading Remarks +To keep this first example as small as possible, some drastic shortcuts were taken and are to be +discussed now. + +Firstly, there is no distinction made between the kinds of requests a client could send. We implied +that the client sends a GET request, that means, that he actually asked for some data. Even when +it is not intended to accept POST requests, a good server should at least recognize that this +request does not constitute a legal request and answer with an error code. This can be easily +implemented by checking if the parameter @code{method} equals the string "GET" and returning a +@code{MHD_NO} if not so. + +Secondly, the above practice of queuing a response upon the first call of the callback function +brings with it some limitations. This is because the content of the message body will not be +received if a response is queued in the first iteration. Furthermore, the connection will be closed +right after the response has been transferred then. + +Both of these issues you will find addressed in the official @code{minimal_example.c} residing in +the @code{src/examples} directory of the @emph{GNU libmicrohttpd} package. The source code of this +program should look very familiar to you by now and easy to understand. + +For our example, the @code{must_copy} and @code{must_free} parameter at the response construction +function could be set to @code{MHD_NO}. In the usual case, responses cannot be sent immediately +after being queued. For example, there might be other data on the system that needs to be sent with +a higher priority. Nevertheless, the queue function will return successfully---raising the problem +that the data we have pointed to may be invalid by the time it is about being sent. This is not an +issue here because we can expect the @code{page} string, which is a constant @emph{string literal} +here, to be static. That means it will be present and unchanged for as long as the program runs. +For dynamic data, one could choose to either have @emph{MHD} free the memory @code{page} points +to itself when it is not longer needed or, alternatively, have the library to make and manage +its own copy of it. + +@heading Exercises +@itemize @bullet +@item +While the server is running, use a program like telnet or netcat to connect to it. Try to form a +valid HTTP1.1 request yourself like +@verbatim +GET /dontcare HTTP1.1 +Host: itsme +<enter> +@end verbatim +@noindent +and see what the server returns to you. + + +@item +Also, try other requests, like POST, and see how our server does not mind and why. +How far in malforming a request can you go before the builtin functionality of @emph{MHD} intervenes +and an altered response is sent? Make sure you read about the status codes in the @emph{RFC}. + + +@item +Add the option @code{MHD_USE_PEDANTIC_CHECKS} to the start function of the daemon in @code{main}. +Mind the special format of the parameter list here which is described in the manual. How indulgent +is the server now to your input? + + +@item +Let the main function take a string as the first command line argument and pass @code{argv[1]} to +the @code{MHD_start_daemon} function as the sixth parameter. The address of this string will be +passed to the callback function via the @code{cls} variable. Decorate the text given at the command +line when the server is started with proper HTML tags and send it as the response instead of the +former static string. + + +@item +@emph{Demanding:} Write a separate function returning a string containing some useful information, +for example, the time. Pass the function's address as the sixth parameter and evaluate this function +on every request anew in @code{AnswerToConnection}. Remember to free the memory of the string +every time after satisfying the request. + +@end itemize diff --git a/doc/introduction.inc b/doc/introduction.inc @@ -0,0 +1,19 @@ +This tutorial is aimed at developers who want to learn how they can add HTTP serving +capabilities to their applications with the @emph{GNU libmicrohttpd} library, +abbreviated @emph{MHD}, and who do not know how to start. It tries to help these +developers to implement common basic HTTP serving tasks by discussing executable +sample programs implementing different features. + +The text is supposed to be a supplement to the API reference manual of +@emph{GNU libmicrohttpd} and for that reason does not explain many of the parameters. +Therefore, the reader should always consult the manual to find the exact meaning +of the functions used in the tutorial. In the same sense, the tutorial seeks to +encourage the use of the @emph{RFCs}, which document the conventions the Internet +is built upon. + +@emph{GNU libmicrohttpd} is assumed to be already installed and it has been +written with respect to version @value{VERSION}. As the library is still in its +beta stages, later versions may show different behaviour. At the time being, +this tutorial has only been tested on @emph{GNU/Linux} machines even though +efforts were made not to rely on anything that would prevent the samples from being +built on similar systems. diff --git a/doc/processingpost.inc b/doc/processingpost.inc @@ -0,0 +1,225 @@ +The previous chapters already have demonstrated a variety of possibilities to send information +to the HTTP server, but it is not recommended that the @emph{GET} method is used to alter the way +the server operates. To induce changes on the server, the @emph{POST} method is preferred over +and is much more powerful than @emph{GET} and will be introduced in this chapter. + +We are going to write an application that asks for the visitor's name and, after the user has posted it, +composes an individual response text. Even though it was not mandatory to use the @emph{post} method here, +as there is no permanent change caused by the post, it is an illustrative example on how to share data +between different functions for the same connection. Furthermore, the reader should be able to extend +it easily. + +@heading GET request +When the first @emph{GET} request arrives, the server shall respond with a HTML page containing an +edit field for the name. + +@verbatim +const char* askpage="<html><body>\ + What's your name, Sir?<br>\ + <form action=\"/namepost\" method=\"post\">\ + <input name=\"name\" type=\"text\"\ + <input type=\"submit\" value=\" Send \"></form>\ + </body></html>"; +@end verbatim +@noindent + +The @code{action} entry is the @emph{URI} to be called by the browser when posting, and the +@code{name} will be used later to be sure it is the editbox's content that has been posted. + +We also prepare the answer page, where the name is to be filled in later, and an error page +as the response for anything but proper @emph{GET} and @emph{POST} requests: + +@verbatim +const char* greatingpage="<html><body><h1>Welcome, %s!</center></h1></body></html>"; + +const char* errorpage="<html><body>This doesn't seem to be right.</body></html>"; +@end verbatim +@noindent + +Whenever we need to send a page, we use an extra function +@code{int SendPage(struct MHD_Connection *connection, const char* page)} +for this, which does not contain anything new and whose implementation is therefore left out here. + + +@heading POST request +Posted data can be of arbitrary and considerable size; for example, if a user uploads a big +image to the server. Similar to the case of the header fields, there may also be different streams +of posted data, such as one containing the text of an editbox and another the state of a button. +Likewise, we will have to register an iterator function that is going to be called maybe several times +not only if there are different POSTs but also if one POST has only been received partly yet and +needs processing before another chunk can be received. + +Such an iterator function is called by a @emph{postprocessor}, which must be created upon arriving +of the post request. We want the iterator function to read the first post data which is tagged +@code{name} and to create an individual greeting string based on the template and the name. +But in order to pass this string to other functions and still be able to differentiate different +connections, we must first define a structure to share the information, holding the most import entries. + +@verbatim +struct ConnectionInfoStruct +{ + int connectiontype; + char *answerstring; + struct MHD_PostProcessor *postprocessor; +}; +@end verbatim +@noindent + +With these information available to the iterator function, it is able to fulfill its task. +Once it has composed the greeting string, it returns @code{MHD_NO} to inform the post processor +that it does not need to be called again. Note that this function does not handle processing +of data for the same @code{key}. If we were to expect that the name will be posted in several +chunks, we had to expand the namestring dynamically as additional parts of it with the same @code{key} +came in. But in this example, the name is assumed to fit entirely inside one single packet. + +@verbatim +int IteratePost(void *coninfo_cls, enum MHD_ValueKind kind, const char *key, + const char *filename, const char *content_type, + const char *transfer_encoding, const char *data, size_t off, size_t size) +{ + struct ConnectionInfoStruct *con_info = (struct ConnectionInfoStruct*)(coninfo_cls); + + + if (0 == strcmp(key, "name")) + { + if ((size>0) && (size<=MAXNAMESIZE)) + { + char *answerstring; + answerstring = malloc(MAXANSWERSIZE); + if (!answerstring) return MHD_NO; + + snprintf(answerstring, MAXANSWERSIZE, greatingpage, data); + con_info->answerstring = answerstring; + } else con_info->answerstring=NULL; + + return MHD_NO; + } + + return MHD_YES; +} +@end verbatim +@noindent + +Once a connection has been established, it can be terminated for many reasons. As these +reasons include unexpected events, we have to register another function that cleans up any resources +that might have been allocated for that connection by us, namely the post processor and the greetings +string. This cleanup function must take into account that it will also be called for finished +requests other than @emph{POST} requests. + +@verbatim +void RequestCompleted(void *cls, struct MHD_Connection *connection, void **con_cls, + enum MHD_RequestTerminationCode toe) +{ + struct ConnectionInfoStruct *con_info = (struct ConnectionInfoStruct*)(*con_cls); + + + if (NULL == con_info) return; + + if (con_info->connectiontype == POST) + { + MHD_destroy_post_processor(con_info->postprocessor); + if (con_info->answerstring) free(con_info->answerstring); + } + + free(con_info); +} +@end verbatim +@noindent + +@emph{GNU libmicrohttpd} is informed that it shall call the above function when the daemon is started +in the main function. + +@verbatim +... +daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, PORT, NULL, NULL, + &AnswerToConnection, NULL, MHD_OPTION_NOTIFY_COMPLETED, + RequestCompleted, NULL, MHD_OPTION_END); +... +@end verbatim +@noindent + +@heading Request handling +With all other functions prepared, we can now discuss the actual request handling. + +On the first iteration for a new request, we start by allocating a new instance of a +@code{ConnectionInfoStruct} structure, which will store all necessary information for later +iterations and other functions. + +@verbatim +int AnswerToConnection(void *cls, struct MHD_Connection *connection, const char *url, + const char *method, const char *version, const char *upload_data, + unsigned int *upload_data_size, void **con_cls) +{ + if(*con_cls==NULL) + { + struct ConnectionInfoStruct *con_info; + + con_info = malloc(sizeof(struct ConnectionInfoStruct)); + if (NULL == con_info) return MHD_NO; +@end verbatim +@noindent + +If the new request is a @emph{POST}, the postprocessor must be created now. In addition, the type +of the request is stored for convenience. +@verbatim + if (0 == strcmp(method, "POST")) + { + con_info->postprocessor = MHD_create_post_processor(connection, POSTBUFFERSIZE, + IteratePost, (void*)con_info); + + if (NULL == con_info->postprocessor) + { + free(con_info); + return MHD_NO; + } + + con_info->connectiontype = POST; + } else con_info->connectiontype = GET; +@end verbatim +@noindent + +The address of our structure will both serve as the indicator for successive iterations and to remember +the particular details about the connection. +@verbatim + *con_cls = (void*)con_info; + return MHD_YES; + } +@end verbatim +@noindent + +The rest of the function will not be executed on the first iteration. A @emph{GET} request is easily +satisfied by sending the question form. +@verbatim + if (0 == strcmp(method, "GET")) + { + return SendPage(connection, askpage); + } +@end verbatim +@noindent + +In case of @emph{POST}, we invoke the post processor for as long as data keeps incoming, setting +@code{*upload_data_size} to zero in order to indicate that we have processed---or at least have +considered---all of it. +@verbatim + if (0 == strcmp(method, "POST")) + { + struct ConnectionInfoStruct *con_info = *con_cls; + + if (*upload_data_size != 0) + { + MHD_post_process(con_info->postprocessor, upload_data, *upload_data_size); + *upload_data_size = 0; + return MHD_YES; + } else return SendPage(connection, con_info->answerstring); + } +@end verbatim +@noindent + +If they are neither @emph{GET} nor @emph{POST} requests, the error page is returned finally. +@verbatim + return SendPage(connection, errorpage); +} +@end verbatim +@noindent + +These were the important parts of the program @code{simplepost.c}. diff --git a/doc/responseheaders.inc b/doc/responseheaders.inc @@ -0,0 +1,171 @@ +Now that we are able to inspect the incoming request in great detail, +this chapter discusses the means to enrich the outgoing responses likewise. + +As you have learned in the @emph{Hello, Browser} chapter, some obligatory +header fields are added and set automatically for simple responses by the library +itself but if more advanced features are desired, additional fields have to be created. +One of the possible fields is the content type field and an example will be developed around it. +This will lead to an application capable of correctly serving different types of files. + + +When we responded with HTML page packed in the static string previously, the client had no choice +but guessing about how to handle the response, because the server hadn't told him. +What if we had sent a picture or a sound file? Would the message have been understood +or merely been displayed as an endless stream of random characters in the browser? +This is what the mime content types are for. The header of the response is extended +by certain information about how the data is to be interpreted. + +To introduce the concept, a picture of the format @emph{PNG} will be sent to the client +and labeled accordingly with @code{image/png}. +Once again, we can base the new example on the @code{hellobrowser} program. + +@verbatim +#define FILENAME "picture.png" +#define MIMETYPE "image/png" + +int AnswerToConnection(void *cls, struct MHD_Connection *connection, + const char *url, const char *method, const char *version, + const char *upload_data, unsigned int *upload_data_size, void **con_cls) +{ + struct MHD_Response *response; + int ret=0; +@end verbatim +@noindent + +We want the program to load the graphics file into memory: +@verbatim +long size; + FILE *fp; + int ret=0; + + if (0 != strcmp(method, "GET")) return MHD_NO; + + size = GetFileSize(FILENAME); + if (size != 0) + { + fp = fopen(FILENAME, "rb"); + if (fp) + { + buffer = malloc(size); + if (buffer) + if (size == fread(buffer, 1, size, fp)) ret=1; + } + + fclose(fp); + } +@end verbatim +@noindent + +The @code{GetFileSize} function, which returns a size of zero if the file could not be opened or +found, is left out on this page for tidiness. + +When dealing with files and allocating memory, there is a lot that could go wrong on the +sider side and if so, the client should be informed with @code{MHD_HTTP_INTERNAL_SERVER_ERROR}. + +@verbatim + if (!ret) + { + const char *errorstr = "<html><body>An internal server error\ + has occured!</body></html>"; + + if (buffer) free(buffer); + response = MHD_create_response_from_data(strlen(errorstr), + (void*)errorstr, MHD_NO, MHD_NO); + + ret = MHD_queue_response (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, response); + + return MHD_YES; + } +@end verbatim +@noindent + +Note that we nevertheless have to create an response object even for sending a simple error code. +Otherwise, the connection would just be closed without comment, leaving the client curious about +what has happened. + +But in the case of success a response will be constructed that contains the buffer filled with the +file's content. + +@verbatim +response = MHD_create_response_from_data(size, (void*)buffer, MHD_YES, MHD_NO); +@end verbatim +@noindent + +Contrary to the above case where a static string will be sent, this time we have to +keep track of the dynamically allocated buffer. As discussed in the @ref{Hello browser example}, +the buffer cannot be safely freed as soon as the function call returns. Instead, we ask the function +to keep charge of freeing the buffer itself when it is not longer needed. Thus, no further @code{free} +command is invoked by us. + +Up to this point, there was little new. The actual novelty is that we enhance the header with the +meta data about the content. Aware of the field's name we want to add, it is as easy as that: +@verbatim +MHD_add_response_header(response, "Content-Type", MIMETYPE); +@end verbatim +@noindent +We do not have to append a colon expected by the protocol hehind the first +field---@emph{GNU libhttpdmicro} will take care of this. + +The function finishes with the well-known lines +@verbatim + ret = MHD_queue_response (connection, MHD_HTTP_OK, response); + MHD_destroy_response (response); + return ret; +} +@end verbatim +@noindent + +The complete program @code{responseheaders.c} is in the @code{examples} section as usual. +Find a @emph{PNG} file you like and save it to the directory the example is run from under the name +@code{picture.png}. You should find the image displayed on your browser if everything worked well. + +@heading Remarks +The include file of the @emph{MHD} library comes with the header types mentioned in @emph{RFC 2616} +already defined as macros. Thus, we could have written @code{MHD_HTTP_HEADER_CONTENT_TYPE} instead +of @code{"Content-Type"} as well. However, one is not limited to these standard headers and could +add custom response headers without violated the protocol. Whether and how the client would react +to these custom header is up to the receiver. Likewise, the client is allowed to send custom request +headers to the server as well, opening up yet more possibilities how client and server could +communicate with each other. + +The method of creating the response from one big chunk of data is only feasible for smaller files. +A public file server satisfying many request at the same time would be choking under these high +demands on memory very soon. Serving responses in smaller parts would be more adequate here and +will be a topic of a future chapter. + +@heading Exercises +@itemize @bullet + +@item +Remember that the original program was written under a few assumptions---a small, static response +being one of them. In order to simulate a very large or hard to reach file that cannot be provided +instantly, postpone the queuing in the callback with the @code{sleep} function for 30 seconds +@emph{if} the file @code{/big.png} is requested (but deliver the same as above). A request for +@code{/picture.png} should provide just the same but without any artificial delays. + +Now start two instances of your browser (or even use two machines) and see how the second client +is put on hold while the first waits for his request on the slow file to be fulfilled. + +Finally, change the sourcecode to use @code{MHD_USE_THREAD_PER_CONNECTION} when the daemon is +started and try again. + + +@item +Did you succeed in implementing the clock exercise yet? This time, let the server save the +program's start time @code{t} and implement a response simulating a countdown that reaches 0 at +@code{t+60}. Returning a message saying on which point the countdown is, the response should +ultimately be to reply "Done" if the program has been running long enough, + +A non official, but widely understood, response header line is @code{Refresh: DELAY; url=URL} with +the uppercase words substituted to tell the client it should request the given resource after +the given delay again. Improve your program in that the browser (any modern browser should work) +automatically reconnects and asks for the status again every 5 seconds or so. The URL would have +to be composed so that it begins with "http://", followed by the @emph{URI} the server is reachable +from the client's point of view. + +Maybe you want also to visualize the countdown as a status bar by creating a +@code{<table>} consisting of one row and @code{n} columns whose fields contain small images of either +a red or a green light. + +@end itemize diff --git a/doc/tutorial.texi b/doc/tutorial.texi @@ -0,0 +1,119 @@ +\input texinfo @c -*-texinfo-*- +@finalout +@setfilename libmicrohttpdtutorial +@settitle A tutorial for GNU libmicrohttpd +@afourpaper + +@set VERSION 0.3.1 beta + +@titlepage +@title A Tutorial for GNU libmicrohttpd +@subtitle written for version @value{VERSION} +@author Sebastian Gerhardt (@email{sebgerhardt@@gmx.net}) +@page +@vskip 0pt plus 1filll +@end titlepage + +@verbatim +Copyright (c) 2008 Sebastian Gerhardt. +Permission is granted to copy, distribute and/or modify this document +under the terms of the GNU Free Documentation License, Version 1.2 +or any later version published by the Free Software Foundation; +with no Invariant Sections, no Front-Cover Texts, and no Back-Cover +Texts. A copy of the license is included in the section entitled "GNU +Free Documentation License". +@end verbatim + +@contents + +@ifnottex +@node Top +@top Top +@end ifnottex + +@menu +* Introduction:: +* Hello browser example:: +* Exploring requests:: +* Response headers:: +* A basic authentication:: +* Processing post data:: +* Bibliography:: +* License text:: +* Example programs:: +@end menu + +@node Introduction +@chapter Introduction +@include introduction.inc + +@node Hello browser example +@chapter Hello browser example +@include hellobrowser.inc + +@node Exploring requests +@chapter Exploring requests +@include exploringrequests.inc + +@node Response headers +@chapter Response headers +@include responseheaders.inc + +@node A basic authentication +@chapter A basic authentication +@include basicauthentication.inc + +@node Processing post data +@chapter Processing post data +@include processingpost.inc + + +@node Bibliography +@appendix Bibliography +@include bibliography.inc + +@node License text +@appendix GNU Free Documentation License +@include fdl-1.2.texi + +@node Example programs +@appendix Example programs +@menu +* hellobrowser.c:: +* logging.c:: +* responseheaders.c:: +* basicauthentication.c:: +* simplepost.c:: +@end menu + +@node hellobrowser.c +@section hellobrowser.c +@smalldisplay +@verbatiminclude examples/hellobrowser.c +@end smalldisplay + +@node logging.c +@section logging.c +@smalldisplay +@verbatiminclude examples/logging.c +@end smalldisplay + +@node responseheaders.c +@section responseheaders.c +@smalldisplay +@verbatiminclude examples/responseheaders.c +@end smalldisplay + +@node basicauthentication.c +@section basicauthentication.c +@smalldisplay +@verbatiminclude examples/basicauthentication.c +@end smalldisplay + +@node simplepost.c +@section simplepost.c +@smalldisplay +@verbatiminclude examples/simplepost.c +@end smalldisplay + +@bye