WSServer
v2.1.0
C-WebSocket-Server
|
WSServer is a fast, configurable, and extendable WebSocket Server for UNIX systems written in C (C11).
As of version 2.0.0 the WSServer has been completely rewritten with many new features, better support, better extendability and generally as a more stable WebSocket server.
Current Version: v2.1.0
The original WebSocket server (v1.0.0 and before) started out as a hobby project in 2012. The idea at the time, was to learn the C language and understand the basics of WebSockets. The initial version of the server worked for some but not all aspects of the protocols present at that time. At that time, different browsers implemented different versions of the WebSocket Protocol, which made it difficult to support all browsers. Today all major browsers support the RFC6455 protocol, which has been stable for many years.
WSServer now supports all aspects of the RFC6455 protocol, including the RFC7692 that enables the permessage-deflate
extension. The implementation is verified by the Autobahn testsuite and by a lot of unit tests. It furthermore support the older protocols HYBI10 and HYBI07 as there are no noteable difference between those and the current stable one. Currently there are no support for older versions of the protocol.
To support further development on this project and financially support the developer having a nice cup of coffee, you can make a donation of your choice here.
To build the WSServer for your UNIX system, simply run: make release
. This will compile extensions, subprotocols, and the binary that will be available from: ./bin/WSServer
.
Currently the WSServer support only one extension namely the permessage-deflate
extension. Read more about this implementation here.
Furthermore it supports two subprotocols: echo and broadcast. The echo subprotocol is a simple protocol that sends whatever message received, back to the same client. This is also the default protocol chosen, if no subprotocol is provided by the client. The broadcast subprotocol is slightly more advanced. It sends a message from one client to all other connected clients. The behaviour is basically as a public chat room.
The server can be configured by providing a -c [path_to_config_file.json]
flag. If no configuration is provided, the server will run with a default configuration, that support WebSocket over HTTP on port 80. You can read more about the structure and description of the configuration file in the configuration section.
WSServer keeps a log file at logs/WSServer.log
. The detail of the log is defined by the log_level
value in the configuration. Default for a release build is 3 (FATAL, ERROR, WARN, INFO). Default for a test build is 5 (FATAL, ERROR, WARN, INFO, DEBUG, TRACE).
The log file is a good tool for discovering misbehavior of the server, such as when the server isn't able to start, since port 80 is already occupied by another server instance.
WSServer does in principle not rely on any third-party libraries in order to serve as a WebSocket server. However if you want the complete feature set the zlib and one of the SSL libraries OpenSSL, WolfSSL, BoringSSL, LibreSSL must be installed on your system in order to support the permessage-deflate
extension and SSL (WSS).
If the installation method is not listed above or your package manager doesn't contain the software, it can all be build from source.
No other dependencies are required with regards to building the server with the full feature set.
If you want to run the Autobahn testsuite and the unit tests yourself, further dependencies are required. These are docker and criterion.
Criterion can be build for FreeBSD using the following guide.
An example of a configuration file can be found at here. A lot of different things are configurable through the configuration file.
The origins
key define a subset of allowed origins. It is always recommended to define this subset. In case no subset is defined a client can connect from anywhere.
A WebSocket URI consists of a scheme, host, port, path and a query. Take for example:
wss://mortz.dk:9011/websocket?csrf-token=asgjh48hs389hdla.
The wss part of the URI is defined as the scheme, the mortz.dk part is defined as the host, the 9011 part is defined as the port, the **/websocket** part is defined as the path and the ?csrf-token=asgjh48hs389hdla part is defined as the query. The specific allowance of all 5 parts of the WebSocket URI are configurable.
The hosts
key define a subset of strings allowed as the host. In the example above, if we only want clients connecting to mortz.dk, we can add that string to the subset.
The paths
key define a subset of strings allowed as the connecting path. In the example above, if we only want clients connecting through the path **/websocket**, we can add that string to the subset. The path **/** will always be a valid connection path. The string values of the paths
key are allowed to use POSIX Extended Regular Expressions syntax.
The queries
key define a subset of strings allowed as the queries. In the example above, if we only want clients to use a specific query csrf-token=[^&]*, we can add that string to the subset. The string values of the paths
key are allowed to use POSIX Extended Regular Expressions syntax. A WebSocket URI without any queries is always allowed.
The scheme and port part of the WebSocket URI is checked based on the ports choosen for http
and https
.
The port
key of the setup
object is used to define the ports that http (ws) version and the https (wss) should be listening to.
A http (ws) version of the server will always be available. The https (wss) version requires further configuration of SSL.
The extensions
key of the setup
object is used to define an array of supported extensions. Each entry in the array is an object itself containing a file
and config
key. The file
key should point to the location of the shared object representing the extension. The basename of the file
key is used as the extension name. The config
key can be used to provide extra configuration to the extension.
The subprotocols
key of the setup
object is used to define an array of supported subprotocols. Each entry in the array is an object itself containing a file
and config
key. The file
key should point to the location of the shared object representing the subprotocol. The basename of the file
key is used as the subprotocol name. The config
key can be used to provide extra configuration to the subprotocol.
The favicon
key of the setup
object is used to define a favicon to serve for HTTP and HTTPS request. A lot of browsers do the request for favicons per default when performing HTTP and HTTPS requests. By defining this with the path to a valid ICO file, the WSServer will return a favicon.
The timeout
key of the setup
object is used to define different timeouts of the WSServer.
The poll
key define a timeout for event polling. By setting it to a positive integer n, the event loop will be interrupted every n milliseconds.
The read
key define a timeout for the READ event. The timeout is checked whenever the server requires to read more data from the client in order to succeed. By setting it to a positive integer n the request will fail if the next read took longer than n milliseconds.
The write
key define a timeout for the WRITE event. The timeout is checked whenever the server requires to write more data to the client than it is currently able to buffer. By setting it to a positive integer n the request will fail if the next write took longer than n milliseconds.
The client
key define a timeout for when the client was last active. By setting it to a positive n integer, the client will be disconnected if it has not been active the last n milliseconds.
The pings
key defines the amount of pings performed within the span of the client
timeout key. If this value is set, it is recommended to use a value stricly higher than 1, as the internal timing of the server is not 100% accurate.
A lot of different sizes can be adjusted for the WSServer. All sizes but the ringbuffer
are defined in bytes.
The payload
size define how large a size of payload the server is willing to accept from the client.
The header
size define how large a HTTP header the server will accept from the client.
The uri
size define how large a URI the server will accept from the client.
The buffer
size define how large the internal read and write buffers should be.
The thread
size define how large each thread of the WSServer can maximally be.
The ringbuffer
size define how many messages about to be written each client can store in their ringbuffer.
The frame
size define the maximal payload size of a single frame.
The fragmented
size define how many fragments (frames) one single message can consist of.
Internally the WSServer runs a threadpool to schedule IO work from the clients.
The worker
key define the amount of threads the threadpool shall consist of. Generally the rule of thumb is that the higher the load, the more threads. However the optimal amount of workers is probably system and hardware dependent.
WSServer supports the wss scheme by the use of one of currently 4 SSL libraries (OpenSSL, WolfSSL, BoringSSL, LibreSSL). The default choice is OpenSSL, but the SSL library can be switched as follows:
Note that if compiled with SSL_LIBRARY_PATH
the binary must be executed with LD_LIBRARY_PATH=/path/to/libressl/lib ./bin/WSServer
.
If LibreSSL or BoringSSL is installed in place of OpenSSL the compilation should work out of the box by running make
.
In order to activate SSL some configuration must be made.
The key
key define the path to the SSL private key of the server. The private key must be in the PEM format.
The cert
key define the path to the SSL server certificate. The certificate must be in the PEM format.
The ca_file
key define the path to the root CA certificate.
The ca_path
key define the path to a folder containing the trusted root CA certifates.
The dhparam
key define the path to the dhparam file. The dhparam file must be in the PEM format.
The cipher_list
key define the ciphers that the server allows usage of.
The cipher_suites
key define the cipher suites that the server allows usage of.
The compression
key define whether compression should be used when communicating over SSL,
The peer_cert
key define whether a peer certificate is required by the client.
The WSServer enables usage of an arbitrary number of extensions. Extensions provide a mechanism for implementations to opt-in to additional protocol features.
The extensions themselves can be implemented in any language that is able to compile into a shared object (*.so* file) with the following public functions:
Where WSS_malloc_t
, WSS_realloc_t
, and WSS_free_t
are defined as:
and wss_frame_t
is defined as:
For the server to be able to use a custom extension one has to configure the path to the shared object in the configuration file as described above.
You can have a look at the extensions folder to see how to implement your own extension.
The WSServer comes with 1 build-in extension, namely the permessage-deflate
extension defined in RFC7692. This extension enables compression and decompression of the frames between client and server.
The WSServer also enables usage of an arbitrary number of subprotocols. Subprotocols are application-level protocol layered over the WebSocket Protocol that are used to define the behaviour of the websocket protocol.
The subprotocols themselves can be implemented in any language that is able to compile into a shared object (*.so* file) with the following public functions:
Where WSS_send
, WSS_malloc_t
, WSS_realloc_t
, and WSS_free_t
are defined as:
and wss_opcode_t
is defined as:
You can have a look at the subprotocols folder to see how to implement your own subprotocol.
Client authentication is not implemented directly in WSServer, but is supported through several means. The onConnect
call of the subprotocol sends information about the connection to the subprotocol, this is information such as the ip, port, path and cookies. This can be used to do client authentication using cookies, using query parameters of the path or simply by having an initial round of authentication messages between the client and server.
As always it is strongly advised to use the origins array of the configuration to only allow for certain origins to use the server.
The echo
subprotocol is a very simple subprotocol that just echo's whatever the client send back to the client. This subprotocol is especially useful for testing and is used in the Autobahn testsuite.
The broadcast
subprotocol is slightly more advanced. It keeps track of when a client is connecting or closing in order to hold a map of those clients that should be broadcastet to. Whenever a client sends a message, the message is broadcastet to all other connected clients.
WSServer automatically generates documentation based on the comments in the code. This documentation can be viewed here.
Furthermore one could take a look at the RFC6455 protocol and the RFC7692 protocol to understand how WebSockets and the permessage-deflate extension works.
WSServer has been heavily tested by the use of unit tests, the Autobahn testsuite and by having code coverage.
A lot of unit tests has been written using the Criterion unit testing library. As of right now the unit tests does not cover all files, and this is still work in progress.
The tests can be run by running make test
.
The Autobahn Testsuite is used to verify that the WSServer complies to the RFC6455 protocol. These tests can be used as both verification and as a measure of performance as a lot of the tests actually times the execution of a successful test.
The tests can be run by running make autobahn
.
You can further see the current results of the tests here.
The coverage report can be generated by running make test
and the latest can be seen here.
Here is a list of prioritized further work that currently can be done:
wss_frame_t
structure away. Use the frames as byte strings instead.Here is a list of the contributors of v2.0.0 and above of the WSServer.
Morten Houmøller Nygaard Nicolas Mora
WSServer makes use of other Open-Source libraries and code snippets. The links listed below have all been used in some way.
WSServer is licenced under the MIT license.