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 Websocket Demo @end verbatim @noindent