summaryrefslogtreecommitdiff
path: root/doc/chapters/websocket.inc
diff options
context:
space:
mode:
Diffstat (limited to 'doc/chapters/websocket.inc')
-rw-r--r--doc/chapters/websocket.inc886
1 files changed, 886 insertions, 0 deletions
diff --git a/doc/chapters/websocket.inc b/doc/chapters/websocket.inc
new file mode 100644
index 00000000..a480fd13
--- /dev/null
+++ b/doc/chapters/websocket.inc
@@ -0,0 +1,886 @@
+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 deamon 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 *con_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 serveral 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 *con_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