largerpost.inc (11170B)
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{SELECT_INTERNALLY} 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 internal errors, such as memory shortage or file access problems, 49 adequately. 50 @verbatim 51 const char* servererrorpage 52 = "<html><body>An internal server error has occurred.</body></html>"; 53 const char* fileexistspage 54 = "<html><body>This file already exists.</body></html>"; 55 @end verbatim 56 @noindent 57 58 It would be tolerable to send all these responses undifferentiated with a @code{200 HTTP_OK} 59 status code but in order to improve the @code{HTTP} conformance of our server a bit, we extend the 60 @code{send_page} function so that it accepts individual status codes. 61 62 @verbatim 63 static enum MHD_Result 64 send_page (struct MHD_Connection *connection, 65 const char* page, int status_code) 66 { 67 enum MHD_Result ret; 68 struct MHD_Response *response; 69 70 response = MHD_create_response_from_buffer (strlen (page), (void*) page, 71 MHD_RESPMEM_MUST_COPY); 72 if (!response) return MHD_NO; 73 74 ret = MHD_queue_response (connection, status_code, response); 75 MHD_destroy_response (response); 76 77 return ret; 78 } 79 @end verbatim 80 @noindent 81 82 Note how we ask @emph{MHD} to make its own copy of the message data. The reason behind this will 83 become clear later. 84 85 86 @heading Connection cycle 87 The decision whether the server is busy or not is made right at the beginning of the connection. To 88 do that at this stage is especially important for @emph{POST} requests because if no response is 89 queued at this point, and @code{MHD_YES} returned, @emph{MHD} will not sent any queued messages until 90 a postprocessor has been created and the post iterator is called at least once. 91 92 @verbatim 93 static enum MHD_Result 94 answer_to_connection (void *cls, struct MHD_Connection *connection, 95 const char *url, 96 const char *method, const char *version, 97 const char *upload_data, 98 size_t *upload_data_size, void **req_cls) 99 { 100 if (NULL == *req_cls) 101 { 102 struct connection_info_struct *con_info; 103 104 if (nr_of_uploading_clients >= MAXCLIENTS) 105 return send_page(connection, busypage, MHD_HTTP_SERVICE_UNAVAILABLE); 106 @end verbatim 107 @noindent 108 109 If the server is not busy, the @code{connection_info} structure is initialized as usual, with 110 the addition of a filepointer for each connection. 111 112 @verbatim 113 con_info = malloc (sizeof (struct connection_info_struct)); 114 if (NULL == con_info) return MHD_NO; 115 con_info->fp = 0; 116 117 if (0 == strcmp (method, "POST")) 118 { 119 ... 120 } 121 else con_info->connectiontype = GET; 122 123 *req_cls = (void*) con_info; 124 125 return MHD_YES; 126 } 127 @end verbatim 128 @noindent 129 130 For @emph{POST} requests, the postprocessor is created and we register a new uploading client. From 131 this point on, there are many possible places for errors to occur that make it necessary to interrupt 132 the uploading process. We need a means of having the proper response message ready at all times. 133 Therefore, the @code{connection_info} structure is extended to hold the most current response 134 message so that whenever a response is sent, the client will get the most informative message. Here, 135 the structure is initialized to "no error". 136 @verbatim 137 if (0 == strcmp (method, "POST")) 138 { 139 con_info->postprocessor 140 = MHD_create_post_processor (connection, POSTBUFFERSIZE, 141 iterate_post, (void*) con_info); 142 143 if (NULL == con_info->postprocessor) 144 { 145 free (con_info); 146 return MHD_NO; 147 } 148 149 nr_of_uploading_clients++; 150 151 con_info->connectiontype = POST; 152 con_info->answercode = MHD_HTTP_OK; 153 con_info->answerstring = completepage; 154 } 155 else con_info->connectiontype = GET; 156 @end verbatim 157 @noindent 158 159 If the connection handler is called for the second time, @emph{GET} requests will be answered with 160 the @emph{form}. We can keep the buffer under function scope, because we asked @emph{MHD} to make its 161 own copy of it for as long as it is needed. 162 @verbatim 163 if (0 == strcmp (method, "GET")) 164 { 165 char buffer[1024]; 166 167 sprintf (buffer, askpage, nr_of_uploading_clients); 168 return send_page (connection, buffer, MHD_HTTP_OK); 169 } 170 @end verbatim 171 @noindent 172 173 174 The rest of the @code{answer_to_connection} function is very similar to the @code{simplepost.c} 175 example, except the more flexible content of the responses. The @emph{POST} data is processed until 176 there is none left and the execution falls through to return an error page if the connection 177 constituted no expected request method. 178 @verbatim 179 if (0 == strcmp (method, "POST")) 180 { 181 struct connection_info_struct *con_info = *req_cls; 182 183 if (0 != *upload_data_size) 184 { 185 MHD_post_process (con_info->postprocessor, 186 upload_data, *upload_data_size); 187 *upload_data_size = 0; 188 189 return MHD_YES; 190 } 191 else 192 return send_page (connection, con_info->answerstring, 193 con_info->answercode); 194 } 195 196 return send_page(connection, errorpage, MHD_HTTP_BAD_REQUEST); 197 } 198 @end verbatim 199 @noindent 200 201 202 @heading Storing to data 203 Unlike the @code{simplepost.c} example, here it is to be expected that post iterator will be called 204 several times now. This means that for any given connection (there might be several concurrent of them) 205 the posted data has to be written to the correct file. That is why we store a file handle in every 206 @code{connection_info}, so that the it is preserved between successive iterations. 207 @verbatim 208 static enum MHD_Result 209 iterate_post (void *coninfo_cls, enum MHD_ValueKind kind, 210 const char *key, 211 const char *filename, const char *content_type, 212 const char *transfer_encoding, const char *data, 213 uint64_t off, size_t size) 214 { 215 struct connection_info_struct *con_info = coninfo_cls; 216 @end verbatim 217 @noindent 218 219 Because the following actions depend heavily on correct file processing, which might be error prone, 220 we default to reporting internal errors in case anything will go wrong. 221 222 @verbatim 223 con_info->answerstring = servererrorpage; 224 con_info->answercode = MHD_HTTP_INTERNAL_SERVER_ERROR; 225 @end verbatim 226 @noindent 227 228 In the "askpage" @emph{form}, we told the client to label its post data with the "file" key. Anything else 229 would be an error. 230 231 @verbatim 232 if (0 != strcmp (key, "file")) return MHD_NO; 233 @end verbatim 234 @noindent 235 236 If the iterator is called for the first time, no file will have been opened yet. The @code{filename} 237 string contains the name of the file (without any paths) the user selected on his system. We want to 238 take this as the name the file will be stored on the server and make sure no file of that name exists 239 (or is being uploaded) before we create one (note that the code below technically contains a 240 race between the two "fopen" calls, but we will overlook this for portability sake). 241 @verbatim 242 if (!con_info->fp) 243 { 244 if (NULL != (fp = fopen (filename, "rb")) ) 245 { 246 fclose (fp); 247 con_info->answerstring = fileexistspage; 248 con_info->answercode = MHD_HTTP_FORBIDDEN; 249 return MHD_NO; 250 } 251 252 con_info->fp = fopen (filename, "ab"); 253 if (!con_info->fp) return MHD_NO; 254 } 255 @end verbatim 256 @noindent 257 258 259 Occasionally, the iterator function will be called even when there are 0 new bytes to process. The 260 server only needs to write data to the file if there is some. 261 @verbatim 262 if (size > 0) 263 { 264 if (!fwrite (data, size, sizeof(char), con_info->fp)) 265 return MHD_NO; 266 } 267 @end verbatim 268 @noindent 269 270 If this point has been reached, everything worked well for this iteration and the response can 271 be set to success again. If the upload has finished, this iterator function will not be called again. 272 @verbatim 273 con_info->answerstring = completepage; 274 con_info->answercode = MHD_HTTP_OK; 275 276 return MHD_YES; 277 } 278 @end verbatim 279 @noindent 280 281 282 The new client was registered when the postprocessor was created. Likewise, we unregister the client 283 on destroying the postprocessor when the request is completed. 284 @verbatim 285 void 286 request_completed (void *cls, struct MHD_Connection *connection, 287 void **req_cls, 288 enum MHD_RequestTerminationCode toe) 289 { 290 struct connection_info_struct *con_info = *req_cls; 291 292 if (NULL == con_info) return; 293 294 if (con_info->connectiontype == POST) 295 { 296 if (NULL != con_info->postprocessor) 297 { 298 MHD_destroy_post_processor (con_info->postprocessor); 299 nr_of_uploading_clients--; 300 } 301 302 if (con_info->fp) fclose (con_info->fp); 303 } 304 305 free (con_info); 306 *req_cls = NULL; 307 } 308 @end verbatim 309 @noindent 310 311 312 This is essentially the whole example @code{largepost.c}. 313 314 315 @heading Remarks 316 Now that the clients are able to create files on the server, security aspects are becoming even more 317 important than before. Aside from proper client authentication, the server should always make sure 318 explicitly that no files will be created outside of a dedicated upload directory. In particular, 319 filenames must be checked to not contain strings like "../".