SockJS-node is a Node.js server side counterpart of SockJS-client browser library written in CoffeeScript.
npm install sockjs
(If you see
rbytes dependecy failing, don't worry, it's optional, SockJS-node will work fine without it.)
An simplified echo SockJS server could look more or less like:
var http = ;var sockjs = ;var echo = sockjs;echo;var server = http;echo;server;
(Take look at examples directory for a complete version.)
Subscribe to SockJS mailing list for discussions and support.
Live QUnit tests and smoke tests
SockJS-client comes with some QUnit tests and a few smoke tests that are using SockJS-node. At the moment they are deployed in few places, just click to see if SockJS is working in your browser:
- http://sockjs.popcnt.org/ and https://sockjs.popcnt.org/ (hosted in Europe)
- http://sockjs.cloudfoundry.com/ (CloudFoundry, websockets disabled, loadbalanced)
- https://sockjs.cloudfoundry.com/ (CloudFoundry SSL, websockets disabled, loadbalanced)
SockJS module is generating a
Server class, similar to
var sockjs_server = sockjs;
options is a hash which can contain:
- sockjs_url (string, required)
- prefix (string)
- A url prefix for the server. All http requests which paths begins with selected prefix will be handled by SockJS. All other requests will be passed through, to previously registered handlers.
- response_limit (integer)
- Most streaming transports save responses on the client side and don't free memory used by delivered messages. Such transports need to be garbage-collected once in a while. `response_limit` sets a minimum number of bytes that can be send over a single http streaming request before it will be closed. After that client needs to open new request. Setting this value to one effectively disables streaming and will make streaming transports to behave like polling transports. The default value is 128K.
- websocket (boolean)
- Some load balancers don't support websockets. This option can be used to disable websockets support by the server. By default websockets are enabled.
- jsessionid (boolean or function)
- Some hosting providers enable sticky sessions only to requests that have JSESSIONID cookie set. This setting controls if the server should set this cookie to a dummy value. By default setting JSESSIONID cookie is disabled. More sophisticated beaviour can be achieved by supplying a function.
- log (function(severity, message))
- It's quite useful, especially for debugging, to see some messages printed by a SockJS-node library. This is done using this `log` function, which is by default set to `console.log`. If this behaviour annoys you for some reason, override `log` setting with a custom handler. The following `severities` are used: `debug` (miscellaneous logs), `info` (requests logs), `error` (serious errors, consider filing an issue).
- heartbeat_delay (milliseconds)
- In order to keep proxies and load balancers from closing long running http requests we need to pretend that the connecion is active and send a heartbeat packet once in a while. This setting controlls how often this is done. By default a heartbeat packet is sent every 25 seconds.
- disconnect_delay (milliseconds)
- The server sends a `close` event when a client receiving connection have not been seen for a while. This delay is configured by this setting. By default the `close` event will be emitted when a receiving connection wasn't seen for 5 seconds.
Once you have create
Server instance you can hook it to the
var http_server = http;sockjs_server;http_server;
options can overshadow options given when creating
Server instance is an
and emits following event:
- Event: connection (connection)
- A new connection has been successfully opened.
All http requests that don't go under the path selected by
will remain unanswered and will be passed to previously registered
handlers. You must install your custom http handlers before calling
Connection instance supports
Node Stream API and
has following methods and properties:
- Property: readable (boolean)
- Is the stream readable?
- Property: writable (boolean)
- Is the stream writable?
- Property: remoteAddress (string)
- Last known IP address of the client.
- Property: remotePort (number)
- Last known port number of the client.
- Property: address (object)
- Hash with 'address' and 'port' fields.
- Property: headers (object)
- Hash containing various headers copied from last receiving request on that connection. Exposed headers include: `origin`, `referer` and `x-forwarded-for` (and friends). We expliclty do not grant access to `cookie` header, as using it may easily lead to security issues (for details read the section "Authorization").
- Property: url (string)
- Url property copied from last request.
- Property: pathname (string)
- `pathname` from parsed url, for convenience.
- Property: prefix (string)
- Prefix of the url on which the request was handled.
- Property: protocol (string)
- Protocol used by the connection. Keep in mind that some protocols are indistinguishable - for example "xhr-polling" and "xdr-polling".
- Property: readyState (integer)
- Current state of the connection: 0-connecting, 1-open, 2-closing, 3-closed.
- Sends a message over opened connection. A message must be a non-empty string. It's illegal to send a message after the connection was closed (either after 'close' or 'end' method or 'close' event).
- close([code], [reason])
- Asks the remote client to disconnect. 'code' and 'reason' parameters are optional and can be used to share the reason of disconnection.
- Asks the remote client to disconnect with default 'code' and 'reason' values.
Connection instance emits the following events:
- Event: data (message)
- A message arrived on the connection. Message is a unicode string.
- Event: close ()
- Connection was closed. This event is triggered exactly once for every connection.
A fully working echo server does need a bit more boilerplate (to
handle requests unanswered by SockJS), see the
for a complete code.
If you want to see samples of running code, take a look at:
- ./examples/echo directory, which contains a full example of a echo server.
- ./examples/test_server a standard SockJS test server.
Connecting to SockJS-node without the client
Although the main point of SockJS it to enable browser-to-server connectivity, it is possible to connect to SockJS from an external application. Any SockJS server complying with 0.3 protocol does support a raw WebSocket url. The raw WebSocket url for the test server looks like:
You can connect any WebSocket RFC 6455 compliant WebSocket client to this url. This can be a command line client, external application, third party code or even a browser (though I don't know why you would want to do so).
Deployment and load balancing
There are two issues that needs to be considered when planning a non-trivial SockJS-node deployment: WebSocket-compatible load balancer and sticky sessions (aka session affinity).
WebSocket compatible load balancer
Often WebSockets don't play nicely with proxies and load balancers. Deploying a SockJS server behind Nginx or Apache could be painful.
Fortunetely recent versions of an excellent load balancer HAProxy are able to proxy WebSocket connections. We propose to put HAProxy as a front line load balancer and use it to split SockJS traffic from normal HTTP data. Take a look at the sample SockJS HAProxy configuration.
The config also shows how to use HAproxy balancing to split traffic between multiple Node.js servers. You can also do balancing using dns names.
If you plan depling more than one SockJS server, you must make sure that all HTTP requests for a single session will hit the same server. SockJS has two mechanisms that can be usefull to achieve that:
- Urls are prefixed with server and session id numbers, like:
/resource/<server_number>/<session_id>/transport. This is usefull for load balancers that support prefix-based affinity (HAProxy does).
JSESSIONIDcookie is being set by SockJS-node. Many load balancers turn on sticky sessions if that cookie is set. This technique is derived from Java applications, where sticky sessions are often neccesary. HAProxy does support this method, as well as some hosting providers, for example CloudFoundry. In order to enable this method on the client side, please supply a
cookie:trueoption to SockJS constructor.
Development and testing
If you want to work on SockJS-node source code, you need to clone the git repo and follow these steps. First you need to install dependencies:
cd sockjs-node npm install --dev ln -s .. node_modules/sockjs
You're ready to compile CoffeeScript:
If compilation succeeds you may want to test if your changes pass all the tests. Currently, there are two separate test suites. For both of them you need to start a SockJS-node test server (by default listening on port 8081):
SockJS-protocol Python tests
To run it run something like:
cd sockjs-protocol make test_deps ./venv/bin/python sockjs-protocol-0.3.py
For details see SockJS-protocol README.
SockJS-client QUnit tests
cd sockjs-client make test
At that point you should have two web servers running: sockjs-node on 8081 and sockjs-client on 8080. When you open the browser on http://localhost:8080/ you should be able run the QUnit tests against your sockjs-node server.
For details see SockJS-client README.
Additionally, if you're doing more serious development consider using
make serve, which will automatically the server when you modify the
Various issues and design considerations
SockJS-node does not expose cookies to the application. This is done deliberately as using cookie-based authorization with SockJS simply doesn't make sense and will lead to security issues.
Cookies are a contract between a browser and an http server, and are identified by a domain name. If a browser has a cookie set for particular domain, it will pass it as a part of all http requests to the host. But to get various transports working, SockJS uses a middleman
- an iframe hosted from target SockJS domain. That means the server will receive requests from the iframe, and not from the real domain. The domain of an iframe is the same as the SockJS domain. The problem is that any website can embedd the iframe and communicate with it - and request establishing SockJS connection. Using cookies for authorization in this scenario will result in granting full access to SockJS communication with your website from any website. This is a classic CSRF attack.
Basically - cookies are not suited for SockJS model. If you want to authorize a session - provide a unique token on a page, send it as a first thing over SockJS connection and validate it on the server side. In essence, this is how cookies work.
Deploying SockJS on Heroku
Long polling is known to cause problems on Heroku, but workaround for SockJS is available.