diff options
Diffstat (limited to 'doc/chapters/largerpost.inc')
-rw-r--r-- | doc/chapters/largerpost.inc | 302 |
1 files changed, 302 insertions, 0 deletions
diff --git a/doc/chapters/largerpost.inc b/doc/chapters/largerpost.inc new file mode 100644 index 00000000..dd3f9d6f --- /dev/null +++ b/doc/chapters/largerpost.inc | |||
@@ -0,0 +1,302 @@ | |||
1 | --- NOTE: This does not work flawlessly with the beta release because there is | ||
2 | a bug preventing early busy messages from being sent --- | ||
3 | |||
4 | The previous chapter introduced a way to upload data to the server but the developed example program | ||
5 | has some shortcomings, such as not being able to handle larger chunks of data. In this chapter, we | ||
6 | are going to discuss a more advanced server program that allows clients to upload a file in order to | ||
7 | have it stored on the server's filesystem. The server shall also watch and limit the number of | ||
8 | clients concurrently uploading, responding with a proper busy message if necessary. | ||
9 | |||
10 | |||
11 | @heading Prepared answers | ||
12 | We choose to operate the server with the @code{SELECT_INTERNALLY} method. This makes it easier to | ||
13 | synchronize the global states at the cost of possible delays for other connections if the processing | ||
14 | of a request is to slow. A variable that needs to be shared for all connections is the total number | ||
15 | of clients which are uploading. | ||
16 | |||
17 | @verbatim | ||
18 | #define MAXCLIENTS 2 | ||
19 | static unsigned char nr_of_uploading_clients = 0; | ||
20 | @end verbatim | ||
21 | @noindent | ||
22 | |||
23 | If there are too many clients uploading, we want the server to respond to all requests with a busy | ||
24 | message | ||
25 | @verbatim | ||
26 | const char* busypage = "<html><body>This server is busy, please try again later.</body></html>"; | ||
27 | @end verbatim | ||
28 | @noindent | ||
29 | |||
30 | Otherwise, the server will send a form that informs the user of the current number of uploading clients, | ||
31 | and ask her to pick a file on her local filesystem for uploading. | ||
32 | @verbatim | ||
33 | const char* askpage = "<html><body>\n\ | ||
34 | Upload a file, please!<br>\n\ | ||
35 | There are %d clients uploading at the moment.<br>\n\ | ||
36 | <form action=\"/filepost\" method=\"post\" enctype=\"multipart/form-data\">\n\ | ||
37 | <input name=\"file\" type=\"file\">\n\ | ||
38 | <input type=\"submit\" value=\" Send \"></form>\n\ | ||
39 | </body></html>"; | ||
40 | @end verbatim | ||
41 | @noindent | ||
42 | |||
43 | If the upload has succeeded, the server will respond with a message saying so. | ||
44 | @verbatim | ||
45 | const char* completepage = "<html><body>The upload has been completed.</body></html>"; | ||
46 | @end verbatim | ||
47 | @noindent | ||
48 | |||
49 | We want the server to report internal errors, such as memory shortage or file access problems, | ||
50 | adequately. | ||
51 | @verbatim | ||
52 | const char* servererrorpage = "<html><body>An internal server error has occured.</body></html>"; | ||
53 | const char* fileexistspage = "<html><body>This file already exists.</body></html>"; | ||
54 | @end verbatim | ||
55 | @noindent | ||
56 | |||
57 | It would be tolerable to send all these responses undifferentiated with an @code{200 HTTP_OK} | ||
58 | status code, but in order to improve the @code{HTTP} conformance of our server a bit, we extend the | ||
59 | @code{send_page} function so that it allows to chose individual status codes. | ||
60 | |||
61 | @verbatim | ||
62 | int send_page (struct MHD_Connection *connection, const char* page, int status_code) | ||
63 | { | ||
64 | int ret; | ||
65 | struct MHD_Response *response; | ||
66 | |||
67 | |||
68 | response = MHD_create_response_from_data (strlen (page), (void*) page, MHD_NO, MHD_YES); | ||
69 | if (!response) return MHD_NO; | ||
70 | |||
71 | ret = MHD_queue_response (connection, status_code, response); | ||
72 | MHD_destroy_response (response); | ||
73 | |||
74 | return ret; | ||
75 | } | ||
76 | @end verbatim | ||
77 | @noindent | ||
78 | |||
79 | Note how we ask @emph{MHD} to make its own copy of the message data. The reason behind this will | ||
80 | become clear later. | ||
81 | |||
82 | |||
83 | @heading Connection cycle | ||
84 | The decision whether the server is busy or not is made right at the beginning of the connection. To | ||
85 | do that at this stage is especially important for @emph{POST} requests because if no response is | ||
86 | queued at this point, and @code{MHD_YES} returned, @emph{MHD} will not sent any queued messages until | ||
87 | a postprocessor has been created and the post iterator was called at least once. | ||
88 | |||
89 | @verbatim | ||
90 | int answer_to_connection (void *cls, struct MHD_Connection *connection, const char *url, | ||
91 | const char *method, const char *version, const char *upload_data, | ||
92 | unsigned int *upload_data_size, void **con_cls) | ||
93 | { | ||
94 | if (NULL == *con_cls) | ||
95 | { | ||
96 | struct connection_info_struct *con_info; | ||
97 | |||
98 | if (nr_of_uploading_clients >= MAXCLIENTS) | ||
99 | return send_page(connection, busypage, MHD_HTTP_SERVICE_UNAVAILABLE); | ||
100 | @end verbatim | ||
101 | @noindent | ||
102 | |||
103 | If the server is not busy, the @code{connection_info} structure is initialized as usual, with | ||
104 | an additional filepointer for each connection. | ||
105 | |||
106 | @verbatim | ||
107 | con_info = malloc (sizeof (struct connection_info_struct)); | ||
108 | if (NULL == con_info) return MHD_NO; | ||
109 | con_info->fp = 0; | ||
110 | |||
111 | if (0 == strcmp (method, "POST")) | ||
112 | { | ||
113 | ... | ||
114 | } | ||
115 | else con_info->connectiontype = GET; | ||
116 | |||
117 | *con_cls = (void*) con_info; | ||
118 | |||
119 | return MHD_YES; | ||
120 | } | ||
121 | @end verbatim | ||
122 | @noindent | ||
123 | |||
124 | For @emph{POST} requests, the postprocessor is created and we register a new uploading client. From | ||
125 | this point on, there are many possible places for errors to occur that make it necessary to interrupt | ||
126 | the uploading process. We need a means of having the proper response message ready at all times. | ||
127 | Therefore, the @code{connection_info} structure is extended to hold the most current response | ||
128 | message so that whenever a response is sent, the client will get the most informative message. Here, | ||
129 | the structure is initialized to "no error". | ||
130 | @verbatim | ||
131 | if (0 == strcmp (method, "POST")) | ||
132 | { | ||
133 | con_info->postprocessor = MHD_create_post_processor (connection, POSTBUFFERSIZE, | ||
134 | iterate_post, (void*) con_info); | ||
135 | |||
136 | if (NULL == con_info->postprocessor) | ||
137 | { | ||
138 | free (con_info); | ||
139 | return MHD_NO; | ||
140 | } | ||
141 | |||
142 | nr_of_uploading_clients++; | ||
143 | |||
144 | con_info->connectiontype = POST; | ||
145 | con_info->answercode = MHD_HTTP_OK; | ||
146 | con_info->answerstring = completepage; | ||
147 | } | ||
148 | else con_info->connectiontype = GET; | ||
149 | @end verbatim | ||
150 | @noindent | ||
151 | |||
152 | If the connection handler is called for the second time, @emph{GET} requests will be answered with | ||
153 | the form. We can keep the buffer under function scope because we have asked @emph{MHD} to make its | ||
154 | own copy of it for as long as it needs one. | ||
155 | @verbatim | ||
156 | if (0 == strcmp (method, "GET")) | ||
157 | { | ||
158 | int ret; | ||
159 | char buffer[1024] = {0}; | ||
160 | |||
161 | sprintf (buffer, askpage, nr_of_uploading_clients); | ||
162 | return send_page (connection, buffer, MHD_HTTP_OK); | ||
163 | } | ||
164 | @end verbatim | ||
165 | @noindent | ||
166 | |||
167 | |||
168 | The rest of the @code{answer_to_connection} function is very similar to the @code{simplepost.c} | ||
169 | example, except the more flexible content of the responses. The @emph{POST} data is processed until | ||
170 | there is none left and the execution falls through to return an error page if the connection | ||
171 | constituted no expected request method. | ||
172 | @verbatim | ||
173 | if (0 == strcmp (method, "POST")) | ||
174 | { | ||
175 | struct connection_info_struct *con_info = *con_cls; | ||
176 | |||
177 | if (0 != *upload_data_size) | ||
178 | { | ||
179 | MHD_post_process(con_info->postprocessor, upload_data, *upload_data_size); | ||
180 | *upload_data_size = 0; | ||
181 | |||
182 | return MHD_YES; | ||
183 | } | ||
184 | else return send_page (connection, con_info->answerstring, con_info->answercode); | ||
185 | } | ||
186 | |||
187 | return send_page(connection, errorpage, MHD_HTTP_BAD_REQUEST); | ||
188 | } | ||
189 | @end verbatim | ||
190 | @noindent | ||
191 | |||
192 | |||
193 | @heading Storing to data | ||
194 | Unlike the @code{simplepost.c} example, it is to be expected that post iterator will be called | ||
195 | several times now. This means that for any given connection, there might be several concurrent of them, | ||
196 | the posted data has to be written to the correct file. That is why we store a file handle in every | ||
197 | @code{connection_info}, so that the it is preserved between successive iterations. | ||
198 | @verbatim | ||
199 | int iterate_post (void *coninfo_cls, enum MHD_ValueKind kind, const char *key, | ||
200 | const char *filename, const char *content_type, | ||
201 | const char *transfer_encoding, const char *data, size_t off, size_t size) | ||
202 | { | ||
203 | struct connection_info_struct *con_info = (struct connection_info_struct*) coninfo_cls; | ||
204 | @end verbatim | ||
205 | @noindent | ||
206 | |||
207 | Because the following actions depend heavily on correct file processing, which might be error prone, | ||
208 | we default to reporting internal errors in case anything will go wrong. | ||
209 | |||
210 | @verbatim | ||
211 | con_info->answerstring = servererrorpage; | ||
212 | con_info->answercode = MHD_HTTP_INTERNAL_SERVER_ERROR; | ||
213 | @end verbatim | ||
214 | @noindent | ||
215 | |||
216 | In the "askpage" form, we told the client to label its post data with the "file" key. Anything else | ||
217 | would be an error. | ||
218 | |||
219 | @verbatim | ||
220 | if (0 != strcmp (key, "file")) return MHD_NO; | ||
221 | @end verbatim | ||
222 | @noindent | ||
223 | |||
224 | If the iterator is called for the first time, no file will have been opened yet. The @code{filename} | ||
225 | string contains the name of the file (without any paths) the user selected on his system. We want to | ||
226 | take this as the name the file will be stored on the server and make sure no file of that name exists | ||
227 | (or is being uploaded) before we create one. | ||
228 | @verbatim | ||
229 | if (!con_info->fp) | ||
230 | { | ||
231 | if (NULL != (fp = fopen (filename, "r")) ) | ||
232 | { | ||
233 | fclose (fp); | ||
234 | con_info->answerstring = fileexistspage; | ||
235 | con_info->answercode = MHD_HTTP_FORBIDDEN; | ||
236 | return MHD_NO; | ||
237 | } | ||
238 | |||
239 | con_info->fp = fopen (filename, "ab"); | ||
240 | if (!con_info->fp) return MHD_NO; | ||
241 | } | ||
242 | @end verbatim | ||
243 | @noindent | ||
244 | |||
245 | |||
246 | Occasionally, the iterator function will be called even when there are 0 new bytes to process. The | ||
247 | server only needs to write data to the file if there is some. | ||
248 | @verbatim | ||
249 | if (size > 0) | ||
250 | { | ||
251 | if (!fwrite (data, size, sizeof(char), con_info->fp)) return MHD_NO; | ||
252 | } | ||
253 | @end verbatim | ||
254 | @noindent | ||
255 | |||
256 | If this point has been reached, everything worked flawless for this iteration and the response can | ||
257 | be set to success again. If the upload has finished, this iterator function will not be called again. | ||
258 | @verbatim | ||
259 | con_info->answerstring = completepage; | ||
260 | con_info->answercode = MHD_HTTP_OK; | ||
261 | |||
262 | return MHD_YES; | ||
263 | } | ||
264 | @end verbatim | ||
265 | @noindent | ||
266 | |||
267 | |||
268 | The new client was registered when the postprocessor was created. Likewise, we unregister the client | ||
269 | on destroying the postprocessor when the request is completed. | ||
270 | @verbatim | ||
271 | void request_completed (void *cls, struct MHD_Connection *connection, void **con_cls, | ||
272 | enum MHD_RequestTerminationCode toe) | ||
273 | { | ||
274 | struct connection_info_struct *con_info = (struct connection_info_struct*) *con_cls; | ||
275 | |||
276 | if (NULL == con_info) return; | ||
277 | |||
278 | if (con_info->connectiontype == POST) | ||
279 | { | ||
280 | if (NULL != con_info->postprocessor) | ||
281 | { | ||
282 | MHD_destroy_post_processor (con_info->postprocessor); | ||
283 | nr_of_uploading_clients--; | ||
284 | } | ||
285 | |||
286 | if (con_info->fp) fclose (con_info->fp); | ||
287 | } | ||
288 | |||
289 | free (con_info); | ||
290 | *con_cls = NULL; | ||
291 | } | ||
292 | @end verbatim | ||
293 | @noindent | ||
294 | |||
295 | |||
296 | This is essentially the whole example @code{largepost.c}. | ||
297 | |||
298 | |||
299 | @heading Remarks | ||
300 | Now that the clients are able to create files on the server, security aspects are becoming even more | ||
301 | important than before. Aside from proper client authentication, the server should always make sure | ||
302 | explicitly that no files will be written outside a dedicated upload directory. | ||