libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

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 "../".