mirror of
https://git.gnunet.org/libmicrohttpd.git
synced 2026-09-25 04:09:31 +03:00
+2
-1
@@ -24,6 +24,7 @@ microhttpd_TEXINFOS = \
|
||||
microhttpd_tutorial_TEXINFOS = \
|
||||
chapters/basicauthentication.inc \
|
||||
chapters/bibliography.inc \
|
||||
chapters/callbackresponse.inc \
|
||||
chapters/exploringrequests.inc \
|
||||
chapters/hellobrowser.inc \
|
||||
chapters/introduction.inc \
|
||||
@@ -32,7 +33,7 @@ microhttpd_tutorial_TEXINFOS = \
|
||||
chapters/responseheaders.inc \
|
||||
chapters/tlsauthentication.inc \
|
||||
chapters/sessions.inc \
|
||||
chapters/websocket.inc
|
||||
chapters/upgrade.inc
|
||||
|
||||
EXTRA_DIST = \
|
||||
$(microhttpd_TEXINFOS) $(microhttpd_tutorial_TEXINFOS) \
|
||||
|
||||
@@ -30,7 +30,7 @@ start there. It will, however, turn out to have still severe weaknesses in
|
||||
terms of security which need consideration.
|
||||
|
||||
Before we will start implementing @emph{Basic Authentication} as described in
|
||||
@emph{RFC 2617}, we will also abandon the simplistic and generally
|
||||
@emph{RFC 7617}, we will also abandon the simplistic and generally
|
||||
problematic practice of responding every request the first time our callback
|
||||
is called for a given connection. Queuing a response upon the first request
|
||||
is akin to generating an error response (even if it is a "200 OK" reply!).
|
||||
@@ -64,8 +64,9 @@ application should suspend the connection).
|
||||
@end enumerate
|
||||
|
||||
But how can we tell whether the callback has been called before for the
|
||||
particular request? Initially, the pointer this parameter references is
|
||||
set by @emph{MHD} in the callback. But it will also be "remembered" on the
|
||||
particular request? This is what the @code{req_cls} parameter of the callback
|
||||
is for: initially, the pointer it references is set to NULL by @emph{MHD}.
|
||||
But whatever we store there will be "remembered" on the
|
||||
next call (for the same request). Thus, we can use the @code{req_cls}
|
||||
location to keep track of the request state. For now, we will simply
|
||||
generate no response until the parameter is non-null---implying the callback
|
||||
@@ -77,7 +78,7 @@ will be pointing to a legal address, so we take this.
|
||||
The first time @code{answer_to_connection} is called, we will not even look at the headers.
|
||||
|
||||
@verbatim
|
||||
static int
|
||||
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,
|
||||
@@ -104,11 +105,13 @@ correct username/password, so every "GET" request will be challenged.
|
||||
@emph{RFC 7617} describes how the server shall ask for authentication by
|
||||
adding a @emph{WWW-Authenticate} response header with the name of the
|
||||
@emph{realm} protected. MHD can generate and queue such a failure response
|
||||
for you using the @code{MHD_queue_basic_auth_fail_response} API. The only
|
||||
for you using the @code{MHD_queue_basic_auth_required_response3} API. The only
|
||||
thing you need to do is construct a response with the error page to be shown
|
||||
to the user if he aborts basic authentication. But first, you should check if
|
||||
the proper credentials were already supplied using the
|
||||
@code{MHD_basic_auth_get_username_password} call.
|
||||
@code{MHD_basic_auth_get_username_password3} call. (The older
|
||||
@code{MHD_queue_basic_auth_fail_response} and
|
||||
@code{MHD_basic_auth_get_username_password} calls are deprecated.)
|
||||
|
||||
Your code would then look like this:
|
||||
@verbatim
|
||||
@@ -135,10 +138,10 @@ answer_to_connection (void *cls, struct MHD_Connection *connection,
|
||||
static const char *page =
|
||||
"<html><body>Authorization required</body></html>";
|
||||
response = MHD_create_response_from_buffer_static (strlen (page), page);
|
||||
ret = MHD_queue_basic_auth_fail_response3 (connection,
|
||||
"admins",
|
||||
MHD_YES,
|
||||
response);
|
||||
ret = MHD_queue_basic_auth_required_response3 (connection,
|
||||
"admins",
|
||||
MHD_YES,
|
||||
response);
|
||||
}
|
||||
else if ((strlen ("root") != auth_info->username_len) ||
|
||||
(0 != memcmp (auth_info->username, "root",
|
||||
@@ -153,10 +156,10 @@ answer_to_connection (void *cls, struct MHD_Connection *connection,
|
||||
static const char *page =
|
||||
"<html><body>Wrong username or password</body></html>";
|
||||
response = MHD_create_response_from_buffer_static (strlen (page), page);
|
||||
ret = MHD_queue_basic_auth_fail_response3 (connection,
|
||||
"admins",
|
||||
MHD_YES,
|
||||
response);
|
||||
ret = MHD_queue_basic_auth_required_response3 (connection,
|
||||
"admins",
|
||||
MHD_YES,
|
||||
response);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
@heading API reference
|
||||
@itemize @bullet
|
||||
@item
|
||||
The @emph{GNU libmicrohttpd} manual by Marco Maggi and Christian Grothoff 2008
|
||||
@uref{http://gnunet.org/libmicrohttpd/microhttpd.html}
|
||||
The @emph{GNU libmicrohttpd} manual by Marco Maggi and Christian Grothoff
|
||||
@uref{https://www.gnu.org/software/libmicrohttpd/manual/libmicrohttpd.html}
|
||||
|
||||
@item
|
||||
All referenced RFCs can be found on the website of @emph{The Internet Engineering Task Force}
|
||||
@uref{http://www.ietf.org/}
|
||||
@uref{https://www.ietf.org/}
|
||||
|
||||
@item
|
||||
@emph{RFC 2616}: Fielding, R., Gettys, J., Mogul, J., Frystyk, H., and T. Berners-Lee,
|
||||
"Hypertext Transfer Protocol -- HTTP/1.1", RFC 2016, January 1997.
|
||||
@emph{RFC 2616}: Fielding, R., Gettys, J., Mogul, J., Frystyk, H., Masinter, L., Leach, P.,
|
||||
and T. Berners-Lee, "Hypertext Transfer Protocol -- HTTP/1.1", RFC 2616, June 1999.
|
||||
|
||||
@item
|
||||
@emph{RFC 2617}: Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P.,
|
||||
@@ -20,13 +20,14 @@ Luotonen, A., and L. Stewart, "HTTP Authentication: Basic and Digest Access Auth
|
||||
@emph{RFC 6455}: Fette, I., Melnikov, A., "The WebSocket Protocol", RFC 6455, December 2011.
|
||||
|
||||
|
||||
@item
|
||||
@item
|
||||
A well--structured @emph{HTML} reference can be found on
|
||||
@uref{http://www.echoecho.com/html.htm}
|
||||
|
||||
For those readers understanding German or French, there is an excellent document both for learning
|
||||
@emph{HTML} and for reference, whose English version unfortunately has been discontinued.
|
||||
@uref{http://de.selfhtml.org/} and @uref{http://fr.selfhtml.org/}
|
||||
For those readers understanding German, there is an excellent document both for learning
|
||||
@emph{HTML} and for reference, whose English and French versions unfortunately have been
|
||||
discontinued.
|
||||
@uref{https://wiki.selfhtml.org/}
|
||||
|
||||
|
||||
@end itemize
|
||||
|
||||
@@ -0,0 +1,467 @@
|
||||
All responses we have built so far were complete before they were
|
||||
queued. @code{MHD_create_response_from_buffer()} needs the whole
|
||||
answer to be sitting in memory already, and
|
||||
@code{MHD_create_response_from_fd()} needs it to be a file that the
|
||||
operating system can read for us. Neither fits an answer that is
|
||||
computed while it is being sent: the output of a long-running report,
|
||||
a log file that is filtered on the fly, the result of a database
|
||||
cursor, or simply a document that is far too large to be worth
|
||||
assembling in a buffer first.
|
||||
|
||||
For those cases @emph{libmicrohttpd} lets the application hand over a
|
||||
callback instead of the data. Whenever @emph{MHD} has room on the
|
||||
socket, it calls that function and asks for the next few bytes. The
|
||||
application only ever needs one modest buffer, and the client starts
|
||||
receiving the answer long before the last byte has been produced.
|
||||
|
||||
|
||||
@heading Creating the response
|
||||
|
||||
The response object is created with
|
||||
|
||||
@verbatim
|
||||
struct MHD_Response *
|
||||
MHD_create_response_from_callback (uint64_t size,
|
||||
size_t block_size,
|
||||
MHD_ContentReaderCallback crc,
|
||||
void *crc_cls,
|
||||
MHD_ContentReaderFreeCallback crfc);
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
The five arguments are:
|
||||
|
||||
@table @code
|
||||
@item size
|
||||
The total length of the body in bytes, or the constant
|
||||
@code{MHD_SIZE_UNKNOWN} if the application does not know it in
|
||||
advance. If a concrete number is given, @emph{MHD} announces it as
|
||||
@code{Content-Length} and expects the callback to deliver exactly that
|
||||
many bytes.
|
||||
|
||||
@item block_size
|
||||
The buffer size @emph{MHD} should use when it queries the callback.
|
||||
The value must not be zero---@code{MHD_create_response_from_callback()}
|
||||
returns @code{NULL} otherwise. @emph{MHD} allocates a buffer of that
|
||||
size together with the response object. Note that this buffer is only
|
||||
used for responses of known size; when chunked transfer encoding is in
|
||||
use (see below) the data is collected in the connection's own memory
|
||||
pool instead, and @code{block_size} has no influence on how much
|
||||
@emph{MHD} asks for. In either case the value is advisory: the
|
||||
callback must always look at the @code{max} argument it is passed and
|
||||
never write more than that.
|
||||
|
||||
@item crc
|
||||
The content reader callback itself, described in the next section.
|
||||
|
||||
@item crc_cls
|
||||
An arbitrary pointer that is passed back to @code{crc} on every
|
||||
invocation. This is where the per-request state goes.
|
||||
|
||||
@item crfc
|
||||
A callback that is invoked once, when the response object is
|
||||
destroyed, so that @code{crc_cls} can be released. May be
|
||||
@code{NULL} if there is nothing to clean up.
|
||||
@end table
|
||||
|
||||
|
||||
@heading The content reader callback
|
||||
|
||||
The callback has the following signature:
|
||||
|
||||
@verbatim
|
||||
typedef ssize_t
|
||||
(*MHD_ContentReaderCallback) (void *cls,
|
||||
uint64_t pos,
|
||||
char *buf,
|
||||
size_t max);
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
@code{cls} is the @code{crc_cls} pointer given at creation time.
|
||||
@code{buf} points to the memory @emph{MHD} wants filled and
|
||||
@code{max} is the number of bytes that may be written there.
|
||||
|
||||
The @code{pos} argument deserves a closer look, because its meaning
|
||||
depends on how the response is used. For a response created from a
|
||||
file descriptor, @emph{MHD} uses it as a file offset and reads with
|
||||
@code{pread()}; that is what makes such a response object reusable for
|
||||
many connections. For a response that is created fresh for each
|
||||
request---which is what we do here---@emph{MHD} guarantees that
|
||||
@code{pos} is simply the sum of all non-negative values the callback
|
||||
has returned so far. In other words: for a stream, @code{pos} is a
|
||||
monotonically increasing count of the bytes already produced, and the
|
||||
callback will never be asked to go back. A generator that has no use
|
||||
for random access can ignore @code{pos} completely. Do note that the
|
||||
guarantee only holds as long as the response object is used for one
|
||||
request; if the same object is queued for several connections, the
|
||||
same content reader may be asked for the same data more than once, and
|
||||
sharing one mutable buffer between them would be wrong.
|
||||
|
||||
The return value tells @emph{MHD} what happened:
|
||||
|
||||
@table @asis
|
||||
@item a positive number
|
||||
That many bytes were written to @code{buf}. Returning more than
|
||||
@code{max} is a fatal application error and makes @emph{MHD} close the
|
||||
connection.
|
||||
|
||||
@item @code{MHD_CONTENT_READER_END_OF_STREAM}
|
||||
The body is complete. With chunked encoding @emph{MHD} terminates the
|
||||
chunk sequence properly and appends any footers; without chunked
|
||||
encoding and an unknown size it simply closes the connection. If a
|
||||
concrete @code{size} was announced, ending the stream early is not
|
||||
legal, and @emph{MHD} treats it essentially like an error---the client
|
||||
receives fewer bytes than the @code{Content-Length} promised.
|
||||
|
||||
@item @code{MHD_CONTENT_READER_END_WITH_ERROR}
|
||||
Something went wrong while generating the data; see below.
|
||||
|
||||
@item @code{0}
|
||||
No data is available @emph{right now}, but the stream is not over.
|
||||
@end table
|
||||
|
||||
@noindent
|
||||
That last case is the dangerous one. The header file states that
|
||||
returning zero is legal only when @emph{MHD} is @emph{not} using an
|
||||
internal sockets polling mode, and this is not a formality:
|
||||
@emph{MHD} does not diagnose the situation, it simply arranges for the
|
||||
next poll to time out immediately and calls the callback again. A
|
||||
callback that returns zero while a request is stalled for a third of a
|
||||
second is called several million times in that period and pins a CPU
|
||||
core at 100%. In external polling mode the effect is milder---the
|
||||
callback is retried whenever the application calls one of the
|
||||
@code{MHD_run*()} functions---but it is still a spin. The proper
|
||||
answer for "no data yet" is to suspend the connection, which is
|
||||
covered further down.
|
||||
|
||||
|
||||
@heading Keeping state per request
|
||||
|
||||
Because the callback is a plain C function, everything it needs to
|
||||
know has to travel through @code{crc_cls}. The natural shape of a
|
||||
streaming handler is therefore: allocate a context on the heap,
|
||||
allocate its data buffer, create the response with that context as
|
||||
@code{crc_cls}, and let the free callback dispose of both.
|
||||
|
||||
@verbatim
|
||||
struct ResponseContext
|
||||
{
|
||||
char *buf; /* heap buffer, refilled again and again */
|
||||
size_t fill; /* number of valid bytes in buf */
|
||||
size_t off; /* number of bytes of buf given to MHD */
|
||||
unsigned int block; /* number of blocks produced so far */
|
||||
};
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
The two offsets are what make the buffer independent of @emph{MHD}'s
|
||||
appetite. A block is generated once and may then be handed out over
|
||||
several invocations, because @code{max} can be smaller than what we
|
||||
have ready:
|
||||
|
||||
@verbatim
|
||||
static ssize_t
|
||||
content_reader (void *cls, uint64_t pos, char *buf, size_t max)
|
||||
{
|
||||
struct ResponseContext *ctx = cls;
|
||||
size_t ready;
|
||||
|
||||
(void) pos;
|
||||
|
||||
if (ctx->off == ctx->fill)
|
||||
{
|
||||
/* Everything we had was passed on, produce the next block. */
|
||||
if (BLOCK_COUNT == ctx->block)
|
||||
return MHD_CONTENT_READER_END_OF_STREAM;
|
||||
generate_block (ctx);
|
||||
}
|
||||
ready = ctx->fill - ctx->off;
|
||||
if (ready > max)
|
||||
ready = max;
|
||||
memcpy (buf, &ctx->buf[ctx->off], ready);
|
||||
ctx->off += ready;
|
||||
return (ssize_t) ready;
|
||||
}
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
@code{generate_block()} is the place where a real application would
|
||||
run its expensive computation, query, or network fetch. The important
|
||||
property is that the same buffer is reused every time, so the memory
|
||||
the server needs does not grow with the size of the document.
|
||||
|
||||
The handler assembles all of this and, from that moment on, hands
|
||||
ownership of the context to the response object:
|
||||
|
||||
@verbatim
|
||||
ctx = malloc (sizeof (struct ResponseContext));
|
||||
if (NULL == ctx)
|
||||
return MHD_NO;
|
||||
ctx->buf = malloc (BUFFER_SIZE);
|
||||
if (NULL == ctx->buf)
|
||||
{
|
||||
free (ctx);
|
||||
return MHD_NO;
|
||||
}
|
||||
ctx->fill = 0;
|
||||
ctx->off = 0;
|
||||
ctx->block = 0;
|
||||
|
||||
response = MHD_create_response_from_callback (MHD_SIZE_UNKNOWN,
|
||||
IO_BLOCK_SIZE,
|
||||
&content_reader,
|
||||
ctx,
|
||||
&free_context);
|
||||
if (NULL == response)
|
||||
{
|
||||
/* No response object exists, so nobody will call the free
|
||||
callback for us. */
|
||||
free_context (ctx);
|
||||
return MHD_NO;
|
||||
}
|
||||
ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
|
||||
MHD_destroy_response (response);
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
Note the error path: as long as
|
||||
@code{MHD_create_response_from_callback()} has not returned a response,
|
||||
the free callback will never run and the application must clean up
|
||||
itself.
|
||||
|
||||
|
||||
@heading Releasing the state
|
||||
|
||||
The free callback is trivial:
|
||||
|
||||
@verbatim
|
||||
static void
|
||||
free_context (void *cls)
|
||||
{
|
||||
struct ResponseContext *ctx = cls;
|
||||
|
||||
free (ctx->buf);
|
||||
free (ctx);
|
||||
}
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
@emph{MHD} invokes it when the last reference to the response object
|
||||
goes away. The call to @code{MHD_destroy_response()} in the handler
|
||||
does not trigger it, because queuing the response took a reference;
|
||||
the callback runs when the connection is finished with the response.
|
||||
That happens after a completed body, but equally after a client that
|
||||
closed the socket in the middle of the download, after a timeout, and
|
||||
when the daemon is stopped. This is precisely what makes the free
|
||||
callback the right place for the cleanup: there is no other hook that
|
||||
covers all of these cases.
|
||||
|
||||
|
||||
@heading Unknown size and chunked encoding
|
||||
|
||||
Passing @code{MHD_SIZE_UNKNOWN} as the size leaves @emph{MHD} with the
|
||||
problem of telling the client where the body ends. For an HTTP/1.1
|
||||
client it solves it by switching to chunked transfer encoding: it adds
|
||||
a @code{Transfer-Encoding: chunked} header and frames every piece the
|
||||
callback returns as one chunk, terminating the body with a zero-length
|
||||
chunk when the callback reports the end of the stream. Nothing has to
|
||||
be done for this; it is a consequence of the unknown size.
|
||||
|
||||
@verbatim
|
||||
< HTTP/1.1 200 OK
|
||||
< Content-Type: text/plain
|
||||
< Transfer-Encoding: chunked
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
An HTTP/1.0 client cannot be sent chunks. In that case @emph{MHD}
|
||||
falls back to the only other framing the protocol offers and marks the
|
||||
connection as one that must be closed: the body is sent raw, without
|
||||
@code{Content-Length}, and its end is the end of the connection. The
|
||||
data arrives intact, but the client has no way to distinguish a
|
||||
complete answer from one that was cut short.
|
||||
|
||||
If the size @emph{is} known---and in the example program the URL
|
||||
@code{/sized} shows this variant---it should be announced, simply by
|
||||
passing the number instead of @code{MHD_SIZE_UNKNOWN}. @emph{MHD}
|
||||
then emits a normal @code{Content-Length} header, no chunk framing is
|
||||
added, and the callback is queried with @code{max} limited by both
|
||||
@code{block_size} and the number of bytes still outstanding. The
|
||||
callback must produce exactly the announced number of bytes.
|
||||
|
||||
|
||||
@heading Aborting a response
|
||||
|
||||
Generating data can fail halfway through---the database connection
|
||||
drops, a file disappears, an allocation fails. Since the response
|
||||
headers, and probably a good part of the body, have already been sent,
|
||||
there is no way to turn the answer into an error page any more.
|
||||
Returning @code{MHD_CONTENT_READER_END_WITH_ERROR} is the way to give
|
||||
up: @emph{MHD} logs the condition and closes the connection
|
||||
immediately, without writing the terminating chunk.
|
||||
|
||||
What the client sees depends on the framing. With chunked encoding it
|
||||
is an abnormally terminated chunk stream, and a well-behaved client
|
||||
reports it as such---@code{curl} for instance fails with "transfer
|
||||
closed with outstanding read data remaining" and a non-zero exit
|
||||
status. With an announced @code{Content-Length} the client notices
|
||||
that the body is shorter than promised. Only in the HTTP/1.0 case
|
||||
discussed above, where the end of the body is the end of the
|
||||
connection, is the failure indistinguishable from a normal ending;
|
||||
that is a property of the protocol and not something @emph{MHD} can
|
||||
repair. The URL @code{/error} of the example program aborts the
|
||||
stream after a few blocks so that this can be observed.
|
||||
|
||||
|
||||
@heading When the data is not ready yet
|
||||
|
||||
So far the generator could always produce the next block on the spot.
|
||||
If it cannot---because the data is coming from another thread, another
|
||||
process, or the network---then, as explained above, returning
|
||||
@code{0} in an internal polling mode turns the daemon into a busy
|
||||
loop. The supported answer is to take the connection out of the event
|
||||
loop until the data has arrived, with the pair
|
||||
|
||||
@verbatim
|
||||
void MHD_suspend_connection (struct MHD_Connection *connection);
|
||||
void MHD_resume_connection (struct MHD_Connection *connection);
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
The daemon has to be started with the @code{MHD_ALLOW_SUSPEND_RESUME}
|
||||
flag; both functions call @code{MHD_PANIC()} and abort the process
|
||||
otherwise. The flag implies @code{MHD_USE_ITC}, so that a resumed
|
||||
connection wakes the polling thread up instead of waiting for the next
|
||||
timeout. Suspending is not available for daemons running with
|
||||
@code{MHD_USE_THREAD_PER_CONNECTION}, and the daemon must not be
|
||||
stopped while connections are still suspended.
|
||||
|
||||
@code{MHD_suspend_connection()} may only be called from the access
|
||||
handler or from the content reader callback, which means the
|
||||
connection handle has to be stored in the context struct so that the
|
||||
callback can reach it. The pattern is then:
|
||||
|
||||
@verbatim
|
||||
if (! ctx->data_available)
|
||||
{
|
||||
MHD_suspend_connection (ctx->connection);
|
||||
return 0;
|
||||
}
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
@code{MHD_resume_connection()}, in contrast, may be called from any
|
||||
thread and at any time. In particular it is explicitly allowed to
|
||||
call it before the suspension has taken effect: the producer may
|
||||
already be done by the time the callback gets around to suspending.
|
||||
@emph{MHD} handles this by remembering the resume request and, when the
|
||||
suspension is finally processed, cancelling it instead of parking the
|
||||
connection, so the wakeup can never be lost. This is what makes the
|
||||
"check, suspend, return 0" sequence above safe without a lock of its
|
||||
own.
|
||||
|
||||
The difference is dramatic in practice. A callback that stalls for
|
||||
three hundred milliseconds and returns @code{0} is entered several
|
||||
million times and burns a full core; the same callback suspending the
|
||||
connection is entered exactly once more and the daemon uses no
|
||||
measurable CPU time at all.
|
||||
|
||||
|
||||
@heading Trailers
|
||||
|
||||
Chunked responses may carry trailers, that is header fields that are
|
||||
transmitted after the body. They are added with
|
||||
|
||||
@verbatim
|
||||
MHD_add_response_footer (response, "X-Checksum", "deadbeef");
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
@emph{MHD} writes them out after the terminating zero-length chunk:
|
||||
|
||||
@verbatim
|
||||
0
|
||||
X-Checksum: deadbeef
|
||||
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
Footers may still be added while the body is being generated---even
|
||||
from inside the content reader callback, immediately before returning
|
||||
@code{MHD_CONTENT_READER_END_OF_STREAM}---which is what makes them
|
||||
useful in the first place: a checksum or a row count over data that
|
||||
has only just been produced cannot be put into the headers, because
|
||||
those are long gone. This is safe as long as the response object
|
||||
belongs to a single request, as it does in the pattern used here.
|
||||
@emph{MHD} does not generate a @code{Trailer} response header by
|
||||
itself; if the client should be told in advance which fields to
|
||||
expect, add it with @code{MHD_add_response_header()}. Be aware that
|
||||
many clients discard trailers silently.
|
||||
|
||||
|
||||
@heading Example code
|
||||
|
||||
The complete program is available as @code{callbackresponse.c}. It
|
||||
serves a document of sixty-four blocks that are produced one at a time
|
||||
into a single heap buffer. @code{GET /} streams it with
|
||||
@code{MHD_SIZE_UNKNOWN} and thus with chunked encoding,
|
||||
@code{GET /sized} streams the same bytes with a @code{Content-Length}
|
||||
announced up front, and @code{GET /error} aborts in the middle.
|
||||
|
||||
|
||||
@heading Remarks
|
||||
|
||||
Calls into the content reader of one response object are serialized by
|
||||
@emph{MHD}, so a per-request context needs no locking of its own as
|
||||
long as no other thread touches it. As soon as a producer thread is
|
||||
involved---the suspend/resume case---the shared fields do need
|
||||
protection.
|
||||
|
||||
The chunk boundaries are not under the application's control and carry
|
||||
no meaning: a client may receive the bytes of one callback invocation
|
||||
spread over several chunks or, in other configurations, several
|
||||
invocations combined. Chunked encoding is a transport detail, and any
|
||||
record structure the application needs must be part of the body
|
||||
itself.
|
||||
|
||||
Finally, returning very small amounts of data per invocation is
|
||||
correct but wasteful, since every invocation may become its own chunk
|
||||
with its own framing overhead. If the producer has more data ready, it
|
||||
should fill as much of @code{buf} as @code{max} permits.
|
||||
|
||||
|
||||
@heading Exercises
|
||||
|
||||
@itemize @bullet
|
||||
|
||||
@item
|
||||
Add a URL that reports how far the transfer has progressed by writing
|
||||
the value of @code{pos} into every line, and confirm with a slow
|
||||
client (@code{curl --limit-rate}) that it really is the number of
|
||||
bytes already sent, and not a block counter.
|
||||
|
||||
@item
|
||||
Register a request completion callback with
|
||||
@code{MHD_OPTION_NOTIFY_COMPLETED} and print the termination code.
|
||||
Then download the document, interrupt the client in the middle, and
|
||||
watch in which order the completion callback and the content reader's
|
||||
free callback are invoked, and that the free callback runs in both
|
||||
cases.
|
||||
|
||||
@item
|
||||
Turn the generator into an asynchronous one. Start a thread that
|
||||
appends a line to a shared buffer once per second, keep the connection
|
||||
handle in the context struct, and suspend the connection whenever the
|
||||
buffer is empty. Do not forget @code{MHD_ALLOW_SUSPEND_RESUME} when
|
||||
starting the daemon. Then remove the call to
|
||||
@code{MHD_suspend_connection()}, leaving only the @code{return 0}, and
|
||||
compare the CPU usage of the server process.
|
||||
|
||||
@item
|
||||
Compute a checksum over the generated document while it is being sent
|
||||
and append it as a trailer. Watch it on the wire---most command line
|
||||
clients will not show it to you, so a raw @code{nc} or a small script
|
||||
speaking HTTP by hand is the easier tool here.
|
||||
|
||||
@end itemize
|
||||
@@ -1,5 +1,5 @@
|
||||
This chapter will deal with the information which the client sends to the
|
||||
server at every request. We are going to examine the most useful fields of such an request
|
||||
server at every request. We are going to examine the most useful fields of such a request
|
||||
and print them out in a readable manner. This could be useful for logging facilities.
|
||||
|
||||
The starting point is the @emph{hellobrowser} program with the former response removed.
|
||||
@@ -9,9 +9,9 @@ just return MHD_NO after we have probed the request. This way, the connection is
|
||||
without much ado by the server.
|
||||
|
||||
@verbatim
|
||||
static int
|
||||
answer_to_connection (void *cls, struct MHD_Connection *connection,
|
||||
const char *url,
|
||||
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)
|
||||
@@ -34,15 +34,16 @@ printf ("New %s request for %s using version %s\n", method, url, version);
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
The rest of the information is a bit more hidden. Nevertheless, there is lot of it sent from common
|
||||
The rest of the information is a bit more hidden. Nevertheless, there is a lot of it sent from common
|
||||
Internet browsers. It is stored in "key-value" pairs and we want to list what we find in the header.
|
||||
As there is no mandatory set of keys a client has to send, each key-value pair is printed out one by
|
||||
one until there are no more left. We do this by writing a separate function which will be called for
|
||||
each pair just like the above function is called for each HTTP request.
|
||||
It can then print out the content of this pair.
|
||||
@verbatim
|
||||
int print_out_key (void *cls, enum MHD_ValueKind kind,
|
||||
const char *key, const char *value)
|
||||
static enum MHD_Result
|
||||
print_out_key (void *cls, enum MHD_ValueKind kind,
|
||||
const char *key, const char *value)
|
||||
{
|
||||
printf ("%s: %s\n", key, value);
|
||||
return MHD_YES;
|
||||
@@ -63,8 +64,8 @@ so the last parameter can be NULL.
|
||||
All in all, this constitutes the complete @code{logging.c} program for this chapter which can be
|
||||
found in the @code{examples} section.
|
||||
|
||||
Connecting with any modern Internet browser should yield a handful of keys. You should try to
|
||||
interpret them with the aid of @emph{RFC 2616}.
|
||||
Connecting with any modern Internet browser should yield a handful of keys. You should try to
|
||||
interpret them with the aid of @emph{RFC 9110}.
|
||||
Especially worth mentioning is the "Host" key which is often used to serve several different websites
|
||||
hosted under one single IP address but reachable by different domain names (this is called virtual hosting).
|
||||
|
||||
@@ -93,9 +94,10 @@ returned accompanied by an informative message.
|
||||
A very interesting information has still been ignored by our logger---the client's IP address.
|
||||
Implement a callback function
|
||||
@verbatim
|
||||
static int on_client_connect (void *cls,
|
||||
const struct sockaddr *addr,
|
||||
socklen_t addrlen)
|
||||
static enum MHD_Result
|
||||
on_client_connect (void *cls,
|
||||
const struct sockaddr *addr,
|
||||
socklen_t addrlen)
|
||||
@end verbatim
|
||||
@noindent
|
||||
that prints out the IP address in an appropriate format. You might want to use the POSIX function
|
||||
|
||||
@@ -14,7 +14,9 @@ After the necessary includes and the definition of the port which our server sho
|
||||
#include <sys/types.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <string.h>
|
||||
#include <microhttpd.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define PORT 8888
|
||||
|
||||
@@ -39,11 +41,12 @@ daemon to sent the reply.
|
||||
|
||||
Talking about the reply, it is defined as a string right after the function header
|
||||
@verbatim
|
||||
int 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)
|
||||
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)
|
||||
{
|
||||
const char *page = "<html><body>Hello, browser!</body></html>";
|
||||
|
||||
@@ -54,21 +57,22 @@ HTTP is a rather strict protocol and the client would certainly consider it "ina
|
||||
just sent the answer string "as is". Instead, it has to be wrapped with additional information stored in so-called headers and footers. Most of the work in this area is done by the library for us---we
|
||||
just have to ask. Our reply string packed in the necessary layers will be called a "response".
|
||||
To obtain such a response we hand our data (the reply--string) and its size over to the
|
||||
@code{MHD_create_response_from_buffer} function. The last two parameters basically tell @emph{MHD}
|
||||
that we do not want it to dispose the message data for us when it has been sent and there also needs
|
||||
no internal copy to be done because the @emph{constant} string won't change anyway.
|
||||
@code{MHD_create_response_from_buffer_static} function. By using the @code{_static}
|
||||
variant we tell @emph{MHD} that the buffer will stay valid and unchanged for at least
|
||||
as long as the response lives, so that @emph{MHD} neither has to free the message data
|
||||
for us when it has been sent nor has to make an internal copy of it---which is exactly
|
||||
the case for our @emph{constant} string.
|
||||
|
||||
@verbatim
|
||||
struct MHD_Response *response;
|
||||
int ret;
|
||||
enum MHD_Result ret;
|
||||
|
||||
response = MHD_create_response_from_buffer (strlen (page),
|
||||
(void*) page, MHD_RESPMEM_PERSISTENT);
|
||||
response = MHD_create_response_from_buffer_static (strlen (page), page);
|
||||
|
||||
@end verbatim
|
||||
|
||||
@noindent
|
||||
Now that the the response has been laced up, it is ready for delivery and can be queued for sending.
|
||||
Now that the response has been laced up, it is ready for delivery and can be queued for sending.
|
||||
This is done by passing it to another @emph{GNU libmicrohttpd} function. As all our work was done in
|
||||
the scope of one function, the recipient is without doubt the one associated with the
|
||||
local variable @code{connection} and consequently this variable is given to the queue function.
|
||||
@@ -91,27 +95,31 @@ already being set at this point to either MHD_YES or MHD_NO in case of success o
|
||||
With the primary task of our server implemented, we can start the actual server daemon which will listen
|
||||
on @code{PORT} for connections. This is done in the main function.
|
||||
@verbatim
|
||||
int main ()
|
||||
int main (void)
|
||||
{
|
||||
struct MHD_Daemon *daemon;
|
||||
|
||||
daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD, PORT, NULL, NULL,
|
||||
daemon = MHD_start_daemon (MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD,
|
||||
PORT, NULL, NULL,
|
||||
&answer_to_connection, NULL, MHD_OPTION_END);
|
||||
if (NULL == daemon) return 1;
|
||||
|
||||
@end verbatim
|
||||
|
||||
@noindent
|
||||
The first parameter is one of three possible modes of operation. Here we want the daemon to run in
|
||||
The first parameter is a bit-wise OR of flags selecting, among other things, the mode of
|
||||
operation. With @code{MHD_USE_INTERNAL_POLLING_THREAD} we want the daemon to run in
|
||||
a separate thread and to manage all incoming connections in the same thread. This means that while
|
||||
producing the response for one connection, the other connections will be put on hold. In this
|
||||
example, where the reply is already known and therefore the request is served quickly, this poses no problem.
|
||||
The additional @code{MHD_USE_AUTO} flag simply lets @emph{MHD} pick the best polling function
|
||||
(@code{epoll}, @code{poll} or @code{select}) available on the platform.
|
||||
|
||||
We will allow all clients to connect regardless of their name or location, therefore we do not check
|
||||
them on connection and set the third and fourth parameter to NULL.
|
||||
|
||||
Parameter five is the address of the function we want to be called whenever a new connection has been
|
||||
established. Our @code{answer_to_connection} knows best what the client wants and needs no additional
|
||||
Parameter five is the address of the function we want to be called whenever a new request has been
|
||||
received. Our @code{answer_to_connection} knows best what the client wants and needs no additional
|
||||
information (which could be passed via the next parameter) so the next (sixth) parameter is NULL. Likewise,
|
||||
we do not need to pass extra options to the daemon so we just write the MHD_OPTION_END as the last parameter.
|
||||
|
||||
@@ -168,16 +176,19 @@ Both of these issues you will find addressed in the official @code{minimal_examp
|
||||
the @code{src/examples} directory of the @emph{MHD} package. The source code of this
|
||||
program should look very familiar to you by now and easy to understand.
|
||||
|
||||
For our example, we create the response from a static (persistent) buffer in memory and thus pass @code{MHD_RESPMEM_PERSISTENT} to the response construction
|
||||
function. In the usual case, responses are not transmitted immediately
|
||||
For our example, we create the response from a static (persistent) buffer in memory and thus use
|
||||
@code{MHD_create_response_from_buffer_static}.
|
||||
In the usual case, responses are not transmitted immediately
|
||||
after being queued. For example, there might be other data on the system that needs to be sent with
|
||||
a higher priority. Nevertheless, the queue function will return successfully---raising the problem
|
||||
that the data we have pointed to may be invalid by the time it is about being sent. This is not an
|
||||
issue here because we can expect the @code{page} string, which is a constant @emph{string literal}
|
||||
here, to be static. That means it will be present and unchanged for as long as the program runs.
|
||||
For dynamic data, one could choose to either have @emph{MHD} free the memory @code{page} points
|
||||
to itself when it is not longer needed (by passing @code{MHD_RESPMEM_MUST_FREE}) or, alternatively, have the library to make and manage
|
||||
its own copy of it (by passing @code{MHD_RESPMEM_MUST_COPY}). Naturally, this last option is the most expensive.
|
||||
to itself when it is no longer needed (by using
|
||||
@code{MHD_create_response_from_buffer_with_free_callback} with @code{&free}) or, alternatively,
|
||||
have the library make and manage its own copy of it (by using
|
||||
@code{MHD_create_response_from_buffer_copy}). Naturally, this last option is the most expensive.
|
||||
|
||||
@heading Exercises
|
||||
@itemize @bullet
|
||||
@@ -200,9 +211,11 @@ and an altered response is sent? Make sure you read about the status codes in th
|
||||
|
||||
|
||||
@item
|
||||
Add the option @code{MHD_USE_PEDANTIC_CHECKS} to the start function of the daemon in @code{main}.
|
||||
Mind the special format of the parameter list here which is described in the manual. How indulgent
|
||||
is the server now to your input?
|
||||
Add the option @code{MHD_OPTION_CLIENT_DISCIPLINE_LVL} with a value of @code{1} to the start
|
||||
function of the daemon in @code{main} (this option supersedes the deprecated
|
||||
@code{MHD_USE_PEDANTIC_CHECKS} flag). Mind the special format of the parameter list here which is
|
||||
described in the manual: the option is followed by its @code{int} value, and @code{MHD_OPTION_END}
|
||||
still terminates the list. How indulgent is the server now to your input?
|
||||
|
||||
|
||||
@item
|
||||
|
||||
+77
-44
@@ -6,7 +6,7 @@ clients concurrently uploading, responding with a proper busy message if necessa
|
||||
|
||||
|
||||
@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{MHD_USE_INTERNAL_POLLING_THREAD} 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.
|
||||
@@ -45,13 +45,17 @@ const char* completepage = "<html><body>The upload has been completed.</body></h
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
We want the server to report internal errors, such as memory shortage or file access problems,
|
||||
adequately.
|
||||
We want the server to report malformed requests and internal errors, such as file access
|
||||
problems, adequately.
|
||||
@verbatim
|
||||
const char* servererrorpage
|
||||
= "<html><body>An internal server error has occurred.</body></html>";
|
||||
= "<html><body>Invalid request.</body></html>";
|
||||
const char* fileexistspage
|
||||
= "<html><body>This file already exists.</body></html>";
|
||||
const char* fileioerror
|
||||
= "<html><body>IO error writing to disk.</body></html>";
|
||||
const char* postprocerror
|
||||
= "<html><body>Error processing POST data.</body></html>";
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
@@ -62,13 +66,12 @@ status code but in order to improve the @code{HTTP} conformance of our server a
|
||||
@verbatim
|
||||
static enum MHD_Result
|
||||
send_page (struct MHD_Connection *connection,
|
||||
const char* page, int status_code)
|
||||
const char* page, unsigned int status_code)
|
||||
{
|
||||
enum MHD_Result ret;
|
||||
struct MHD_Response *response;
|
||||
|
||||
response = MHD_create_response_from_buffer (strlen (page), (void*) page,
|
||||
MHD_RESPMEM_MUST_COPY);
|
||||
response = MHD_create_response_from_buffer_copy (strlen (page), page);
|
||||
if (!response) return MHD_NO;
|
||||
|
||||
ret = MHD_queue_response (connection, status_code, response);
|
||||
@@ -79,14 +82,14 @@ send_page (struct MHD_Connection *connection,
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
Note how we ask @emph{MHD} to make its own copy of the message data. The reason behind this will
|
||||
become clear later.
|
||||
Note how we ask @emph{MHD} to make its own copy of the message data---that is what the
|
||||
@code{_copy} suffix stands for. The reason behind this will 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
|
||||
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 send any queued messages until
|
||||
a postprocessor has been created and the post iterator is called at least once.
|
||||
|
||||
@verbatim
|
||||
@@ -107,12 +110,14 @@ answer_to_connection (void *cls, struct MHD_Connection *connection,
|
||||
@noindent
|
||||
|
||||
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. The status code is set to zero, which
|
||||
we use as the marker for "no answer decided yet".
|
||||
|
||||
@verbatim
|
||||
con_info = malloc (sizeof (struct connection_info_struct));
|
||||
if (NULL == con_info) return MHD_NO;
|
||||
con_info->fp = 0;
|
||||
con_info->answercode = 0;
|
||||
con_info->fp = NULL;
|
||||
|
||||
if (0 == strcmp (method, "POST"))
|
||||
{
|
||||
@@ -131,8 +136,7 @@ For @emph{POST} requests, the postprocessor is created and we register a new upl
|
||||
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.
|
||||
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".
|
||||
message so that whenever a response is sent, the client will get the most informative message.
|
||||
@verbatim
|
||||
if (0 == strcmp (method, "POST"))
|
||||
{
|
||||
@@ -149,8 +153,6 @@ the structure is initialized to "no error".
|
||||
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
|
||||
@@ -164,7 +166,7 @@ own copy of it for as long as it is needed.
|
||||
{
|
||||
char buffer[1024];
|
||||
|
||||
sprintf (buffer, askpage, nr_of_uploading_clients);
|
||||
snprintf (buffer, sizeof (buffer), askpage, nr_of_uploading_clients);
|
||||
return send_page (connection, buffer, MHD_HTTP_OK);
|
||||
}
|
||||
@end verbatim
|
||||
@@ -174,7 +176,9 @@ own copy of it for as long as it is needed.
|
||||
The rest of the @code{answer_to_connection} function is very similar to the @code{simplepost.c}
|
||||
example, except the more flexible content of the responses. The @emph{POST} data is processed until
|
||||
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. Note that once we know the answer---because something
|
||||
went wrong---we no longer feed the data to the post processor, but we still have to consume it,
|
||||
or the client would never get to read our response.
|
||||
@verbatim
|
||||
if (0 == strcmp (method, "POST"))
|
||||
{
|
||||
@@ -182,15 +186,33 @@ constituted no expected request method.
|
||||
|
||||
if (0 != *upload_data_size)
|
||||
{
|
||||
MHD_post_process (con_info->postprocessor,
|
||||
upload_data, *upload_data_size);
|
||||
if (0 == con_info->answercode)
|
||||
{
|
||||
if (MHD_YES !=
|
||||
MHD_post_process (con_info->postprocessor,
|
||||
upload_data, *upload_data_size))
|
||||
{
|
||||
con_info->answerstring = postprocerror;
|
||||
con_info->answercode = MHD_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
}
|
||||
*upload_data_size = 0;
|
||||
|
||||
return MHD_YES;
|
||||
}
|
||||
else
|
||||
return send_page (connection, con_info->answerstring,
|
||||
con_info->answercode);
|
||||
|
||||
if (NULL != con_info->fp)
|
||||
{
|
||||
fclose (con_info->fp);
|
||||
con_info->fp = NULL;
|
||||
}
|
||||
if (0 == con_info->answercode)
|
||||
{
|
||||
con_info->answerstring = completepage;
|
||||
con_info->answercode = MHD_HTTP_OK;
|
||||
}
|
||||
return send_page (connection, con_info->answerstring,
|
||||
con_info->answercode);
|
||||
}
|
||||
|
||||
return send_page(connection, errorpage, MHD_HTTP_BAD_REQUEST);
|
||||
@@ -199,11 +221,11 @@ constituted no expected request method.
|
||||
@noindent
|
||||
|
||||
|
||||
@heading Storing to data
|
||||
@heading Storing the data
|
||||
Unlike the @code{simplepost.c} example, here it is to be expected that post iterator will be called
|
||||
several times now. This means that for any given connection (there might be several concurrent of them)
|
||||
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 it is preserved between successive iterations.
|
||||
@verbatim
|
||||
static enum MHD_Result
|
||||
iterate_post (void *coninfo_cls, enum MHD_ValueKind kind,
|
||||
@@ -213,23 +235,25 @@ iterate_post (void *coninfo_cls, enum MHD_ValueKind kind,
|
||||
uint64_t off, size_t size)
|
||||
{
|
||||
struct connection_info_struct *con_info = coninfo_cls;
|
||||
FILE *fp;
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
Because the following actions depend heavily on correct file processing, which might be error prone,
|
||||
we default to reporting internal errors in case anything will go wrong.
|
||||
|
||||
@verbatim
|
||||
con_info->answerstring = servererrorpage;
|
||||
con_info->answercode = MHD_HTTP_INTERNAL_SERVER_ERROR;
|
||||
@end verbatim
|
||||
@noindent
|
||||
we record the page to answer with as soon as anything goes wrong. Note that the iterator always
|
||||
returns @code{MHD_YES}: returning @code{MHD_NO} would abort the post processor and thus make it
|
||||
impossible to send our carefully chosen error page to the client.
|
||||
|
||||
In the "askpage" @emph{form}, we told the client to label its post data with the "file" key. Anything else
|
||||
would be an error.
|
||||
|
||||
@verbatim
|
||||
if (0 != strcmp (key, "file")) return MHD_NO;
|
||||
if (0 != strcmp (key, "file"))
|
||||
{
|
||||
con_info->answerstring = servererrorpage;
|
||||
con_info->answercode = MHD_HTTP_BAD_REQUEST;
|
||||
return MHD_YES;
|
||||
}
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
@@ -241,16 +265,24 @@ race between the two "fopen" calls, but we will overlook this for portability sa
|
||||
@verbatim
|
||||
if (!con_info->fp)
|
||||
{
|
||||
if (0 != con_info->answercode) /* something went wrong before */
|
||||
return MHD_YES;
|
||||
|
||||
if (NULL != (fp = fopen (filename, "rb")) )
|
||||
{
|
||||
fclose (fp);
|
||||
con_info->answerstring = fileexistspage;
|
||||
con_info->answercode = MHD_HTTP_FORBIDDEN;
|
||||
return MHD_NO;
|
||||
return MHD_YES;
|
||||
}
|
||||
|
||||
con_info->fp = fopen (filename, "ab");
|
||||
if (!con_info->fp) return MHD_NO;
|
||||
if (!con_info->fp)
|
||||
{
|
||||
con_info->answerstring = fileioerror;
|
||||
con_info->answercode = MHD_HTTP_INTERNAL_SERVER_ERROR;
|
||||
return MHD_YES;
|
||||
}
|
||||
}
|
||||
@end verbatim
|
||||
@noindent
|
||||
@@ -259,20 +291,21 @@ race between the two "fopen" calls, but we will overlook this for portability sa
|
||||
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;
|
||||
if (!fwrite (data, sizeof (char), size, con_info->fp))
|
||||
{
|
||||
con_info->answerstring = fileioerror;
|
||||
con_info->answercode = MHD_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
}
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
If this point has been reached, everything worked well for this iteration and the response can
|
||||
be set to success again. If the upload has finished, this iterator function will not be called again.
|
||||
If this point has been reached without setting an answer code, everything worked well for this
|
||||
iteration. If the upload has finished, this iterator function will not be called again and
|
||||
@code{answer_to_connection} will declare success.
|
||||
@verbatim
|
||||
con_info->answerstring = completepage;
|
||||
con_info->answercode = MHD_HTTP_OK;
|
||||
|
||||
return MHD_YES;
|
||||
}
|
||||
@end verbatim
|
||||
@@ -282,7 +315,7 @@ 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
|
||||
static void
|
||||
request_completed (void *cls, struct MHD_Connection *connection,
|
||||
void **req_cls,
|
||||
enum MHD_RequestTerminationCode toe)
|
||||
|
||||
@@ -17,7 +17,7 @@ edit field for the name.
|
||||
const char* askpage = "<html><body>\
|
||||
What's your name, Sir?<br>\
|
||||
<form action=\"/namepost\" method=\"post\">\
|
||||
<input name=\"name\" type=\"text\"\
|
||||
<input name=\"name\" type=\"text\">\
|
||||
<input type=\"submit\" value=\" Send \"></form>\
|
||||
</body></html>";
|
||||
@end verbatim
|
||||
@@ -30,7 +30,7 @@ We also prepare the answer page, where the name is to be filled in later, and an
|
||||
as the response for anything but proper @emph{GET} and @emph{POST} requests:
|
||||
|
||||
@verbatim
|
||||
const char* greatingpage="<html><body><h1>Welcome, %s!</center></h1></body></html>";
|
||||
const char* greetingpage="<html><body><h1>Welcome, %s!</h1></body></html>";
|
||||
|
||||
const char* errorpage="<html><body>This doesn't seem to be right.</body></html>";
|
||||
@end verbatim
|
||||
@@ -67,11 +67,13 @@ struct connection_info_struct
|
||||
@noindent
|
||||
|
||||
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
|
||||
chunks, we had to expand the namestring dynamically as additional parts of it with the same @code{key}
|
||||
came in. But in this example, the name is assumed to fit entirely inside one single packet.
|
||||
Once it has composed the greeting string, it returns @code{MHD_YES} so that the post processor
|
||||
keeps parsing the remainder of the upload. Returning @code{MHD_NO} would instead abort the
|
||||
parsing and make the next call to @code{MHD_post_process} fail. 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 chunks, we had to expand the namestring dynamically as additional parts of it
|
||||
with the same @code{key} came in. But in this example, the name is assumed to fit entirely inside
|
||||
one single packet.
|
||||
|
||||
@verbatim
|
||||
static enum MHD_Result
|
||||
@@ -90,12 +92,10 @@ iterate_post (void *coninfo_cls, enum MHD_ValueKind kind, const char *key,
|
||||
answerstring = malloc (MAXANSWERSIZE);
|
||||
if (!answerstring) return MHD_NO;
|
||||
|
||||
snprintf (answerstring, MAXANSWERSIZE, greatingpage, data);
|
||||
snprintf (answerstring, MAXANSWERSIZE, greetingpage, data);
|
||||
con_info->answerstring = answerstring;
|
||||
}
|
||||
else con_info->answerstring = NULL;
|
||||
|
||||
return MHD_NO;
|
||||
}
|
||||
|
||||
return MHD_YES;
|
||||
@@ -110,7 +110,7 @@ string. This cleanup function must take into account that it will also be called
|
||||
requests other than @emph{POST} requests.
|
||||
|
||||
@verbatim
|
||||
void
|
||||
static void
|
||||
request_completed (void *cls, struct MHD_Connection *connection,
|
||||
void **req_cls,
|
||||
enum MHD_RequestTerminationCode toe)
|
||||
@@ -136,7 +136,8 @@ in the main function.
|
||||
|
||||
@verbatim
|
||||
...
|
||||
daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD, PORT, NULL, NULL,
|
||||
daemon = MHD_start_daemon (MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD,
|
||||
PORT, NULL, NULL,
|
||||
&answer_to_connection, NULL,
|
||||
MHD_OPTION_NOTIFY_COMPLETED, &request_completed, NULL,
|
||||
MHD_OPTION_END);
|
||||
@@ -218,8 +219,10 @@ considered---all of it.
|
||||
|
||||
if (*upload_data_size != 0)
|
||||
{
|
||||
MHD_post_process (con_info->postprocessor, upload_data,
|
||||
*upload_data_size);
|
||||
if (MHD_YES !=
|
||||
MHD_post_process (con_info->postprocessor, upload_data,
|
||||
*upload_data_size))
|
||||
return MHD_NO;
|
||||
*upload_data_size = 0;
|
||||
|
||||
return MHD_YES;
|
||||
|
||||
@@ -30,7 +30,6 @@ answer_to_connection (void *cls, struct MHD_Connection *connection,
|
||||
const char *upload_data,
|
||||
size_t *upload_data_size, void **req_cls)
|
||||
{
|
||||
unsigned char *buffer = NULL;
|
||||
struct MHD_Response *response;
|
||||
@end verbatim
|
||||
@noindent
|
||||
@@ -58,46 +57,24 @@ server side and if so, the client should be informed with @code{MHD_HTTP_INTERNA
|
||||
|
||||
@verbatim
|
||||
/* error accessing file */
|
||||
if (fd != -1) close (fd);
|
||||
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_RESPMEM_PERSISTENT);
|
||||
if (response)
|
||||
MHD_create_response_from_buffer_static (strlen (errorstr),
|
||||
errorstr);
|
||||
if (NULL != response)
|
||||
{
|
||||
ret =
|
||||
MHD_queue_response (connection, MHD_HTTP_INTERNAL_SERVER_ERROR,
|
||||
response);
|
||||
MHD_destroy_response (response);
|
||||
|
||||
return MHD_YES;
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
return MHD_NO;
|
||||
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,
|
||||
response);
|
||||
MHD_destroy_response (response);
|
||||
|
||||
return MHD_YES;
|
||||
}
|
||||
else return MHD_NO;
|
||||
}
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
@@ -113,10 +90,8 @@ But in the case of success a response will be constructed directly from the file
|
||||
}
|
||||
|
||||
response =
|
||||
MHD_create_response_from_fd_at_offset (sbuf.st_size, fd, 0);
|
||||
MHD_create_response_from_fd_at_offset64 ((uint64_t) sbuf.st_size, fd, 0);
|
||||
MHD_add_response_header (response, "Content-Type", MIMETYPE);
|
||||
ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
|
||||
MHD_destroy_response (response);
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
@@ -129,7 +104,7 @@ 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.
|
||||
field---@emph{GNU libmicrohttpd} will take care of this.
|
||||
|
||||
The function finishes with the well-known lines
|
||||
@verbatim
|
||||
@@ -145,7 +120,7 @@ Find a @emph{PNG} file you like and save it to the directory the example is run
|
||||
@code{picture.png}. You should find the image displayed on your browser if everything worked well.
|
||||
|
||||
@heading Remarks
|
||||
The include file of the @emph{MHD} library comes with the header types mentioned in @emph{RFC 2616}
|
||||
The include file of the @emph{MHD} library comes with the header types mentioned in @emph{RFC 9110}
|
||||
already defined as macros. Thus, we could have written @code{MHD_HTTP_HEADER_CONTENT_TYPE} instead
|
||||
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
|
||||
@@ -169,8 +144,8 @@ instantly, postpone the queuing in the callback with the @code{sleep} function f
|
||||
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
|
||||
started and try again.
|
||||
Finally, change the sourcecode to add @code{MHD_USE_THREAD_PER_CONNECTION} to the daemon's flags
|
||||
(this flag must be combined with @code{MHD_USE_INTERNAL_POLLING_THREAD}) and try again.
|
||||
|
||||
|
||||
@item
|
||||
|
||||
+15
-11
@@ -30,15 +30,15 @@ Here, "KEY" is the name we chose for our session cookie.
|
||||
|
||||
MHD requires the user to provide the full cookie format string in order to set
|
||||
cookies. In order to generate a unique cookie, our example creates a random
|
||||
64-character text string to be used as the value of the cookie:
|
||||
32-character text string to be used as the value of the cookie:
|
||||
|
||||
@verbatim
|
||||
char value[128];
|
||||
char raw_value[65];
|
||||
char raw_value[33];
|
||||
|
||||
for (unsigned int i=0;i<sizeof (raw_value);i++)
|
||||
raw_value = 'A' + (rand () % 26); /* bad PRNG! */
|
||||
raw_value[64] = '\0';
|
||||
for (unsigned int i=0;i<sizeof (raw_value) - 1;i++)
|
||||
raw_value[i] = 'A' + (rand () % 26); /* bad PRNG! */
|
||||
raw_value[sizeof (raw_value) - 1] = '\0';
|
||||
snprintf (value, sizeof (value),
|
||||
"%s=%s",
|
||||
"KEY",
|
||||
@@ -49,13 +49,17 @@ Given this cookie value, we can then set the cookie header in our HTTP response
|
||||
as follows:
|
||||
|
||||
@verbatim
|
||||
assert (MHD_YES ==
|
||||
MHD_set_connection_value (connection,
|
||||
MHD_HEADER_KIND,
|
||||
MHD_HTTP_HEADER_SET_COOKIE,
|
||||
value));
|
||||
if (MHD_NO ==
|
||||
MHD_add_response_header (response,
|
||||
MHD_HTTP_HEADER_SET_COOKIE,
|
||||
value))
|
||||
fprintf (stderr, "Failed to set session cookie header!\n");
|
||||
@end verbatim
|
||||
|
||||
Note that the header has to be added @emph{before} the response is queued with
|
||||
@code{MHD_queue_response}; MHD refuses to modify a response that is already
|
||||
queued.
|
||||
|
||||
|
||||
@heading Remark: Session expiration
|
||||
|
||||
@@ -75,7 +79,7 @@ forms (which are dynamically created using values from previous
|
||||
POST requests from the same session) is available
|
||||
as the example @code{sessions.c}.
|
||||
|
||||
Note that the example uses a simple, $O(n)$ linked list traversal to
|
||||
Note that the example uses a simple, @math{O(n)} linked list traversal to
|
||||
look up sessions and to expire old sessions. Using a hash table and a
|
||||
heap would be more appropriate if a large number of concurrent
|
||||
sessions is expected.
|
||||
|
||||
@@ -3,9 +3,9 @@ any traffic, including the credentials, could be intercepted by anyone between
|
||||
the browser client and the server. Protecting the data while it is sent over
|
||||
unsecured lines will be the goal of this chapter.
|
||||
|
||||
Since version 0.4, the @emph{MHD} library includes support for encrypting the
|
||||
traffic by employing SSL/TSL. If @emph{GNU libmicrohttpd} has been configured to
|
||||
support these, encryption and decryption can be applied transparently on the
|
||||
The @emph{MHD} library includes support for encrypting the traffic by employing
|
||||
TLS (the protocol formerly known as SSL). If @emph{GNU libmicrohttpd} has been configured to
|
||||
support this, encryption and decryption can be applied transparently on the
|
||||
data being sent, with only minimal changes to the actual source code of the example.
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@ data being sent, with only minimal changes to the actual source code of the exam
|
||||
First, a private key for the server will be generated. With this key, the server
|
||||
will later be able to authenticate itself to the client---preventing anyone else
|
||||
from stealing the password by faking its identity. The @emph{OpenSSL} suite, which
|
||||
is available on many operating systems, can generate such a key. For the scope of
|
||||
this tutorial, we will be content with a 1024 bit key:
|
||||
is available on many operating systems, can generate such a key. 1024 bit keys are
|
||||
no longer considered secure, so we use a 2048 bit key:
|
||||
@verbatim
|
||||
> openssl genrsa -out server.key 1024
|
||||
> openssl genrsa -out server.key 2048
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
@@ -25,13 +25,16 @@ In addition to the key, a certificate describing the server in human readable to
|
||||
is also needed. This certificate will be attested with our aforementioned key. In this way,
|
||||
we obtain a self-signed certificate, valid for one year.
|
||||
|
||||
To avoid unnecessary error messages in the browser, the certificate needs to carry a
|
||||
@emph{subjectAltName} that matches the @emph{URI}, for example, "localhost" or the domain
|
||||
(current browsers ignore the common name and look at the @emph{subjectAltName} only).
|
||||
|
||||
@verbatim
|
||||
> openssl req -days 365 -out server.pem -new -x509 -key server.key
|
||||
> openssl req -days 365 -out server.pem -new -x509 -key server.key \
|
||||
-addext "subjectAltName=DNS:localhost"
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
To avoid unnecessary error messages in the browser, the certificate needs to
|
||||
have a name that matches the @emph{URI}, for example, "localhost" or the domain.
|
||||
If you plan to have a publicly reachable server, you will need to ask a trusted third party,
|
||||
called @emph{Certificate Authority}, or @emph{CA}, to attest the certificate for you. This way,
|
||||
any visitor can make sure the server's identity is real.
|
||||
@@ -48,7 +51,7 @@ We merely have to extend the server program so that it loads the two files into
|
||||
|
||||
@verbatim
|
||||
int
|
||||
main ()
|
||||
main (void)
|
||||
{
|
||||
struct MHD_Daemon *daemon;
|
||||
char *key_pem;
|
||||
@@ -68,7 +71,7 @@ main ()
|
||||
and then we point the @emph{MHD} daemon to it upon initialization.
|
||||
@verbatim
|
||||
|
||||
daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_SSL,
|
||||
daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_TLS,
|
||||
PORT, NULL, NULL,
|
||||
&answer_to_connection, NULL,
|
||||
MHD_OPTION_HTTPS_MEM_KEY, key_pem,
|
||||
@@ -138,7 +141,7 @@ To do this, you will need to link your application against @emph{gnutls}.
|
||||
Next, when you start the MHD daemon, you must specify the root CA that you're
|
||||
willing to trust:
|
||||
@verbatim
|
||||
daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_SSL,
|
||||
daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_TLS,
|
||||
PORT, NULL, NULL,
|
||||
&answer_to_connection, NULL,
|
||||
MHD_OPTION_HTTPS_MEM_KEY, key_pem,
|
||||
@@ -157,7 +160,7 @@ obtain the raw GnuTLS session handle from @emph{MHD} using
|
||||
#include <gnutls/x509.h>
|
||||
|
||||
gnutls_session_t tls_session;
|
||||
union MHD_ConnectionInfo *ci;
|
||||
const union MHD_ConnectionInfo *ci;
|
||||
|
||||
ci = MHD_get_connection_info (connection,
|
||||
MHD_CONNECTION_INFO_GNUTLS_SESSION);
|
||||
@@ -333,7 +336,7 @@ of one domain is that you need to provide a callback instead of the key
|
||||
and certificate. For example, when you start the MHD daemon, you could
|
||||
do this:
|
||||
@verbatim
|
||||
daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_SSL,
|
||||
daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_TLS,
|
||||
PORT, NULL, NULL,
|
||||
&answer_to_connection, NULL,
|
||||
MHD_OPTION_HTTPS_CERT_CALLBACK, &sni_callback,
|
||||
@@ -348,11 +351,11 @@ as follows:
|
||||
@deftypefn {Function Pointer} int {*gnutls_certificate_retrieve_function2} (gnutls_session_t, const gnutls_datum_t* req_ca_dn, int nreqs, const gnutls_pk_algorithm_t* pk_algos, int pk_algos_length, gnutls_pcert_st** pcert, unsigned int *pcert_length, gnutls_privkey_t * pkey)
|
||||
|
||||
@table @var
|
||||
@item req_ca_cert
|
||||
@item req_ca_dn
|
||||
is only used in X.509 certificates. Contains a list with the CA names that the server considers trusted. Normally we should send a certificate that is signed by one of these CAs. These names are DER encoded. To get a more meaningful value use the function @code{gnutls_x509_rdn_get()}.
|
||||
|
||||
@item pk_algos
|
||||
contains a list with server’s acceptable signature algorithms. The certificate returned should support the server’s given algorithms.
|
||||
contains a list with server's acceptable signature algorithms. The certificate returned should support the server's given algorithms.
|
||||
|
||||
@item pcert
|
||||
should contain a single certificate and public or a list of them.
|
||||
|
||||
@@ -0,0 +1,422 @@
|
||||
HTTP is a request/response protocol: the client asks, the server
|
||||
answers, and the connection is either closed or reused for the next
|
||||
request. Some applications need something else entirely --- a
|
||||
bidirectional byte stream that neither side has to poll for. Rather
|
||||
than opening a second connection on another port, @emph{RFC 9110} lets
|
||||
a client ask the server to switch the already established connection
|
||||
over to a different protocol. The client sends an ordinary request
|
||||
carrying an @code{Upgrade:} header field, and if the server agrees it
|
||||
answers with the status code @code{101 Switching Protocols}. Once
|
||||
that response has gone out, HTTP is over: the two sides simply talk
|
||||
whatever protocol they agreed on over the same TCP connection.
|
||||
|
||||
Since version 0.9.52 @emph{libmicrohttpd} supports this protocol
|
||||
switch. The API is deliberately protocol-agnostic --- @emph{MHD}
|
||||
performs the HTTP part of the handshake and then hands you the socket.
|
||||
What you send over that socket afterwards is entirely your business;
|
||||
@emph{MHD} neither parses nor generates a single byte of it any more.
|
||||
|
||||
The best known user of this mechanism is the Websocket protocol
|
||||
(@emph{RFC 6455}), which uses the upgrade API as a means to an end and
|
||||
then needs a good deal of machinery of its own for framing, masking
|
||||
and the closing handshake. This chapter takes the opposite approach:
|
||||
it shows the upgrade API on its own, with a protocol so simple that no
|
||||
helper library is needed. Our server switches to a line-based echo
|
||||
protocol: every line the client sends is sent straight back, until the
|
||||
client sends the line @code{QUIT}.
|
||||
|
||||
@heading Enabling upgrades
|
||||
|
||||
Upgrading is not allowed by default, because handing raw sockets to
|
||||
application code has consequences the library cannot control. You opt
|
||||
in with the @code{MHD_ALLOW_UPGRADE} flag:
|
||||
|
||||
@verbatim
|
||||
daemon = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION |
|
||||
MHD_USE_INTERNAL_POLLING_THREAD |
|
||||
MHD_ALLOW_UPGRADE |
|
||||
MHD_USE_ERROR_LOG,
|
||||
PORT, NULL, NULL,
|
||||
&access_handler, NULL,
|
||||
MHD_OPTION_END);
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
The choice of threading mode matters more here than anywhere else in
|
||||
this tutorial, because it decides whether your code is allowed to
|
||||
block.
|
||||
|
||||
With @code{MHD_USE_THREAD_PER_CONNECTION} every connection already has
|
||||
a thread of its own, and that thread is the one that will run your
|
||||
upgrade callback. Blocking in it stalls exactly one client, so you
|
||||
may write the straightforward loop of @code{recv()} and @code{send()}
|
||||
calls that everybody has in their head. This is what makes the
|
||||
example in this chapter short enough to read in one go.
|
||||
|
||||
Without it --- that is, with a single internal polling thread, a
|
||||
thread pool, epoll, or an external event loop --- your callback runs
|
||||
@emph{inside} @emph{MHD}'s event loop. Blocking there freezes every
|
||||
other connection the same loop is serving. In that case the callback
|
||||
must return immediately, and you have to register the socket in your
|
||||
own event loop (or hand it to a thread you started yourself) and drive
|
||||
the conversation from there.
|
||||
|
||||
@heading Deciding whether to upgrade
|
||||
|
||||
A client that wants to switch protocols says so twice: it names the
|
||||
protocol in the @code{Upgrade:} header field, and it lists the token
|
||||
@code{Upgrade} in the @code{Connection:} header field. Both fields
|
||||
are comma-separated lists, so a browser may well send
|
||||
@code{Connection: keep-alive, Upgrade}. Comparing the whole field
|
||||
value against the string @code{"Upgrade"} would reject such a request;
|
||||
the example therefore contains a small helper @code{has_token()} that
|
||||
walks the list. Note also that a protocol switch is a HTTP/1.1
|
||||
feature; @code{MHD_queue_response()} will refuse an upgrade response
|
||||
on a HTTP/1.0 connection.
|
||||
|
||||
@verbatim
|
||||
if ( (0 != strcmp (method, MHD_HTTP_METHOD_GET)) ||
|
||||
(0 != strcmp (version, MHD_HTTP_VERSION_1_1)) ||
|
||||
(! has_token (MHD_lookup_connection_value (connection,
|
||||
MHD_HEADER_KIND,
|
||||
MHD_HTTP_HEADER_CONNECTION),
|
||||
"Upgrade")) ||
|
||||
(! has_token (MHD_lookup_connection_value (connection,
|
||||
MHD_HEADER_KIND,
|
||||
MHD_HTTP_HEADER_UPGRADE),
|
||||
PROTOCOL)) )
|
||||
return answer_plain (connection,
|
||||
MHD_HTTP_UPGRADE_REQUIRED,
|
||||
PAGE_UPGRADE_REQUIRED,
|
||||
1);
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
Refusing is not an error condition: the client asked a question and
|
||||
gets an ordinary HTTP answer. Which answer depends on who is at
|
||||
fault. If the request is malformed --- for instance an
|
||||
@code{Upgrade:} field without the matching @code{Connection:} token
|
||||
--- @code{MHD_HTTP_BAD_REQUEST} is appropriate. If the request is
|
||||
perfectly well-formed but simply does not speak the protocol this
|
||||
resource requires, @code{MHD_HTTP_UPGRADE_REQUIRED} (426) is the
|
||||
better answer, and it should carry an @code{Upgrade:} header field
|
||||
naming the protocol the server would have accepted. Our example
|
||||
folds both cases into a 426 for brevity. Either way you queue a
|
||||
perfectly normal response and the connection stays a HTTP connection.
|
||||
|
||||
@heading Creating the upgrade response
|
||||
|
||||
If we do want to switch, we create a special response object with
|
||||
@code{MHD_create_response_for_upgrade()}. It takes the callback that
|
||||
will receive the socket and a closure pointer for it, and returns
|
||||
@code{NULL} on error. The response has no body --- it is a header and
|
||||
nothing else.
|
||||
|
||||
@verbatim
|
||||
response = MHD_create_response_for_upgrade (&upgrade_handler,
|
||||
NULL);
|
||||
if (NULL == response)
|
||||
return MHD_NO;
|
||||
MHD_add_response_header (response,
|
||||
MHD_HTTP_HEADER_UPGRADE,
|
||||
PROTOCOL);
|
||||
ret = MHD_queue_response (connection,
|
||||
MHD_HTTP_SWITCHING_PROTOCOLS,
|
||||
response);
|
||||
MHD_destroy_response (response);
|
||||
return ret;
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
Two details are easy to get wrong here.
|
||||
|
||||
The first is the @code{Connection: Upgrade} response header field, and
|
||||
the surprise is that you must @emph{not} add it.
|
||||
@code{MHD_create_response_for_upgrade()} puts it into the response
|
||||
itself, and @code{MHD_queue_response()} then verifies that it is still
|
||||
there and still contains the @code{upgrade} token before it accepts
|
||||
the response. @emph{MHD} does merge repeated @code{Connection:}
|
||||
header fields into a single field rather than emitting two of them,
|
||||
but it only de-duplicates the @code{close} token, so adding
|
||||
@code{Upgrade} a second time puts @code{Connection: Upgrade, Upgrade}
|
||||
on the wire. Clients tolerate that, but there is no reason to send
|
||||
it.
|
||||
|
||||
What @emph{MHD} does @emph{not} invent for you is the @code{Upgrade:}
|
||||
field naming the new protocol --- that one is yours to add, since only
|
||||
you know what you are switching to. Do not forget it:
|
||||
@code{MHD_queue_response()} does not check for it, so in a normal
|
||||
build the client simply receives a 101 that fails to say what it
|
||||
switched to, while a library built with assertions enabled aborts when
|
||||
it performs the switch.
|
||||
|
||||
The second is the status code. @code{MHD_queue_response()} rejects an
|
||||
upgrade response with any status other than
|
||||
@code{MHD_HTTP_SWITCHING_PROTOCOLS}, and conversely it rejects status
|
||||
101 combined with a response that is not an upgrade response. On any
|
||||
of these mistakes it returns @code{MHD_NO} and, with
|
||||
@code{MHD_USE_ERROR_LOG} enabled, explains itself on @code{stderr};
|
||||
this is by far the quickest way to debug a handshake that does not
|
||||
happen.
|
||||
|
||||
The response object itself follows the usual rules.
|
||||
@code{MHD_queue_response()} takes its own reference, so calling
|
||||
@code{MHD_destroy_response()} immediately afterwards is correct and is
|
||||
what all the other examples in this tutorial do. The object is only
|
||||
really freed once the last connection using it is done with it, which
|
||||
for an upgrade means after the callback has returned. As with any
|
||||
other response you may build one upgrade response and queue it for
|
||||
many connections; the callback is then simply invoked once per
|
||||
connection.
|
||||
|
||||
@heading The upgrade callback
|
||||
|
||||
After the 101 response has been written to the socket, @emph{MHD}
|
||||
calls the @code{MHD_UpgradeHandler} you registered:
|
||||
|
||||
@verbatim
|
||||
static void
|
||||
upgrade_handler (void *cls,
|
||||
struct MHD_Connection *connection,
|
||||
void *req_cls,
|
||||
const char *extra_in,
|
||||
size_t extra_in_size,
|
||||
MHD_socket sock,
|
||||
struct MHD_UpgradeResponseHandle *urh)
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
@table @code
|
||||
@item cls
|
||||
The closure that was passed to
|
||||
@code{MHD_create_response_for_upgrade()}. Since the same response
|
||||
object may serve many connections, this is per-@emph{response} state,
|
||||
not per-connection state.
|
||||
|
||||
@item connection
|
||||
The original HTTP connection. It is handed over so that you can have
|
||||
a last look at the request --- @code{MHD_lookup_connection_value()}
|
||||
still works here, which is how a real protocol picks up, say, a
|
||||
subprotocol name or an authentication token. Do not queue anything on
|
||||
it any more.
|
||||
|
||||
@item req_cls
|
||||
Whatever your access handler left in @code{*req_cls}. This is the
|
||||
natural place to put per-connection state that the access handler
|
||||
already computed.
|
||||
|
||||
@item extra_in / extra_in_size
|
||||
Bytes of your new protocol that the client sent immediately behind the
|
||||
request header, and that @emph{MHD} therefore already read off the
|
||||
socket while parsing the request. They are @emph{gone} from the
|
||||
socket: if you go straight to @code{recv()} you silently lose them.
|
||||
Process this buffer first, exactly as if you had read it yourself.
|
||||
The buffer belongs to @emph{MHD} and is only valid for the duration of
|
||||
the call, so copy anything you want to keep. A client that waits for
|
||||
the 101 before it starts talking will produce
|
||||
@code{extra_in_size == 0}, which is why this argument is so easy to
|
||||
overlook and so annoying when it finally bites.
|
||||
|
||||
@item sock
|
||||
The socket. For a plain HTTP connection this is the connection to the
|
||||
client; for HTTPS it is one end of a socketpair with @emph{MHD}
|
||||
relaying the TLS traffic, so socket options and @code{getpeername()}
|
||||
will not give you what you expect. Read and write it as you please,
|
||||
and you may call @code{shutdown()} on it, but you must never call
|
||||
@code{close()}.
|
||||
|
||||
@item urh
|
||||
The handle for @code{MHD_upgrade_action()}, described below.
|
||||
@end table
|
||||
|
||||
The loop itself is then unremarkable, apart from consuming
|
||||
@code{extra_in} before the first @code{recv()}:
|
||||
|
||||
@verbatim
|
||||
make_blocking (sock);
|
||||
es.line_len = 0;
|
||||
done = echo_feed (sock, &es, extra_in, extra_in_size);
|
||||
while (0 == done)
|
||||
{
|
||||
got = recv (sock, buf, sizeof (buf), 0);
|
||||
if (0 > got)
|
||||
{
|
||||
if (EINTR == errno)
|
||||
continue;
|
||||
break; /* read error */
|
||||
}
|
||||
if (0 == got)
|
||||
break; /* client closed the connection */
|
||||
done = echo_feed (sock, &es, buf, (size_t) got);
|
||||
}
|
||||
MHD_upgrade_action (urh,
|
||||
MHD_UPGRADE_ACTION_CLOSE);
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
@heading Blocking or not blocking
|
||||
|
||||
The call to @code{make_blocking()} deserves a warning, because the
|
||||
current behaviour is the opposite of what the threading modes suggest.
|
||||
@emph{MHD} puts every accepted client socket into non-blocking mode,
|
||||
and it does so regardless of the threading mode --- in fact the
|
||||
library refuses to perform the switch at all, logging @code{Cannot
|
||||
execute "upgrade" as the socket is in the blocking mode}, if it finds
|
||||
the socket blocking. So even under
|
||||
@code{MHD_USE_THREAD_PER_CONNECTION}, where you are perfectly entitled
|
||||
to block, the socket you are handed is non-blocking and a
|
||||
@code{recv()} on an idle connection returns @code{-1} with
|
||||
@code{EAGAIN} instead of waiting.
|
||||
|
||||
If you want the simple blocking loop shown above, you therefore have
|
||||
to ask for it. The example does so with a small
|
||||
@code{make_blocking()} helper --- @code{fcntl()} clearing
|
||||
@code{O_NONBLOCK} on POSIX systems, @code{ioctlsocket()} on W32 ---
|
||||
which is the only operating-system-dependent code in the whole
|
||||
program.
|
||||
|
||||
Under any other threading mode you leave the socket alone, keep it
|
||||
non-blocking, and add it to your own event loop instead.
|
||||
|
||||
@heading Closing the connection
|
||||
|
||||
The last obligation of the callback is to hand the socket back:
|
||||
|
||||
@verbatim
|
||||
MHD_upgrade_action (urh,
|
||||
MHD_UPGRADE_ACTION_CLOSE);
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
This is not a formality. An upgraded connection is put into an
|
||||
internal ``suspended'' state, and it stays there --- socket open,
|
||||
connection structure allocated, slot counted against the connection
|
||||
limit --- until the application says it is done. Only
|
||||
@code{MHD_UPGRADE_ACTION_CLOSE} says that. Calling @code{close()} on
|
||||
the socket yourself does not, and additionally leaves @emph{MHD}
|
||||
holding a file descriptor number that the operating system may already
|
||||
have handed to somebody else. Call the action exactly once, on every
|
||||
path out of the callback, including the error paths.
|
||||
|
||||
@code{MHD_upgrade_action()} takes two further actions in the current
|
||||
release, @code{MHD_UPGRADE_ACTION_CORK_ON} and
|
||||
@code{MHD_UPGRADE_ACTION_CORK_OFF}. They switch @code{TCP_CORK} (or
|
||||
@code{TCP_NOPUSH}) on the underlying socket so that several small
|
||||
writes can be coalesced into a single segment. They are a hint, not a
|
||||
guarantee: the call returns @code{MHD_NO} if the platform has no such
|
||||
option or if the socket is not a TCP socket, which is the normal case
|
||||
for HTTPS connections. The header file marks this part of the API as
|
||||
not yet finalised, so do not build anything load-bearing on it.
|
||||
|
||||
@heading Remarks
|
||||
|
||||
@code{MHD_stop_daemon()} does @emph{not} interrupt an upgraded
|
||||
connection. It marks the connection as closed internally and logs
|
||||
@code{Initiated daemon shutdown while "upgraded" connection was not
|
||||
closed}, but it neither shuts down nor closes the socket --- and then
|
||||
it waits for the connection's thread, which means it blocks until your
|
||||
callback has returned. A callback parked in a blocking @code{recv()}
|
||||
therefore hangs the shutdown for as long as the client keeps the
|
||||
connection open. Any server that wants to terminate on demand needs
|
||||
its own way out of that loop: a global shutdown flag combined with a
|
||||
@code{shutdown()} on the sockets you are holding, a self-pipe added to
|
||||
a @code{select()} in the loop, or a receive timeout on the socket.
|
||||
Our example sidesteps the problem by only stopping the daemon when the
|
||||
user presses return at a moment when nothing is connected.
|
||||
|
||||
Error handling in the callback is entirely yours. @emph{MHD} will not
|
||||
tell you that a client vanished and will not time an upgraded
|
||||
connection out --- @code{MHD_OPTION_CONNECTION_TIMEOUT} stops applying
|
||||
the moment the connection is upgraded. Treat @code{0} from
|
||||
@code{recv()} as a clean disconnect and a negative return as a broken
|
||||
one, and remember that a peer which disappears without a FIN produces
|
||||
neither until the TCP stack gives up, which can take a very long time.
|
||||
Whatever happens, end by calling @code{MHD_upgrade_action()} with
|
||||
@code{MHD_UPGRADE_ACTION_CLOSE}.
|
||||
|
||||
Finally, keep in mind that an upgraded connection is a connection that
|
||||
@emph{MHD} can no longer recycle, and that intermediaries are entitled
|
||||
to be unhelpful: @code{Upgrade:} is listed in @code{Connection:} and
|
||||
is therefore a hop-by-hop header field. A proxy that does not
|
||||
understand your protocol is allowed to drop it, in which case your
|
||||
server never sees an upgrade request at all and the client gets an
|
||||
ordinary HTTP response where it expected a 101. This is the main
|
||||
reason why deployed protocols on top of this mechanism --- Websockets
|
||||
above all --- make the handshake verifiable by both ends instead of
|
||||
trusting that the request arrived intact.
|
||||
|
||||
The complete program is available as @code{upgrade.c} in the
|
||||
@code{examples} section. Start it and talk to it with any tool that
|
||||
lets you type raw bytes at a socket --- @code{telnet localhost 8888}
|
||||
will do --- by sending
|
||||
|
||||
@verbatim
|
||||
GET / HTTP/1.1
|
||||
Host: localhost
|
||||
Connection: Upgrade
|
||||
Upgrade: echo
|
||||
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
followed by an empty line. The server answers with
|
||||
|
||||
@verbatim
|
||||
HTTP/1.1 101 Switching Protocols
|
||||
Date: ...
|
||||
Connection: Upgrade
|
||||
Upgrade: echo
|
||||
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
and from then on echoes every line back until you type @code{QUIT}.
|
||||
Note the absence of a @code{Content-Length:} field: a 101 response has
|
||||
no body, and everything after the empty line already belongs to the
|
||||
new protocol.
|
||||
|
||||
For a real-world protocol built on exactly this API, read
|
||||
@emph{RFC 6455} and note how much of it exists only to make the
|
||||
handshake and the framing verifiable --- none of which the upgrade API
|
||||
itself has an opinion about.
|
||||
|
||||
@heading Exercises
|
||||
|
||||
@itemize @bullet
|
||||
|
||||
@item
|
||||
Point a browser at the server. The browser sends a plain @code{GET /}
|
||||
and receives the @code{426} page. Now add a second resource that
|
||||
serves a small HTML page and switch the upgrade to a different URL, so
|
||||
that the same server can be both browsed and upgraded.
|
||||
|
||||
@item
|
||||
Make the echo server useful: keep a list of the sockets of all
|
||||
upgraded connections and forward every line one client sends to all
|
||||
the others, turning the example into a minimal chat server. You will
|
||||
need a mutex around the list, and you will discover why the closing
|
||||
rules matter --- a socket must leave the list before its callback
|
||||
returns.
|
||||
|
||||
@item
|
||||
Fix the shutdown behaviour discussed in the remarks. Add a global
|
||||
``quitting'' flag and a list of active sockets, have @code{main()} set
|
||||
the flag and call @code{shutdown (sock, SHUT_RDWR)} on every entry
|
||||
before it calls @code{MHD_stop_daemon()}, and verify that the daemon
|
||||
now stops even while a client is connected and silent.
|
||||
|
||||
@item
|
||||
Change the daemon flags to plain
|
||||
@code{MHD_USE_INTERNAL_POLLING_THREAD} without
|
||||
@code{MHD_USE_THREAD_PER_CONNECTION} and observe that the server now
|
||||
serves exactly one upgraded client and stops answering everybody else.
|
||||
Then repair it: leave the socket non-blocking, start a thread from
|
||||
within the callback, and return immediately.
|
||||
|
||||
@item
|
||||
Have the client send a line longer than @code{LINE_SIZE}. The example
|
||||
silently drops the excess. What should a real protocol do instead,
|
||||
and which status code would you have used if the problem had been
|
||||
detectable during the handshake?
|
||||
|
||||
@end itemize
|
||||
@@ -1,886 +0,0 @@
|
||||
Websockets are a genuine way to implement push notifications,
|
||||
where the server initiates the communication while the client can be idle.
|
||||
Usually a HTTP communication is half-duplex and always requested by the client,
|
||||
but websockets are full-duplex and only initialized by the client.
|
||||
In the further communication both sites can use the websocket at any time
|
||||
to send data to the other site.
|
||||
|
||||
To initialize a websocket connection the client sends a special HTTP request
|
||||
to the server and initializes
|
||||
a handshake between client and server which switches from the HTTP protocol
|
||||
to the websocket protocol.
|
||||
Thus both the server as well as the client must support websockets.
|
||||
If proxys are used, they must support websockets too.
|
||||
In this chapter we take a look on server and client, but with a focus on
|
||||
the server with @emph{libmicrohttpd}.
|
||||
|
||||
Since version 0.9.52 @emph{libmicrohttpd} supports upgrading requests,
|
||||
which is required for switching from the HTTP protocol.
|
||||
Since version 0.9.74 the library @emph{libmicrohttpd_ws} has been added
|
||||
to support the websocket protocol.
|
||||
|
||||
@heading Upgrading connections with libmicrohttpd
|
||||
|
||||
To support websockets we need to enable upgrading of HTTP connections first.
|
||||
This is done by passing the flag @code{MHD_ALLOW_UPGRADE} to
|
||||
@code{MHD_start_daemon()}.
|
||||
|
||||
|
||||
@verbatim
|
||||
daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD |
|
||||
MHD_USE_THREAD_PER_CONNECTION |
|
||||
MHD_ALLOW_UPGRADE |
|
||||
MHD_USE_ERROR_LOG,
|
||||
PORT, NULL, NULL,
|
||||
&access_handler, NULL,
|
||||
MHD_OPTION_END);
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
|
||||
The next step is to turn a specific request into an upgraded connection.
|
||||
This done in our @code{access_handler} by calling
|
||||
@code{MHD_create_response_for_upgrade()}.
|
||||
An @code{upgrade_handler} will be passed to perform the low-level actions
|
||||
on the socket.
|
||||
|
||||
@emph{Please note that the socket here is just a regular socket as provided
|
||||
by the operating system.
|
||||
To use it as a websocket, some more steps from the following
|
||||
chapters are required.}
|
||||
|
||||
|
||||
@verbatim
|
||||
static enum MHD_Result
|
||||
access_handler (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 **ptr)
|
||||
{
|
||||
/* ... */
|
||||
/* some code to decide whether to upgrade or not */
|
||||
/* ... */
|
||||
|
||||
/* create the response for upgrade */
|
||||
response = MHD_create_response_for_upgrade (&upgrade_handler,
|
||||
NULL);
|
||||
|
||||
/* ... */
|
||||
/* additional headers, etc. */
|
||||
/* ... */
|
||||
|
||||
ret = MHD_queue_response (connection,
|
||||
MHD_HTTP_SWITCHING_PROTOCOLS,
|
||||
response);
|
||||
MHD_destroy_response (response);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
|
||||
In the @code{upgrade_handler} we receive the low-level socket,
|
||||
which is used for the communication with the specific client.
|
||||
In addition to the low-level socket we get:
|
||||
@itemize @bullet
|
||||
@item
|
||||
Some data, which has been read too much while @emph{libmicrohttpd} was
|
||||
switching the protocols.
|
||||
This value is usually empty, because it would mean that the client
|
||||
has sent data before the handshake was complete.
|
||||
|
||||
@item
|
||||
A @code{struct MHD_UpgradeResponseHandle} which is used to perform
|
||||
special actions like closing, corking or uncorking the socket.
|
||||
These commands are executed by passing the handle
|
||||
to @code{MHD_upgrade_action()}.
|
||||
|
||||
|
||||
@end itemize
|
||||
|
||||
Depending of the flags specified while calling @code{MHD_start_deamon()}
|
||||
our @code{upgrade_handler} is either executed in the same thread
|
||||
as our daemon or in a thread specific for each connection.
|
||||
If it is executed in the same thread then @code{upgrade_handler} is
|
||||
a blocking call for our webserver and
|
||||
we should finish it as fast as possible (i. e. by creating a thread and
|
||||
passing the information there).
|
||||
If @code{MHD_USE_THREAD_PER_CONNECTION} was passed to
|
||||
@code{MHD_start_daemon()} then a separate thread is used and
|
||||
thus our @code{upgrade_handler} needs not to start a separate thread.
|
||||
|
||||
An @code{upgrade_handler}, which is called with a separate thread
|
||||
per connection, could look like this:
|
||||
|
||||
|
||||
@verbatim
|
||||
static void
|
||||
upgrade_handler (void *cls,
|
||||
struct MHD_Connection *connection,
|
||||
void *req_cls,
|
||||
const char *extra_in,
|
||||
size_t extra_in_size,
|
||||
MHD_socket fd,
|
||||
struct MHD_UpgradeResponseHandle *urh)
|
||||
{
|
||||
/* ... */
|
||||
/* do something with the socket `fd` like `recv()` or `send()` */
|
||||
/* ... */
|
||||
|
||||
/* close the socket when it is not needed anymore */
|
||||
MHD_upgrade_action (urh,
|
||||
MHD_UPGRADE_ACTION_CLOSE);
|
||||
}
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
|
||||
This is all you need to know for upgrading connections
|
||||
with @emph{libmicrohttpd}.
|
||||
The next chapters focus on using the websocket protocol
|
||||
with @emph{libmicrohttpd_ws}.
|
||||
|
||||
|
||||
@heading Websocket handshake with libmicrohttpd_ws
|
||||
|
||||
To request a websocket connection the client must send
|
||||
the following information with the HTTP request:
|
||||
|
||||
@itemize @bullet
|
||||
@item
|
||||
A @code{GET} request must be sent.
|
||||
|
||||
@item
|
||||
The version of the HTTP protocol must be 1.1 or higher.
|
||||
|
||||
@item
|
||||
A @code{Host} header field must be sent
|
||||
|
||||
@item
|
||||
A @code{Upgrade} header field containing the keyword "websocket"
|
||||
(case-insensitive).
|
||||
Please note that the client could pass multiple protocols separated by comma.
|
||||
|
||||
@item
|
||||
A @code{Connection} header field that includes the token "Upgrade"
|
||||
(case-insensitive).
|
||||
Please note that the client could pass multiple tokens separated by comma.
|
||||
|
||||
@item
|
||||
A @code{Sec-WebSocket-Key} header field with a base64-encoded value.
|
||||
The decoded the value is 16 bytes long
|
||||
and has been generated randomly by the client.
|
||||
|
||||
@item
|
||||
A @code{Sec-WebSocket-Version} header field with the value "13".
|
||||
|
||||
@end itemize
|
||||
|
||||
|
||||
Optionally the client can also send the following information:
|
||||
|
||||
|
||||
@itemize @bullet
|
||||
@item
|
||||
A @code{Origin} header field can be used to determine the source
|
||||
of the client (i. e. the website).
|
||||
|
||||
@item
|
||||
A @code{Sec-WebSocket-Protocol} header field can contain a list
|
||||
of supported protocols by the client, which can be sent over the websocket.
|
||||
|
||||
@item
|
||||
A @code{Sec-WebSocket-Extensions} header field which may contain extensions
|
||||
to the websocket protocol. The extensions must be registered by IANA.
|
||||
|
||||
@end itemize
|
||||
|
||||
|
||||
A valid example request from the client could look like this:
|
||||
|
||||
|
||||
@verbatim
|
||||
GET /chat HTTP/1.1
|
||||
Host: server.example.com
|
||||
Upgrade: websocket
|
||||
Connection: Upgrade
|
||||
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
|
||||
Sec-WebSocket-Version: 13
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
|
||||
To complete the handshake the server must respond with
|
||||
some specific response headers:
|
||||
|
||||
@itemize @bullet
|
||||
@item
|
||||
The HTTP response code @code{101 Switching Protocols} must be answered.
|
||||
|
||||
@item
|
||||
An @code{Upgrade} header field containing the value "websocket" must be sent.
|
||||
|
||||
@item
|
||||
A @code{Connection} header field containing the value "Upgrade" must be sent.
|
||||
|
||||
@item
|
||||
A @code{Sec-WebSocket-Accept} header field containing a value, which
|
||||
has been calculated from the @code{Sec-WebSocket-Key} request header field,
|
||||
must be sent.
|
||||
|
||||
@end itemize
|
||||
|
||||
|
||||
Optionally the server may send following headers:
|
||||
|
||||
|
||||
@itemize @bullet
|
||||
@item
|
||||
A @code{Sec-WebSocket-Protocol} header field containing a protocol
|
||||
of the list specified in the corresponding request header field.
|
||||
|
||||
@item
|
||||
A @code{Sec-WebSocket-Extension} header field containing all used extensions
|
||||
of the list specified in the corresponding request header field.
|
||||
|
||||
@end itemize
|
||||
|
||||
|
||||
A valid websocket HTTP response could look like this:
|
||||
|
||||
@verbatim
|
||||
HTTP/1.1 101 Switching Protocols
|
||||
Upgrade: websocket
|
||||
Connection: Upgrade
|
||||
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
|
||||
To upgrade a connection to a websocket the @emph{libmicrohttpd_ws} provides
|
||||
some helper functions for the @code{access_handler} callback function:
|
||||
|
||||
@itemize @bullet
|
||||
@item
|
||||
@code{MHD_websocket_check_http_version()} checks whether the HTTP version
|
||||
is 1.1 or above.
|
||||
|
||||
@item
|
||||
@code{MHD_websocket_check_connection_header()} checks whether the value
|
||||
of the @code{Connection} request header field contains
|
||||
an "Upgrade" token (case-insensitive).
|
||||
|
||||
@item
|
||||
@code{MHD_websocket_check_upgrade_header()} checks whether the value
|
||||
of the @code{Upgrade} request header field contains
|
||||
the "websocket" keyword (case-insensitive).
|
||||
|
||||
@item
|
||||
@code{MHD_websocket_check_version_header()} checks whether the value
|
||||
of the @code{Sec-WebSocket-Version} request header field is "13".
|
||||
|
||||
@item
|
||||
@code{MHD_websocket_create_accept_header()} takes the value from
|
||||
the @code{Sec-WebSocket-Key} request header and calculates the value
|
||||
for the @code{Sec-WebSocket-Accept} response header field.
|
||||
|
||||
@end itemize
|
||||
|
||||
|
||||
The @code{access_handler} example of the previous chapter can now be
|
||||
extended with these helper functions to perform the websocket handshake:
|
||||
|
||||
@verbatim
|
||||
static enum MHD_Result
|
||||
access_handler (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 **ptr)
|
||||
{
|
||||
static int aptr;
|
||||
struct MHD_Response *response;
|
||||
enum MHD_Result ret;
|
||||
|
||||
(void) cls; /* Unused. Silent compiler warning. */
|
||||
(void) upload_data; /* Unused. Silent compiler warning. */
|
||||
(void) upload_data_size; /* Unused. Silent compiler warning. */
|
||||
|
||||
if (0 != strcmp (method, "GET"))
|
||||
return MHD_NO; /* unexpected method */
|
||||
if (&aptr != *ptr)
|
||||
{
|
||||
/* do never respond on first call */
|
||||
*ptr = &aptr;
|
||||
return MHD_YES;
|
||||
}
|
||||
*ptr = NULL; /* reset when done */
|
||||
|
||||
if (0 == strcmp (url, "/"))
|
||||
{
|
||||
/* Default page for visiting the server */
|
||||
struct MHD_Response *response = MHD_create_response_from_buffer (
|
||||
strlen (PAGE),
|
||||
PAGE,
|
||||
MHD_RESPMEM_PERSISTENT);
|
||||
ret = MHD_queue_response (connection,
|
||||
MHD_HTTP_OK,
|
||||
response);
|
||||
MHD_destroy_response (response);
|
||||
}
|
||||
else if (0 == strcmp (url, "/chat"))
|
||||
{
|
||||
char is_valid = 1;
|
||||
const char* value = NULL;
|
||||
char sec_websocket_accept[29];
|
||||
|
||||
if (0 != MHD_websocket_check_http_version (version))
|
||||
{
|
||||
is_valid = 0;
|
||||
}
|
||||
value = MHD_lookup_connection_value (connection,
|
||||
MHD_HEADER_KIND,
|
||||
MHD_HTTP_HEADER_CONNECTION);
|
||||
if (0 != MHD_websocket_check_connection_header (value))
|
||||
{
|
||||
is_valid = 0;
|
||||
}
|
||||
value = MHD_lookup_connection_value (connection,
|
||||
MHD_HEADER_KIND,
|
||||
MHD_HTTP_HEADER_UPGRADE);
|
||||
if (0 != MHD_websocket_check_upgrade_header (value))
|
||||
{
|
||||
is_valid = 0;
|
||||
}
|
||||
value = MHD_lookup_connection_value (connection,
|
||||
MHD_HEADER_KIND,
|
||||
MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION);
|
||||
if (0 != MHD_websocket_check_version_header (value))
|
||||
{
|
||||
is_valid = 0;
|
||||
}
|
||||
value = MHD_lookup_connection_value (connection,
|
||||
MHD_HEADER_KIND,
|
||||
MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY);
|
||||
if (0 != MHD_websocket_create_accept_header (value, sec_websocket_accept))
|
||||
{
|
||||
is_valid = 0;
|
||||
}
|
||||
|
||||
if (1 == is_valid)
|
||||
{
|
||||
/* upgrade the connection */
|
||||
response = MHD_create_response_for_upgrade (&upgrade_handler,
|
||||
NULL);
|
||||
MHD_add_response_header (response,
|
||||
MHD_HTTP_HEADER_CONNECTION,
|
||||
"Upgrade");
|
||||
MHD_add_response_header (response,
|
||||
MHD_HTTP_HEADER_UPGRADE,
|
||||
"websocket");
|
||||
MHD_add_response_header (response,
|
||||
MHD_HTTP_HEADER_SEC_WEBSOCKET_ACCEPT,
|
||||
sec_websocket_accept);
|
||||
ret = MHD_queue_response (connection,
|
||||
MHD_HTTP_SWITCHING_PROTOCOLS,
|
||||
response);
|
||||
MHD_destroy_response (response);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* return error page */
|
||||
struct MHD_Response*response = MHD_create_response_from_buffer (
|
||||
strlen (PAGE_INVALID_WEBSOCKET_REQUEST),
|
||||
PAGE_INVALID_WEBSOCKET_REQUEST,
|
||||
MHD_RESPMEM_PERSISTENT);
|
||||
ret = MHD_queue_response (connection,
|
||||
MHD_HTTP_BAD_REQUEST,
|
||||
response);
|
||||
MHD_destroy_response (response);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
struct MHD_Response*response = MHD_create_response_from_buffer (
|
||||
strlen (PAGE_NOT_FOUND),
|
||||
PAGE_NOT_FOUND,
|
||||
MHD_RESPMEM_PERSISTENT);
|
||||
ret = MHD_queue_response (connection,
|
||||
MHD_HTTP_NOT_FOUND,
|
||||
response);
|
||||
MHD_destroy_response (response);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
Please note that we skipped the check of the Host header field here,
|
||||
because we don't know the host for this example.
|
||||
|
||||
@heading Decoding/encoding the websocket protocol with libmicrohttpd_ws
|
||||
|
||||
Once the websocket connection is established you can receive/send frame data
|
||||
with the low-level socket functions @code{recv()} and @code{send()}.
|
||||
The frame data which goes over the low-level socket is encoded according
|
||||
to the websocket protocol.
|
||||
To use received payload data, you need to decode the frame data first.
|
||||
To send payload data, you need to encode it into frame data first.
|
||||
|
||||
@emph{libmicrohttpd_ws} provides several functions for encoding of
|
||||
payload data and decoding of frame data:
|
||||
|
||||
@itemize @bullet
|
||||
@item
|
||||
@code{MHD_websocket_decode()} decodes received frame data.
|
||||
The payload data may be of any kind, depending upon what the client has sent.
|
||||
So this decode function is used for all kind of frames and returns
|
||||
the frame type along with the payload data.
|
||||
|
||||
@item
|
||||
@code{MHD_websocket_encode_text()} encodes text.
|
||||
The text must be encoded with UTF-8.
|
||||
|
||||
@item
|
||||
@code{MHD_websocket_encode_binary()} encodes binary data.
|
||||
|
||||
@item
|
||||
@code{MHD_websocket_encode_ping()} encodes a ping request to
|
||||
check whether the websocket is still valid and to test latency.
|
||||
|
||||
@item
|
||||
@code{MHD_websocket_encode_ping()} encodes a pong response to
|
||||
answer a received ping request.
|
||||
|
||||
@item
|
||||
@code{MHD_websocket_encode_close()} encodes a close request.
|
||||
|
||||
@item
|
||||
@code{MHD_websocket_free()} frees data returned by the encode/decode functions.
|
||||
|
||||
@end itemize
|
||||
|
||||
Since you could receive or send fragmented data (i. e. due to a too
|
||||
small buffer passed to @code{recv}) all of these encode/decode
|
||||
functions require a pointer to a @code{struct MHD_WebSocketStream} passed
|
||||
as argument.
|
||||
In this structure @emph{libmicrohttpd_ws} stores information
|
||||
about encoding/decoding of the particular websocket.
|
||||
For each websocket you need a unique @code{struct MHD_WebSocketStream}
|
||||
to encode/decode with this library.
|
||||
|
||||
To create or destroy @code{struct MHD_WebSocketStream}
|
||||
we have additional functions:
|
||||
|
||||
@itemize @bullet
|
||||
@item
|
||||
@code{MHD_websocket_stream_init()} allocates and initializes
|
||||
a new @code{struct MHD_WebSocketStream}.
|
||||
You can specify some options here to alter the behavior of the websocket stream.
|
||||
|
||||
@item
|
||||
@code{MHD_websocket_stream_free()} frees a previously allocated
|
||||
@code{struct MHD_WebSocketStream}.
|
||||
|
||||
@end itemize
|
||||
|
||||
With these encode/decode functions we can improve our @code{upgrade_handler}
|
||||
callback function from an earlier example to a working websocket:
|
||||
|
||||
|
||||
@verbatim
|
||||
static void
|
||||
upgrade_handler (void *cls,
|
||||
struct MHD_Connection *connection,
|
||||
void *req_cls,
|
||||
const char *extra_in,
|
||||
size_t extra_in_size,
|
||||
MHD_socket fd,
|
||||
struct MHD_UpgradeResponseHandle *urh)
|
||||
{
|
||||
/* make the socket blocking (operating-system-dependent code) */
|
||||
make_blocking (fd);
|
||||
|
||||
/* create a websocket stream for this connection */
|
||||
struct MHD_WebSocketStream* ws;
|
||||
int result = MHD_websocket_stream_init (&ws,
|
||||
0,
|
||||
0);
|
||||
if (0 != result)
|
||||
{
|
||||
/* Couldn't create the websocket stream.
|
||||
* So we close the socket and leave
|
||||
*/
|
||||
MHD_upgrade_action (urh,
|
||||
MHD_UPGRADE_ACTION_CLOSE);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Let's wait for incoming data */
|
||||
const size_t buf_len = 256;
|
||||
char buf[buf_len];
|
||||
ssize_t got;
|
||||
while (MHD_WEBSOCKET_VALIDITY_VALID == MHD_websocket_stream_is_valid (ws))
|
||||
{
|
||||
got = recv (fd,
|
||||
buf,
|
||||
buf_len,
|
||||
0);
|
||||
if (0 >= got)
|
||||
{
|
||||
/* the TCP/IP socket has been closed */
|
||||
break;
|
||||
}
|
||||
|
||||
/* parse the entire received data */
|
||||
size_t buf_offset = 0;
|
||||
while (buf_offset < (size_t) got)
|
||||
{
|
||||
size_t new_offset = 0;
|
||||
char *frame_data = NULL;
|
||||
size_t frame_len = 0;
|
||||
int status = MHD_websocket_decode (ws,
|
||||
buf + buf_offset,
|
||||
((size_t) got) - buf_offset,
|
||||
&new_offset,
|
||||
&frame_data,
|
||||
&frame_len);
|
||||
if (0 > status)
|
||||
{
|
||||
/* an error occurred and the connection must be closed */
|
||||
if (NULL != frame_data)
|
||||
{
|
||||
MHD_websocket_free (ws, frame_data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
buf_offset += new_offset;
|
||||
if (0 < status)
|
||||
{
|
||||
/* the frame is complete */
|
||||
switch (status)
|
||||
{
|
||||
case MHD_WEBSOCKET_STATUS_TEXT_FRAME:
|
||||
/* The client has sent some text.
|
||||
* We will display it and answer with a text frame.
|
||||
*/
|
||||
if (NULL != frame_data)
|
||||
{
|
||||
printf ("Received message: %s\n", frame_data);
|
||||
MHD_websocket_free (ws, frame_data);
|
||||
frame_data = NULL;
|
||||
}
|
||||
result = MHD_websocket_encode_text (ws,
|
||||
"Hello",
|
||||
5, /* length of "Hello" */
|
||||
0,
|
||||
&frame_data,
|
||||
&frame_len,
|
||||
NULL);
|
||||
if (0 == result)
|
||||
{
|
||||
send_all (fd,
|
||||
frame_data,
|
||||
frame_len);
|
||||
}
|
||||
break;
|
||||
|
||||
case MHD_WEBSOCKET_STATUS_CLOSE_FRAME:
|
||||
/* if we receive a close frame, we will respond with one */
|
||||
MHD_websocket_free (ws,
|
||||
frame_data);
|
||||
frame_data = NULL;
|
||||
|
||||
result = MHD_websocket_encode_close (ws,
|
||||
0,
|
||||
NULL,
|
||||
0,
|
||||
&frame_data,
|
||||
&frame_len);
|
||||
if (0 == result)
|
||||
{
|
||||
send_all (fd,
|
||||
frame_data,
|
||||
frame_len);
|
||||
}
|
||||
break;
|
||||
|
||||
case MHD_WEBSOCKET_STATUS_PING_FRAME:
|
||||
/* if we receive a ping frame, we will respond */
|
||||
/* with the corresponding pong frame */
|
||||
{
|
||||
char *pong = NULL;
|
||||
size_t pong_len = 0;
|
||||
result = MHD_websocket_encode_pong (ws,
|
||||
frame_data,
|
||||
frame_len,
|
||||
&pong,
|
||||
&pong_len);
|
||||
if (0 == result)
|
||||
{
|
||||
send_all (fd,
|
||||
pong,
|
||||
pong_len);
|
||||
}
|
||||
MHD_websocket_free (ws,
|
||||
pong);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
/* Other frame types are ignored
|
||||
* in this minimal example.
|
||||
* This is valid, because they become
|
||||
* automatically skipped if we receive them unexpectedly
|
||||
*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (NULL != frame_data)
|
||||
{
|
||||
MHD_websocket_free (ws, frame_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* free the websocket stream */
|
||||
MHD_websocket_stream_free (ws);
|
||||
|
||||
/* close the socket when it is not needed anymore */
|
||||
MHD_upgrade_action (urh,
|
||||
MHD_UPGRADE_ACTION_CLOSE);
|
||||
}
|
||||
|
||||
/* This helper function is used for the case that
|
||||
* we need to resend some data
|
||||
*/
|
||||
static void
|
||||
send_all (MHD_socket fd,
|
||||
const char *buf,
|
||||
size_t len)
|
||||
{
|
||||
ssize_t ret;
|
||||
size_t off;
|
||||
|
||||
for (off = 0; off < len; off += ret)
|
||||
{
|
||||
ret = send (fd,
|
||||
&buf[off],
|
||||
(int) (len - off),
|
||||
0);
|
||||
if (0 > ret)
|
||||
{
|
||||
if (EAGAIN == errno)
|
||||
{
|
||||
ret = 0;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (0 == ret)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* This helper function contains operating-system-dependent code and
|
||||
* is used to make a socket blocking.
|
||||
*/
|
||||
static void
|
||||
make_blocking (MHD_socket fd)
|
||||
{
|
||||
#if defined(MHD_POSIX_SOCKETS)
|
||||
int flags;
|
||||
|
||||
flags = fcntl (fd, F_GETFL);
|
||||
if (-1 == flags)
|
||||
return;
|
||||
if ((flags & ~O_NONBLOCK) != flags)
|
||||
if (-1 == fcntl (fd, F_SETFL, flags & ~O_NONBLOCK))
|
||||
abort ();
|
||||
#elif defined(MHD_WINSOCK_SOCKETS)
|
||||
unsigned long flags = 0;
|
||||
|
||||
ioctlsocket (fd, FIONBIO, &flags);
|
||||
#endif /* MHD_WINSOCK_SOCKETS */
|
||||
}
|
||||
|
||||
@end verbatim
|
||||
@noindent
|
||||
|
||||
|
||||
Please note that the websocket in this example is only half-duplex.
|
||||
It waits until the blocking @code{recv()} call returns and
|
||||
only does then something.
|
||||
In this example all frame types are decoded by @emph{libmicrohttpd_ws},
|
||||
but we only do something when a text, ping or close frame is received.
|
||||
Binary and pong frames are ignored in our code.
|
||||
This is legit, because the server is only required to implement at
|
||||
least support for ping frame or close frame (the other frame types
|
||||
could be skipped in theory, because they don't require an answer).
|
||||
The pong frame doesn't require an answer and whether text frames or
|
||||
binary frames get an answer simply belongs to your server application.
|
||||
So this is a valid minimal example.
|
||||
|
||||
Until this point you've learned everything you need to basically
|
||||
use websockets with @emph{libmicrohttpd} and @emph{libmicrohttpd_ws}.
|
||||
These libraries offer much more functions for some specific cases.
|
||||
|
||||
|
||||
The further chapters of this tutorial focus on some specific problems
|
||||
and the client site programming.
|
||||
|
||||
|
||||
@heading Using full-duplex websockets
|
||||
|
||||
To use full-duplex websockets you can simply create two threads
|
||||
per websocket connection.
|
||||
One of these threads is used for receiving data with
|
||||
a blocking @code{recv()} call and the other thread is triggered
|
||||
by the application internal codes and sends the data.
|
||||
|
||||
A full-duplex websocket example is implemented in the example file
|
||||
@code{websocket_chatserver_example.c}.
|
||||
|
||||
@heading Error handling
|
||||
|
||||
The most functions of @emph{libmicrohttpd_ws} return a value
|
||||
of @code{enum MHD_WEBSOCKET_STATUS}.
|
||||
The values of this enumeration can be converted into an integer
|
||||
and have an easy interpretation:
|
||||
|
||||
@itemize @bullet
|
||||
@item
|
||||
If the value is less than zero an error occurred and the call has failed.
|
||||
Check the enumeration values for more specific information.
|
||||
|
||||
@item
|
||||
If the value is equal to zero, the call succeeded.
|
||||
|
||||
@item
|
||||
If the value is greater than zero, the call succeeded and the value
|
||||
specifies the decoded frame type.
|
||||
Currently positive values are only returned by @code{MHD_websocket_decode()}
|
||||
(of the functions with this return enumeration type).
|
||||
|
||||
@end itemize
|
||||
|
||||
A websocket stream can also get broken when invalid frame data is received.
|
||||
Also the other site could send a close frame which puts the stream into
|
||||
a state where it may not be used for regular communication.
|
||||
Whether a stream has become broken, can be checked with
|
||||
@code{MHD_websocket_stream_is_valid()}.
|
||||
|
||||
|
||||
@heading Fragmentation
|
||||
|
||||
In addition to the regular TCP/IP fragmentation the websocket protocol also
|
||||
supports fragmentation.
|
||||
Fragmentation could be used for continuous payload data such as video data
|
||||
from a webcam.
|
||||
Whether or not you want to receive fragmentation is specified upon
|
||||
initialization of the websocket stream.
|
||||
If you pass @code{MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS} in the flags parameter
|
||||
of @code{MHD_websocket_stream_init()} then you can receive fragments.
|
||||
If you don't pass this flag (in the most cases you just pass zero as flags)
|
||||
then you don't want to handle fragments on your own.
|
||||
@emph{libmicrohttpd_ws} removes then the fragmentation for you
|
||||
in the background.
|
||||
You only get the completely assembled frames.
|
||||
|
||||
Upon encoding you specify whether or not you want to create a fragmented frame
|
||||
by passing a flag to the corresponding encode function.
|
||||
Only @code{MHD_websocket_encode_text()} and @code{MHD_websocket_encode_binary()}
|
||||
can be used for fragmentation, because the other frame types may
|
||||
not be fragmented.
|
||||
Encoding fragmented frames is independent of
|
||||
the @code{MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS} flag upon initialization.
|
||||
|
||||
@heading Quick guide to websockets in JavaScript
|
||||
|
||||
Websockets are supported in all modern web browsers.
|
||||
You initialize a websocket connection by creating an instance of
|
||||
the @code{WebSocket} class provided by the web browser.
|
||||
|
||||
There are some simple rules for using websockets in the browser:
|
||||
|
||||
@itemize @bullet
|
||||
@item
|
||||
When you initialize the instance of the websocket class you must pass an URL.
|
||||
The URL must either start with @code{ws://}
|
||||
(for not encrypted websocket protocol) or @code{wss://}
|
||||
(for TLS-encrypted websocket protocol).
|
||||
|
||||
@strong{IMPORTANT:} If your website is accessed via @code{https://}
|
||||
then you are in a security context, which means that you are only allowed to
|
||||
access other secure protocols.
|
||||
So you can only use @code{wss://} for websocket connections then.
|
||||
If you try to @code{ws://} instead then your websocket connection will
|
||||
automatically fail.
|
||||
|
||||
@item
|
||||
The WebSocket class uses events to handle the receiving of data.
|
||||
JavaScript is per definition a single-threaded language so
|
||||
the receiving events will never overlap.
|
||||
Sending is done directly by calling a method of the instance of
|
||||
the WebSocket class.
|
||||
|
||||
@end itemize
|
||||
|
||||
|
||||
Here is a short example for receiving/sending data to the same host
|
||||
as the website is running on:
|
||||
|
||||
@verbatim
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Websocket Demo</title>
|
||||
<script>
|
||||
|
||||
let url = 'ws' + (window.location.protocol === 'https:' ? 's' : '') + '://' +
|
||||
window.location.host + '/chat';
|
||||
let socket = null;
|
||||
|
||||
window.onload = function(event) {
|
||||
socket = new WebSocket(url);
|
||||
socket.onopen = function(event) {
|
||||
document.write('The websocket connection has been established.<br>');
|
||||
|
||||
// Send some text
|
||||
socket.send('Hello from JavaScript!');
|
||||
}
|
||||
|
||||
socket.onclose = function(event) {
|
||||
document.write('The websocket connection has been closed.<br>');
|
||||
}
|
||||
|
||||
socket.onerror = function(event) {
|
||||
document.write('An error occurred during the websocket communication.<br>');
|
||||
}
|
||||
|
||||
socket.onmessage = function(event) {
|
||||
document.write('Websocket message received: ' + event.data + '<br>');
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@end verbatim
|
||||
@noindent
|
||||
@@ -1,3 +1,4 @@
|
||||
/upgrade
|
||||
/tlsauthentication
|
||||
/simplepost
|
||||
/sessions
|
||||
@@ -5,6 +6,7 @@
|
||||
/logging
|
||||
/largepost
|
||||
/hellobrowser
|
||||
/callbackresponse
|
||||
/basicauthentication
|
||||
*.exe
|
||||
*.o
|
||||
|
||||
+18
-12
@@ -21,10 +21,16 @@ $(top_builddir)/src/microhttpd/libmicrohttpd.la: $(top_builddir)/src/microhttpd/
|
||||
|
||||
# example programs
|
||||
noinst_PROGRAMS = \
|
||||
callbackresponse \
|
||||
hellobrowser \
|
||||
logging \
|
||||
responseheaders
|
||||
|
||||
|
||||
if ENABLE_UPGRADE
|
||||
noinst_PROGRAMS += \
|
||||
upgrade
|
||||
endif
|
||||
|
||||
if ENABLE_BAUTH
|
||||
noinst_PROGRAMS += \
|
||||
basicauthentication
|
||||
@@ -42,19 +48,20 @@ if HAVE_W32
|
||||
AM_CPPFLAGS += -DWINDOWS
|
||||
endif
|
||||
|
||||
if HAVE_EXPERIMENTAL
|
||||
noinst_PROGRAMS += websocket
|
||||
endif
|
||||
|
||||
basicauthentication_SOURCES = \
|
||||
basicauthentication.c
|
||||
basicauthentication_LDADD = \
|
||||
$(top_builddir)/src/microhttpd/libmicrohttpd.la
|
||||
|
||||
callbackresponse_SOURCES = \
|
||||
callbackresponse.c
|
||||
callbackresponse_LDADD = \
|
||||
$(top_builddir)/src/microhttpd/libmicrohttpd.la
|
||||
|
||||
hellobrowser_SOURCES = \
|
||||
hellobrowser.c
|
||||
hellobrowser.c
|
||||
hellobrowser_LDADD = \
|
||||
$(top_builddir)/src/microhttpd/libmicrohttpd.la
|
||||
$(top_builddir)/src/microhttpd/libmicrohttpd.la
|
||||
|
||||
logging_SOURCES = \
|
||||
logging.c
|
||||
@@ -86,8 +93,7 @@ largepost_SOURCES = \
|
||||
largepost_LDADD = \
|
||||
$(top_builddir)/src/microhttpd/libmicrohttpd.la
|
||||
|
||||
websocket_SOURCES = \
|
||||
websocket.c
|
||||
websocket_LDADD = \
|
||||
$(top_builddir)/src/microhttpd/libmicrohttpd.la \
|
||||
$(top_builddir)/src/microhttpd_ws/libmicrohttpd_ws.la
|
||||
upgrade_SOURCES = \
|
||||
upgrade.c
|
||||
upgrade_LDADD = \
|
||||
$(top_builddir)/src/microhttpd/libmicrohttpd.la
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
/* Feel free to use this example code in any way
|
||||
you see fit (Public Domain) */
|
||||
|
||||
/**
|
||||
* @file callbackresponse.c
|
||||
* @brief Example for generating a response on the fly with
|
||||
* MHD_create_response_from_callback(). The body is not a
|
||||
* file and is never assembled in memory as a whole; it is
|
||||
* produced block by block into a single heap buffer that is
|
||||
* refilled every time libmicrohttpd asks for more data.
|
||||
* "GET /" streams the document with an unknown size (and thus
|
||||
* with chunked transfer encoding), "GET /sized" streams the
|
||||
* very same document with a "Content-Length" announced up
|
||||
* front, and "GET /error" aborts the stream in the middle to
|
||||
* show MHD_CONTENT_READER_END_WITH_ERROR.
|
||||
* @author Christian Grothoff
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#ifndef _WIN32
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#else
|
||||
#include <winsock2.h>
|
||||
#endif
|
||||
#include <microhttpd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define PORT 8888
|
||||
|
||||
/**
|
||||
* Length of one generated line, including the newline character.
|
||||
*/
|
||||
#define LINE_SIZE 64
|
||||
|
||||
/**
|
||||
* Size of the heap buffer that is refilled over and over again. This
|
||||
* is the amount of data we produce in one go; it has nothing to do
|
||||
* with the block size we pass to MHD.
|
||||
*/
|
||||
#define BUFFER_SIZE (64 * LINE_SIZE)
|
||||
|
||||
/**
|
||||
* Number of times the buffer is refilled before the stream ends. The
|
||||
* complete document is BLOCK_COUNT * BUFFER_SIZE bytes long.
|
||||
*/
|
||||
#define BLOCK_COUNT 64
|
||||
|
||||
/**
|
||||
* Block size we ask MHD to use when it queries our callback. MHD
|
||||
* allocates a buffer of this size together with the response; it is an
|
||||
* advisory value and MHD may ask for less.
|
||||
*/
|
||||
#define IO_BLOCK_SIZE 1024
|
||||
|
||||
/**
|
||||
* For the "/error" URL: number of blocks to deliver before the stream
|
||||
* is aborted with MHD_CONTENT_READER_END_WITH_ERROR.
|
||||
*/
|
||||
#define ABORT_AFTER 3
|
||||
|
||||
|
||||
/**
|
||||
* State we keep for one response that is being streamed. One such
|
||||
* structure is allocated per request and released by the free callback
|
||||
* that we hand to MHD_create_response_from_callback().
|
||||
*/
|
||||
struct ResponseContext
|
||||
{
|
||||
/**
|
||||
* Heap buffer holding the block that is currently being delivered.
|
||||
* Refilled by generate_block() whenever it has been drained.
|
||||
*/
|
||||
char *buf;
|
||||
|
||||
/**
|
||||
* Number of valid bytes in @e buf.
|
||||
*/
|
||||
size_t fill;
|
||||
|
||||
/**
|
||||
* Number of bytes of @e buf that were already handed to MHD.
|
||||
*/
|
||||
size_t off;
|
||||
|
||||
/**
|
||||
* Number of blocks generated so far, and thus the number of the
|
||||
* block that generate_block() will produce next.
|
||||
*/
|
||||
unsigned int block;
|
||||
|
||||
/**
|
||||
* Non-zero if this stream is supposed to fail in the middle.
|
||||
*/
|
||||
int fail;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Produce the next block of the document into @a ctx->buf. This
|
||||
* stands in for whatever expensive computation, database query or
|
||||
* network fetch a real application would perform here. Note that the
|
||||
* same buffer is reused for every block, so the memory needed by the
|
||||
* server does not grow with the size of the document.
|
||||
*
|
||||
* Every line is exactly LINE_SIZE bytes long, which is what allows us
|
||||
* to announce an exact "Content-Length" for the "/sized" URL.
|
||||
*
|
||||
* @param ctx the per-response state to refill
|
||||
*/
|
||||
static void
|
||||
generate_block (struct ResponseContext *ctx)
|
||||
{
|
||||
unsigned int i;
|
||||
char *line;
|
||||
char label[32];
|
||||
int len;
|
||||
|
||||
for (i = 0; i < BUFFER_SIZE / LINE_SIZE; i++)
|
||||
{
|
||||
line = &ctx->buf[i * LINE_SIZE];
|
||||
memset (line, '.', LINE_SIZE);
|
||||
len = snprintf (label,
|
||||
sizeof (label),
|
||||
"block %3u line %3u ",
|
||||
ctx->block,
|
||||
i);
|
||||
if ((0 < len) && (LINE_SIZE > (size_t) len))
|
||||
memcpy (line, label, (size_t) len);
|
||||
line[LINE_SIZE - 1] = '\n';
|
||||
}
|
||||
ctx->fill = BUFFER_SIZE;
|
||||
ctx->off = 0;
|
||||
ctx->block++;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback used by MHD to obtain the next piece of the response body.
|
||||
*
|
||||
* @param cls our `struct ResponseContext`
|
||||
* @param pos number of bytes already returned for this response
|
||||
* @param buf where to copy the data
|
||||
* @param max maximum number of bytes to copy to @a buf
|
||||
* @return number of bytes written to @a buf,
|
||||
* MHD_CONTENT_READER_END_OF_STREAM for the regular end,
|
||||
* MHD_CONTENT_READER_END_WITH_ERROR to abort the transfer
|
||||
*/
|
||||
static ssize_t
|
||||
content_reader (void *cls,
|
||||
uint64_t pos,
|
||||
char *buf,
|
||||
size_t max)
|
||||
{
|
||||
struct ResponseContext *ctx = cls;
|
||||
size_t ready;
|
||||
|
||||
/* We generate a pure stream and never look back, so the position is
|
||||
of no interest to us. MHD guarantees that it is the sum of all
|
||||
non-negative values we returned so far. */
|
||||
(void) pos; /* Unused. Silent compiler warning. */
|
||||
|
||||
if (ctx->off == ctx->fill)
|
||||
{
|
||||
/* Everything we had was passed on, produce the next block. */
|
||||
if (BLOCK_COUNT == ctx->block)
|
||||
return MHD_CONTENT_READER_END_OF_STREAM;
|
||||
if ((0 != ctx->fail) && (ABORT_AFTER == ctx->block))
|
||||
return MHD_CONTENT_READER_END_WITH_ERROR;
|
||||
generate_block (ctx);
|
||||
}
|
||||
/* Hand over as much of the current block as MHD is willing to take;
|
||||
the rest follows on the next invocation. */
|
||||
ready = ctx->fill - ctx->off;
|
||||
if (ready > max)
|
||||
ready = max;
|
||||
memcpy (buf, &ctx->buf[ctx->off], ready);
|
||||
ctx->off += ready;
|
||||
return (ssize_t) ready;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Release the resources of a `struct ResponseContext`. MHD calls this
|
||||
* once the response is destroyed, which happens no matter whether the
|
||||
* body was transmitted completely or the client went away early.
|
||||
*
|
||||
* @param cls our `struct ResponseContext`
|
||||
*/
|
||||
static void
|
||||
free_context (void *cls)
|
||||
{
|
||||
struct ResponseContext *ctx = cls;
|
||||
|
||||
free (ctx->buf);
|
||||
free (ctx);
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
struct ResponseContext *ctx;
|
||||
struct MHD_Response *response;
|
||||
enum MHD_Result ret;
|
||||
uint64_t size;
|
||||
(void) cls; /* Unused. Silent compiler warning. */
|
||||
(void) version; /* Unused. Silent compiler warning. */
|
||||
(void) upload_data; /* Unused. Silent compiler warning. */
|
||||
(void) upload_data_size; /* Unused. Silent compiler warning. */
|
||||
(void) req_cls; /* Unused. Silent compiler warning. */
|
||||
|
||||
if (0 != strcmp (method, MHD_HTTP_METHOD_GET))
|
||||
return MHD_NO;
|
||||
|
||||
/* Allocate the state of this particular response before the response
|
||||
object exists; from now on the free callback owns it. */
|
||||
ctx = malloc (sizeof (struct ResponseContext));
|
||||
if (NULL == ctx)
|
||||
return MHD_NO;
|
||||
ctx->buf = malloc (BUFFER_SIZE);
|
||||
if (NULL == ctx->buf)
|
||||
{
|
||||
free (ctx);
|
||||
return MHD_NO;
|
||||
}
|
||||
ctx->fill = 0;
|
||||
ctx->off = 0;
|
||||
ctx->block = 0;
|
||||
ctx->fail = (0 == strcmp (url, "/error"));
|
||||
|
||||
/* "/sized" announces the length in advance, everything else is sent
|
||||
with an unknown size and thus with chunked transfer encoding. */
|
||||
if (0 == strcmp (url, "/sized"))
|
||||
size = (uint64_t) BLOCK_COUNT * BUFFER_SIZE;
|
||||
else
|
||||
size = MHD_SIZE_UNKNOWN;
|
||||
|
||||
response = MHD_create_response_from_callback (size,
|
||||
IO_BLOCK_SIZE,
|
||||
&content_reader,
|
||||
ctx,
|
||||
&free_context);
|
||||
if (NULL == response)
|
||||
{
|
||||
/* The response was never created, so nobody will call the free
|
||||
callback for us. */
|
||||
free_context (ctx);
|
||||
return MHD_NO;
|
||||
}
|
||||
(void) MHD_add_response_header (response,
|
||||
MHD_HTTP_HEADER_CONTENT_TYPE,
|
||||
"text/plain");
|
||||
ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
|
||||
MHD_destroy_response (response);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main (void)
|
||||
{
|
||||
struct MHD_Daemon *daemon;
|
||||
|
||||
daemon = MHD_start_daemon (MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD,
|
||||
PORT, NULL, NULL,
|
||||
&answer_to_connection, NULL, MHD_OPTION_END);
|
||||
if (NULL == daemon)
|
||||
return 1;
|
||||
|
||||
(void) getchar ();
|
||||
|
||||
MHD_stop_daemon (daemon);
|
||||
return 0;
|
||||
}
|
||||
@@ -92,7 +92,9 @@ send_page (struct MHD_Connection *connection,
|
||||
enum MHD_Result ret;
|
||||
struct MHD_Response *response;
|
||||
|
||||
response = MHD_create_response_from_buffer_static (strlen (page), page);
|
||||
/* NOTE: we let MHD make its own copy of the page, as some of the
|
||||
pages we serve live in a buffer on the stack of the caller. */
|
||||
response = MHD_create_response_from_buffer_copy (strlen (page), page);
|
||||
if (! response)
|
||||
return MHD_NO;
|
||||
if (MHD_YES !=
|
||||
@@ -319,7 +321,7 @@ answer_to_connection (void *cls,
|
||||
con_info->answercode);
|
||||
}
|
||||
|
||||
/* Note a GET or a POST, generate error */
|
||||
/* Not a GET or a POST, generate error */
|
||||
return send_page (connection,
|
||||
errorpage,
|
||||
MHD_HTTP_BAD_REQUEST);
|
||||
|
||||
@@ -63,7 +63,7 @@ answer_to_connection (void *cls, struct MHD_Connection *connection,
|
||||
return MHD_NO;
|
||||
}
|
||||
response =
|
||||
MHD_create_response_from_fd_at_offset64 ((size_t) sbuf.st_size,
|
||||
MHD_create_response_from_fd_at_offset64 ((uint64_t) sbuf.st_size,
|
||||
fd,
|
||||
0);
|
||||
if (MHD_YES !=
|
||||
|
||||
@@ -449,9 +449,8 @@ not_found_page (const void *cls,
|
||||
/* unsupported HTTP method */
|
||||
response = MHD_create_response_from_buffer_static (strlen (NOT_FOUND_ERROR),
|
||||
NOT_FOUND_ERROR);
|
||||
ret = MHD_queue_response (connection,
|
||||
MHD_HTTP_NOT_FOUND,
|
||||
response);
|
||||
/* NOTE: headers must be added _before_ the response is queued,
|
||||
MHD refuses to modify a response that was already queued. */
|
||||
if (MHD_YES !=
|
||||
MHD_add_response_header (response,
|
||||
MHD_HTTP_HEADER_CONTENT_TYPE,
|
||||
@@ -461,6 +460,9 @@ not_found_page (const void *cls,
|
||||
"Failed to set content type header!\n");
|
||||
/* return response without content type anyway ... */
|
||||
}
|
||||
ret = MHD_queue_response (connection,
|
||||
MHD_HTTP_NOT_FOUND,
|
||||
response);
|
||||
MHD_destroy_response (response);
|
||||
return ret;
|
||||
}
|
||||
@@ -474,7 +476,7 @@ static const struct Page pages[] = {
|
||||
{ "/2", "text/html", &fill_v1_v2_form, NULL },
|
||||
{ "/S", "text/html", &serve_simple_form, SUBMIT_PAGE },
|
||||
{ "/F", "text/html", &serve_simple_form, LAST_PAGE },
|
||||
{ NULL, NULL, ¬_found_page, NULL } /* 404 */
|
||||
{ NULL, "text/html", ¬_found_page, NULL } /* 404 */
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ static const char *askpage =
|
||||
"</body></html>";
|
||||
|
||||
#define GREETINGPAGE \
|
||||
"<html><body><h1>Welcome, %s!</center></h1></body></html>"
|
||||
"<html><body><h1>Welcome, %s!</h1></body></html>"
|
||||
|
||||
static const char *errorpage =
|
||||
"<html><body>This doesn't seem to be right.</body></html>";
|
||||
@@ -174,12 +174,12 @@ answer_to_connection (void *cls,
|
||||
}
|
||||
return MHD_YES;
|
||||
}
|
||||
if (GET == con_info->connectiontype)
|
||||
if (0 == strcmp (method, "GET"))
|
||||
{
|
||||
return send_page (connection,
|
||||
askpage);
|
||||
}
|
||||
if (POST == con_info->connectiontype)
|
||||
if (0 == strcmp (method, "POST"))
|
||||
{
|
||||
if (0 != *upload_data_size)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,458 @@
|
||||
/* Feel free to use this example code in any way
|
||||
you see fit (Public Domain) */
|
||||
|
||||
/**
|
||||
* @file upgrade.c
|
||||
* @brief Minimal example for the HTTP "Upgrade" API of libmicrohttpd.
|
||||
* A "GET /" carrying "Connection: Upgrade" and "Upgrade: echo"
|
||||
* is answered with "101 Switching Protocols"; afterwards the
|
||||
* raw socket speaks a trivial line-based echo protocol until
|
||||
* the client sends the line "QUIT" or closes the connection.
|
||||
* @author Christian Grothoff
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#ifndef _WIN32
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <fcntl.h>
|
||||
#else
|
||||
#include <winsock2.h>
|
||||
#endif
|
||||
#include <microhttpd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define PORT 8888
|
||||
|
||||
/**
|
||||
* Name of the protocol we are willing to switch to. Any name would
|
||||
* do; "echo" is not registered with IANA and is only used here to keep
|
||||
* the example self-contained.
|
||||
*/
|
||||
#define PROTOCOL "echo"
|
||||
|
||||
/**
|
||||
* Maximum length of a line of the echo protocol. Longer lines are
|
||||
* truncated.
|
||||
*/
|
||||
#define LINE_SIZE 1024
|
||||
|
||||
#define PAGE_UPGRADE_REQUIRED \
|
||||
"This resource speaks the \"" PROTOCOL "\" protocol only.\n"
|
||||
|
||||
#define PAGE_NOT_FOUND \
|
||||
"Not found.\n"
|
||||
|
||||
|
||||
/**
|
||||
* State of the line-based echo protocol for one connection.
|
||||
*/
|
||||
struct EchoState
|
||||
{
|
||||
/**
|
||||
* Bytes of the line received so far (without the line terminator).
|
||||
*/
|
||||
char line[LINE_SIZE];
|
||||
|
||||
/**
|
||||
* Number of valid bytes in @e line.
|
||||
*/
|
||||
size_t line_len;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Compare the first @a len bytes of @a a and @a b, ignoring case.
|
||||
* Only ASCII letters are folded, which is all HTTP tokens may contain.
|
||||
*
|
||||
* @param a first string
|
||||
* @param b second string
|
||||
* @param len number of bytes to compare
|
||||
* @return 1 if the strings match, 0 if they do not
|
||||
*/
|
||||
static int
|
||||
equal_caseless (const char *a,
|
||||
const char *b,
|
||||
size_t len)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
char ca = a[i];
|
||||
char cb = b[i];
|
||||
|
||||
if (('A' <= ca) && ('Z' >= ca))
|
||||
ca = (char) (ca - 'A' + 'a');
|
||||
if (('A' <= cb) && ('Z' >= cb))
|
||||
cb = (char) (cb - 'A' + 'a');
|
||||
if (ca != cb)
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the comma-separated list @a value contains the token
|
||||
* @a token. Both the "Connection:" and the "Upgrade:" request header
|
||||
* fields are lists, so a client may legitimately send
|
||||
* "Connection: keep-alive, Upgrade"; a plain string comparison against
|
||||
* the whole field value would reject such a request.
|
||||
*
|
||||
* @param value the header field value, may be NULL
|
||||
* @param token the token to look for, compared case-insensitively
|
||||
* @return 1 if @a token is one of the tokens in @a value, 0 if not
|
||||
*/
|
||||
static int
|
||||
has_token (const char *value,
|
||||
const char *token)
|
||||
{
|
||||
const size_t token_len = strlen (token);
|
||||
const char *pos = value;
|
||||
|
||||
if (NULL == value)
|
||||
return 0;
|
||||
while ('\0' != *pos)
|
||||
{
|
||||
const char *end;
|
||||
size_t len;
|
||||
|
||||
/* skip separators and optional whitespace before the token */
|
||||
while ((',' == *pos) || (' ' == *pos) || ('\t' == *pos))
|
||||
pos++;
|
||||
end = pos;
|
||||
while (('\0' != *end) && (',' != *end))
|
||||
end++;
|
||||
len = (size_t) (end - pos);
|
||||
/* strip optional whitespace after the token */
|
||||
while ((0 != len) &&
|
||||
((' ' == pos[len - 1]) || ('\t' == pos[len - 1])))
|
||||
len--;
|
||||
if ((len == token_len) &&
|
||||
(equal_caseless (pos, token, token_len)))
|
||||
return 1;
|
||||
pos = end;
|
||||
if ('\0' != *pos)
|
||||
pos++; /* skip the comma */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send @a len bytes from @a buf over @a sock, retrying until either
|
||||
* everything was sent or the connection broke.
|
||||
*
|
||||
* @param sock the socket to write to
|
||||
* @param buf the data to send
|
||||
* @param len number of bytes in @a buf
|
||||
* @return 1 on success, 0 if the connection broke
|
||||
*/
|
||||
static int
|
||||
send_all (MHD_socket sock,
|
||||
const char *buf,
|
||||
size_t len)
|
||||
{
|
||||
ssize_t ret;
|
||||
size_t off;
|
||||
|
||||
for (off = 0; off < len; off += (size_t) ret)
|
||||
{
|
||||
ret = send (sock,
|
||||
buf + off,
|
||||
(int) (len - off),
|
||||
0);
|
||||
if (0 > ret)
|
||||
{
|
||||
if (EINTR == errno)
|
||||
{
|
||||
ret = 0;
|
||||
continue;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (0 == ret)
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make @a sock blocking. MHD hands out the socket in non-blocking
|
||||
* mode, see the tutorial chapter for the reason why. This function
|
||||
* contains the only operating-system-dependent code of this example.
|
||||
*
|
||||
* @param sock the socket to modify
|
||||
*/
|
||||
static void
|
||||
make_blocking (MHD_socket sock)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
int flags;
|
||||
|
||||
flags = fcntl (sock, F_GETFL);
|
||||
if (-1 == flags)
|
||||
abort ();
|
||||
if ((flags & ~O_NONBLOCK) != flags)
|
||||
if (-1 == fcntl (sock, F_SETFL, flags & ~O_NONBLOCK))
|
||||
abort ();
|
||||
#else /* _WIN32 */
|
||||
unsigned long flags = 0;
|
||||
|
||||
if (0 != ioctlsocket (sock, (int) FIONBIO, &flags))
|
||||
abort ();
|
||||
#endif /* _WIN32 */
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Feed @a size bytes of received data into the echo protocol state
|
||||
* machine, echoing back every complete line.
|
||||
*
|
||||
* @param sock the socket to echo to
|
||||
* @param es the protocol state of this connection
|
||||
* @param data the received bytes
|
||||
* @param size number of bytes in @a data
|
||||
* @return 1 if the client asked us to stop or the connection broke,
|
||||
* 0 if we should keep going
|
||||
*/
|
||||
static int
|
||||
echo_feed (MHD_socket sock,
|
||||
struct EchoState *es,
|
||||
const char *data,
|
||||
size_t size)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
{
|
||||
if ('\n' != data[i])
|
||||
{
|
||||
if (es->line_len < sizeof (es->line))
|
||||
es->line[es->line_len++] = data[i];
|
||||
/* else: line too long, drop the excess bytes */
|
||||
continue;
|
||||
}
|
||||
/* A complete line was received; drop the CR of a CRLF terminator */
|
||||
if ((0 != es->line_len) &&
|
||||
('\r' == es->line[es->line_len - 1]))
|
||||
es->line_len--;
|
||||
if ((4 == es->line_len) &&
|
||||
(equal_caseless (es->line, "QUIT", 4)))
|
||||
{
|
||||
(void) send_all (sock, "BYE\r\n", 5);
|
||||
es->line_len = 0;
|
||||
return 1;
|
||||
}
|
||||
if ((! send_all (sock, es->line, es->line_len)) ||
|
||||
(! send_all (sock, "\r\n", 2)))
|
||||
return 1;
|
||||
es->line_len = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called by MHD once the "101 Switching Protocols" response was sent.
|
||||
* From here on MHD is out of the picture: @a sock is an ordinary
|
||||
* socket and we are free to speak whatever protocol we like on it.
|
||||
*
|
||||
* As the daemon runs with #MHD_USE_THREAD_PER_CONNECTION, this
|
||||
* function has a thread of its own and may block for as long as it
|
||||
* wants.
|
||||
*
|
||||
* @param cls closure given to #MHD_create_response_for_upgrade()
|
||||
* @param connection the original HTTP connection
|
||||
* @param req_cls last value of @a req_cls of the access handler
|
||||
* @param extra_in bytes the client sent after the request header and
|
||||
* which MHD read together with the header
|
||||
* @param extra_in_size number of bytes in @a extra_in
|
||||
* @param sock the socket to talk to the client on
|
||||
* @param urh handle to pass to #MHD_upgrade_action()
|
||||
*/
|
||||
static void
|
||||
upgrade_handler (void *cls,
|
||||
struct MHD_Connection *connection,
|
||||
void *req_cls,
|
||||
const char *extra_in,
|
||||
size_t extra_in_size,
|
||||
MHD_socket sock,
|
||||
struct MHD_UpgradeResponseHandle *urh)
|
||||
{
|
||||
struct EchoState es;
|
||||
char buf[256];
|
||||
ssize_t got;
|
||||
int done;
|
||||
|
||||
(void) cls; /* Unused. Silent compiler warning. */
|
||||
(void) connection; /* Unused. Silent compiler warning. */
|
||||
(void) req_cls; /* Unused. Silent compiler warning. */
|
||||
|
||||
/* MHD hands out a non-blocking socket; this example wants to use
|
||||
plain blocking recv()/send() calls on it. */
|
||||
make_blocking (sock);
|
||||
|
||||
es.line_len = 0;
|
||||
|
||||
/* The client may have pipelined payload directly behind the request
|
||||
header, in which case MHD has read it already. Those bytes are
|
||||
gone from the socket, so they must be processed first. */
|
||||
done = echo_feed (sock,
|
||||
&es,
|
||||
extra_in,
|
||||
extra_in_size);
|
||||
|
||||
while (0 == done)
|
||||
{
|
||||
got = recv (sock,
|
||||
buf,
|
||||
sizeof (buf),
|
||||
0);
|
||||
if (0 > got)
|
||||
{
|
||||
if (EINTR == errno)
|
||||
continue;
|
||||
break; /* read error */
|
||||
}
|
||||
if (0 == got)
|
||||
break; /* client closed the connection */
|
||||
done = echo_feed (sock,
|
||||
&es,
|
||||
buf,
|
||||
(size_t) got);
|
||||
}
|
||||
|
||||
/* Hand the socket back to MHD. This is the only legal way to get
|
||||
rid of it; never call close() on it ourselves, and note that the
|
||||
connection is not cleaned up before this call happens. */
|
||||
MHD_upgrade_action (urh,
|
||||
MHD_UPGRADE_ACTION_CLOSE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Answer an ordinary HTTP request from a static string.
|
||||
*
|
||||
* @param connection the connection to answer
|
||||
* @param status_code the HTTP status code to use
|
||||
* @param page the response body
|
||||
* @param offer_upgrade if true, advertise our protocol in an
|
||||
* "Upgrade:" header field
|
||||
* @return #MHD_YES on success, #MHD_NO on error
|
||||
*/
|
||||
static enum MHD_Result
|
||||
answer_plain (struct MHD_Connection *connection,
|
||||
unsigned int status_code,
|
||||
const char *page,
|
||||
int offer_upgrade)
|
||||
{
|
||||
struct MHD_Response *response;
|
||||
enum MHD_Result ret;
|
||||
|
||||
response = MHD_create_response_from_buffer_static (strlen (page),
|
||||
page);
|
||||
if (NULL == response)
|
||||
return MHD_NO;
|
||||
if (offer_upgrade)
|
||||
(void) MHD_add_response_header (response,
|
||||
MHD_HTTP_HEADER_UPGRADE,
|
||||
PROTOCOL);
|
||||
ret = MHD_queue_response (connection,
|
||||
status_code,
|
||||
response);
|
||||
MHD_destroy_response (response);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static enum MHD_Result
|
||||
access_handler (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)
|
||||
{
|
||||
struct MHD_Response *response;
|
||||
enum MHD_Result ret;
|
||||
|
||||
(void) cls; /* Unused. Silent compiler warning. */
|
||||
(void) upload_data; /* Unused. Silent compiler warning. */
|
||||
(void) upload_data_size; /* Unused. Silent compiler warning. */
|
||||
(void) req_cls; /* Unused. Silent compiler warning. */
|
||||
|
||||
if (0 != strcmp (url, "/"))
|
||||
return answer_plain (connection,
|
||||
MHD_HTTP_NOT_FOUND,
|
||||
PAGE_NOT_FOUND,
|
||||
0);
|
||||
/* Upgrading requires HTTP/1.1 and a request that actually asks for
|
||||
our protocol. A client that does not ask gets a plain 426. */
|
||||
if ((0 != strcmp (method, MHD_HTTP_METHOD_GET)) ||
|
||||
(0 != strcmp (version, MHD_HTTP_VERSION_1_1)) ||
|
||||
(! has_token (MHD_lookup_connection_value (connection,
|
||||
MHD_HEADER_KIND,
|
||||
MHD_HTTP_HEADER_CONNECTION),
|
||||
"Upgrade")) ||
|
||||
(! has_token (MHD_lookup_connection_value (connection,
|
||||
MHD_HEADER_KIND,
|
||||
MHD_HTTP_HEADER_UPGRADE),
|
||||
PROTOCOL)))
|
||||
return answer_plain (connection,
|
||||
MHD_HTTP_UPGRADE_REQUIRED,
|
||||
PAGE_UPGRADE_REQUIRED,
|
||||
1);
|
||||
|
||||
/* The request is fine, switch protocols. MHD already put
|
||||
"Connection: Upgrade" into the response for us, we only have to
|
||||
name the protocol we switch to. */
|
||||
response = MHD_create_response_for_upgrade (&upgrade_handler,
|
||||
NULL);
|
||||
if (NULL == response)
|
||||
return MHD_NO;
|
||||
if (MHD_YES !=
|
||||
MHD_add_response_header (response,
|
||||
MHD_HTTP_HEADER_UPGRADE,
|
||||
PROTOCOL))
|
||||
{
|
||||
MHD_destroy_response (response);
|
||||
return MHD_NO;
|
||||
}
|
||||
ret = MHD_queue_response (connection,
|
||||
MHD_HTTP_SWITCHING_PROTOCOLS,
|
||||
response);
|
||||
/* The response may be destroyed right away: MHD keeps its own
|
||||
reference until the upgrade handler has been called. */
|
||||
MHD_destroy_response (response);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main (void)
|
||||
{
|
||||
struct MHD_Daemon *daemon;
|
||||
|
||||
daemon = MHD_start_daemon (MHD_USE_THREAD_PER_CONNECTION
|
||||
| MHD_USE_INTERNAL_POLLING_THREAD
|
||||
| MHD_ALLOW_UPGRADE
|
||||
| MHD_USE_ERROR_LOG,
|
||||
PORT, NULL, NULL,
|
||||
&access_handler, NULL,
|
||||
MHD_OPTION_END);
|
||||
if (NULL == daemon)
|
||||
return 1;
|
||||
|
||||
(void) getchar ();
|
||||
|
||||
MHD_stop_daemon (daemon);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,449 +0,0 @@
|
||||
/* Feel free to use this example code in any way
|
||||
you see fit (Public Domain) */
|
||||
|
||||
#include <sys/types.h>
|
||||
#ifndef _WIN32
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <fcntl.h>
|
||||
#else
|
||||
#include <winsock2.h>
|
||||
#endif
|
||||
#include <microhttpd.h>
|
||||
#include <microhttpd_ws.h>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define PORT 80
|
||||
|
||||
#define PAGE \
|
||||
"<!DOCTYPE html>\n" \
|
||||
"<html>\n" \
|
||||
"<head>\n" \
|
||||
"<meta charset=\"UTF-8\">\n" \
|
||||
"<title>Websocket Demo</title>\n" \
|
||||
"<script>\n" \
|
||||
"\n" \
|
||||
"let url = 'ws' + (window.location.protocol === 'https:' ? 's' : '')" \
|
||||
" + ':/" "/' +\n" \
|
||||
" window.location.host + '/chat';\n" \
|
||||
"let socket = null;\n" \
|
||||
"\n" \
|
||||
"window.onload = function(event) {\n" \
|
||||
" socket = new WebSocket(url);\n" \
|
||||
" socket.onopen = function(event) {\n" \
|
||||
" document.write('The websocket connection has been " \
|
||||
"established.<br>');\n" \
|
||||
"\n" \
|
||||
" /" "/ Send some text\n" \
|
||||
" socket.send('Hello from JavaScript!');\n" \
|
||||
" }\n" \
|
||||
"\n" \
|
||||
" socket.onclose = function(event) {\n" \
|
||||
" document.write('The websocket connection has been closed.<br>');\n" \
|
||||
" }\n" \
|
||||
"\n" \
|
||||
" socket.onerror = function(event) {\n" \
|
||||
" document.write('An error occurred during the websocket " \
|
||||
"communication.<br>');\n" \
|
||||
" }\n" \
|
||||
"\n" \
|
||||
" socket.onmessage = function(event) {\n" \
|
||||
" document.write('Websocket message received: ' + " \
|
||||
"event.data + '<br>');\n" \
|
||||
" }\n" \
|
||||
"}\n" \
|
||||
"\n" \
|
||||
"</script>\n" \
|
||||
"</head>\n" \
|
||||
"<body>\n" \
|
||||
"</body>\n" \
|
||||
"</html>"
|
||||
|
||||
#define PAGE_NOT_FOUND \
|
||||
"404 Not Found"
|
||||
|
||||
#define PAGE_INVALID_WEBSOCKET_REQUEST \
|
||||
"Invalid WebSocket request!"
|
||||
|
||||
static void
|
||||
send_all (MHD_socket fd,
|
||||
const char *buf,
|
||||
size_t len);
|
||||
|
||||
static void
|
||||
make_blocking (MHD_socket fd);
|
||||
|
||||
static void
|
||||
upgrade_handler (void *cls,
|
||||
struct MHD_Connection *connection,
|
||||
void *req_cls,
|
||||
const char *extra_in,
|
||||
size_t extra_in_size,
|
||||
MHD_socket fd,
|
||||
struct MHD_UpgradeResponseHandle *urh)
|
||||
{
|
||||
/* make the socket blocking (operating-system-dependent code) */
|
||||
make_blocking (fd);
|
||||
|
||||
/* create a websocket stream for this connection */
|
||||
struct MHD_WebSocketStream *ws;
|
||||
int result = MHD_websocket_stream_init (&ws,
|
||||
0,
|
||||
0);
|
||||
if (0 != result)
|
||||
{
|
||||
/* Couldn't create the websocket stream.
|
||||
* So we close the socket and leave
|
||||
*/
|
||||
MHD_upgrade_action (urh,
|
||||
MHD_UPGRADE_ACTION_CLOSE);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Let's wait for incoming data */
|
||||
const size_t buf_len = 256;
|
||||
char buf[buf_len];
|
||||
ssize_t got;
|
||||
while (MHD_WEBSOCKET_VALIDITY_VALID == MHD_websocket_stream_is_valid (ws))
|
||||
{
|
||||
got = recv (fd,
|
||||
buf,
|
||||
buf_len,
|
||||
0);
|
||||
if (0 >= got)
|
||||
{
|
||||
/* the TCP/IP socket has been closed */
|
||||
break;
|
||||
}
|
||||
|
||||
/* parse the entire received data */
|
||||
size_t buf_offset = 0;
|
||||
while (buf_offset < (size_t) got)
|
||||
{
|
||||
size_t new_offset = 0;
|
||||
char *frame_data = NULL;
|
||||
size_t frame_len = 0;
|
||||
int status = MHD_websocket_decode (ws,
|
||||
buf + buf_offset,
|
||||
((size_t) got) - buf_offset,
|
||||
&new_offset,
|
||||
&frame_data,
|
||||
&frame_len);
|
||||
if (0 > status)
|
||||
{
|
||||
/* an error occurred and the connection must be closed */
|
||||
if (NULL != frame_data)
|
||||
{
|
||||
MHD_websocket_free (ws, frame_data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
buf_offset += new_offset;
|
||||
if (0 < status)
|
||||
{
|
||||
/* the frame is complete */
|
||||
switch (status)
|
||||
{
|
||||
case MHD_WEBSOCKET_STATUS_TEXT_FRAME:
|
||||
/* The client has sent some text.
|
||||
* We will display it and answer with a text frame.
|
||||
*/
|
||||
if (NULL != frame_data)
|
||||
{
|
||||
printf ("Received message: %s\n", frame_data);
|
||||
MHD_websocket_free (ws, frame_data);
|
||||
frame_data = NULL;
|
||||
}
|
||||
result = MHD_websocket_encode_text (ws,
|
||||
"Hello",
|
||||
5, /* length of "Hello" */
|
||||
0,
|
||||
&frame_data,
|
||||
&frame_len,
|
||||
NULL);
|
||||
if (0 == result)
|
||||
{
|
||||
send_all (fd,
|
||||
frame_data,
|
||||
frame_len);
|
||||
}
|
||||
break;
|
||||
|
||||
case MHD_WEBSOCKET_STATUS_CLOSE_FRAME:
|
||||
/* if we receive a close frame, we will respond with one */
|
||||
MHD_websocket_free (ws,
|
||||
frame_data);
|
||||
frame_data = NULL;
|
||||
|
||||
result = MHD_websocket_encode_close (ws,
|
||||
0,
|
||||
NULL,
|
||||
0,
|
||||
&frame_data,
|
||||
&frame_len);
|
||||
if (0 == result)
|
||||
{
|
||||
send_all (fd,
|
||||
frame_data,
|
||||
frame_len);
|
||||
}
|
||||
break;
|
||||
|
||||
case MHD_WEBSOCKET_STATUS_PING_FRAME:
|
||||
/* if we receive a ping frame, we will respond */
|
||||
/* with the corresponding pong frame */
|
||||
{
|
||||
char *pong = NULL;
|
||||
size_t pong_len = 0;
|
||||
result = MHD_websocket_encode_pong (ws,
|
||||
frame_data,
|
||||
frame_len,
|
||||
&pong,
|
||||
&pong_len);
|
||||
if (0 == result)
|
||||
{
|
||||
send_all (fd,
|
||||
pong,
|
||||
pong_len);
|
||||
}
|
||||
MHD_websocket_free (ws,
|
||||
pong);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
/* Other frame types are ignored
|
||||
* in this minimal example.
|
||||
* This is valid, because they become
|
||||
* automatically skipped if we receive them unexpectedly
|
||||
*/
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (NULL != frame_data)
|
||||
{
|
||||
MHD_websocket_free (ws, frame_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* free the websocket stream */
|
||||
MHD_websocket_stream_free (ws);
|
||||
|
||||
/* close the socket when it is not needed anymore */
|
||||
MHD_upgrade_action (urh,
|
||||
MHD_UPGRADE_ACTION_CLOSE);
|
||||
}
|
||||
|
||||
|
||||
/* This helper function is used for the case that
|
||||
* we need to resend some data
|
||||
*/
|
||||
static void
|
||||
send_all (MHD_socket fd,
|
||||
const char *buf,
|
||||
size_t len)
|
||||
{
|
||||
ssize_t ret;
|
||||
size_t off;
|
||||
|
||||
for (off = 0; off < len; off += ret)
|
||||
{
|
||||
ret = send (fd,
|
||||
&buf[off],
|
||||
(int) (len - off),
|
||||
0);
|
||||
if (0 > ret)
|
||||
{
|
||||
if (EAGAIN == errno)
|
||||
{
|
||||
ret = 0;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (0 == ret)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* This helper function contains operating-system-dependent code and
|
||||
* is used to make a socket blocking.
|
||||
*/
|
||||
static void
|
||||
make_blocking (MHD_socket fd)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
int flags;
|
||||
|
||||
flags = fcntl (fd, F_GETFL);
|
||||
if (-1 == flags)
|
||||
abort ();
|
||||
if ((flags & ~O_NONBLOCK) != flags)
|
||||
if (-1 == fcntl (fd, F_SETFL, flags & ~O_NONBLOCK))
|
||||
abort ();
|
||||
#else /* _WIN32 */
|
||||
unsigned long flags = 0;
|
||||
|
||||
if (0 != ioctlsocket (fd, (int) FIONBIO, &flags))
|
||||
abort ();
|
||||
#endif /* _WIN32 */
|
||||
}
|
||||
|
||||
|
||||
static enum MHD_Result
|
||||
access_handler (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)
|
||||
{
|
||||
static int aptr;
|
||||
struct MHD_Response *response;
|
||||
int ret;
|
||||
|
||||
(void) cls; /* Unused. Silent compiler warning. */
|
||||
(void) upload_data; /* Unused. Silent compiler warning. */
|
||||
(void) upload_data_size; /* Unused. Silent compiler warning. */
|
||||
|
||||
if (0 != strcmp (method, "GET"))
|
||||
return MHD_NO; /* unexpected method */
|
||||
if (&aptr != *req_cls)
|
||||
{
|
||||
/* do never respond on first call */
|
||||
*req_cls = &aptr;
|
||||
return MHD_YES;
|
||||
}
|
||||
*req_cls = NULL; /* reset when done */
|
||||
|
||||
if (0 == strcmp (url, "/"))
|
||||
{
|
||||
/* Default page for visiting the server */
|
||||
struct MHD_Response *response;
|
||||
response = MHD_create_response_from_buffer_static (strlen (PAGE),
|
||||
PAGE);
|
||||
ret = MHD_queue_response (connection,
|
||||
MHD_HTTP_OK,
|
||||
response);
|
||||
MHD_destroy_response (response);
|
||||
}
|
||||
else if (0 == strcmp (url, "/chat"))
|
||||
{
|
||||
char is_valid = 1;
|
||||
const char *value = NULL;
|
||||
char sec_websocket_accept[29];
|
||||
|
||||
if (0 != MHD_websocket_check_http_version (version))
|
||||
{
|
||||
is_valid = 0;
|
||||
}
|
||||
value = MHD_lookup_connection_value (connection,
|
||||
MHD_HEADER_KIND,
|
||||
MHD_HTTP_HEADER_CONNECTION);
|
||||
if (0 != MHD_websocket_check_connection_header (value))
|
||||
{
|
||||
is_valid = 0;
|
||||
}
|
||||
value = MHD_lookup_connection_value (connection,
|
||||
MHD_HEADER_KIND,
|
||||
MHD_HTTP_HEADER_UPGRADE);
|
||||
if (0 != MHD_websocket_check_upgrade_header (value))
|
||||
{
|
||||
is_valid = 0;
|
||||
}
|
||||
value = MHD_lookup_connection_value (connection,
|
||||
MHD_HEADER_KIND,
|
||||
MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION);
|
||||
if (0 != MHD_websocket_check_version_header (value))
|
||||
{
|
||||
is_valid = 0;
|
||||
}
|
||||
value = MHD_lookup_connection_value (connection,
|
||||
MHD_HEADER_KIND,
|
||||
MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY);
|
||||
if (0 != MHD_websocket_create_accept_header (value, sec_websocket_accept))
|
||||
{
|
||||
is_valid = 0;
|
||||
}
|
||||
|
||||
if (1 == is_valid)
|
||||
{
|
||||
/* upgrade the connection */
|
||||
response = MHD_create_response_for_upgrade (&upgrade_handler,
|
||||
NULL);
|
||||
MHD_add_response_header (response,
|
||||
MHD_HTTP_HEADER_UPGRADE,
|
||||
"websocket");
|
||||
MHD_add_response_header (response,
|
||||
MHD_HTTP_HEADER_SEC_WEBSOCKET_ACCEPT,
|
||||
sec_websocket_accept);
|
||||
ret = MHD_queue_response (connection,
|
||||
MHD_HTTP_SWITCHING_PROTOCOLS,
|
||||
response);
|
||||
MHD_destroy_response (response);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* return error page */
|
||||
struct MHD_Response *response;
|
||||
response =
|
||||
MHD_create_response_from_buffer_static (strlen (
|
||||
PAGE_INVALID_WEBSOCKET_REQUEST),
|
||||
PAGE_INVALID_WEBSOCKET_REQUEST);
|
||||
ret = MHD_queue_response (connection,
|
||||
MHD_HTTP_BAD_REQUEST,
|
||||
response);
|
||||
MHD_destroy_response (response);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
struct MHD_Response *response;
|
||||
response =
|
||||
MHD_create_response_from_buffer_static (strlen (PAGE_NOT_FOUND),
|
||||
PAGE_NOT_FOUND);
|
||||
ret = MHD_queue_response (connection,
|
||||
MHD_HTTP_NOT_FOUND,
|
||||
response);
|
||||
MHD_destroy_response (response);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main (int argc,
|
||||
char *const *argv)
|
||||
{
|
||||
(void) argc; /* Unused. Silent compiler warning. */
|
||||
(void) argv; /* Unused. Silent compiler warning. */
|
||||
struct MHD_Daemon *daemon;
|
||||
|
||||
daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD
|
||||
| MHD_USE_THREAD_PER_CONNECTION
|
||||
| MHD_ALLOW_UPGRADE
|
||||
| MHD_USE_ERROR_LOG,
|
||||
PORT, NULL, NULL,
|
||||
&access_handler, NULL,
|
||||
MHD_OPTION_END);
|
||||
|
||||
if (NULL == daemon)
|
||||
return 1;
|
||||
(void) getc (stdin);
|
||||
|
||||
MHD_stop_daemon (daemon);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
\input texinfo @c -*-texinfo-*-
|
||||
@finalout
|
||||
@setfilename libmicrohttpd-tutorial.info
|
||||
@set UPDATED 2 April 2016
|
||||
@set UPDATED-MONTH April 2016
|
||||
@set EDITION 0.9.48
|
||||
@set VERSION 0.9.48
|
||||
@set UPDATED 28 July 2026
|
||||
@set UPDATED-MONTH July 2026
|
||||
@set EDITION 1.0.8
|
||||
@set VERSION 1.0.8
|
||||
@settitle A tutorial for GNU libmicrohttpd
|
||||
@c Unify all the indices into concept index.
|
||||
@syncodeindex fn cp
|
||||
@@ -24,7 +24,7 @@ updated @value{UPDATED}.
|
||||
|
||||
Copyright (c) 2008 Sebastian Gerhardt.
|
||||
|
||||
Copyright (c) 2010, 2011, 2012, 2013, 2016, 2021 Christian Grothoff.
|
||||
Copyright (c) 2010, 2011, 2012, 2013, 2016, 2021, 2026 Christian Grothoff.
|
||||
@quotation
|
||||
Permission is granted to copy, distribute and/or modify this document
|
||||
under the terms of the GNU Free Documentation License, Version 1.3
|
||||
@@ -67,8 +67,9 @@ Free Documentation License".
|
||||
* Processing POST data::
|
||||
* Improved processing of POST data::
|
||||
* Session management::
|
||||
* Generating responses on the fly::
|
||||
* Adding a layer of security::
|
||||
* Websockets::
|
||||
* Upgrading connections::
|
||||
* Bibliography::
|
||||
* License text::
|
||||
* Example programs::
|
||||
@@ -106,13 +107,17 @@ Free Documentation License".
|
||||
@chapter Session management
|
||||
@include chapters/sessions.inc
|
||||
|
||||
@node Generating responses on the fly
|
||||
@chapter Generating responses on the fly
|
||||
@include chapters/callbackresponse.inc
|
||||
|
||||
@node Adding a layer of security
|
||||
@chapter Adding a layer of security
|
||||
@include chapters/tlsauthentication.inc
|
||||
|
||||
@node Websockets
|
||||
@chapter Websockets
|
||||
@include chapters/websocket.inc
|
||||
@node Upgrading connections
|
||||
@chapter Upgrading connections
|
||||
@include chapters/upgrade.inc
|
||||
|
||||
@node Bibliography
|
||||
@appendix Bibliography
|
||||
@@ -132,8 +137,9 @@ Free Documentation License".
|
||||
* simplepost.c::
|
||||
* largepost.c::
|
||||
* sessions.c::
|
||||
* callbackresponse.c::
|
||||
* tlsauthentication.c::
|
||||
* websocket.c::
|
||||
* upgrade.c::
|
||||
@end menu
|
||||
|
||||
@node hellobrowser.c
|
||||
@@ -178,16 +184,22 @@ Free Documentation License".
|
||||
@verbatiminclude examples/sessions.c
|
||||
@end smalldisplay
|
||||
|
||||
@node callbackresponse.c
|
||||
@section callbackresponse.c
|
||||
@smalldisplay
|
||||
@verbatiminclude examples/callbackresponse.c
|
||||
@end smalldisplay
|
||||
|
||||
@node tlsauthentication.c
|
||||
@section tlsauthentication.c
|
||||
@smalldisplay
|
||||
@verbatiminclude examples/tlsauthentication.c
|
||||
@end smalldisplay
|
||||
|
||||
@node websocket.c
|
||||
@section websocket.c
|
||||
@node upgrade.c
|
||||
@section upgrade.c
|
||||
@smalldisplay
|
||||
@verbatiminclude examples/websocket.c
|
||||
@verbatiminclude examples/upgrade.c
|
||||
@end smalldisplay
|
||||
|
||||
@bye
|
||||
|
||||
@@ -67,7 +67,6 @@ Free Documentation License".
|
||||
* microhttpd-post:: Adding a @code{POST} processor.
|
||||
* microhttpd-info:: Obtaining and modifying status information.
|
||||
* microhttpd-util:: Utilities.
|
||||
* microhttpd-websocket:: Websockets.
|
||||
|
||||
Appendices
|
||||
|
||||
@@ -1246,493 +1245,6 @@ list.
|
||||
@end deftp
|
||||
|
||||
|
||||
@deftp {Enumeration} MHD_WEBSOCKET_FLAG
|
||||
@cindex websocket
|
||||
Options for the MHD websocket stream.
|
||||
|
||||
This is used for initialization of a websocket stream when calling
|
||||
@code{MHD_websocket_stream_init} or @code{MHD_websocket_stream_init2} and
|
||||
alters the behavior of the websocket stream.
|
||||
|
||||
Note that websocket streams are only available if you include the header file
|
||||
@code{microhttpd_ws.h} and compiled @emph{libmicrohttpd} with websockets.
|
||||
|
||||
@table @code
|
||||
@item MHD_WEBSOCKET_FLAG_SERVER
|
||||
The websocket stream is initialized in server mode (default).
|
||||
Thus all outgoing payload will not be masked.
|
||||
All incoming payload must be masked.
|
||||
|
||||
This flag cannot be used together with @code{MHD_WEBSOCKET_FLAG_CLIENT}.
|
||||
|
||||
@item MHD_WEBSOCKET_FLAG_CLIENT
|
||||
The websocket stream is initialized in client mode.
|
||||
You will usually never use that mode in combination with @emph{libmicrohttpd},
|
||||
because @emph{libmicrohttpd} provides a server and not a client.
|
||||
In client mode all outgoing payload will be masked
|
||||
(XOR-ed with random values).
|
||||
All incoming payload must be unmasked.
|
||||
If you use this mode, you must always call @code{MHD_websocket_stream_init2}
|
||||
instead of @code{MHD_websocket_stream_init}, because you need
|
||||
to pass a random number generator callback function for masking.
|
||||
|
||||
This flag cannot be used together with @code{MHD_WEBSOCKET_FLAG_SERVER}.
|
||||
|
||||
@item MHD_WEBSOCKET_FLAG_NO_FRAGMENTS
|
||||
You don't want to get fragmented data while decoding (default).
|
||||
Fragmented frames will be internally put together until
|
||||
they are complete.
|
||||
Whether or not data is fragmented is decided
|
||||
by the sender of the data during encoding.
|
||||
|
||||
This cannot be used together with @code{MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS}.
|
||||
|
||||
@item MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS
|
||||
You want fragmented data, if it appears while decoding.
|
||||
You will receive the content of the fragmented frame,
|
||||
but if you are decoding text, you will never get an unfinished
|
||||
UTF-8 sequence (if the sequence appears between two fragments).
|
||||
Instead the text will end before the unfinished UTF-8 sequence.
|
||||
With the next fragment, which finishes the UTF-8 sequence,
|
||||
you will get the complete UTF-8 sequence.
|
||||
|
||||
This cannot be used together with @code{MHD_WEBSOCKET_FLAG_NO_FRAGMENTS}.
|
||||
|
||||
@item MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR
|
||||
If the websocket stream becomes invalid during decoding due to
|
||||
protocol errors, a matching close frame will automatically
|
||||
be generated.
|
||||
The close frame will be returned via the parameters
|
||||
@code{payload} and @code{payload_len} of @code{MHD_websocket_decode} and
|
||||
the return value is negative (a value of @code{enum MHD_WEBSOCKET_STATUS}).
|
||||
|
||||
The generated close frame must be freed by the caller
|
||||
with @code{MHD_websocket_free}.
|
||||
|
||||
@end table
|
||||
@end deftp
|
||||
|
||||
|
||||
@deftp {Enumeration} MHD_WEBSOCKET_FRAGMENTATION
|
||||
@cindex websocket
|
||||
This enumeration is used to specify the fragmentation behavior
|
||||
when encoding of data (text/binary) for a websocket stream.
|
||||
This is used with @code{MHD_websocket_encode_text} or
|
||||
@code{MHD_websocket_encode_binary}.
|
||||
|
||||
Note that websocket streams are only available if you include the header file
|
||||
@code{microhttpd_ws.h} and compiled @emph{libmicrohttpd} with websockets.
|
||||
|
||||
@table @code
|
||||
@item MHD_WEBSOCKET_FRAGMENTATION_NONE
|
||||
You don't want to use fragmentation.
|
||||
The encoded frame consists of only one frame.
|
||||
|
||||
@item MHD_WEBSOCKET_FRAGMENTATION_FIRST
|
||||
You want to use fragmentation.
|
||||
The encoded frame is the first frame of
|
||||
a series of data frames of the same type
|
||||
(text or binary).
|
||||
You may send control frames (ping, pong or close)
|
||||
between these data frames.
|
||||
|
||||
@item MHD_WEBSOCKET_FRAGMENTATION_FOLLOWING
|
||||
You want to use fragmentation.
|
||||
The encoded frame is not the first frame of
|
||||
the series of data frames, but also not the last one.
|
||||
You may send control frames (ping, pong or close)
|
||||
between these data frames.
|
||||
|
||||
@item MHD_WEBSOCKET_FRAGMENTATION_LAST
|
||||
You want to use fragmentation.
|
||||
The encoded frame is the last frame of
|
||||
the series of data frames, but also not the first one.
|
||||
After this frame, you may send all types of frames again.
|
||||
|
||||
@end table
|
||||
@end deftp
|
||||
|
||||
|
||||
@deftp {Enumeration} MHD_WEBSOCKET_STATUS
|
||||
@cindex websocket
|
||||
This enumeration is used for the return value of almost
|
||||
every websocket stream function.
|
||||
Errors are negative and values equal to or above zero mean a success.
|
||||
Positive values are only used by @code{MHD_websocket_decode}.
|
||||
|
||||
Note that websocket streams are only available if you include the header file
|
||||
@code{microhttpd_ws.h} and compiled @emph{libmicrohttpd} with websockets.
|
||||
|
||||
@table @code
|
||||
@item MHD_WEBSOCKET_STATUS_OK
|
||||
The call succeeded.
|
||||
Especially for @code{MHD_websocket_decode} this means that no error occurred,
|
||||
but also no frame has been completed yet.
|
||||
For other functions this means simply a success.
|
||||
|
||||
@item MHD_WEBSOCKET_STATUS_TEXT_FRAME
|
||||
@code{MHD_websocket_decode} has decoded a text frame.
|
||||
The parameters @code{payload} and @code{payload_len} are filled with
|
||||
the decoded text (if any).
|
||||
You must free the returned @code{payload} after use with
|
||||
@code{MHD_websocket_free}.
|
||||
|
||||
@item MHD_WEBSOCKET_STATUS_BINARY_FRAME
|
||||
@code{MHD_websocket_decode} has decoded a binary frame.
|
||||
The parameters @code{payload} and @code{payload_len} are filled with
|
||||
the decoded binary data (if any).
|
||||
You must free the returned @code{payload} after use with
|
||||
@code{MHD_websocket_free}.
|
||||
|
||||
@item MHD_WEBSOCKET_STATUS_CLOSE_FRAME
|
||||
@code{MHD_websocket_decode} has decoded a close frame.
|
||||
This means you must close the socket using @code{MHD_upgrade_action}
|
||||
with @code{MHD_UPGRADE_ACTION_CLOSE}.
|
||||
You may respond with a close frame before closing.
|
||||
The parameters @code{payload} and @code{payload_len} are filled with
|
||||
the close reason (if any).
|
||||
The close reason starts with a two byte sequence of close code
|
||||
in network byte order (see @code{enum MHD_WEBSOCKET_CLOSEREASON}).
|
||||
After these two bytes a UTF-8 encoded close reason may follow.
|
||||
You can call @code{MHD_websocket_split_close_reason} to split that
|
||||
close reason.
|
||||
You must free the returned @code{payload} after use with
|
||||
@code{MHD_websocket_free}.
|
||||
|
||||
@item MHD_WEBSOCKET_STATUS_PING_FRAME
|
||||
@code{MHD_websocket_decode} has decoded a ping frame.
|
||||
You should respond to this with a pong frame.
|
||||
The pong frame must contain the same binary data as
|
||||
the corresponding ping frame (if it had any).
|
||||
The parameters @code{payload} and @code{payload_len} are filled with
|
||||
the binary ping data (if any).
|
||||
You must free the returned @code{payload} after use with
|
||||
@code{MHD_websocket_free}.
|
||||
|
||||
@item MHD_WEBSOCKET_STATUS_PONG_FRAME
|
||||
@code{MHD_websocket_decode} has decoded a pong frame.
|
||||
You should usually only receive pong frames if you sent
|
||||
a ping frame before.
|
||||
The binary data should be equal to your ping frame and can be
|
||||
used to distinguish the response if you sent multiple ping frames.
|
||||
The parameters @code{payload} and @code{payload_len} are filled with
|
||||
the binary pong data (if any).
|
||||
You must free the returned @code{payload} after use with
|
||||
@code{MHD_websocket_free}.
|
||||
|
||||
@item MHD_WEBSOCKET_STATUS_TEXT_FIRST_FRAGMENT
|
||||
@code{MHD_websocket_decode} has decoded a text frame fragment.
|
||||
The parameters @code{payload} and @code{payload_len} are filled with
|
||||
the decoded text (if any).
|
||||
This is like @code{MHD_WEBSOCKET_STATUS_TEXT_FRAME}, but it can only
|
||||
appear if you specified @code{MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS} during
|
||||
the call of @code{MHD_websocket_stream_init} or
|
||||
@code{MHD_websocket_stream_init2}.
|
||||
You must free the returned @code{payload} after use with
|
||||
@code{MHD_websocket_free}.
|
||||
|
||||
@item MHD_WEBSOCKET_STATUS_TEXT_FIRST_FRAGMENT
|
||||
@code{MHD_websocket_decode} has decoded a binary frame fragment.
|
||||
The parameters @code{payload} and @code{payload_len} are filled with
|
||||
the decoded binary data (if any).
|
||||
This is like @code{MHD_WEBSOCKET_STATUS_BINARY_FRAME}, but it can only
|
||||
appear if you specified @code{MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS} during
|
||||
the call of @code{MHD_websocket_stream_init} or
|
||||
@code{MHD_websocket_stream_init2}.
|
||||
You must free the returned @code{payload} after use with
|
||||
@code{MHD_websocket_free}.
|
||||
|
||||
@item MHD_WEBSOCKET_STATUS_TEXT_NEXT_FRAGMENT
|
||||
@code{MHD_websocket_decode} has decoded the next text frame fragment.
|
||||
The parameters @code{payload} and @code{payload_len} are filled with
|
||||
the decoded text (if any).
|
||||
This is like @code{MHD_WEBSOCKET_STATUS_TEXT_FIRST_FRAGMENT}, but it appears
|
||||
only after the first and before the last fragment of a series of fragments.
|
||||
It can only appear if you specified @code{MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS}
|
||||
during the call of @code{MHD_websocket_stream_init} or
|
||||
@code{MHD_websocket_stream_init2}.
|
||||
You must free the returned @code{payload} after use with
|
||||
@code{MHD_websocket_free}.
|
||||
|
||||
@item MHD_WEBSOCKET_STATUS_BINARY_NEXT_FRAGMENT
|
||||
@code{MHD_websocket_decode} has decoded the next binary frame fragment.
|
||||
The parameters @code{payload} and @code{payload_len} are filled with
|
||||
the decoded binary data (if any).
|
||||
This is like @code{MHD_WEBSOCKET_STATUS_BINARY_FIRST_FRAGMENT}, but it appears
|
||||
only after the first and before the last fragment of a series of fragments.
|
||||
It can only appear if you specified @code{MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS}
|
||||
during the call of @code{MHD_websocket_stream_init} or
|
||||
@code{MHD_websocket_stream_init2}.
|
||||
You must free the returned @code{payload} after use with
|
||||
@code{MHD_websocket_free}.
|
||||
|
||||
@item MHD_WEBSOCKET_STATUS_TEXT_LAST_FRAGMENT
|
||||
@code{MHD_websocket_decode} has decoded the last text frame fragment.
|
||||
The parameters @code{payload} and @code{payload_len} are filled with
|
||||
the decoded text (if any).
|
||||
This is like @code{MHD_WEBSOCKET_STATUS_TEXT_FIRST_FRAGMENT}, but it appears
|
||||
only for the last fragment of a series of fragments.
|
||||
It can only appear if you specified @code{MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS}
|
||||
during the call of @code{MHD_websocket_stream_init} or
|
||||
@code{MHD_websocket_stream_init2}.
|
||||
You must free the returned @code{payload} after use with
|
||||
@code{MHD_websocket_free}.
|
||||
|
||||
@item MHD_WEBSOCKET_STATUS_BINARY_LAST_FRAGMENT
|
||||
@code{MHD_websocket_decode} has decoded the last binary frame fragment.
|
||||
The parameters @code{payload} and @code{payload_len} are filled with
|
||||
the decoded binary data (if any).
|
||||
This is like @code{MHD_WEBSOCKET_STATUS_BINARY_FIRST_FRAGMENT}, but it appears
|
||||
only for the last fragment of a series of fragments.
|
||||
It can only appear if you specified @code{MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS}
|
||||
during the call of @code{MHD_websocket_stream_init} or
|
||||
@code{MHD_websocket_stream_init2}.
|
||||
You must free the returned @code{payload} after use with
|
||||
@code{MHD_websocket_free}.
|
||||
|
||||
@item MHD_WEBSOCKET_STATUS_PROTOCOL_ERROR
|
||||
The call failed and the stream is invalid now for decoding.
|
||||
You must close the websocket now using @code{MHD_upgrade_action}
|
||||
with @code{MHD_UPGRADE_ACTION_CLOSE}.
|
||||
You may send a close frame before closing.
|
||||
This is only used by @code{MHD_websocket_decode} and happens
|
||||
if the stream contains errors (i. e. invalid byte data).
|
||||
|
||||
@item MHD_WEBSOCKET_STATUS_STREAM_BROKEN
|
||||
You tried to decode something, but the stream has already
|
||||
been marked invalid.
|
||||
You must close the websocket now using @code{MHD_upgrade_action}
|
||||
with @code{MHD_UPGRADE_ACTION_CLOSE}.
|
||||
You may send a close frame before closing.
|
||||
This is only used by @code{MHD_websocket_decode} and happens
|
||||
if you call @code{MDM_websocket_decode} again after
|
||||
has been invalidated.
|
||||
You can call @code{MHD_websocket_stream_is_valid} at any time
|
||||
to check whether a stream is invalid or not.
|
||||
|
||||
@item MHD_WEBSOCKET_STATUS_MEMORY_ERROR
|
||||
A memory allocation failed. The stream remains valid.
|
||||
If this occurred while decoding, the decoding could be
|
||||
possible later if enough memory is available.
|
||||
This could happen while decoding if you received a too big data frame.
|
||||
You could try to specify max_payload_size during the call of
|
||||
@code{MHD_websocket_stream_init} or @code{MHD_websocket_stream_init2} to
|
||||
avoid this and close the websocket instead.
|
||||
|
||||
@item MHD_WEBSOCKET_STATUS_PARAMETER_ERROR
|
||||
You passed invalid parameters during the function call
|
||||
(i. e. a NULL pointer for a required parameter).
|
||||
The stream remains valid.
|
||||
|
||||
@item MHD_WEBSOCKET_STATUS_MAXIMUM_SIZE_EXCEEDED
|
||||
The maximum payload size has been exceeded.
|
||||
If you got this return code from @code{MHD_websocket_decode} then
|
||||
the stream becomes invalid and the websocket must be closed
|
||||
using @code{MHD_upgrade_action} with @code{MHD_UPGRADE_ACTION_CLOSE}.
|
||||
You may send a close frame before closing.
|
||||
The maximum payload size is specified during the call of
|
||||
@code{MHD_websocket_stream_init} or @code{MHD_websocket_stream_init2}.
|
||||
This can also appear if you specified 0 as maximum payload size
|
||||
when the message is greater than the maximum allocatable memory size
|
||||
(i. e. more than 4 GiB on 32 bit systems).
|
||||
If you got this return code from @code{MHD_websocket_encode_close},
|
||||
@code{MHD_websocket_encode_ping} or @code{MHD_websocket_encode_pong} then
|
||||
you passed to much payload data. The stream remains valid then.
|
||||
|
||||
@item MHD_WEBSOCKET_STATUS_UTF8_ENCODING_ERROR
|
||||
An UTF-8 sequence is invalid.
|
||||
If you got this return code from @code{MHD_websocket_decode} then
|
||||
the stream becomes invalid and you must close the websocket
|
||||
using @code{MHD_upgrade_action} with @code{MHD_UPGRADE_ACTION_CLOSE}.
|
||||
You may send a close frame before closing.
|
||||
If you got this from @code{MHD_websocket_encode_text} or
|
||||
@code{MHD_websocket_encode_close} then you passed invalid UTF-8 text.
|
||||
The stream remains valid then.
|
||||
|
||||
@item MHD_WEBSOCKET_STATUS_NO_WEBSOCKET_HANDSHAKE_HEADER
|
||||
A check routine for the HTTP headers came to the conclusion that
|
||||
the header value isn't valid for a websocket handshake request.
|
||||
This value can only be returned from the following functions:
|
||||
@code{MHD_websocket_check_http_version},
|
||||
@code{MHD_websocket_check_connection_header},
|
||||
@code{MHD_websocket_check_upgrade_header},
|
||||
@code{MHD_websocket_check_version_header},
|
||||
@code{MHD_websocket_create_accept_header}
|
||||
|
||||
@end table
|
||||
@end deftp
|
||||
|
||||
|
||||
@deftp {Enumeration} MHD_WEBSOCKET_CLOSEREASON
|
||||
@cindex websocket
|
||||
Enumeration of possible close reasons for websocket close frames.
|
||||
|
||||
The possible values are specified in RFC 6455 7.4.1
|
||||
These close reasons here are the default set specified by RFC 6455,
|
||||
but also other close reasons could be used.
|
||||
|
||||
The definition is for short:
|
||||
@itemize @bullet
|
||||
@item 0-999 are never used (if you pass 0 in
|
||||
@code{MHD_websocket_encode_close} then no close reason is used).
|
||||
@item 1000-2999 are specified by RFC 6455.
|
||||
@item 3000-3999 are specified by libraries, etc. but must be registered by IANA.
|
||||
@item 4000-4999 are reserved for private use.
|
||||
@end itemize
|
||||
|
||||
Note that websocket streams are only available if you include the header file
|
||||
@code{microhttpd_ws.h} and compiled @emph{libmicrohttpd} with websockets.
|
||||
|
||||
@table @code
|
||||
@item MHD_WEBSOCKET_CLOSEREASON_NO_REASON
|
||||
This value is used as placeholder for @code{MHD_websocket_encode_close}
|
||||
to tell that you don't want to specify any reason.
|
||||
If you use this value then no reason text may be used.
|
||||
This value cannot be a result of decoding, because this value
|
||||
is not a valid close reason for the websocket protocol.
|
||||
|
||||
@item MHD_WEBSOCKET_CLOSEREASON_REGULAR
|
||||
You close the websocket because it fulfilled its purpose and shall
|
||||
now be closed in a normal, planned way.
|
||||
|
||||
@item MHD_WEBSOCKET_CLOSEREASON_GOING_AWAY
|
||||
You close the websocket because you are shutting down the server or
|
||||
something similar.
|
||||
|
||||
@item MHD_WEBSOCKET_CLOSEREASON_PROTOCOL_ERROR
|
||||
You close the websocket because a protocol error occurred
|
||||
during decoding (i. e. invalid byte data).
|
||||
|
||||
@item MHD_WEBSOCKET_CLOSEREASON_UNSUPPORTED_DATATYPE
|
||||
You close the websocket because you received data which you don't accept.
|
||||
For example if you received a binary frame,
|
||||
but your application only expects text frames.
|
||||
|
||||
@item MHD_WEBSOCKET_CLOSEREASON_MALFORMED_UTF8
|
||||
You close the websocket because it contains malformed UTF-8.
|
||||
The UTF-8 validity is automatically checked by @code{MHD_websocket_decode},
|
||||
so you don't need to check it on your own.
|
||||
UTF-8 is specified in RFC 3629.
|
||||
|
||||
@item MHD_WEBSOCKET_CLOSEREASON_POLICY_VIOLATED
|
||||
You close the websocket because you received a frame which is too big
|
||||
to process.
|
||||
You can specify the maximum allowed payload size during the call of
|
||||
@code{MHD_websocket_stream_init} or @code{MHD_websocket_stream_init2}.
|
||||
|
||||
@item MHD_WEBSOCKET_CLOSEREASON_MISSING_EXTENSION
|
||||
This status code can be sent by the client if it
|
||||
expected a specific extension, but this extension hasn't been negotiated.
|
||||
|
||||
@item MHD_WEBSOCKET_CLOSEREASON_UNEXPECTED_CONDITION
|
||||
The server closes the websocket because it encountered
|
||||
an unexpected condition that prevented it from fulfilling the request.
|
||||
|
||||
@end table
|
||||
@end deftp
|
||||
|
||||
|
||||
@deftp {Enumeration} MHD_WEBSOCKET_UTF8STEP
|
||||
@cindex websocket
|
||||
Enumeration of possible UTF-8 check steps for websocket functions
|
||||
|
||||
These values are used during the encoding of fragmented text frames
|
||||
or for error analysis while encoding text frames.
|
||||
Its values specify the next step of the UTF-8 check.
|
||||
UTF-8 sequences consist of one to four bytes.
|
||||
This enumeration just says how long the current UTF-8 sequence is
|
||||
and what is the next expected byte.
|
||||
|
||||
Note that websocket streams are only available if you include the header file
|
||||
@code{microhttpd_ws.h} and compiled @emph{libmicrohttpd} with websockets.
|
||||
|
||||
@table @code
|
||||
@item MHD_WEBSOCKET_UTF8STEP_NORMAL
|
||||
There is no open UTF-8 sequence.
|
||||
The next byte must be 0x00-0x7F or 0xC2-0xF4.
|
||||
|
||||
@item MHD_WEBSOCKET_UTF8STEP_UTF2TAIL_1OF1
|
||||
The second byte of a two byte UTF-8 sequence.
|
||||
The first byte was 0xC2-0xDF.
|
||||
The next byte must be 0x80-0xBF.
|
||||
|
||||
@item MHD_WEBSOCKET_UTF8STEP_UTF3TAIL1_1OF2
|
||||
The second byte of a three byte UTF-8 sequence.
|
||||
The first byte was 0xE0.
|
||||
The next byte must be 0xA0-0xBF.
|
||||
|
||||
@item MHD_WEBSOCKET_UTF8STEP_UTF3TAIL2_1OF2
|
||||
The second byte of a three byte UTF-8 sequence.
|
||||
The first byte was 0xED.
|
||||
The next byte must by 0x80-0x9F.
|
||||
|
||||
@item MHD_WEBSOCKET_UTF8STEP_UTF3TAIL_1OF2
|
||||
The second byte of a three byte UTF-8 sequence.
|
||||
The first byte was 0xE1-0xEC or 0xEE-0xEF.
|
||||
The next byte must be 0x80-0xBF.
|
||||
|
||||
@item MHD_WEBSOCKET_UTF8STEP_UTF3TAIL_2OF2
|
||||
The third byte of a three byte UTF-8 sequence.
|
||||
The next byte must be 0x80-0xBF.
|
||||
|
||||
@item MHD_WEBSOCKET_UTF8STEP_UTF4TAIL1_1OF3
|
||||
The second byte of a four byte UTF-8 sequence.
|
||||
The first byte was 0xF0.
|
||||
The next byte must be 0x90-0xBF.
|
||||
|
||||
@item MHD_WEBSOCKET_UTF8STEP_UTF4TAIL2_1OF3
|
||||
The second byte of a four byte UTF-8 sequence.
|
||||
The first byte was 0xF4.
|
||||
The next byte must be 0x80-0x8F.
|
||||
|
||||
@item MHD_WEBSOCKET_UTF8STEP_UTF4TAIL_1OF3
|
||||
The second byte of a four byte UTF-8 sequence.
|
||||
The first byte was 0xF1-0xF3.
|
||||
The next byte must be 0x80-0xBF.
|
||||
|
||||
@item MHD_WEBSOCKET_UTF8STEP_UTF4TAIL_2OF3
|
||||
The third byte of a four byte UTF-8 sequence.
|
||||
The next byte must be 0x80-0xBF.
|
||||
|
||||
@item MHD_WEBSOCKET_UTF8STEP_UTF4TAIL_3OF3
|
||||
The fourth byte of a four byte UTF-8 sequence.
|
||||
The next byte must be 0x80-0xBF.
|
||||
|
||||
@end table
|
||||
@end deftp
|
||||
|
||||
|
||||
@deftp {Enumeration} MHD_WEBSOCKET_VALIDITY
|
||||
@cindex websocket
|
||||
Enumeration of validity values of a websocket stream
|
||||
|
||||
These values are used for @code{MHD_websocket_stream_is_valid}
|
||||
and specify the validity status.
|
||||
|
||||
Note that websocket streams are only available if you include the header file
|
||||
@code{microhttpd_ws.h} and compiled @emph{libmicrohttpd} with websockets.
|
||||
|
||||
@table @code
|
||||
@item MHD_WEBSOCKET_VALIDITY_INVALID
|
||||
The stream is invalid.
|
||||
It cannot be used for decoding anymore.
|
||||
|
||||
@item MHD_WEBSOCKET_VALIDITY_VALID
|
||||
The stream is valid.
|
||||
Decoding works as expected.
|
||||
|
||||
@item MHD_WEBSOCKET_VALIDITY_ONLY_VALID_FOR_CONTROL_FRAMES
|
||||
The stream has received a close frame and
|
||||
is partly invalid.
|
||||
You can still use the stream for decoding,
|
||||
but if a data frame is received an error will be reported.
|
||||
After a close frame has been sent, no data frames
|
||||
may follow from the sender of the close frame.
|
||||
|
||||
@end table
|
||||
@end deftp
|
||||
|
||||
|
||||
@c ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
@c ------------------------------------------------------------
|
||||
@@ -1778,11 +1290,6 @@ Information about an MHD daemon.
|
||||
@end deftp
|
||||
|
||||
|
||||
@deftp {C Struct} MHD_WebSocketStream
|
||||
@cindex websocket
|
||||
Information about a MHD websocket stream.
|
||||
@end deftp
|
||||
|
||||
|
||||
@c ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
@@ -2042,95 +1549,6 @@ iteration.
|
||||
@end deftypefn
|
||||
|
||||
|
||||
@deftypefn {Function Pointer} void* {*MHD_WebSocketMallocCallback} (size_t buf_len)
|
||||
@cindex websocket
|
||||
This callback function is used internally by many websocket functions
|
||||
for allocating data.
|
||||
By default @code{malloc} is used.
|
||||
You can use your own allocation function with @code{MHD_websocket_stream_init2}
|
||||
if you wish to.
|
||||
This can be useful for operating systems like Windows
|
||||
where @code{malloc}, @code{realloc} and @code{free} are compiler-dependent.
|
||||
You can call the associated @code{malloc} callback of
|
||||
a websocket stream with @code{MHD_websocket_malloc}.
|
||||
|
||||
@table @var
|
||||
@item buf_len
|
||||
size of the buffer to allocate in bytes.
|
||||
@end table
|
||||
|
||||
Return the pointer of the allocated buffer or @code{NULL} on failure.
|
||||
@end deftypefn
|
||||
|
||||
|
||||
@deftypefn {Function Pointer} void* {*MHD_WebSocketReallocCallback} (void *buf, size_t new_buf_len)
|
||||
@cindex websocket
|
||||
This callback function is used internally by many websocket
|
||||
functions for reallocating data.
|
||||
By default @code{realloc} is used.
|
||||
You can use your own reallocation function with
|
||||
@code{MHD_websocket_stream_init2} if you wish to.
|
||||
This can be useful for operating systems like Windows
|
||||
where @code{malloc}, @code{realloc} and @code{free} are compiler-dependent.
|
||||
You can call the associated @code{realloc} callback of
|
||||
a websocket stream with @code{MHD_websocket_realloc}.
|
||||
|
||||
@table @var
|
||||
@item buf
|
||||
current buffer, may be @code{NULL};
|
||||
|
||||
@item new_buf_len
|
||||
new size of the buffer in bytes.
|
||||
@end table
|
||||
|
||||
Return the pointer of the reallocated buffer or @code{NULL} on failure.
|
||||
On failure the old pointer must remain valid.
|
||||
@end deftypefn
|
||||
|
||||
|
||||
@deftypefn {Function Pointer} void {*MHD_WebSocketFreeCallback} (void *buf)
|
||||
@cindex websocket
|
||||
This callback function is used internally by many websocket
|
||||
functions for freeing data.
|
||||
By default @code{free} is used.
|
||||
You can use your own free function with
|
||||
@code{MHD_websocket_stream_init2} if you wish to.
|
||||
This can be useful for operating systems like Windows
|
||||
where @code{malloc}, @code{realloc} and @code{free} are compiler-dependent.
|
||||
You can call the associated @code{free} callback of
|
||||
a websocket stream with @code{MHD_websocket_free}.
|
||||
|
||||
@table @var
|
||||
@item cls
|
||||
current buffer to free, this may be @code{NULL} then nothing happens.
|
||||
@end table
|
||||
@end deftypefn
|
||||
|
||||
|
||||
@deftypefn {Function Pointer} size_t {*MHD_WebSocketRandomNumberGenerator} (void *cls, void* buf, size_t buf_len)
|
||||
@cindex websocket
|
||||
This callback function is used for generating random numbers
|
||||
for masking payload data in client mode.
|
||||
If you use websockets in server mode with @emph{libmicrohttpd} then
|
||||
you don't need a random number generator, because
|
||||
the server doesn't mask its outgoing messages.
|
||||
However if you wish to use a websocket stream in client mode,
|
||||
you must pass this callback function to @code{MHD_websocket_stream_init2}.
|
||||
|
||||
@table @var
|
||||
@item cls
|
||||
closure specified in @code{MHD_websocket_stream_init2};
|
||||
@item buf
|
||||
buffer to fill with random values;
|
||||
@item buf_len
|
||||
size of buffer in bytes.
|
||||
@end table
|
||||
|
||||
Return the number of generated random bytes.
|
||||
The return value should usually equal to buf_len.
|
||||
@end deftypefn
|
||||
|
||||
|
||||
@c ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
@c ------------------------------------------------------------
|
||||
@@ -4018,703 +3436,6 @@ shorter afterwards due to elimination of escape sequences).
|
||||
|
||||
|
||||
|
||||
@c ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
@c ------------------------------------------------------------
|
||||
@node microhttpd-websocket
|
||||
@chapter Websocket functions.
|
||||
|
||||
@noindent
|
||||
Websocket functions provide what you need to use an upgraded connection
|
||||
as a websocket.
|
||||
These functions are only available if you include the header file
|
||||
@code{microhttpd_ws.h} and compiled @emph{libmicrohttpd} with websockets.
|
||||
|
||||
@menu
|
||||
* microhttpd-websocket handshake:: Websocket handshake functions
|
||||
* microhttpd-websocket stream:: Websocket stream functions
|
||||
* microhttpd-websocket decode:: Websocket decode functions
|
||||
* microhttpd-websocket encode:: Websocket encode functions
|
||||
* microhttpd-websocket memory:: Websocket memory functions
|
||||
@end menu
|
||||
|
||||
@c ------------------------------------------------------------
|
||||
@node microhttpd-websocket handshake
|
||||
@section Websocket handshake functions
|
||||
|
||||
|
||||
@deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_check_http_version (const char* http_version)
|
||||
@cindex websocket
|
||||
Checks the HTTP version of the incoming request.
|
||||
Websocket requests are only allowed for HTTP/1.1 or above.
|
||||
|
||||
@table @var
|
||||
@item http_version
|
||||
The value of the @code{version} parameter of your
|
||||
@code{access_handler} callback.
|
||||
If you pass @code{NULL} then this is handled like a not
|
||||
matching HTTP version.
|
||||
@end table
|
||||
|
||||
Returns 0 when the HTTP version is
|
||||
valid for a websocket request and
|
||||
a value less than zero when the HTTP version isn't
|
||||
valid for a websocket request.
|
||||
Can be compared with @code{enum MHD_WEBSOCKET_STATUS}.
|
||||
@end deftypefun
|
||||
|
||||
|
||||
@deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_check_connection_header (const char* connection_header)
|
||||
@cindex websocket
|
||||
Checks the value of the @code{Connection} HTTP request header.
|
||||
Websocket requests require the token @code{Upgrade} in
|
||||
the @code{Connection} HTTP request header.
|
||||
|
||||
@table @var
|
||||
@item connection_header
|
||||
Value of the @code{Connection} request header.
|
||||
You can get this request header value by passing
|
||||
@code{MHD_HTTP_HEADER_CONNECTION} to
|
||||
@code{MHD_lookup_connection_value()}.
|
||||
If you pass @code{NULL} then this is handled like a not
|
||||
matching @code{Connection} header value.
|
||||
@end table
|
||||
|
||||
Returns 0 when the @code{Connection} header is
|
||||
valid for a websocket request and
|
||||
a value less than zero when the @code{Connection} header isn't
|
||||
valid for a websocket request.
|
||||
Can be compared with @code{enum MHD_WEBSOCKET_STATUS}.
|
||||
@end deftypefun
|
||||
|
||||
|
||||
@deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_check_upgrade_header (const char* upgrade_header)
|
||||
@cindex websocket
|
||||
Checks the value of the @code{Upgrade} HTTP request header.
|
||||
Websocket requests require the value @code{websocket} in
|
||||
the @code{Upgrade} HTTP request header.
|
||||
|
||||
@table @var
|
||||
@item upgrade_header
|
||||
Value of the @code{Upgrade} request header.
|
||||
You can get this request header value by passing
|
||||
@code{MHD_HTTP_HEADER_UPGRADE} to
|
||||
@code{MHD_lookup_connection_value()}.
|
||||
If you pass @code{NULL} then this is handled like a not
|
||||
matching @code{Upgrade} header value.
|
||||
@end table
|
||||
|
||||
Returns 0 when the @code{Upgrade} header is
|
||||
valid for a websocket request and
|
||||
a value less than zero when the @code{Upgrade} header isn't
|
||||
valid for a websocket request.
|
||||
Can be compared with @code{enum MHD_WEBSOCKET_STATUS}.
|
||||
@end deftypefun
|
||||
|
||||
|
||||
@deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_check_version_header (const char* version_header)
|
||||
@cindex websocket
|
||||
Checks the value of the @code{Sec-WebSocket-Version} HTTP request header.
|
||||
Websocket requests require the value @code{13} in
|
||||
the @code{Sec-WebSocket-Version} HTTP request header.
|
||||
|
||||
@table @var
|
||||
@item version_header
|
||||
Value of the @code{Sec-WebSocket-Version} request header.
|
||||
You can get this request header value by passing
|
||||
@code{MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION} to
|
||||
@code{MHD_lookup_connection_value()}.
|
||||
If you pass @code{NULL} then this is handled like a not
|
||||
matching @code{Sec-WebSocket-Version} header value.
|
||||
@end table
|
||||
|
||||
Returns 0 when the @code{Sec-WebSocket-Version} header is
|
||||
valid for a websocket request and
|
||||
a value less than zero when the @code{Sec-WebSocket-Version} header isn't
|
||||
valid for a websocket request.
|
||||
Can be compared with @code{enum MHD_WEBSOCKET_STATUS}.
|
||||
@end deftypefun
|
||||
|
||||
|
||||
@deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_create_accept_header (const char* sec_websocket_key, char* sec_websocket_accept)
|
||||
@cindex websocket
|
||||
Checks the value of the @code{Sec-WebSocket-Key}
|
||||
HTTP request header and generates the value for
|
||||
the @code{Sec-WebSocket-Accept} HTTP response header.
|
||||
The generated value must be sent to the client.
|
||||
|
||||
@table @var
|
||||
@item sec_websocket_key
|
||||
Value of the @code{Sec-WebSocket-Key} request header.
|
||||
You can get this request header value by passing
|
||||
@code{MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY} to
|
||||
@code{MHD_lookup_connection_value()}.
|
||||
If you pass @code{NULL} then this is handled like a not
|
||||
matching @code{Sec-WebSocket-Key} header value.
|
||||
|
||||
@item sec_websocket_accept
|
||||
Response buffer, which will receive
|
||||
the generated value for the @code{Sec-WebSocket-Accept}
|
||||
HTTP response header.
|
||||
This buffer must be at least 29 bytes long and
|
||||
will contain the response value plus a terminating @code{NUL}
|
||||
character on success.
|
||||
Must not be @code{NULL}.
|
||||
You can add this HTTP header to your response by passing
|
||||
@code{MHD_HTTP_HEADER_SEC_WEBSOCKET_ACCEPT} to
|
||||
@code{MHD_add_response_header()}.
|
||||
@end table
|
||||
|
||||
Returns 0 when the @code{Sec-WebSocket-Key} header was
|
||||
not empty and a result value for the @code{Sec-WebSocket-Accept}
|
||||
was calculated.
|
||||
A value less than zero is returned when the @code{Sec-WebSocket-Key}
|
||||
header isn't valid for a websocket request or when any
|
||||
error occurred.
|
||||
Can be compared with @code{enum MHD_WEBSOCKET_STATUS}.
|
||||
@end deftypefun
|
||||
|
||||
|
||||
@c ------------------------------------------------------------
|
||||
@node microhttpd-websocket stream
|
||||
@section Websocket stream functions
|
||||
|
||||
@deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_stream_init (struct MHD_WebSocketStream **ws, int flags, size_t max_payload_size)
|
||||
@cindex websocket
|
||||
Creates a new websocket stream, used for decoding/encoding.
|
||||
|
||||
@table @var
|
||||
@item ws
|
||||
pointer a variable to fill with the newly created
|
||||
@code{struct MHD_WebSocketStream},
|
||||
receives @code{NULL} on error. May not be @code{NULL}.
|
||||
|
||||
If not required anymore, free the created websocket stream with
|
||||
@code{MHD_websocket_stream_free()}.
|
||||
|
||||
@item flags
|
||||
combination of @code{enum MHD_WEBSOCKET_FLAG} values to
|
||||
modify the behavior of the websocket stream.
|
||||
|
||||
@item max_payload_size
|
||||
maximum size for incoming payload data in bytes. Use 0 to allow each size.
|
||||
@end table
|
||||
|
||||
Returns 0 on success, negative values on error.
|
||||
Can be compared with @code{enum MHD_WEBSOCKET_STATUS}.
|
||||
@end deftypefun
|
||||
|
||||
|
||||
@deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_stream_init2 (struct MHD_WebSocketStream **ws, int flags, size_t max_payload_size, MHD_WebSocketMallocCallback callback_malloc, MHD_WebSocketReallocCallback callback_realloc, MHD_WebSocketFreeCallback callback_free, void* cls_rng, MHD_WebSocketRandomNumberGenerator callback_rng)
|
||||
@cindex websocket
|
||||
Creates a new websocket stream, used for decoding/encoding,
|
||||
but with custom memory functions for malloc, realloc and free.
|
||||
Also a random number generator can be specified for client mode.
|
||||
|
||||
@table @var
|
||||
@item ws
|
||||
pointer a variable to fill with the newly created
|
||||
@code{struct MHD_WebSocketStream},
|
||||
receives @code{NULL} on error. Must not be @code{NULL}.
|
||||
|
||||
If not required anymore, free the created websocket stream with
|
||||
@code{MHD_websocket_stream_free}.
|
||||
|
||||
@item flags
|
||||
combination of @code{enum MHD_WEBSOCKET_FLAG} values to
|
||||
modify the behavior of the websocket stream.
|
||||
|
||||
@item max_payload_size
|
||||
maximum size for incoming payload data in bytes. Use 0 to allow each size.
|
||||
|
||||
@item callback_malloc
|
||||
callback function for allocating memory. Must not be @code{NULL}.
|
||||
The shorter @code{MHD_websocket_stream_init()} passes a reference to @code{malloc} here.
|
||||
|
||||
@item callback_realloc
|
||||
callback function for reallocating memory. Must not be @code{NULL}.
|
||||
The shorter @code{MHD_websocket_stream_init()} passes a reference to @code{realloc} here.
|
||||
|
||||
@item callback_free
|
||||
callback function for freeing memory. Must not be @code{NULL}.
|
||||
The shorter @code{MHD_websocket_stream_init()} passes a reference to @code{free} here.
|
||||
|
||||
@item cls_rng
|
||||
closure for the random number generator.
|
||||
This is only required when
|
||||
@code{MHD_WEBSOCKET_FLAG_CLIENT} is passed in @code{flags}.
|
||||
The given value is passed to the random number generator callback.
|
||||
May be @code{NULL} if not needed.
|
||||
Should be @code{NULL} when you are not using @code{MHD_WEBSOCKET_FLAG_CLIENT}.
|
||||
The shorter @code{MHD_websocket_stream_init} passes @code{NULL} here.
|
||||
|
||||
@item callback_rng
|
||||
callback function for a secure random number generator.
|
||||
This is only required when @code{MHD_WEBSOCKET_FLAG_CLIENT} is
|
||||
passed in @code{flags} and must not be @code{NULL} then.
|
||||
Should be @code{NULL} otherwise.
|
||||
The shorter @code{MHD_websocket_stream_init()} passes @code{NULL} here.
|
||||
@end table
|
||||
|
||||
Returns 0 on success, negative values on error.
|
||||
Can be compared with @code{enum MHD_WEBSOCKET_STATUS}.
|
||||
@end deftypefun
|
||||
|
||||
|
||||
@deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_stream_free (struct MHD_WebSocketStream *ws)
|
||||
@cindex websocket
|
||||
Frees a previously allocated websocket stream
|
||||
|
||||
@table @var
|
||||
@item ws
|
||||
websocket stream to free, this value may be @code{NULL}.
|
||||
@end table
|
||||
|
||||
Returns 0 on success, negative values on error.
|
||||
Can be compared with @code{enum MHD_WEBSOCKET_STATUS}.
|
||||
@end deftypefun
|
||||
|
||||
|
||||
@deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_stream_invalidate (struct MHD_WebSocketStream *ws)
|
||||
@cindex websocket
|
||||
Invalidates a websocket stream.
|
||||
After invalidation a websocket stream cannot be used for decoding anymore.
|
||||
Encoding is still possible.
|
||||
|
||||
@table @var
|
||||
@item ws
|
||||
websocket stream to invalidate.
|
||||
@end table
|
||||
|
||||
Returns 0 on success, negative values on error.
|
||||
Can be compared with @code{enum MHD_WEBSOCKET_STATUS}.
|
||||
@end deftypefun
|
||||
|
||||
|
||||
@deftypefun {enum MHD_WEBSOCKET_VALIDITY} MHD_websocket_stream_is_valid (struct MHD_WebSocketStream *ws)
|
||||
@cindex websocket
|
||||
Queries whether a websocket stream is valid.
|
||||
Invalidated websocket streams cannot be used for decoding anymore.
|
||||
Encoding is still possible.
|
||||
|
||||
@table @var
|
||||
@item ws
|
||||
websocket stream to invalidate.
|
||||
@end table
|
||||
|
||||
Returns 0 if invalid, 1 if valid for all types or
|
||||
2 if valid only for control frames.
|
||||
Can be compared with @code{enum MHD_WEBSOCKET_VALIDITY}.
|
||||
@end deftypefun
|
||||
|
||||
|
||||
@c ------------------------------------------------------------
|
||||
@node microhttpd-websocket decode
|
||||
@section Websocket decode functions
|
||||
|
||||
|
||||
@deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_decode (struct MHD_WebSocketStream* ws, const char* streambuf, size_t streambuf_len, size_t* streambuf_read_len, char** payload, size_t* payload_len)
|
||||
@cindex websocket
|
||||
Decodes a byte sequence for a websocket stream.
|
||||
Decoding is done until either a frame is complete or
|
||||
the end of the byte sequence is reached.
|
||||
|
||||
@table @var
|
||||
@item ws
|
||||
websocket stream for decoding.
|
||||
|
||||
@item streambuf
|
||||
byte sequence for decoding.
|
||||
This is what you typically received via @code{recv()}.
|
||||
|
||||
@item streambuf_len
|
||||
length of the byte sequence in parameter @code{streambuf}.
|
||||
|
||||
@item streambuf_read_len
|
||||
pointer to a variable, which receives the number of bytes,
|
||||
that has been processed by this call.
|
||||
This value may be less than the value of @code{streambuf_len} when
|
||||
a frame is decoded before the end of the buffer is reached.
|
||||
The remaining bytes of @code{buf} must be passed to
|
||||
the next call of this function.
|
||||
|
||||
@item payload
|
||||
pointer to a variable, which receives the allocated buffer with the payload
|
||||
data of the decoded frame. Must not be @code{NULL}.
|
||||
If no decoded data is available or an error occurred @code{NULL} is returned.
|
||||
When the returned value is not @code{NULL} then the buffer contains always
|
||||
@code{payload_len} bytes plus one terminating @code{NUL} character
|
||||
(regardless of the frame type).
|
||||
|
||||
The caller must free this buffer using @code{MHD_websocket_free()}.
|
||||
|
||||
If you passed the flag @code{MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR}
|
||||
upon creation of the websocket stream and a decoding error occurred
|
||||
(function return value less than 0), then this buffer contains
|
||||
a generated close frame, which must be sent via the socket to the recipient.
|
||||
|
||||
If you passed the flag @code{MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS}
|
||||
upon creation of the websocket stream then
|
||||
this payload may only be a part of the complete message.
|
||||
Only complete UTF-8 sequences are returned for fragmented text frames.
|
||||
If necessary the UTF-8 sequence will be completed with the next text fragment.
|
||||
|
||||
@item payload_len
|
||||
pointer to a variable, which receives length of the result
|
||||
@code{payload} buffer in bytes.
|
||||
Must not be @code{NULL}.
|
||||
This receives 0 when no data is available, when the decoded payload
|
||||
has a length of zero or when an error occurred.
|
||||
@end table
|
||||
|
||||
Returns a value greater than zero when a frame is complete.
|
||||
Compare with @code{enum MHD_WEBSOCKET_STATUS} to distinguish the frame type.
|
||||
Returns 0 when the call succeeded, but no frame is available.
|
||||
Returns a value less than zero on errors.
|
||||
@end deftypefun
|
||||
|
||||
|
||||
@deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_split_close_reason (const char* payload, size_t payload_len, unsigned short* reason_code, const char** reason_utf8, size_t* reason_utf8_len)
|
||||
@cindex websocket
|
||||
Splits the payload of a decoded close frame.
|
||||
|
||||
@table @var
|
||||
@item payload
|
||||
payload of the close frame.
|
||||
This parameter may only be @code{NULL} if @code{payload_len} is 0.
|
||||
|
||||
@item payload_len
|
||||
length of @code{payload}.
|
||||
|
||||
@item reason_code
|
||||
pointer to a variable, which receives the numeric close reason.
|
||||
If there was no close reason, this is 0.
|
||||
This value can be compared with @code{enum MHD_WEBSOCKET_CLOSEREASON}.
|
||||
May be @code{NULL}.
|
||||
|
||||
@item reason_utf8
|
||||
pointer to a variable, which receives the literal close reason.
|
||||
If there was no literal close reason, this will be @code{NULL}.
|
||||
May be @code{NULL}.
|
||||
|
||||
Please note that no memory is allocated in this function.
|
||||
If not @code{NULL} the returned value of this parameter
|
||||
points to a position in the specified @code{payload}.
|
||||
|
||||
@item reason_utf8_len
|
||||
pointer to a variable, which receives the length of the literal close reason.
|
||||
If there was no literal close reason, this is 0.
|
||||
May be @code{NULL}.
|
||||
@end table
|
||||
|
||||
Returns 0 on success or a value less than zero on errors.
|
||||
Can be compared with @code{enum MHD_WEBSOCKET_STATUS}.
|
||||
@end deftypefun
|
||||
|
||||
|
||||
@c ------------------------------------------------------------
|
||||
@node microhttpd-websocket encode
|
||||
@section Websocket encode functions
|
||||
|
||||
|
||||
@deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_encode_text (struct MHD_WebSocketStream* ws, const char* payload_utf8, size_t payload_utf8_len, int fragmentation, char** frame, size_t* frame_len, int* utf8_step)
|
||||
@cindex websocket
|
||||
Encodes an UTF-8 encoded text into websocket text frame
|
||||
|
||||
@table @var
|
||||
@item ws
|
||||
websocket stream;
|
||||
|
||||
@item payload_utf8
|
||||
text to send. This must be UTF-8 encoded.
|
||||
If you don't want UTF-8 then send a binary frame
|
||||
with @code{MHD_websocket_encode_binary()} instead.
|
||||
May be be @code{NULL} if @code{payload_utf8_len} is 0,
|
||||
must not be @code{NULL} otherwise.
|
||||
|
||||
@item payload_utf8_len
|
||||
length of @code{payload_utf8} in bytes.
|
||||
|
||||
@item fragmentation
|
||||
A value of @code{enum MHD_WEBSOCKET_FRAGMENTATION}
|
||||
to specify the fragmentation behavior.
|
||||
Specify @code{MHD_WEBSOCKET_FRAGMENTATION_NONE} or just 0
|
||||
if you don't want to use fragmentation (default).
|
||||
|
||||
@item frame
|
||||
pointer to a variable, which receives a buffer with the encoded text frame.
|
||||
Must not be @code{NULL}.
|
||||
The buffer contains what you typically send via @code{send()} to the recipient.
|
||||
If no encoded data is available the variable receives @code{NULL}.
|
||||
|
||||
If the variable is not @code{NULL} then the buffer contains always
|
||||
@code{frame_len} bytes plus one terminating @code{NUL} character.
|
||||
The caller must free this buffer using @code{MHD_websocket_free()}.
|
||||
|
||||
@item frame_len
|
||||
pointer to a variable, which receives the length of the encoded frame in bytes.
|
||||
Must not be @code{NULL}.
|
||||
|
||||
@item utf8_step
|
||||
If fragmentation is used (the parameter @code{fragmentation} is not 0)
|
||||
then is parameter is required and must not be @code{NULL}.
|
||||
If no fragmentation is used, this parameter is optional and
|
||||
should be @code{NULL}.
|
||||
|
||||
This parameter is a pointer to a variable which contains the last check status
|
||||
of the UTF-8 sequence. It is required to continue a previous
|
||||
UTF-8 sequence check when fragmentation is used, because a UTF-8 sequence
|
||||
could be split upon fragments.
|
||||
|
||||
@code{enum MHD_WEBSOCKET_UTF8STEP} is used for this value.
|
||||
If you start a new fragment using
|
||||
@code{MHD_WEBSOCKET_FRAGMENTATION_NONE} or
|
||||
@code{MHD_WEBSOCKET_FRAGMENTATION_FIRST} the old value of this variable
|
||||
will be discarded and the value of this variable will be initialized
|
||||
to @code{MHD_WEBSOCKET_UTF8STEP_NORMAL}.
|
||||
On all other fragmentation modes the previous value of the pointed variable
|
||||
will be used to continue the UTF-8 sequence check.
|
||||
@end table
|
||||
|
||||
Returns 0 on success or a value less than zero on errors.
|
||||
Can be compared with @code{enum MHD_WEBSOCKET_STATUS}.
|
||||
@end deftypefun
|
||||
|
||||
|
||||
@deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_encode_binary (struct MHD_WebSocketStream* ws, const char* payload, size_t payload_len, int fragmentation, char** frame, size_t* frame_len)
|
||||
@cindex websocket
|
||||
Encodes binary data into websocket binary frame
|
||||
|
||||
@table @var
|
||||
@item ws
|
||||
websocket stream;
|
||||
|
||||
@item payload
|
||||
binary data to send.
|
||||
May be be @code{NULL} if @code{payload_len} is 0,
|
||||
must not be @code{NULL} otherwise.
|
||||
|
||||
@item payload_len
|
||||
length of @code{payload} in bytes.
|
||||
|
||||
@item fragmentation
|
||||
A value of @code{enum MHD_WEBSOCKET_FRAGMENTATION}
|
||||
to specify the fragmentation behavior.
|
||||
Specify @code{MHD_WEBSOCKET_FRAGMENTATION_NONE} or just 0
|
||||
if you don't want to use fragmentation (default).
|
||||
|
||||
@item frame
|
||||
pointer to a variable, which receives a buffer with the encoded binary frame.
|
||||
Must not be @code{NULL}.
|
||||
The buffer contains what you typically send via @code{send()} to the recipient.
|
||||
If no encoded data is available the variable receives @code{NULL}.
|
||||
|
||||
If the variable is not @code{NULL} then the buffer contains always
|
||||
@code{frame_len} bytes plus one terminating @code{NUL} character.
|
||||
The caller must free this buffer using @code{MHD_websocket_free()}.
|
||||
|
||||
@item frame_len
|
||||
pointer to a variable, which receives the length of the encoded frame in bytes.
|
||||
Must not be @code{NULL}.
|
||||
@end table
|
||||
|
||||
Returns 0 on success or a value less than zero on errors.
|
||||
Can be compared with @code{enum MHD_WEBSOCKET_STATUS}.
|
||||
@end deftypefun
|
||||
|
||||
|
||||
@deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_encode_ping (struct MHD_WebSocketStream* ws, const char* payload, size_t payload_len, char** frame, size_t* frame_len)
|
||||
@cindex websocket
|
||||
Encodes a websocket ping frame.
|
||||
Ping frames are used to check whether a recipient is still available
|
||||
and what latency the websocket connection has.
|
||||
|
||||
@table @var
|
||||
@item ws
|
||||
websocket stream;
|
||||
|
||||
@item payload
|
||||
binary ping data to send.
|
||||
May be @code{NULL} if @code{payload_len} is 0.
|
||||
|
||||
@item payload_len
|
||||
length of @code{payload} in bytes.
|
||||
This may not exceed 125 bytes.
|
||||
|
||||
@item frame
|
||||
pointer to a variable, which receives a buffer with the encoded ping frame.
|
||||
Must not be @code{NULL}.
|
||||
The buffer contains what you typically send via @code{send()} to the recipient.
|
||||
If no encoded data is available the variable receives @code{NULL}.
|
||||
|
||||
If the variable is not @code{NULL} then the buffer contains always
|
||||
@code{frame_len} bytes plus one terminating @code{NUL} character.
|
||||
The caller must free this buffer using @code{MHD_websocket_free()}.
|
||||
|
||||
@item frame_len
|
||||
pointer to a variable, which receives the length of the encoded frame in bytes.
|
||||
Must not be @code{NULL}.
|
||||
@end table
|
||||
|
||||
Returns 0 on success or a value less than zero on errors.
|
||||
Can be compared with @code{enum MHD_WEBSOCKET_STATUS}.
|
||||
@end deftypefun
|
||||
|
||||
|
||||
@deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_encode_pong (struct MHD_WebSocketStream* ws, const char* payload, size_t payload_len, char** frame, size_t* frame_len)
|
||||
@cindex websocket
|
||||
Encodes a websocket pong frame.
|
||||
Pong frames are used to answer a previously received websocket ping frame.
|
||||
|
||||
@table @var
|
||||
@item ws
|
||||
websocket stream;
|
||||
|
||||
@item payload
|
||||
binary pong data to send, which should be
|
||||
the decoded payload from the received ping frame.
|
||||
May be @code{NULL} if @code{payload_len} is 0.
|
||||
|
||||
@item payload_len
|
||||
length of @code{payload} in bytes.
|
||||
This may not exceed 125 bytes.
|
||||
|
||||
@item frame
|
||||
pointer to a variable, which receives a buffer with the encoded pong frame.
|
||||
Must not be @code{NULL}.
|
||||
The buffer contains what you typically send via @code{send()} to the recipient.
|
||||
If no encoded data is available the variable receives @code{NULL}.
|
||||
|
||||
If the variable is not @code{NULL} then the buffer contains always
|
||||
@code{frame_len} bytes plus one terminating @code{NUL} character.
|
||||
The caller must free this buffer using @code{MHD_websocket_free()}.
|
||||
|
||||
@item frame_len
|
||||
pointer to a variable, which receives the length of the encoded frame in bytes.
|
||||
Must not be @code{NULL}.
|
||||
@end table
|
||||
|
||||
Returns 0 on success or a value less than zero on errors.
|
||||
Can be compared with @code{enum MHD_WEBSOCKET_STATUS}.
|
||||
@end deftypefun
|
||||
|
||||
|
||||
@deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_encode_close (struct MHD_WebSocketStream* ws, unsigned short reason_code, const char* reason_utf8, size_t reason_utf8_len, char** frame, size_t* frame_len)
|
||||
@cindex websocket
|
||||
Encodes a websocket close frame.
|
||||
Close frames are used to close a websocket connection in a formal way.
|
||||
|
||||
@table @var
|
||||
@item ws
|
||||
websocket stream;
|
||||
|
||||
@item reason_code
|
||||
reason for close.
|
||||
You can use @code{enum MHD_WEBSOCKET_CLOSEREASON} for typical reasons,
|
||||
but you are not limited to these values.
|
||||
The allowed values are specified in RFC 6455 7.4.
|
||||
If you don't want to enter a reason, you can specify
|
||||
@code{MHD_WEBSOCKET_CLOSEREASON_NO_REASON} (or just 0) then
|
||||
no reason is encoded.
|
||||
|
||||
@item reason_utf8
|
||||
An UTF-8 encoded text reason why the connection is closed.
|
||||
This may be @code{NULL} if @code{reason_utf8_len} is 0.
|
||||
This must be @code{NULL} if @code{reason_code} equals to zero
|
||||
(@code{MHD_WEBSOCKET_CLOSEREASON_NO_REASON}).
|
||||
|
||||
@item reason_utf8_len
|
||||
length of the UTF-8 encoded text reason in bytes.
|
||||
This may not exceed 123 bytes.
|
||||
|
||||
@item frame
|
||||
pointer to a variable, which receives a buffer with the encoded close frame.
|
||||
Must not be @code{NULL}.
|
||||
The buffer contains what you typically send via @code{send()} to the recipient.
|
||||
If no encoded data is available the variable receives @code{NULL}.
|
||||
|
||||
If the variable is not @code{NULL} then the buffer contains always
|
||||
@code{frame_len} bytes plus one terminating @code{NUL} character.
|
||||
The caller must free this buffer using @code{MHD_websocket_free()}.
|
||||
|
||||
@item frame_len
|
||||
pointer to a variable, which receives the length of the encoded frame in bytes.
|
||||
Must not be @code{NULL}.
|
||||
@end table
|
||||
|
||||
Returns 0 on success or a value less than zero on errors.
|
||||
Can be compared with @code{enum MHD_WEBSOCKET_STATUS}.
|
||||
@end deftypefun
|
||||
|
||||
|
||||
@c ------------------------------------------------------------
|
||||
@node microhttpd-websocket memory
|
||||
@section Websocket memory functions
|
||||
|
||||
|
||||
@deftypefun {void*} MHD_websocket_malloc (struct MHD_WebSocketStream* ws, size_t buf_len)
|
||||
@cindex websocket
|
||||
Allocates memory with the associated @code{malloc()} function
|
||||
of the websocket stream.
|
||||
The memory allocation function could be different for a websocket stream if
|
||||
@code{MHD_websocket_stream_init2()} has been used for initialization.
|
||||
|
||||
@table @var
|
||||
@item ws
|
||||
websocket stream;
|
||||
|
||||
@item buf_len
|
||||
size of the buffer to allocate in bytes.
|
||||
@end table
|
||||
|
||||
Returns the pointer of the allocated buffer or @code{NULL} on failure.
|
||||
@end deftypefun
|
||||
|
||||
|
||||
@deftypefun {void*} MHD_websocket_realloc (struct MHD_WebSocketStream* ws, void* buf, size_t new_buf_len)
|
||||
@cindex websocket
|
||||
Reallocates memory with the associated @code{realloc()} function
|
||||
of the websocket stream.
|
||||
The memory reallocation function could be different for a websocket stream if
|
||||
@code{MHD_websocket_stream_init2()} has been used for initialization.
|
||||
|
||||
@table @var
|
||||
@item ws
|
||||
websocket stream;
|
||||
|
||||
@item buf
|
||||
current buffer, may be @code{NULL};
|
||||
|
||||
@item new_buf_len
|
||||
new size of the buffer in bytes.
|
||||
@end table
|
||||
|
||||
Return the pointer of the reallocated buffer or @code{NULL} on failure.
|
||||
On failure the old pointer remains valid.
|
||||
@end deftypefun
|
||||
|
||||
|
||||
@deftypefun {void} MHD_websocket_free (struct MHD_WebSocketStream* ws, void* buf)
|
||||
@cindex websocket
|
||||
Frees memory with the associated @code{free()} function
|
||||
of the websocket stream.
|
||||
The memory free function could be different for a websocket stream if
|
||||
@code{MHD_websocket_stream_init2()} has been used for initialization.
|
||||
|
||||
@table @var
|
||||
@item ws
|
||||
websocket stream;
|
||||
|
||||
@item buf
|
||||
buffer to free, this may be @code{NULL} then nothing happens.
|
||||
@end table
|
||||
|
||||
@end deftypefun
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@c ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user