libWebSockets.rts: Managing WebSockets traffic with Stingray Traffic Manager

This article describes how to inspect and load-balance WebSockets traffic using Stingray Traffic Manager, and when necessary, how to manage WebSockets and HTTP traffic that is received on the same IP address and port.

 

Overview

 

WebSockets is an emerging protocol that is used by many web developers to provide responsive and interactive applications.  It's commonly used for talk and email applications, real-time games, and stock market and other monitoring applications.

 

By design, WebSockets is intended to resemble HTTP.  It is transported over tcp/80, and the initial handshake resembles an HTTP transaction, but the underlying protocol is a simple bidirectional TCP connection.

 

For more information on the protocol, refer to the Wikipedia summary and RFC 6455.

 

 

Basic WebSockets load balancing

 

ws1.png

Basic WebSockets Load Balancing

 

Basic WebSockets load balancing is straightforward.  You must use the 'Generic Streaming' protocol type to ensure that Stingray will correctly handle the asynchronous nature of websockets traffic.

 

 

Inspecting and modifying the WebSocket handshake

 

A WebSocket handshake message resembles an HTTP request, but you cannot use the built-in http.* TrafficScript functions to manage it because these are only available in HTTP-type virtual servers.

 

The libWebSockets.rts library (see below) implements analogous functions that you can use instead:

 

 

Paste the libWebSockets.txt library to your Rules catalog and reference it from your TrafficScript rule as follows:


 

import libWebSockets.rts as ws;










 

 

You can then use the ws.* functions to inspect and modify WebSockets handshakes.  Common operations include fixing up host headers and URLs in the request, and selecting the target servers (the 'pool') based on the attributes of the request.

 

 

import libWebSockets.rts as ws;

if( ws.getHeader( "Host" ) == "echo.example.com" ) {
   ws.setHeader( "Host", "www.example.com" );
   ws.setPath( "/echo" );
   pool.use( "WebSockets servers" );
}










 

 

Ensure that the rules associated with WebSockets virtual server are configured to run at the Request stage, and to run 'Once', not 'Every'.  The rule should just be triggered to read and process the initial client handshake, and does not need to run against subsequent messages in the websocket connection:

 

ws4.png

Code to handle the WebSocket handshake should be configured as a Request Rule, with 'Run Once'

 

 

SSL-encrypted WebSockets

 

Stingray can SSL-decrypt TCP connections, and this operates fully with the SSL-encrypted wss:// protocol:

  • Configure your virtual server to listen on port 443 (or another port if necessary)
  • Enable SSL decryption on the virtual server, using a suitable certificate


Note that when testing this capability, we found that Chrome refused to connect to WebSocket services with untrusted or invalid certificates, and did not issue a warning or prompt to trust the certificate.  Other web browsers may operate similarly.  In Chrome's case, it was necessary to access the virtual server directly (https://), save the certificate and then import it into the certificate store.


Stingray can also SSL-encrypt downstream TCP connections (enable SSL encryption in the pool containing the real websocket servers) and this operates fully with SSL-enabled origin WebSockets servers.

 

 

Handling HTTP and WebSockets traffic

 

HTTP traffic should be handled by an HTTP-type virtual server rather than a Generic Streaming one.  HTTP virtual servers can employ HTTP optimizations (keepalive handling, HTTP upgrades, Compression, Caching, HTTP Session Persistence) and can access the http.* TrafficScript functions in their rules.

 

If possible, you should run two public-facing virtual servers, listening on two separate IP addresses.  For example, HTTP traffic should be directed to www.site.com (which resolves to the public IP for the HTTP virtual server) and WebSockets traffic should be directed to ws.site.com (resolving to the other public IP):

ws2.pngConfigure two virtual servers, each listening on the appropriate IP address

 

Sometimes, this is not possible – the WebSockets code is hardwired to the main www domain, or it's not possible to obtain a second public IP address. In that case, all traffic can be directed to the WebSockets virtual server and then HTTP traffic can be demultiplexed and forwarded internally to an HTTP virtual server:

 

ws3.pngListen on a single IP address, and split off the HTTP traffic to a second HTTP virtual server


The following TrafficScript code, attached to the 'WS Virtual Server', will detect if the request is an HTTP request (rather than a WebSockets one) and hand the request off internally to an HTTP virtual server by way of a special 'Loopback Pool':

 

 

import libWebSockets.rts as ws;

if( !ws.isWS() ) pool.use( "Loopback Pool" );






 

 

Notes: Testing WebSockets

 

The implementation described in this article was developed using the following browser-based client, load-balancing traffic to public 'echo' servers (ws://echo.websocket.org/, wss://echo.websocket.org, ws://ajf.me:8080/).

 

 

At the time of testing:

  • echo.websocket.org did not respond to ping tests, so the default ping health monitor needed to be removed
  • Chrome24 refused to connect to SSL-enabled wss resources unless they had a trusted certificate, and did not warn otherwise


If you find this solution useful, please let us know in the comments below.