mirror of
https://git.gnunet.org/libmicrohttpd.git
synced 2026-09-25 04:09:31 +03:00
887 lines
27 KiB
PHP
887 lines
27 KiB
PHP
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;
|
|
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 != *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
|