aboutsummaryrefslogtreecommitdiff
path: root/doc/chapters/largerpost.inc
diff options
context:
space:
mode:
Diffstat (limited to 'doc/chapters/largerpost.inc')
-rw-r--r--doc/chapters/largerpost.inc302
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
2a bug preventing early busy messages from being sent ---
3
4The previous chapter introduced a way to upload data to the server but the developed example program
5has some shortcomings, such as not being able to handle larger chunks of data. In this chapter, we
6are going to discuss a more advanced server program that allows clients to upload a file in order to
7have it stored on the server's filesystem. The server shall also watch and limit the number of
8clients concurrently uploading, responding with a proper busy message if necessary.
9
10
11@heading Prepared answers
12We choose to operate the server with the @code{SELECT_INTERNALLY} method. This makes it easier to
13synchronize the global states at the cost of possible delays for other connections if the processing
14of a request is to slow. A variable that needs to be shared for all connections is the total number
15of clients which are uploading.
16
17@verbatim
18#define MAXCLIENTS 2
19static unsigned char nr_of_uploading_clients = 0;
20@end verbatim
21@noindent
22
23If there are too many clients uploading, we want the server to respond to all requests with a busy
24message
25@verbatim
26const char* busypage = "<html><body>This server is busy, please try again later.</body></html>";
27@end verbatim
28@noindent
29
30Otherwise, the server will send a form that informs the user of the current number of uploading clients,
31and ask her to pick a file on her local filesystem for uploading.
32@verbatim
33const 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
43If the upload has succeeded, the server will respond with a message saying so.
44@verbatim
45const char* completepage = "<html><body>The upload has been completed.</body></html>";
46@end verbatim
47@noindent
48
49We want the server to report internal errors, such as memory shortage or file access problems,
50adequately.
51@verbatim
52const char* servererrorpage = "<html><body>An internal server error has occured.</body></html>";
53const char* fileexistspage = "<html><body>This file already exists.</body></html>";
54@end verbatim
55@noindent
56
57It would be tolerable to send all these responses undifferentiated with an @code{200 HTTP_OK}
58status 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
62int 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
79Note how we ask @emph{MHD} to make its own copy of the message data. The reason behind this will
80become clear later.
81
82
83@heading Connection cycle
84The decision whether the server is busy or not is made right at the beginning of the connection. To
85do that at this stage is especially important for @emph{POST} requests because if no response is
86queued at this point, and @code{MHD_YES} returned, @emph{MHD} will not sent any queued messages until
87a postprocessor has been created and the post iterator was called at least once.
88
89@verbatim
90int 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
103If the server is not busy, the @code{connection_info} structure is initialized as usual, with
104an 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
124For @emph{POST} requests, the postprocessor is created and we register a new uploading client. From
125this point on, there are many possible places for errors to occur that make it necessary to interrupt
126the uploading process. We need a means of having the proper response message ready at all times.
127Therefore, the @code{connection_info} structure is extended to hold the most current response
128message so that whenever a response is sent, the client will get the most informative message. Here,
129the 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
152If the connection handler is called for the second time, @emph{GET} requests will be answered with
153the form. We can keep the buffer under function scope because we have asked @emph{MHD} to make its
154own 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
168The rest of the @code{answer_to_connection} function is very similar to the @code{simplepost.c}
169example, except the more flexible content of the responses. The @emph{POST} data is processed until
170there is none left and the execution falls through to return an error page if the connection
171constituted 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
194Unlike the @code{simplepost.c} example, it is to be expected that post iterator will be called
195several times now. This means that for any given connection, there might be several concurrent of them,
196the 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
199int 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
207Because the following actions depend heavily on correct file processing, which might be error prone,
208we default to reporting internal errors in case anything will go wrong.
209
210@verbatim
211con_info->answerstring = servererrorpage;
212con_info->answercode = MHD_HTTP_INTERNAL_SERVER_ERROR;
213@end verbatim
214@noindent
215
216In the "askpage" form, we told the client to label its post data with the "file" key. Anything else
217would be an error.
218
219@verbatim
220 if (0 != strcmp (key, "file")) return MHD_NO;
221@end verbatim
222@noindent
223
224If the iterator is called for the first time, no file will have been opened yet. The @code{filename}
225string contains the name of the file (without any paths) the user selected on his system. We want to
226take 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
246Occasionally, the iterator function will be called even when there are 0 new bytes to process. The
247server only needs to write data to the file if there is some.
248@verbatim
249if (size > 0)
250 {
251 if (!fwrite (data, size, sizeof(char), con_info->fp)) return MHD_NO;
252 }
253@end verbatim
254@noindent
255
256If this point has been reached, everything worked flawless for this iteration and the response can
257be 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
268The new client was registered when the postprocessor was created. Likewise, we unregister the client
269on destroying the postprocessor when the request is completed.
270@verbatim
271void 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
296This is essentially the whole example @code{largepost.c}.
297
298
299@heading Remarks
300Now that the clients are able to create files on the server, security aspects are becoming even more
301important than before. Aside from proper client authentication, the server should always make sure
302explicitly that no files will be written outside a dedicated upload directory.