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
|
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
|
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.
|
clients concurrently uploading, responding with a proper busy message if necessary.
|
||||||
|
|
||||||
|
|
||||||
@heading Prepared answers
|
@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
|
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
|
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.
|
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
|
If there are too many clients uploading, we want the server to respond to all requests with a busy
|
||||||
message.
|
message.
|
||||||
@verbatim
|
@verbatim
|
||||||
const char* busypage =
|
const char* busypage =
|
||||||
"<html><body>This server is busy, please try again later.</body></html>";
|
"<html><body>This server is busy, please try again later.</body></html>";
|
||||||
@end verbatim
|
@end verbatim
|
||||||
@noindent
|
@noindent
|
||||||
|
|
||||||
Otherwise, the server will send a @emph{form} that informs the user of the current number of uploading clients,
|
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.
|
and ask her to pick a file on her local filesystem which is to be uploaded.
|
||||||
@verbatim
|
@verbatim
|
||||||
const char* askpage = "<html><body>\n\
|
const char* askpage = "<html><body>\n\
|
||||||
Upload a file, please!<br>\n\
|
Upload a file, please!<br>\n\
|
||||||
@@ -46,31 +46,31 @@ const char* completepage = "<html><body>The upload has been completed.</body></h
|
|||||||
@noindent
|
@noindent
|
||||||
|
|
||||||
We want the server to report internal errors, such as memory shortage or file access problems,
|
We want the server to report internal errors, such as memory shortage or file access problems,
|
||||||
adequately.
|
adequately.
|
||||||
@verbatim
|
@verbatim
|
||||||
const char* servererrorpage
|
const char* servererrorpage
|
||||||
= "<html><body>An internal server error has occurred.</body></html>";
|
= "<html><body>An internal server error has occurred.</body></html>";
|
||||||
const char* fileexistspage
|
const char* fileexistspage
|
||||||
= "<html><body>This file already exists.</body></html>";
|
= "<html><body>This file already exists.</body></html>";
|
||||||
@end verbatim
|
@end verbatim
|
||||||
@noindent
|
@noindent
|
||||||
|
|
||||||
It would be tolerable to send all these responses undifferentiated with a @code{200 HTTP_OK}
|
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
|
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.
|
@code{send_page} function so that it accepts individual status codes.
|
||||||
|
|
||||||
@verbatim
|
@verbatim
|
||||||
static int
|
static enum MHD_Result
|
||||||
send_page (struct MHD_Connection *connection,
|
send_page (struct MHD_Connection *connection,
|
||||||
const char* page, int status_code)
|
const char* page, int status_code)
|
||||||
{
|
{
|
||||||
int ret;
|
enum MHD_Result ret;
|
||||||
struct MHD_Response *response;
|
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);
|
MHD_RESPMEM_MUST_COPY);
|
||||||
if (!response) return MHD_NO;
|
if (!response) return MHD_NO;
|
||||||
|
|
||||||
ret = MHD_queue_response (connection, status_code, response);
|
ret = MHD_queue_response (connection, status_code, response);
|
||||||
MHD_destroy_response (response);
|
MHD_destroy_response (response);
|
||||||
|
|
||||||
@@ -84,29 +84,29 @@ become clear later.
|
|||||||
|
|
||||||
|
|
||||||
@heading Connection cycle
|
@heading Connection cycle
|
||||||
The decision whether the server is busy or not is made right at the beginning of the connection. To
|
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
|
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
|
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.
|
a postprocessor has been created and the post iterator is called at least once.
|
||||||
|
|
||||||
@verbatim
|
@verbatim
|
||||||
static int
|
static enum MHD_Result
|
||||||
answer_to_connection (void *cls, struct MHD_Connection *connection,
|
answer_to_connection (void *cls, struct MHD_Connection *connection,
|
||||||
const char *url,
|
const char *url,
|
||||||
const char *method, const char *version,
|
const char *method, const char *version,
|
||||||
const char *upload_data,
|
const char *upload_data,
|
||||||
size_t *upload_data_size, void **req_cls)
|
size_t *upload_data_size, void **req_cls)
|
||||||
{
|
{
|
||||||
if (NULL == *req_cls)
|
if (NULL == *req_cls)
|
||||||
{
|
{
|
||||||
struct connection_info_struct *con_info;
|
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);
|
return send_page(connection, busypage, MHD_HTTP_SERVICE_UNAVAILABLE);
|
||||||
@end verbatim
|
@end verbatim
|
||||||
@noindent
|
@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.
|
the addition of a filepointer for each connection.
|
||||||
|
|
||||||
@verbatim
|
@verbatim
|
||||||
@@ -114,44 +114,44 @@ the addition of a filepointer for each connection.
|
|||||||
if (NULL == con_info) return MHD_NO;
|
if (NULL == con_info) return MHD_NO;
|
||||||
con_info->fp = 0;
|
con_info->fp = 0;
|
||||||
|
|
||||||
if (0 == strcmp (method, "POST"))
|
if (0 == strcmp (method, "POST"))
|
||||||
{
|
{
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
else con_info->connectiontype = GET;
|
else con_info->connectiontype = GET;
|
||||||
|
|
||||||
*req_cls = (void*) con_info;
|
*req_cls = (void*) con_info;
|
||||||
|
|
||||||
return MHD_YES;
|
return MHD_YES;
|
||||||
}
|
}
|
||||||
@end verbatim
|
@end verbatim
|
||||||
@noindent
|
@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
|
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
|
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,
|
message so that whenever a response is sent, the client will get the most informative message. Here,
|
||||||
the structure is initialized to "no error".
|
the structure is initialized to "no error".
|
||||||
@verbatim
|
@verbatim
|
||||||
if (0 == strcmp (method, "POST"))
|
if (0 == strcmp (method, "POST"))
|
||||||
{
|
{
|
||||||
con_info->postprocessor
|
con_info->postprocessor
|
||||||
= MHD_create_post_processor (connection, POSTBUFFERSIZE,
|
= MHD_create_post_processor (connection, POSTBUFFERSIZE,
|
||||||
iterate_post, (void*) con_info);
|
iterate_post, (void*) con_info);
|
||||||
|
|
||||||
if (NULL == con_info->postprocessor)
|
if (NULL == con_info->postprocessor)
|
||||||
{
|
{
|
||||||
free (con_info);
|
free (con_info);
|
||||||
return MHD_NO;
|
return MHD_NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
nr_of_uploading_clients++;
|
nr_of_uploading_clients++;
|
||||||
|
|
||||||
con_info->connectiontype = POST;
|
con_info->connectiontype = POST;
|
||||||
con_info->answercode = MHD_HTTP_OK;
|
con_info->answercode = MHD_HTTP_OK;
|
||||||
con_info->answerstring = completepage;
|
con_info->answerstring = completepage;
|
||||||
}
|
}
|
||||||
else con_info->connectiontype = GET;
|
else con_info->connectiontype = GET;
|
||||||
@end verbatim
|
@end verbatim
|
||||||
@noindent
|
@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
|
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.
|
own copy of it for as long as it is needed.
|
||||||
@verbatim
|
@verbatim
|
||||||
if (0 == strcmp (method, "GET"))
|
if (0 == strcmp (method, "GET"))
|
||||||
{
|
{
|
||||||
int ret;
|
|
||||||
char buffer[1024];
|
char buffer[1024];
|
||||||
|
|
||||||
sprintf (buffer, askpage, nr_of_uploading_clients);
|
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
|
@end verbatim
|
||||||
@noindent
|
@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
|
there is none left and the execution falls through to return an error page if the connection
|
||||||
constituted no expected request method.
|
constituted no expected request method.
|
||||||
@verbatim
|
@verbatim
|
||||||
if (0 == strcmp (method, "POST"))
|
if (0 == strcmp (method, "POST"))
|
||||||
{
|
{
|
||||||
struct connection_info_struct *con_info = *req_cls;
|
struct connection_info_struct *con_info = *req_cls;
|
||||||
|
|
||||||
if (0 != *upload_data_size)
|
if (0 != *upload_data_size)
|
||||||
{
|
{
|
||||||
MHD_post_process (con_info->postprocessor,
|
MHD_post_process (con_info->postprocessor,
|
||||||
upload_data, *upload_data_size);
|
upload_data, *upload_data_size);
|
||||||
*upload_data_size = 0;
|
*upload_data_size = 0;
|
||||||
|
|
||||||
return MHD_YES;
|
return MHD_YES;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return send_page (connection, con_info->answerstring,
|
return send_page (connection, con_info->answerstring,
|
||||||
con_info->answercode);
|
con_info->answercode);
|
||||||
}
|
}
|
||||||
|
|
||||||
return send_page(connection, errorpage, MHD_HTTP_BAD_REQUEST);
|
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
|
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.
|
@code{connection_info}, so that the it is preserved between successive iterations.
|
||||||
@verbatim
|
@verbatim
|
||||||
static int
|
static enum MHD_Result
|
||||||
iterate_post (void *coninfo_cls, enum MHD_ValueKind kind,
|
iterate_post (void *coninfo_cls, enum MHD_ValueKind kind,
|
||||||
const char *key,
|
const char *key,
|
||||||
const char *filename, const char *content_type,
|
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)
|
uint64_t off, size_t size)
|
||||||
{
|
{
|
||||||
struct connection_info_struct *con_info = coninfo_cls;
|
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;
|
con_info->answercode = MHD_HTTP_FORBIDDEN;
|
||||||
return MHD_NO;
|
return MHD_NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
con_info->fp = fopen (filename, "ab");
|
con_info->fp = fopen (filename, "ab");
|
||||||
if (!con_info->fp) return MHD_NO;
|
if (!con_info->fp) return MHD_NO;
|
||||||
}
|
}
|
||||||
@end verbatim
|
@end verbatim
|
||||||
@noindent
|
@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.
|
server only needs to write data to the file if there is some.
|
||||||
@verbatim
|
@verbatim
|
||||||
if (size > 0)
|
if (size > 0)
|
||||||
{
|
{
|
||||||
if (!fwrite (data, size, sizeof(char), con_info->fp))
|
if (!fwrite (data, size, sizeof(char), con_info->fp))
|
||||||
return MHD_NO;
|
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
|
The new client was registered when the postprocessor was created. Likewise, we unregister the client
|
||||||
on destroying the postprocessor when the request is completed.
|
on destroying the postprocessor when the request is completed.
|
||||||
@verbatim
|
@verbatim
|
||||||
void request_completed (void *cls, struct MHD_Connection *connection,
|
void
|
||||||
|
request_completed (void *cls, struct MHD_Connection *connection,
|
||||||
void **req_cls,
|
void **req_cls,
|
||||||
enum MHD_RequestTerminationCode toe)
|
enum MHD_RequestTerminationCode toe)
|
||||||
{
|
{
|
||||||
@@ -293,17 +293,17 @@ void request_completed (void *cls, struct MHD_Connection *connection,
|
|||||||
|
|
||||||
if (con_info->connectiontype == POST)
|
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--;
|
nr_of_uploading_clients--;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (con_info->fp) fclose (con_info->fp);
|
if (con_info->fp) fclose (con_info->fp);
|
||||||
}
|
}
|
||||||
|
|
||||||
free (con_info);
|
free (con_info);
|
||||||
*req_cls = NULL;
|
*req_cls = NULL;
|
||||||
}
|
}
|
||||||
@end verbatim
|
@end verbatim
|
||||||
@noindent
|
@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
|
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
|
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.
|
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
|
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.
|
@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:
|
as the response for anything but proper @emph{GET} and @emph{POST} requests:
|
||||||
|
|
||||||
@verbatim
|
@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
|
Whenever we need to send a page, we use an extra function
|
||||||
@code{int send_page(struct MHD_Connection *connection, const char* page)}
|
@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.
|
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
|
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
|
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.
|
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
|
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.
|
needs processing before another chunk can be received.
|
||||||
|
|
||||||
Such an iterator function is called by a @emph{postprocessor}, which must be created upon arriving
|
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
|
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
|
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.
|
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;
|
int connectiontype;
|
||||||
char *answerstring;
|
char *answerstring;
|
||||||
struct MHD_PostProcessor *postprocessor;
|
struct MHD_PostProcessor *postprocessor;
|
||||||
};
|
};
|
||||||
@end verbatim
|
@end verbatim
|
||||||
@noindent
|
@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
|
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
|
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
|
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.
|
came in. But in this example, the name is assumed to fit entirely inside one single packet.
|
||||||
|
|
||||||
@verbatim
|
@verbatim
|
||||||
static int
|
static enum MHD_Result
|
||||||
iterate_post (void *coninfo_cls, enum MHD_ValueKind kind, const char *key,
|
iterate_post (void *coninfo_cls, enum MHD_ValueKind kind, const char *key,
|
||||||
const char *filename, const char *content_type,
|
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)
|
uint64_t off, size_t size)
|
||||||
{
|
{
|
||||||
struct connection_info_struct *con_info = coninfo_cls;
|
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;
|
char *answerstring;
|
||||||
answerstring = malloc (MAXANSWERSIZE);
|
answerstring = malloc (MAXANSWERSIZE);
|
||||||
if (!answerstring) return MHD_NO;
|
if (!answerstring) return MHD_NO;
|
||||||
|
|
||||||
snprintf (answerstring, MAXANSWERSIZE, greatingpage, data);
|
snprintf (answerstring, MAXANSWERSIZE, greatingpage, data);
|
||||||
con_info->answerstring = answerstring;
|
con_info->answerstring = answerstring;
|
||||||
}
|
}
|
||||||
else con_info->answerstring = NULL;
|
else con_info->answerstring = NULL;
|
||||||
|
|
||||||
return MHD_NO;
|
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
|
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
|
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
|
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.
|
requests other than @emph{POST} requests.
|
||||||
|
|
||||||
@verbatim
|
@verbatim
|
||||||
void request_completed (void *cls, struct MHD_Connection *connection,
|
void
|
||||||
|
request_completed (void *cls, struct MHD_Connection *connection,
|
||||||
void **req_cls,
|
void **req_cls,
|
||||||
enum MHD_RequestTerminationCode toe)
|
enum MHD_RequestTerminationCode toe)
|
||||||
{
|
{
|
||||||
struct connection_info_struct *con_info = *req_cls;
|
struct connection_info_struct *con_info = *req_cls;
|
||||||
|
|
||||||
if (NULL == con_info) return;
|
if (NULL == con_info)
|
||||||
|
return;
|
||||||
if (con_info->connectiontype == POST)
|
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);
|
if (con_info->answerstring) free (con_info->answerstring);
|
||||||
}
|
}
|
||||||
|
|
||||||
free (con_info);
|
free (con_info);
|
||||||
*req_cls = NULL;
|
*req_cls = NULL;
|
||||||
}
|
}
|
||||||
@end verbatim
|
@end verbatim
|
||||||
@noindent
|
@noindent
|
||||||
@@ -135,7 +137,7 @@ in the main function.
|
|||||||
@verbatim
|
@verbatim
|
||||||
...
|
...
|
||||||
daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD, PORT, NULL, NULL,
|
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_NOTIFY_COMPLETED, &request_completed, NULL,
|
||||||
MHD_OPTION_END);
|
MHD_OPTION_END);
|
||||||
...
|
...
|
||||||
@@ -145,19 +147,19 @@ daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD, PORT, NULL, NULL,
|
|||||||
@heading Request handling
|
@heading Request handling
|
||||||
With all other functions prepared, we can now discuss the actual 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
|
@code{struct connection_info_struct} structure, which will store all necessary information for later
|
||||||
iterations and other functions.
|
iterations and other functions.
|
||||||
|
|
||||||
@verbatim
|
@verbatim
|
||||||
static int
|
static enum MHD_Result
|
||||||
answer_to_connection (void *cls, struct MHD_Connection *connection,
|
answer_to_connection (void *cls, struct MHD_Connection *connection,
|
||||||
const char *url,
|
const char *url,
|
||||||
const char *method, const char *version,
|
const char *method, const char *version,
|
||||||
const char *upload_data,
|
const char *upload_data,
|
||||||
size_t *upload_data_size, void **req_cls)
|
size_t *upload_data_size, void **req_cls)
|
||||||
{
|
{
|
||||||
if(NULL == *req_cls)
|
if(NULL == *req_cls)
|
||||||
{
|
{
|
||||||
struct connection_info_struct *con_info;
|
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
|
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.
|
of the request is stored for convenience.
|
||||||
@verbatim
|
@verbatim
|
||||||
if (0 == strcmp (method, "POST"))
|
if (0 == strcmp (method, "POST"))
|
||||||
{
|
{
|
||||||
con_info->postprocessor
|
con_info->postprocessor
|
||||||
= MHD_create_post_processor (connection, POSTBUFFERSIZE,
|
= MHD_create_post_processor (connection, POSTBUFFERSIZE,
|
||||||
iterate_post, (void*) con_info);
|
iterate_post, (void*) con_info);
|
||||||
|
|
||||||
if (NULL == con_info->postprocessor)
|
if (NULL == con_info->postprocessor)
|
||||||
{
|
{
|
||||||
free (con_info);
|
free (con_info);
|
||||||
return MHD_NO;
|
return MHD_NO;
|
||||||
}
|
}
|
||||||
con_info->connectiontype = POST;
|
con_info->connectiontype = POST;
|
||||||
}
|
}
|
||||||
else con_info->connectiontype = GET;
|
else con_info->connectiontype = GET;
|
||||||
@end verbatim
|
@end verbatim
|
||||||
@noindent
|
@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 address of our structure will both serve as the indicator for successive iterations and to remember
|
||||||
the particular details about the connection.
|
the particular details about the connection.
|
||||||
@verbatim
|
@verbatim
|
||||||
*req_cls = (void*) con_info;
|
*req_cls = (void*) con_info;
|
||||||
return MHD_YES;
|
return MHD_YES;
|
||||||
}
|
}
|
||||||
@end verbatim
|
@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
|
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.
|
satisfied by sending the question form.
|
||||||
@verbatim
|
@verbatim
|
||||||
if (0 == strcmp (method, "GET"))
|
if (0 == strcmp (method, "GET"))
|
||||||
{
|
{
|
||||||
return send_page (connection, askpage);
|
return send_page (connection, askpage);
|
||||||
}
|
}
|
||||||
@end verbatim
|
@end verbatim
|
||||||
@noindent
|
@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
|
@code{*upload_data_size} to zero in order to indicate that we have processed---or at least have
|
||||||
considered---all of it.
|
considered---all of it.
|
||||||
@verbatim
|
@verbatim
|
||||||
if (0 == strcmp (method, "POST"))
|
if (0 == strcmp (method, "POST"))
|
||||||
{
|
{
|
||||||
struct connection_info_struct *con_info = *req_cls;
|
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);
|
||||||
*upload_data_size = 0;
|
*upload_data_size = 0;
|
||||||
|
|
||||||
return MHD_YES;
|
return MHD_YES;
|
||||||
}
|
}
|
||||||
else if (NULL != con_info->answerstring)
|
else if (NULL != con_info->answerstring)
|
||||||
return send_page (connection, con_info->answerstring);
|
return send_page (connection, con_info->answerstring);
|
||||||
}
|
}
|
||||||
@end verbatim
|
@end verbatim
|
||||||
@noindent
|
@noindent
|
||||||
|
|
||||||
Finally, if they are neither @emph{GET} nor @emph{POST} requests, the error page is returned.
|
Finally, if they are neither @emph{GET} nor @emph{POST} requests, the error page is returned.
|
||||||
@verbatim
|
@verbatim
|
||||||
return send_page(connection, errorpage);
|
return send_page(connection, errorpage);
|
||||||
}
|
}
|
||||||
@end verbatim
|
@end verbatim
|
||||||
@noindent
|
@noindent
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Now that we are able to inspect the incoming request in great detail,
|
Now that we are able to inspect the incoming request in great detail,
|
||||||
this chapter discusses the means to enrich the outgoing responses likewise.
|
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
|
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.
|
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.
|
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
|
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
|
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?
|
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
|
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}.
|
and labeled accordingly with @code{image/png}.
|
||||||
Once again, we can base the new example on the @code{hellobrowser} program.
|
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 FILENAME "picture.png"
|
||||||
#define MIMETYPE "image/png"
|
#define MIMETYPE "image/png"
|
||||||
|
|
||||||
static int
|
static enum MHD_Result
|
||||||
answer_to_connection (void *cls, struct MHD_Connection *connection,
|
answer_to_connection (void *cls, struct MHD_Connection *connection,
|
||||||
const char *url,
|
const char *url,
|
||||||
const char *method, const char *version,
|
const char *method, const char *version,
|
||||||
const char *upload_data,
|
const char *upload_data,
|
||||||
size_t *upload_data_size, void **req_cls)
|
size_t *upload_data_size, void **req_cls)
|
||||||
{
|
{
|
||||||
unsigned char *buffer = NULL;
|
unsigned char *buffer = NULL;
|
||||||
struct MHD_Response *response;
|
struct MHD_Response *response;
|
||||||
@end verbatim
|
@end verbatim
|
||||||
@noindent
|
@noindent
|
||||||
|
|
||||||
We want the program to open the file for reading and determine its size:
|
We want the program to open the file for reading and determine its size:
|
||||||
@verbatim
|
@verbatim
|
||||||
int fd;
|
int fd;
|
||||||
int ret;
|
enum MHD_Result ret;
|
||||||
struct stat sbuf;
|
struct stat sbuf;
|
||||||
|
|
||||||
if (0 != strcmp (method, "GET"))
|
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
|
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}.
|
server side and if so, the client should be informed with @code{MHD_HTTP_INTERNAL_SERVER_ERROR}.
|
||||||
|
|
||||||
@verbatim
|
@verbatim
|
||||||
/* error accessing file */
|
/* error accessing file */
|
||||||
if (fd != -1) close (fd);
|
if (fd != -1) close (fd);
|
||||||
const char *errorstr =
|
const char *errorstr =
|
||||||
"<html><body>An internal server error has occurred!\
|
"<html><body>An internal server error has occurred!\
|
||||||
</body></html>";
|
</body></html>";
|
||||||
response =
|
response =
|
||||||
MHD_create_response_from_buffer (strlen (errorstr),
|
MHD_create_response_from_buffer (strlen (errorstr),
|
||||||
(void *) errorstr,
|
(void *) errorstr,
|
||||||
MHD_RESPMEM_PERSISTENT);
|
MHD_RESPMEM_PERSISTENT);
|
||||||
if (response)
|
if (response)
|
||||||
{
|
{
|
||||||
@@ -77,25 +77,25 @@ server side and if so, the client should be informed with @code{MHD_HTTP_INTERNA
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
return MHD_NO;
|
return MHD_NO;
|
||||||
if (!ret)
|
if (!ret)
|
||||||
{
|
{
|
||||||
const char *errorstr = "<html><body>An internal server error has occurred!\
|
const char *errorstr = "<html><body>An internal server error has occurred!\
|
||||||
</body></html>";
|
</body></html>";
|
||||||
|
|
||||||
if (buffer) free(buffer);
|
if (buffer) free(buffer);
|
||||||
|
|
||||||
response = MHD_create_response_from_buffer (strlen(errorstr), (void*) errorstr,
|
response = MHD_create_response_from_buffer (strlen(errorstr), (void*) errorstr,
|
||||||
MHD_RESPMEM_PERSISTENT);
|
MHD_RESPMEM_PERSISTENT);
|
||||||
|
|
||||||
if (response)
|
if (response)
|
||||||
{
|
{
|
||||||
ret = MHD_queue_response (connection,
|
ret = MHD_queue_response (connection,
|
||||||
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||||
response);
|
response);
|
||||||
MHD_destroy_response (response);
|
MHD_destroy_response (response);
|
||||||
|
|
||||||
return MHD_YES;
|
return MHD_YES;
|
||||||
}
|
}
|
||||||
else return MHD_NO;
|
else return MHD_NO;
|
||||||
}
|
}
|
||||||
@end verbatim
|
@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);
|
MHD_add_response_header(response, "Content-Type", MIMETYPE);
|
||||||
@end verbatim
|
@end verbatim
|
||||||
@noindent
|
@noindent
|
||||||
We do not have to append a colon expected by the protocol behind the first
|
We do not have to append a colon expected by the protocol behind the first
|
||||||
field---@emph{GNU libhttpdmicro} will take care of this.
|
field---@emph{GNU libhttpdmicro} will take care of this.
|
||||||
|
|
||||||
The function finishes with the well-known lines
|
The function finishes with the well-known lines
|
||||||
@verbatim
|
@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
|
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
|
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
|
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.
|
communicate with each other.
|
||||||
|
|
||||||
The method of creating the response from a file on disk only works for static content.
|
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
|
@item
|
||||||
Remember that the original program was written under a few assumptions---a static response
|
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
|
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
|
@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.
|
@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
|
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.
|
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.
|
started and try again.
|
||||||
|
|
||||||
|
|
||||||
@item
|
@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
|
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
|
@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,
|
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
|
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)
|
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
|
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
|
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.
|
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
|
@code{<table>} consisting of one row and @code{n} columns whose fields contain small images of either
|
||||||
a red or a green light.
|
a red or a green light.
|
||||||
|
|
||||||
|
|||||||
@@ -308,7 +308,7 @@ access_handler (void *cls,
|
|||||||
{
|
{
|
||||||
static int aptr;
|
static int aptr;
|
||||||
struct MHD_Response *response;
|
struct MHD_Response *response;
|
||||||
int ret;
|
enum MHD_Result ret;
|
||||||
|
|
||||||
(void) cls; /* Unused. Silent compiler warning. */
|
(void) cls; /* Unused. Silent compiler warning. */
|
||||||
(void) upload_data; /* Unused. Silent compiler warning. */
|
(void) upload_data; /* Unused. Silent compiler warning. */
|
||||||
|
|||||||
Reference in New Issue
Block a user