Extension:WebSocket
This extension allows the wiki to respond dynamically to change events using WebSockets instead of Ajax polling. It currently doesn't have any fall-back to Ajax because it was written primarily to augment extensions that already use Ajax polling such as AjaxComments, so these extensions will just continue with Ajax polling as usual if the WebSocket extension is not present or is present but not connected. The extension is designed to serve multiple wikis running on the same host, and can support either insecure or SSL connections at the same time.
Contents
Installation and configuration
The extension itself is installed the usual way with an include of WebSocket.php in your LocalSettings.php file. Note that the extension must reside within a directory called "WebSocket" directly in the wikis extensions directory.
There is also a background Perl daemon (WebSocket.pl) which is started by the PHP if it's found not to be running. The daemon uses the Net::WebSocket::Server library to implement a minimal WebSocket service that listens insecurely on the specified port, and using SSL on port + 1.
There are some Perl dependencies which can be installed via CPAN on Debian-like systems as follows:
The configuration options for the extension are set after the include statement in your LocalSettings.php and are as follows.
Name | Default | Description |
---|---|---|
WebSocket::$port | 1729 | Port the WebSocket daemon will run on (and SSL would on 1730) |
WebSocket::$rewrite | false | Configure URL rewriting so that the WebSocket port doesn't need to be public (explained in detail below) |
WebSocket::$perl | /usr/bin/perl | Location of the Perl interpreter |
WebSocket::$log | false | Set to a writable file to log events and errors from the daemon |
WebSocket::$ssl_cert | false | If the site is running SSL then the WebSocket will also need to be SSL and the path to your site certificate is in this setting |
WebSocket::$ssl_key | false | The path to your site's SSL key file is in this setting |
Here's a sample configuration that would go in LocalSettings.php:
Usage
This extension is intended to be used by other extensions that currently implement an Ajax polling system to determine if a particular event has occurred. Such extensions can be adjusted so that their Ajax polling code includes a condition to poll only if a WebSocket connection is not present. The WebSocket and it's state are available in a global object called window.webSocket which is defined in websocket.js. Here's an example snippet where the setTimeout callback that would re-call the updateData function again after five seconds has been made conditional upon their being no WebSocket connection.
The extension which is upgrading to WebSocket must also tell the webSocket object to connect to the daemon and declare it's data-updating function as a callback for the desired message type as in the following example. We also add the data-updating method to the disconnected event of the WebSocket so that the regular Ajax updating will resume if the WebSocket becomes unavailable for some reason.
Event names
Using a simple string name like the examples above creates an event that will be specific to your extension, but if there are many wikis running on the server and all sharing the same WebSocket daemon, then all of them that are running the same extension will receive the events. The WebSocket extension provides a configuration option to the client side called wsWikiID which is a string composed of the wiki database name and table prefix so that it's unique to each wiki. This can be used to make the name event name, for example.
Another possibility is that the events may only be relevant to the current article, for example the AjaxComments extension needs to be notified whenever comments on the current page change, so it defines an event name which includes not only the wiki ID but also the article ID as well:
Note that this methods of naming only makes sense when the messages are originating from the client side or from MediaWiki in response to a request. If the events are coming from the server environment outside of the MediaWiki context, then it will be sending messages destined for all wikis and pages.
Messaging
A message of the type we've declared our callback for must be sent by the source of the changing state. Normally this would be within MediaWiki, for example when an article with specific properties has saved. Here's an example PHP snippet of how to broadcast such a message to all clients when a new article revision is created:
Events may also arise on the client side in which case the JavaScript object's send method can be used,
It's also possible that changes to data of interest to the clients originates outside of the MediaWiki context, such as items being added to log files, or incoming emails etc. Here's an example Perl snippet that triggers our call to updateFooData (these dependencies can be installed via CPAN). This example demonstrates how to create an SSL connection which uses the wss:// protocol, port + 1 and has the ssl_no_verify option set to avoid having to pass certificate information.
Message recipients
Each wiki client has a unique ID which is automatically added to a "from" argument in the send method. When messages are sent an optional third argument (or "to" field in JSON context) can be added to the function which is an array of recipient IDs. If the argument is missing then the message is broadcast to all clients (but only those hooked in to the event corresponding to the "type" argument will actually receive it).
When the PHP WebSocket::send or the JavaScript webSocket.send methods are used, the ID of the currently running client instance is automatically used for the from argument. When the sending is done from the daemon, no from field is used since it merely acts as a forwarder and expects no messages directly to itself. Other code running outside of the PHP context that send messages may however need to receive responses, so in that case they can simply set the from argument to a unique ID (or even a fixed ID such as "1" if there's only ever one instance of that code running).
The Perl daemon keeps a record of sockets mapped by from ID so that it can forward messages to the intended recipients when it sees a to field in messages it receives, so merely including a from argument is all that's required to become a sender that can be replied to.
The "rewrite" option
This option is used if the server environment doesn't allow new ports to be open to the public. If it's set then the WebSocket requests will be directed to the same domain and port as the wiki is running on with the URI of /websocket. The web-server must be configured to match this URL and redirect it to the local WebSocket daemon's port, but since the protocol is not HTTP, some special configuration is required as shown in the following examples.
A typical Nginx configuration might contain something like the following example snippet which matches the format of the URLs intended to be passed to the WebSocket daemon, extracting the port from the URL format to be used in the forwarded request. Note that the extracted port in the $1 variable cannot be used with the DNS resolver in Nginx so "127.0.0.1" has to be used rather than "localhost".
In Apache web-server mod_proxy_wstunnel should first be installed with a2enmod proxy_wstunnel which adds WebSocket support to it's proxy module. Then a simple rewrite rule can be added which uses the "P" option to direct the rewrite result to the WebSocket daemon via the proxy module. See also this post for more advanced Apache WebSocket configuration.
SSL
The extension is designed to serve multiple wikis running on the same host, and can support either insecure or SSL connections at the same time. If a site is running in SSL then the WebSocket requests coming from the JavaScript must also be SSL, and this means that the daemon must be be able to handle both types of connection (even if using rewrite because the proxy can't convert the data from one form to another). Also this means that the WebSocket::$ssl_cert and WebSocket::$ssl_key configuration settings are compulsory if the wiki connection uses SSL, and the extension will raise a fatal error if they're not present during an SSL connection.
The Perl daemon listens insecurely on the specified port, and listens with SSL on port + 1. The PHP and JavaScript send methods determine whether SSL should be used and what port should be used, but scripts running outside the web environment can choose whichever method they like. The are a couple of client sample scripts to send messages both insecurely and then using SSL to the daemon for development and testing purposes, client.pl and client.php.
Examples
The AjaxComments extension has been upgraded to use a WebSocket instead of Ajax polling if this extension is installed with it. The modifications that were made to the extension to enable WebSocket support can be seen here.