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