Home | History | Annotate | Download | only in chapters
      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 occured.</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 int 
     64 send_page (struct MHD_Connection *connection, 
     65 	   const char* page, int status_code)
     66 {
     67   int 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 int 
     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 **con_cls)
     99 {
    100   if (NULL == *con_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       *con_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       int ret;
    166       char buffer[1024];
    167         
    168       sprintf (buffer, askpage, nr_of_uploading_clients);
    169       return send_page (connection, buffer, MHD_HTTP_OK);     
    170     } 
    171 @end verbatim
    172 @noindent
    173 
    174 
    175 The rest of the @code{answer_to_connection} function is very similar to the @code{simplepost.c}
    176 example, except the more flexible content of the responses. The @emph{POST} data is processed until
    177 there is none left and the execution falls through to return an error page if the connection
    178 constituted no expected request method.
    179 @verbatim
    180   if (0 == strcmp (method, "POST")) 
    181     {
    182       struct connection_info_struct *con_info = *con_cls;
    183        
    184       if (0 != *upload_data_size) 
    185         { 
    186           MHD_post_process (con_info->postprocessor,
    187 	                    upload_data, *upload_data_size);
    188           *upload_data_size = 0;
    189           
    190           return MHD_YES;
    191         } 
    192       else 
    193         return send_page (connection, con_info->answerstring, 
    194 	       		  con_info->answercode);
    195     } 
    196 
    197   return send_page(connection, errorpage, MHD_HTTP_BAD_REQUEST);
    198 }
    199 @end verbatim
    200 @noindent
    201 
    202 
    203 @heading Storing to data
    204 Unlike the @code{simplepost.c} example, here it is to be expected that post iterator will be called
    205 several times now. This means that for any given connection (there might be several concurrent of them)
    206 the posted data has to be written to the correct file. That is why we store a file handle in every
    207 @code{connection_info}, so that the it is preserved between successive iterations.
    208 @verbatim
    209 static int 
    210 iterate_post (void *coninfo_cls, enum MHD_ValueKind kind, 
    211 	      const char *key,
    212 	      const char *filename, const char *content_type,
    213               const char *transfer_encoding, const char *data, 
    214 	      uint64_t off, size_t size)
    215 {
    216   struct connection_info_struct *con_info = coninfo_cls;
    217 @end verbatim
    218 @noindent
    219 
    220 Because the following actions depend heavily on correct file processing, which might be error prone,
    221 we default to reporting internal errors in case anything will go wrong.
    222 
    223 @verbatim
    224 con_info->answerstring = servererrorpage;
    225 con_info->answercode = MHD_HTTP_INTERNAL_SERVER_ERROR;
    226 @end verbatim
    227 @noindent
    228 
    229 In the "askpage" @emph{form}, we told the client to label its post data with the "file" key. Anything else
    230 would be an error.
    231 
    232 @verbatim
    233   if (0 != strcmp (key, "file")) return MHD_NO;
    234 @end verbatim
    235 @noindent
    236 
    237 If the iterator is called for the first time, no file will have been opened yet. The @code{filename}
    238 string contains the name of the file (without any paths) the user selected on his system. We want to
    239 take this as the name the file will be stored on the server and make sure no file of that name exists
    240 (or is being uploaded) before we create one (note that the code below technically contains a
    241 race between the two "fopen" calls, but we will overlook this for portability sake).
    242 @verbatim
    243   if (!con_info->fp)
    244     {
    245       if (NULL != (fp = fopen (filename, "rb")) )
    246         {
    247           fclose (fp);
    248           con_info->answerstring = fileexistspage;
    249           con_info->answercode = MHD_HTTP_FORBIDDEN;
    250           return MHD_NO;
    251         }
    252       
    253       con_info->fp = fopen (filename, "ab");
    254       if (!con_info->fp) return MHD_NO;    
    255     }
    256 @end verbatim
    257 @noindent
    258 
    259 
    260 Occasionally, the iterator function will be called even when there are 0 new bytes to process. The 
    261 server only needs to write data to the file if there is some.
    262 @verbatim
    263 if (size > 0) 
    264     {  
    265       if (!fwrite (data, size, sizeof(char), con_info->fp))
    266         return MHD_NO;
    267     }
    268 @end verbatim
    269 @noindent
    270 
    271 If this point has been reached, everything worked well for this iteration and the response can
    272 be set to success again. If the upload has finished, this iterator function will not be called again.
    273 @verbatim
    274   con_info->answerstring = completepage;
    275   con_info->answercode = MHD_HTTP_OK;
    276 
    277   return MHD_YES;
    278 }
    279 @end verbatim
    280 @noindent
    281 
    282 
    283 The new client was registered when the postprocessor was created. Likewise, we unregister the client
    284 on destroying the postprocessor when the request is completed.
    285 @verbatim
    286 void request_completed (void *cls, struct MHD_Connection *connection, 
    287      		        void **con_cls,
    288                         enum MHD_RequestTerminationCode toe)
    289 {
    290   struct connection_info_struct *con_info = *con_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   *con_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 "../".
    320