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