mirror of
https://git.gnunet.org/libmicrohttpd.git
synced 2026-09-25 04:09:31 +03:00
fix rvalues (#9667)
This commit is contained in:
+69
-69
@@ -1,12 +1,12 @@
|
||||
The previous chapter introduced a way to upload data to the server, but the developed example program
|
||||
The previous chapter introduced a way to upload data to the server, but the developed example program
|
||||
has some shortcomings, such as not being able to handle larger chunks of data. In this chapter, we
|
||||
are going to discuss a more advanced server program that allows clients to upload a file in order to
|
||||
are going to discuss a more advanced server program that allows clients to upload a file in order to
|
||||
have it stored on the server's filesystem. The server shall also watch and limit the number of
|
||||
clients concurrently uploading, responding with a proper busy message if necessary.
|
||||
|
||||
|
||||
@heading Prepared answers
|
||||
We choose to operate the server with the @code{SELECT_INTERNALLY} method. This makes it easier to
|
||||
We choose to operate the server with the @code{SELECT_INTERNALLY} method. This makes it easier to
|
||||
synchronize the global states at the cost of possible delays for other connections if the processing
|
||||
of a request is too slow. One of these variables that needs to be shared for all connections is the
|
||||
total number of clients that are uploading.
|
||||
@@ -20,13 +20,13 @@ static unsigned int nr_of_uploading_clients = 0;
|
||||
If there are too many clients uploading, we want the server to respond to all requests with a busy
|
||||
message.
|
||||
@verbatim
|
||||
const char* busypage =
|
||||
const char* busypage =
|
||||
"<html><body>This server is busy, please try again later.</body></html>";
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
Otherwise, the server will send a @emph{form} that informs the user of the current number of uploading clients,
|
||||
and ask her to pick a file on her local filesystem which is to be uploaded.
|
||||
Otherwise, the server will send a @emph{form} that informs the user of the current number of uploading clients,
|
||||
and ask her to pick a file on her local filesystem which is to be uploaded.
|
||||
@verbatim
|
||||
const char* askpage = "<html><body>\n\
|
||||
Upload a file, please!<br>\n\
|
||||
@@ -46,31 +46,31 @@ const char* completepage = "<html><body>The upload has been completed.</body></h
|
||||
@noindent
|
||||
|
||||
We want the server to report internal errors, such as memory shortage or file access problems,
|
||||
adequately.
|
||||
adequately.
|
||||
@verbatim
|
||||
const char* servererrorpage
|
||||
const char* servererrorpage
|
||||
= "<html><body>An internal server error has occurred.</body></html>";
|
||||
const char* fileexistspage
|
||||
= "<html><body>This file already exists.</body></html>";
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
It would be tolerable to send all these responses undifferentiated with a @code{200 HTTP_OK}
|
||||
status code but in order to improve the @code{HTTP} conformance of our server a bit, we extend the
|
||||
@code{send_page} function so that it accepts individual status codes.
|
||||
It would be tolerable to send all these responses undifferentiated with a @code{200 HTTP_OK}
|
||||
status code but in order to improve the @code{HTTP} conformance of our server a bit, we extend the
|
||||
@code{send_page} function so that it accepts individual status codes.
|
||||
|
||||
@verbatim
|
||||
static int
|
||||
send_page (struct MHD_Connection *connection,
|
||||
static enum MHD_Result
|
||||
send_page (struct MHD_Connection *connection,
|
||||
const char* page, int status_code)
|
||||
{
|
||||
int ret;
|
||||
enum MHD_Result ret;
|
||||
struct MHD_Response *response;
|
||||
|
||||
response = MHD_create_response_from_buffer (strlen (page), (void*) page,
|
||||
|
||||
response = MHD_create_response_from_buffer (strlen (page), (void*) page,
|
||||
MHD_RESPMEM_MUST_COPY);
|
||||
if (!response) return MHD_NO;
|
||||
|
||||
|
||||
ret = MHD_queue_response (connection, status_code, response);
|
||||
MHD_destroy_response (response);
|
||||
|
||||
@@ -84,29 +84,29 @@ become clear later.
|
||||
|
||||
|
||||
@heading Connection cycle
|
||||
The decision whether the server is busy or not is made right at the beginning of the connection. To
|
||||
do that at this stage is especially important for @emph{POST} requests because if no response is
|
||||
The decision whether the server is busy or not is made right at the beginning of the connection. To
|
||||
do that at this stage is especially important for @emph{POST} requests because if no response is
|
||||
queued at this point, and @code{MHD_YES} returned, @emph{MHD} will not sent any queued messages until
|
||||
a postprocessor has been created and the post iterator is called at least once.
|
||||
|
||||
@verbatim
|
||||
static int
|
||||
answer_to_connection (void *cls, struct MHD_Connection *connection,
|
||||
const char *url,
|
||||
const char *method, const char *version,
|
||||
const char *upload_data,
|
||||
static enum MHD_Result
|
||||
answer_to_connection (void *cls, struct MHD_Connection *connection,
|
||||
const char *url,
|
||||
const char *method, const char *version,
|
||||
const char *upload_data,
|
||||
size_t *upload_data_size, void **req_cls)
|
||||
{
|
||||
if (NULL == *req_cls)
|
||||
if (NULL == *req_cls)
|
||||
{
|
||||
struct connection_info_struct *con_info;
|
||||
|
||||
if (nr_of_uploading_clients >= MAXCLIENTS)
|
||||
if (nr_of_uploading_clients >= MAXCLIENTS)
|
||||
return send_page(connection, busypage, MHD_HTTP_SERVICE_UNAVAILABLE);
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
If the server is not busy, the @code{connection_info} structure is initialized as usual, with
|
||||
If the server is not busy, the @code{connection_info} structure is initialized as usual, with
|
||||
the addition of a filepointer for each connection.
|
||||
|
||||
@verbatim
|
||||
@@ -114,44 +114,44 @@ the addition of a filepointer for each connection.
|
||||
if (NULL == con_info) return MHD_NO;
|
||||
con_info->fp = 0;
|
||||
|
||||
if (0 == strcmp (method, "POST"))
|
||||
{
|
||||
if (0 == strcmp (method, "POST"))
|
||||
{
|
||||
...
|
||||
}
|
||||
}
|
||||
else con_info->connectiontype = GET;
|
||||
|
||||
*req_cls = (void*) con_info;
|
||||
|
||||
|
||||
return MHD_YES;
|
||||
}
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
For @emph{POST} requests, the postprocessor is created and we register a new uploading client. From
|
||||
For @emph{POST} requests, the postprocessor is created and we register a new uploading client. From
|
||||
this point on, there are many possible places for errors to occur that make it necessary to interrupt
|
||||
the uploading process. We need a means of having the proper response message ready at all times.
|
||||
the uploading process. We need a means of having the proper response message ready at all times.
|
||||
Therefore, the @code{connection_info} structure is extended to hold the most current response
|
||||
message so that whenever a response is sent, the client will get the most informative message. Here,
|
||||
the structure is initialized to "no error".
|
||||
@verbatim
|
||||
if (0 == strcmp (method, "POST"))
|
||||
{
|
||||
con_info->postprocessor
|
||||
= MHD_create_post_processor (connection, POSTBUFFERSIZE,
|
||||
iterate_post, (void*) con_info);
|
||||
if (0 == strcmp (method, "POST"))
|
||||
{
|
||||
con_info->postprocessor
|
||||
= MHD_create_post_processor (connection, POSTBUFFERSIZE,
|
||||
iterate_post, (void*) con_info);
|
||||
|
||||
if (NULL == con_info->postprocessor)
|
||||
if (NULL == con_info->postprocessor)
|
||||
{
|
||||
free (con_info);
|
||||
free (con_info);
|
||||
return MHD_NO;
|
||||
}
|
||||
|
||||
nr_of_uploading_clients++;
|
||||
|
||||
|
||||
con_info->connectiontype = POST;
|
||||
con_info->answercode = MHD_HTTP_OK;
|
||||
con_info->answerstring = completepage;
|
||||
}
|
||||
}
|
||||
else con_info->connectiontype = GET;
|
||||
@end verbatim
|
||||
@noindent
|
||||
@@ -160,14 +160,13 @@ If the connection handler is called for the second time, @emph{GET} requests wil
|
||||
the @emph{form}. We can keep the buffer under function scope, because we asked @emph{MHD} to make its
|
||||
own copy of it for as long as it is needed.
|
||||
@verbatim
|
||||
if (0 == strcmp (method, "GET"))
|
||||
if (0 == strcmp (method, "GET"))
|
||||
{
|
||||
int ret;
|
||||
char buffer[1024];
|
||||
|
||||
|
||||
sprintf (buffer, askpage, nr_of_uploading_clients);
|
||||
return send_page (connection, buffer, MHD_HTTP_OK);
|
||||
}
|
||||
return send_page (connection, buffer, MHD_HTTP_OK);
|
||||
}
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
@@ -177,22 +176,22 @@ example, except the more flexible content of the responses. The @emph{POST} data
|
||||
there is none left and the execution falls through to return an error page if the connection
|
||||
constituted no expected request method.
|
||||
@verbatim
|
||||
if (0 == strcmp (method, "POST"))
|
||||
if (0 == strcmp (method, "POST"))
|
||||
{
|
||||
struct connection_info_struct *con_info = *req_cls;
|
||||
|
||||
if (0 != *upload_data_size)
|
||||
{
|
||||
|
||||
if (0 != *upload_data_size)
|
||||
{
|
||||
MHD_post_process (con_info->postprocessor,
|
||||
upload_data, *upload_data_size);
|
||||
*upload_data_size = 0;
|
||||
|
||||
|
||||
return MHD_YES;
|
||||
}
|
||||
else
|
||||
return send_page (connection, con_info->answerstring,
|
||||
}
|
||||
else
|
||||
return send_page (connection, con_info->answerstring,
|
||||
con_info->answercode);
|
||||
}
|
||||
}
|
||||
|
||||
return send_page(connection, errorpage, MHD_HTTP_BAD_REQUEST);
|
||||
}
|
||||
@@ -206,11 +205,11 @@ several times now. This means that for any given connection (there might be seve
|
||||
the posted data has to be written to the correct file. That is why we store a file handle in every
|
||||
@code{connection_info}, so that the it is preserved between successive iterations.
|
||||
@verbatim
|
||||
static int
|
||||
iterate_post (void *coninfo_cls, enum MHD_ValueKind kind,
|
||||
static enum MHD_Result
|
||||
iterate_post (void *coninfo_cls, enum MHD_ValueKind kind,
|
||||
const char *key,
|
||||
const char *filename, const char *content_type,
|
||||
const char *transfer_encoding, const char *data,
|
||||
const char *transfer_encoding, const char *data,
|
||||
uint64_t off, size_t size)
|
||||
{
|
||||
struct connection_info_struct *con_info = coninfo_cls;
|
||||
@@ -249,19 +248,19 @@ race between the two "fopen" calls, but we will overlook this for portability sa
|
||||
con_info->answercode = MHD_HTTP_FORBIDDEN;
|
||||
return MHD_NO;
|
||||
}
|
||||
|
||||
|
||||
con_info->fp = fopen (filename, "ab");
|
||||
if (!con_info->fp) return MHD_NO;
|
||||
if (!con_info->fp) return MHD_NO;
|
||||
}
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
|
||||
Occasionally, the iterator function will be called even when there are 0 new bytes to process. The
|
||||
Occasionally, the iterator function will be called even when there are 0 new bytes to process. The
|
||||
server only needs to write data to the file if there is some.
|
||||
@verbatim
|
||||
if (size > 0)
|
||||
{
|
||||
if (size > 0)
|
||||
{
|
||||
if (!fwrite (data, size, sizeof(char), con_info->fp))
|
||||
return MHD_NO;
|
||||
}
|
||||
@@ -283,7 +282,8 @@ be set to success again. If the upload has finished, this iterator function will
|
||||
The new client was registered when the postprocessor was created. Likewise, we unregister the client
|
||||
on destroying the postprocessor when the request is completed.
|
||||
@verbatim
|
||||
void request_completed (void *cls, struct MHD_Connection *connection,
|
||||
void
|
||||
request_completed (void *cls, struct MHD_Connection *connection,
|
||||
void **req_cls,
|
||||
enum MHD_RequestTerminationCode toe)
|
||||
{
|
||||
@@ -293,17 +293,17 @@ void request_completed (void *cls, struct MHD_Connection *connection,
|
||||
|
||||
if (con_info->connectiontype == POST)
|
||||
{
|
||||
if (NULL != con_info->postprocessor)
|
||||
if (NULL != con_info->postprocessor)
|
||||
{
|
||||
MHD_destroy_post_processor (con_info->postprocessor);
|
||||
MHD_destroy_post_processor (con_info->postprocessor);
|
||||
nr_of_uploading_clients--;
|
||||
}
|
||||
|
||||
if (con_info->fp) fclose (con_info->fp);
|
||||
if (con_info->fp) fclose (con_info->fp);
|
||||
}
|
||||
|
||||
free (con_info);
|
||||
*req_cls = NULL;
|
||||
*req_cls = NULL;
|
||||
}
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
The previous chapters already have demonstrated a variety of possibilities to send information
|
||||
The previous chapters already have demonstrated a variety of possibilities to send information
|
||||
to the HTTP server, but it is not recommended that the @emph{GET} method is used to alter the way
|
||||
the server operates. To induce changes on the server, the @emph{POST} method is preferred over
|
||||
and is much more powerful than @emph{GET} and will be introduced in this chapter.
|
||||
@@ -26,7 +26,7 @@ const char* askpage = "<html><body>\
|
||||
The @code{action} entry is the @emph{URI} to be called by the browser when posting, and the
|
||||
@code{name} will be used later to be sure it is the editbox's content that has been posted.
|
||||
|
||||
We also prepare the answer page, where the name is to be filled in later, and an error page
|
||||
We also prepare the answer page, where the name is to be filled in later, and an error page
|
||||
as the response for anything but proper @emph{GET} and @emph{POST} requests:
|
||||
|
||||
@verbatim
|
||||
@@ -38,7 +38,7 @@ const char* errorpage="<html><body>This doesn't seem to be right.</body></html>"
|
||||
|
||||
Whenever we need to send a page, we use an extra function
|
||||
@code{int send_page(struct MHD_Connection *connection, const char* page)}
|
||||
for this, which does not contain anything new and whose implementation is therefore
|
||||
for this, which does not contain anything new and whose implementation is therefore
|
||||
not discussed further in the tutorial.
|
||||
|
||||
|
||||
@@ -46,13 +46,13 @@ not discussed further in the tutorial.
|
||||
Posted data can be of arbitrary and considerable size; for example, if a user uploads a big
|
||||
image to the server. Similar to the case of the header fields, there may also be different streams
|
||||
of posted data, such as one containing the text of an editbox and another the state of a button.
|
||||
Likewise, we will have to register an iterator function that is going to be called maybe several times
|
||||
Likewise, we will have to register an iterator function that is going to be called maybe several times
|
||||
not only if there are different POSTs but also if one POST has only been received partly yet and
|
||||
needs processing before another chunk can be received.
|
||||
|
||||
Such an iterator function is called by a @emph{postprocessor}, which must be created upon arriving
|
||||
of the post request. We want the iterator function to read the first post data which is tagged
|
||||
@code{name} and to create an individual greeting string based on the template and the name.
|
||||
@code{name} and to create an individual greeting string based on the template and the name.
|
||||
But in order to pass this string to other functions and still be able to differentiate different
|
||||
connections, we must first define a structure to share the information, holding the most import entries.
|
||||
|
||||
@@ -61,12 +61,12 @@ struct connection_info_struct
|
||||
{
|
||||
int connectiontype;
|
||||
char *answerstring;
|
||||
struct MHD_PostProcessor *postprocessor;
|
||||
struct MHD_PostProcessor *postprocessor;
|
||||
};
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
With these information available to the iterator function, it is able to fulfill its task.
|
||||
With these information available to the iterator function, it is able to fulfill its task.
|
||||
Once it has composed the greeting string, it returns @code{MHD_NO} to inform the post processor
|
||||
that it does not need to be called again. Note that this function does not handle processing
|
||||
of data for the same @code{key}. If we were to expect that the name will be posted in several
|
||||
@@ -74,10 +74,10 @@ chunks, we had to expand the namestring dynamically as additional parts of it wi
|
||||
came in. But in this example, the name is assumed to fit entirely inside one single packet.
|
||||
|
||||
@verbatim
|
||||
static int
|
||||
static enum MHD_Result
|
||||
iterate_post (void *coninfo_cls, enum MHD_ValueKind kind, const char *key,
|
||||
const char *filename, const char *content_type,
|
||||
const char *transfer_encoding, const char *data,
|
||||
const char *transfer_encoding, const char *data,
|
||||
uint64_t off, size_t size)
|
||||
{
|
||||
struct connection_info_struct *con_info = coninfo_cls;
|
||||
@@ -89,10 +89,10 @@ iterate_post (void *coninfo_cls, enum MHD_ValueKind kind, const char *key,
|
||||
char *answerstring;
|
||||
answerstring = malloc (MAXANSWERSIZE);
|
||||
if (!answerstring) return MHD_NO;
|
||||
|
||||
|
||||
snprintf (answerstring, MAXANSWERSIZE, greatingpage, data);
|
||||
con_info->answerstring = answerstring;
|
||||
}
|
||||
con_info->answerstring = answerstring;
|
||||
}
|
||||
else con_info->answerstring = NULL;
|
||||
|
||||
return MHD_NO;
|
||||
@@ -106,25 +106,27 @@ iterate_post (void *coninfo_cls, enum MHD_ValueKind kind, const char *key,
|
||||
Once a connection has been established, it can be terminated for many reasons. As these
|
||||
reasons include unexpected events, we have to register another function that cleans up any resources
|
||||
that might have been allocated for that connection by us, namely the post processor and the greetings
|
||||
string. This cleanup function must take into account that it will also be called for finished
|
||||
string. This cleanup function must take into account that it will also be called for finished
|
||||
requests other than @emph{POST} requests.
|
||||
|
||||
@verbatim
|
||||
void request_completed (void *cls, struct MHD_Connection *connection,
|
||||
void
|
||||
request_completed (void *cls, struct MHD_Connection *connection,
|
||||
void **req_cls,
|
||||
enum MHD_RequestTerminationCode toe)
|
||||
{
|
||||
struct connection_info_struct *con_info = *req_cls;
|
||||
|
||||
if (NULL == con_info) return;
|
||||
if (NULL == con_info)
|
||||
return;
|
||||
if (con_info->connectiontype == POST)
|
||||
{
|
||||
MHD_destroy_post_processor (con_info->postprocessor);
|
||||
MHD_destroy_post_processor (con_info->postprocessor);
|
||||
if (con_info->answerstring) free (con_info->answerstring);
|
||||
}
|
||||
|
||||
|
||||
free (con_info);
|
||||
*req_cls = NULL;
|
||||
*req_cls = NULL;
|
||||
}
|
||||
@end verbatim
|
||||
@noindent
|
||||
@@ -135,7 +137,7 @@ in the main function.
|
||||
@verbatim
|
||||
...
|
||||
daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD, PORT, NULL, NULL,
|
||||
&answer_to_connection, NULL,
|
||||
&answer_to_connection, NULL,
|
||||
MHD_OPTION_NOTIFY_COMPLETED, &request_completed, NULL,
|
||||
MHD_OPTION_END);
|
||||
...
|
||||
@@ -145,19 +147,19 @@ daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD, PORT, NULL, NULL,
|
||||
@heading Request handling
|
||||
With all other functions prepared, we can now discuss the actual request handling.
|
||||
|
||||
On the first iteration for a new request, we start by allocating a new instance of a
|
||||
On the first iteration for a new request, we start by allocating a new instance of a
|
||||
@code{struct connection_info_struct} structure, which will store all necessary information for later
|
||||
iterations and other functions.
|
||||
|
||||
@verbatim
|
||||
static int
|
||||
answer_to_connection (void *cls, struct MHD_Connection *connection,
|
||||
const char *url,
|
||||
const char *method, const char *version,
|
||||
const char *upload_data,
|
||||
static enum MHD_Result
|
||||
answer_to_connection (void *cls, struct MHD_Connection *connection,
|
||||
const char *url,
|
||||
const char *method, const char *version,
|
||||
const char *upload_data,
|
||||
size_t *upload_data_size, void **req_cls)
|
||||
{
|
||||
if(NULL == *req_cls)
|
||||
if(NULL == *req_cls)
|
||||
{
|
||||
struct connection_info_struct *con_info;
|
||||
|
||||
@@ -170,19 +172,19 @@ answer_to_connection (void *cls, struct MHD_Connection *connection,
|
||||
If the new request is a @emph{POST}, the postprocessor must be created now. In addition, the type
|
||||
of the request is stored for convenience.
|
||||
@verbatim
|
||||
if (0 == strcmp (method, "POST"))
|
||||
{
|
||||
con_info->postprocessor
|
||||
= MHD_create_post_processor (connection, POSTBUFFERSIZE,
|
||||
iterate_post, (void*) con_info);
|
||||
if (0 == strcmp (method, "POST"))
|
||||
{
|
||||
con_info->postprocessor
|
||||
= MHD_create_post_processor (connection, POSTBUFFERSIZE,
|
||||
iterate_post, (void*) con_info);
|
||||
|
||||
if (NULL == con_info->postprocessor)
|
||||
if (NULL == con_info->postprocessor)
|
||||
{
|
||||
free (con_info);
|
||||
free (con_info);
|
||||
return MHD_NO;
|
||||
}
|
||||
con_info->connectiontype = POST;
|
||||
}
|
||||
}
|
||||
else con_info->connectiontype = GET;
|
||||
@end verbatim
|
||||
@noindent
|
||||
@@ -190,7 +192,7 @@ of the request is stored for convenience.
|
||||
The address of our structure will both serve as the indicator for successive iterations and to remember
|
||||
the particular details about the connection.
|
||||
@verbatim
|
||||
*req_cls = (void*) con_info;
|
||||
*req_cls = (void*) con_info;
|
||||
return MHD_YES;
|
||||
}
|
||||
@end verbatim
|
||||
@@ -199,10 +201,10 @@ the particular details about the connection.
|
||||
The rest of the function will not be executed on the first iteration. A @emph{GET} request is easily
|
||||
satisfied by sending the question form.
|
||||
@verbatim
|
||||
if (0 == strcmp (method, "GET"))
|
||||
if (0 == strcmp (method, "GET"))
|
||||
{
|
||||
return send_page (connection, askpage);
|
||||
}
|
||||
return send_page (connection, askpage);
|
||||
}
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
@@ -210,27 +212,27 @@ In case of @emph{POST}, we invoke the post processor for as long as data keeps i
|
||||
@code{*upload_data_size} to zero in order to indicate that we have processed---or at least have
|
||||
considered---all of it.
|
||||
@verbatim
|
||||
if (0 == strcmp (method, "POST"))
|
||||
if (0 == strcmp (method, "POST"))
|
||||
{
|
||||
struct connection_info_struct *con_info = *req_cls;
|
||||
|
||||
if (*upload_data_size != 0)
|
||||
if (*upload_data_size != 0)
|
||||
{
|
||||
MHD_post_process (con_info->postprocessor, upload_data,
|
||||
MHD_post_process (con_info->postprocessor, upload_data,
|
||||
*upload_data_size);
|
||||
*upload_data_size = 0;
|
||||
|
||||
|
||||
return MHD_YES;
|
||||
}
|
||||
else if (NULL != con_info->answerstring)
|
||||
}
|
||||
else if (NULL != con_info->answerstring)
|
||||
return send_page (connection, con_info->answerstring);
|
||||
}
|
||||
}
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
Finally, if they are neither @emph{GET} nor @emph{POST} requests, the error page is returned.
|
||||
@verbatim
|
||||
return send_page(connection, errorpage);
|
||||
return send_page(connection, errorpage);
|
||||
}
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Now that we are able to inspect the incoming request in great detail,
|
||||
this chapter discusses the means to enrich the outgoing responses likewise.
|
||||
|
||||
As you have learned in the @emph{Hello, Browser} chapter, some obligatory
|
||||
As you have learned in the @emph{Hello, Browser} chapter, some obligatory
|
||||
header fields are added and set automatically for simple responses by the library
|
||||
itself but if more advanced features are desired, additional fields have to be created.
|
||||
One of the possible fields is the content type field and an example will be developed around it.
|
||||
@@ -9,13 +9,13 @@ This will lead to an application capable of correctly serving different types of
|
||||
|
||||
|
||||
When we responded with HTML page packed in the static string previously, the client had no choice
|
||||
but guessing about how to handle the response, because the server had not told him.
|
||||
but guessing about how to handle the response, because the server had not told him.
|
||||
What if we had sent a picture or a sound file? Would the message have been understood
|
||||
or merely been displayed as an endless stream of random characters in the browser?
|
||||
This is what the mime content types are for. The header of the response is extended
|
||||
by certain information about how the data is to be interpreted.
|
||||
by certain information about how the data is to be interpreted.
|
||||
|
||||
To introduce the concept, a picture of the format @emph{PNG} will be sent to the client
|
||||
To introduce the concept, a picture of the format @emph{PNG} will be sent to the client
|
||||
and labeled accordingly with @code{image/png}.
|
||||
Once again, we can base the new example on the @code{hellobrowser} program.
|
||||
|
||||
@@ -23,22 +23,22 @@ Once again, we can base the new example on the @code{hellobrowser} program.
|
||||
#define FILENAME "picture.png"
|
||||
#define MIMETYPE "image/png"
|
||||
|
||||
static int
|
||||
answer_to_connection (void *cls, struct MHD_Connection *connection,
|
||||
const char *url,
|
||||
const char *method, const char *version,
|
||||
const char *upload_data,
|
||||
static enum MHD_Result
|
||||
answer_to_connection (void *cls, struct MHD_Connection *connection,
|
||||
const char *url,
|
||||
const char *method, const char *version,
|
||||
const char *upload_data,
|
||||
size_t *upload_data_size, void **req_cls)
|
||||
{
|
||||
unsigned char *buffer = NULL;
|
||||
struct MHD_Response *response;
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
|
||||
We want the program to open the file for reading and determine its size:
|
||||
@verbatim
|
||||
int fd;
|
||||
int ret;
|
||||
enum MHD_Result ret;
|
||||
struct stat sbuf;
|
||||
|
||||
if (0 != strcmp (method, "GET"))
|
||||
@@ -56,15 +56,15 @@ We want the program to open the file for reading and determine its size:
|
||||
When dealing with files, there is a lot that could go wrong on the
|
||||
server side and if so, the client should be informed with @code{MHD_HTTP_INTERNAL_SERVER_ERROR}.
|
||||
|
||||
@verbatim
|
||||
@verbatim
|
||||
/* error accessing file */
|
||||
if (fd != -1) close (fd);
|
||||
const char *errorstr =
|
||||
"<html><body>An internal server error has occurred!\
|
||||
</body></html>";
|
||||
response =
|
||||
MHD_create_response_from_buffer (strlen (errorstr),
|
||||
(void *) errorstr,
|
||||
MHD_create_response_from_buffer (strlen (errorstr),
|
||||
(void *) errorstr,
|
||||
MHD_RESPMEM_PERSISTENT);
|
||||
if (response)
|
||||
{
|
||||
@@ -77,25 +77,25 @@ server side and if so, the client should be informed with @code{MHD_HTTP_INTERNA
|
||||
}
|
||||
else
|
||||
return MHD_NO;
|
||||
if (!ret)
|
||||
if (!ret)
|
||||
{
|
||||
const char *errorstr = "<html><body>An internal server error has occurred!\
|
||||
</body></html>";
|
||||
|
||||
if (buffer) free(buffer);
|
||||
|
||||
|
||||
response = MHD_create_response_from_buffer (strlen(errorstr), (void*) errorstr,
|
||||
MHD_RESPMEM_PERSISTENT);
|
||||
|
||||
if (response)
|
||||
{
|
||||
ret = MHD_queue_response (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
{
|
||||
ret = MHD_queue_response (connection,
|
||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
response);
|
||||
MHD_destroy_response (response);
|
||||
|
||||
return MHD_YES;
|
||||
}
|
||||
return MHD_YES;
|
||||
}
|
||||
else return MHD_NO;
|
||||
}
|
||||
@end verbatim
|
||||
@@ -128,8 +128,8 @@ meta data about the content. Aware of the field's name we want to add, it is as
|
||||
MHD_add_response_header(response, "Content-Type", MIMETYPE);
|
||||
@end verbatim
|
||||
@noindent
|
||||
We do not have to append a colon expected by the protocol behind the first
|
||||
field---@emph{GNU libhttpdmicro} will take care of this.
|
||||
We do not have to append a colon expected by the protocol behind the first
|
||||
field---@emph{GNU libhttpdmicro} will take care of this.
|
||||
|
||||
The function finishes with the well-known lines
|
||||
@verbatim
|
||||
@@ -150,7 +150,7 @@ already defined as macros. Thus, we could have written @code{MHD_HTTP_HEADER_CON
|
||||
of @code{"Content-Type"} as well. However, one is not limited to these standard headers and could
|
||||
add custom response headers without violating the protocol. Whether, and how, the client would react
|
||||
to these custom header is up to the receiver. Likewise, the client is allowed to send custom request
|
||||
headers to the server as well, opening up yet more possibilities how client and server could
|
||||
headers to the server as well, opening up yet more possibilities how client and server could
|
||||
communicate with each other.
|
||||
|
||||
The method of creating the response from a file on disk only works for static content.
|
||||
@@ -162,31 +162,31 @@ Serving dynamically created responses will be a topic of a future chapter.
|
||||
@item
|
||||
Remember that the original program was written under a few assumptions---a static response
|
||||
using a local file being one of them. In order to simulate a very large or hard to reach file that cannot be provided
|
||||
instantly, postpone the queuing in the callback with the @code{sleep} function for 30 seconds
|
||||
instantly, postpone the queuing in the callback with the @code{sleep} function for 30 seconds
|
||||
@emph{if} the file @code{/big.png} is requested (but deliver the same as above). A request for
|
||||
@code{/picture.png} should provide just the same but without any artificial delays.
|
||||
|
||||
Now start two instances of your browser (or even use two machines) and see how the second client
|
||||
is put on hold while the first waits for his request on the slow file to be fulfilled.
|
||||
|
||||
Finally, change the sourcecode to use @code{MHD_USE_THREAD_PER_CONNECTION} when the daemon is
|
||||
Finally, change the sourcecode to use @code{MHD_USE_THREAD_PER_CONNECTION} when the daemon is
|
||||
started and try again.
|
||||
|
||||
|
||||
@item
|
||||
Did you succeed in implementing the clock exercise yet? This time, let the server save the
|
||||
Did you succeed in implementing the clock exercise yet? This time, let the server save the
|
||||
program's start time @code{t} and implement a response simulating a countdown that reaches 0 at
|
||||
@code{t+60}. Returning a message saying on which point the countdown is, the response should
|
||||
ultimately be to reply "Done" if the program has been running long enough,
|
||||
|
||||
An unofficial, but widely understood, response header line is @code{Refresh: DELAY; url=URL} with
|
||||
the uppercase words substituted to tell the client it should request the given resource after
|
||||
the uppercase words substituted to tell the client it should request the given resource after
|
||||
the given delay again. Improve your program in that the browser (any modern browser should work)
|
||||
automatically reconnects and asks for the status again every 5 seconds or so. The URL would have
|
||||
to be composed so that it begins with "http://", followed by the @emph{URI} the server is reachable
|
||||
from the client's point of view.
|
||||
|
||||
Maybe you want also to visualize the countdown as a status bar by creating a
|
||||
Maybe you want also to visualize the countdown as a status bar by creating a
|
||||
@code{<table>} consisting of one row and @code{n} columns whose fields contain small images of either
|
||||
a red or a green light.
|
||||
|
||||
|
||||
@@ -308,7 +308,7 @@ access_handler (void *cls,
|
||||
{
|
||||
static int aptr;
|
||||
struct MHD_Response *response;
|
||||
int ret;
|
||||
enum MHD_Result ret;
|
||||
|
||||
(void) cls; /* Unused. Silent compiler warning. */
|
||||
(void) upload_data; /* Unused. Silent compiler warning. */
|
||||
|
||||
Reference in New Issue
Block a user