libwebsockets
Lightweight C library for HTML5 websockets
|
lwsws is an implementation of a very lightweight, ws-capable generic web server, which uses libwebsockets to implement everything underneath.
If you are basically implementing a standalone server with lws, you can avoid reinventing the wheel and use a debugged server including lws.
Just enable -DLWS_WITH_LWSWS=1 at cmake-time.
It enables libuv and plugin support automatically.
NOTICE on Ubuntu, the default libuv package is called "libuv-0.10". This is ancient.
You should replace this with libuv1 and libuv1-dev before proceeding.
lwsws uses JSON config files, they're pure JSON except:
There is a single file intended for global settings
/etc/lwsws/conf
and a config directory intended to take one file per vhost
/etc/lwsws/conf.d/warmcat.com
To get started quickly, an example config reproducing the old test server on port 7681, non-SSL is provided. To set it up
Lws supports automatic provisioning and renewal of TLS certificates.
See ./READMEs/README.plugin-acme.md for examples of how to set it up on an lwsws vhost.
reject-service-keywords
allows you to return an HTTP error code and message of your choice if a keyword is found in the user agenttimeout-secs
lets you set the global timeout for various network-related operations in lws, in seconds. It defaults to 5.One server can run many vhosts, where SSL is in use SNI is used to match the connection to a vhost and its vhost-specific SSL keys during SSL negotiation.
Listing multiple vhosts looks something like this
That sets up three vhosts all called "localhost" on ports 443 and 7681 with SSL, and port 80 without SSL but with a forced redirect to https://localhost
The vhost name field is used to match on incoming SNI or Host: header, so it must always be the host name used to reach the vhost externally.
Vhosts by default have available the union of any initial protocols from context creation time, and any protocols exposed by plugins.
Vhosts can select which plugins they want to offer and give them per-vhost settings using this syntax
The "x":"y" parameters like "status":"ok" are made available to the protocol during its per-vhost LWS_CALLBACK_PROTOCOL_INIT (in is a pointer to a linked list of struct lws_protocol_vhost_options containing the name and value pointers).
To indicate that a protocol should be used when no Protocol: header is sent by the client, you can use "default": "1"
Similarly, if your vhost is serving a raw protocol, you can mark the protocol to be selected using "raw": "1"
See also "apply-listen-accept" below.
host-ssl-cert
, host-ssl-ca
and host-ssl-key
are given, then the vhost supports SSL.Each vhost may have its own certs, SNI is used during the initial connection negotiation to figure out which certs to use by the server name it's asking for from the request DNS name.
keeplive-timeout
(in secs) defaults to 60 for lwsws, it may be set as a vhost optioninterface
lets you specify which network interface to listen on, if not given listens on all. If the network interface is not usable (eg, ethernet cable out) it will be logged at startup with such vhost not listening, and lws will poll for it and bind a listen socket to the interface if and when it becomes available.0600
mode, but you can control the user and group for the socket fd at creation time. This allows you to use unix user and groups to control who may open the other end of the unix socket on the local system."enable-client-ssl"
: "1"
enables the vhost's client SSL context, you will need this if you plan to create client conections on the vhost that will use SSL. You don't need it if you only want http / ws client connections.If you need to allow weaker ciphers, you can provide an alternative list here per-vhost.
The values are derived from /usr/include/openssl/ssl.h
would equate to
allows you to set arbitrary headers on every file served by the vhost
recommended vhost headers for good client security are
Where mounts are given in the vhost definition, then directory contents may be auto-served if it matches the mountpoint.
Mount protocols are used to control what kind of translation happens
Eg, with this mountpoint
The uri /file.jpg would serve /var/www/mysite.com/file.jpg, since / matched.
This will cause your local url /proxytest
to serve content fetched from libwebsockets.org over ssl; whether it's served from your server using ssl is unrelated and depends how you configured your local server. Notice if you will use the proxying feature, LWS_WITH_HTTP_PROXY
is required to be enabled at cmake, and for https
proxy origins, your lwsws configuration must include "init-ssl": "1"
and the vhost with the proxy mount must have "enable-client-ssl": "1"
, even if you are not using ssl to serve.
/proxytest/abc
, or /proxytest/abc?def=ghi
etc map to the origin + the part past /proxytest
, so links and img src urls etc work as do all urls under the origin path.
In addition link and src urls in the document are rewritten so / or the origin url part are rewritten to the mountpoint part.
1) Some protocols may want "per-mount options" in name:value format. You can provide them using "pmo"
{ "mountpoint": "/stuff", "origin": "callback://myprotocol", "pmo": [{ "myname": "myvalue" }] }
2) When using a cgi:// protocol origin at a mountpoint, you may also give cgi environment variables specific to the mountpoint like this
This allows you to customize one cgi depending on the mountpoint (and / or vhost).
3) It's also possible to set the cgi timeout (in secs) per cgi:// mount, like this
4) callback://
protocol may be used when defining a mount to associate a named protocol callback with the URL namespace area. For example
All handling of client access to /formtest[anything] will be passed to the callback registered to the protocol "protocol-post-demo".
This is useful for handling POST http body content or general non-cgi http payload generation inside a plugin.
See the related notes in README.coding.md
5) Cache policy of the files in the mount can also be set. If no options are given, the content is marked uncacheable.
6) You can also define a list of additional mimetypes per-mount
Normally a file suffix MUST match one of the canned mimetypes or one of the extra mimetypes, or the file is not served. This adds a little bit of security because even if there is a bug somewhere and the mount dirs are circumvented, lws will not serve, eg, /etc/passwd.
If you provide an extra mimetype entry
"*": ""
Then any file is served, if the mimetype was not known then it is served without a Content-Type: header.
7) A mount can be protected by HTTP Basic Auth. This only makes sense when using https, since otherwise the password can be sniffed.
You can add a basic-auth
entry on an http mount like this
Before serving anything, lws will signal to the browser that a username / password combination is required, and it will pop up a dialog. When the user has filled it in, lwsws checks the user:password string against the text file named in the basic-auth
entry.
The file should contain user:pass one per line
The file should be readable by lwsws, and for a little bit of extra security not have a file suffix, so lws would reject to serve it even if it could find it on a mount.
After successful authentication, WSI_TOKEN_HTTP_AUTHORIZATION
contains the authenticated username.
In the case you want to also protect being able to connect to a ws protocol on a particular vhost by requiring the http part can authenticate using Basic Auth before the ws upgrade, this is also possible. In this case, the "basic-auth": and filepath to the credentials file is passed as a pvo in the "ws-protocols" section of the vhost definition.
You can make a vhost insist to get a client certificate from the peer before allowing the connection with
the connection will only proceed if the client certificate was signed by the same CA as the server has been told to trust.
Lws supports some unusual modes for vhost listen sockets, which may be configured entirely using the JSON per-vhost config language in the related vhost configuration section.
There are three main uses for them
1) A vhost bound to a specific role and protocol, not http. This binds all incoming connections on the vhost listen socket to the "raw-proxy" role and protocol "myprotocol".
2) A vhost that wants to treat noncompliant connections for http or https as belonging to a secondary fallback role and protocol. This causes non-https connections to an https listener to stop being treated as https, to lose the tls wrapper, and bind to role "raw-proxy" and protocol "myprotocol". For example, connect a browser on your external IP :443 as usual and it serves as normal, but if you have configured the raw-proxy to portforward 127.0.0.1:22, then connecting your ssh client to your external port 443 will instead proxy your sshd over :443 with no http or tls getting in the way.
3) A vhost wants to either redirect stray http traffic back to https, or to actually serve http on an https listen socket (this is not recommended since it allows anyone to drop the security assurances of https by accident or design).
...or,
Protcols and extensions may also be provided from "plugins", these are lightweight dynamic libraries. They are scanned for at init time, and any protocols and extensions found are added to the list given at context creation time.
Protocols receive init (LWS_CALLBACK_PROTOCOL_INIT) and destruction (LWS_CALLBACK_PROTOCOL_DESTROY) callbacks per-vhost, and there are arrangements they can make per-vhost allocations and get hold of the correct pointer from the wsi at the callback.
This allows a protocol to choose to strictly segregate data on a per-vhost basis, and also allows the plugin to handle its own initialization and context storage.
To help that happen conveniently, there are some new apis
dumb increment, mirror and status protocol plugins are provided as examples.
Packages that have their own lws plugins can install them in their own preferred dir and ask lwsws to scan there by using a config fragment like this, in its own conf.d/ file managed by the other package
One provided protocol can be used to monitor the server status.
Enable the protocol like this on a vhost's ws-protocols section
"update-ms"
is used to control how often updated JSON is sent on a ws link.
And map the provided HTML into the vhost in the mounts section
You might choose to put it on its own vhost which has "interface": "lo", so it's not externally visible, or use the Basic Auth support to require authentication to access it.
"hide-vhosts": "{0 | 1}"
lets you control if information about your vhosts is included. Since this includes mounts, you might not want to leak that information, mount names, etc.
"filespath":"{path}"
lets you give a server filepath which is read and sent to the browser on each refresh. For example, you can provide server temperature information on most Linux systems by giving an appropriate path down /sys.
This may be given multiple times.
You may send lwsws a HUP
signal, by, eg
This causes lwsws to "deprecate" the existing lwsws process, and remove and close all of its listen sockets, but otherwise allowing it to continue to run, until all of its open connections close.
When a deprecated lwsws process has no open connections left, it is destroyed automatically.
After sending the SIGHUP to the main lwsws process, a new lwsws process, which can pick up the newly-available listen sockets, and use the current configuration files, is automatically started.
The new configuration may differ from the original one in arbitrary ways, the new context is created from scratch each time without reference to the original one.
Notes
1) Protocols that provide a "shared world" like mirror will have as many "worlds" as there are lwsws processes still active. People connected to a deprecated lwsws process remain connected to the existing peers.
But any new connections will apply to the new lwsws process, which does not share per-vhost "shared world" data with the deprecated process. That means no new connections on the deprecated context, ie a "shrinking world" for those guys, and a "growing world" for people who connect after the SIGHUP.
2) The new lwsws process owes nothing to the previous one. It starts with fresh plugins, fresh configuration, fresh root privileges if that how you start it.
The plugins may have been updated in arbitrary ways including struct size changes etc, and lwsws or lws may also have been updated arbitrarily.
3) A root parent process is left up that is not able to do anything except respond to SIGHUP or SIGTERM. Actual serving and network listening etc happens in child processes which use the privileges set in the lwsws config files.
lwsws needs a service file like this as /usr/lib/systemd/system/lwsws.service
You can find this prepared in ./lwsws/usr-lib-systemd-system-lwsws.service
For correct operation with logrotate, /etc/logrotate.d/lwsws
(if that's where we're putting the logs) should contain
You can find this prepared in /lwsws/etc-logrotate.d-lwsws
Prepare the log directory like this
Hopefully you won't need to debug lwsws itself, but you may want to debug your plugins. start lwsws like this to have everything running under gdb
this will give nice backtraces in lwsws itself and in plugins, if they were built with symbols.
You can just run lwsws under valgrind as usual and get valid results. However the results / analysis part of valgrind runs after the plugins have removed themselves, this means valgrind backtraces into plugin code is opaque, without source-level info because the dynamic library is gone.
There's a simple workaround, use LD_PRELOAD=<plugin.so> before running lwsws, this has the loader bring the plugin in before executing lwsws as if it was a direct dependency. That means it's still mapped until the whole process exits after valgtind has done its thing.