1
|
[![NPM version](https://badge.fury.io/js/sockjs.svg)](http://badge.fury.io/js/sockjs)
|
2
|
|
3
|
SockJS family:
|
4
|
|
5
|
* [SockJS-client](https://github.com/sockjs/sockjs-client) JavaScript client library
|
6
|
* [SockJS-node](https://github.com/sockjs/sockjs-node) Node.js server
|
7
|
* [SockJS-erlang](https://github.com/sockjs/sockjs-erlang) Erlang server
|
8
|
* [SockJS-tornado](https://github.com/MrJoes/sockjs-tornado) Python/Tornado server
|
9
|
* [vert.x](https://github.com/eclipse/vert.x) Java/vert.x server
|
10
|
|
11
|
Work in progress:
|
12
|
|
13
|
* [SockJS-ruby](https://github.com/nyarly/sockjs-ruby)
|
14
|
* [SockJS-netty](https://github.com/cgbystrom/sockjs-netty)
|
15
|
* [SockJS-gevent](https://github.com/sdiehl/sockjs-gevent) ([and a fork](https://github.com/njoyce/sockjs-gevent))
|
16
|
* [pyramid-SockJS](https://github.com/fafhrd91/pyramid_sockjs)
|
17
|
* [wildcloud-websockets](https://github.com/wildcloud/wildcloud-websockets)
|
18
|
* [SockJS-cyclone](https://github.com/flaviogrossi/sockjs-cyclone)
|
19
|
* [SockJS-twisted](https://github.com/Fugiman/sockjs-twisted/)
|
20
|
* [wai-SockJS](https://github.com/Palmik/wai-sockjs)
|
21
|
* [SockJS-perl](https://github.com/vti/sockjs-perl)
|
22
|
* [SockJS-go](https://github.com/igm/sockjs-go/)
|
23
|
|
24
|
What is SockJS?
|
25
|
===============
|
26
|
|
27
|
SockJS is a JavaScript library (for browsers) that provides a WebSocket-like
|
28
|
object. SockJS gives you a coherent, cross-browser, Javascript API
|
29
|
which creates a low latency, full duplex, cross-domain communication
|
30
|
channel between the browser and the web server, with WebSockets or without.
|
31
|
This necessitates the use of a server, which this is one version of, for Node.js.
|
32
|
|
33
|
|
34
|
SockJS-node server
|
35
|
==================
|
36
|
|
37
|
SockJS-node is a Node.js server side counterpart of
|
38
|
[SockJS-client browser library](https://github.com/sockjs/sockjs-client)
|
39
|
written in CoffeeScript.
|
40
|
|
41
|
To install `sockjs-node` run:
|
42
|
|
43
|
npm install sockjs
|
44
|
|
45
|
A simplified echo SockJS server could look more or less like:
|
46
|
|
47
|
```javascript
|
48
|
var http = require('http');
|
49
|
var sockjs = require('sockjs');
|
50
|
|
51
|
var echo = sockjs.createServer({ sockjs_url: 'http://cdn.jsdelivr.net/sockjs/1.0.1/sockjs.min.js' });
|
52
|
echo.on('connection', function(conn) {
|
53
|
conn.on('data', function(message) {
|
54
|
conn.write(message);
|
55
|
});
|
56
|
conn.on('close', function() {});
|
57
|
});
|
58
|
|
59
|
var server = http.createServer();
|
60
|
echo.installHandlers(server, {prefix:'/echo'});
|
61
|
server.listen(9999, '0.0.0.0');
|
62
|
```
|
63
|
|
64
|
(Take look at
|
65
|
[examples](https://github.com/sockjs/sockjs-node/tree/master/examples/echo)
|
66
|
directory for a complete version.)
|
67
|
|
68
|
Subscribe to
|
69
|
[SockJS mailing list](https://groups.google.com/forum/#!forum/sockjs) for
|
70
|
discussions and support.
|
71
|
|
72
|
|
73
|
SockJS-node API
|
74
|
---------------
|
75
|
|
76
|
The API design is based on common Node APIs like the
|
77
|
[Streams API](http://nodejs.org/docs/v0.5.8/api/streams.html) or the
|
78
|
[Http.Server API](http://nodejs.org/docs/v0.5.8/api/http.html#http.Server).
|
79
|
|
80
|
### Server class
|
81
|
|
82
|
SockJS module is generating a `Server` class, similar to
|
83
|
[Node.js http.createServer](http://nodejs.org/docs/v0.5.8/api/http.html#http.createServer)
|
84
|
module.
|
85
|
|
86
|
```javascript
|
87
|
var sockjs_server = sockjs.createServer(options);
|
88
|
```
|
89
|
|
90
|
Where `options` is a hash which can contain:
|
91
|
|
92
|
<dl>
|
93
|
<dt>sockjs_url (string, required)</dt>
|
94
|
<dd>Transports which don't support cross-domain communication natively
|
95
|
('eventsource' to name one) use an iframe trick. A simple page is
|
96
|
served from the SockJS server (using its foreign domain) and is
|
97
|
placed in an invisible iframe. Code run from this iframe doesn't
|
98
|
need to worry about cross-domain issues, as it's being run from
|
99
|
domain local to the SockJS server. This iframe also does need to
|
100
|
load SockJS javascript client library, and this option lets you specify
|
101
|
its url (if you're unsure, point it to
|
102
|
<a href="http://cdn.jsdelivr.net/sockjs/1.0.1/sockjs.min.js">
|
103
|
the latest minified SockJS client release</a>, this is the default).
|
104
|
You must explicitly specify this url on the server side for security
|
105
|
reasons - we don't want the possibility of running any foreign
|
106
|
javascript within the SockJS domain (aka cross site scripting attack).
|
107
|
Also, sockjs javascript library is probably already cached by the
|
108
|
browser - it makes sense to reuse the sockjs url you're using in
|
109
|
normally.</dd>
|
110
|
|
111
|
<dt>prefix (string regex)</dt>
|
112
|
<dd>A url prefix for the server. All http requests which paths begins
|
113
|
with selected prefix will be handled by SockJS. All other requests
|
114
|
will be passed through, to previously registered handlers.</dd>
|
115
|
|
116
|
<dt>response_limit (integer)</dt>
|
117
|
<dd>Most streaming transports save responses on the client side and
|
118
|
don't free memory used by delivered messages. Such transports need
|
119
|
to be garbage-collected once in a while. `response_limit` sets
|
120
|
a minimum number of bytes that can be send over a single http streaming
|
121
|
request before it will be closed. After that client needs to open
|
122
|
new request. Setting this value to one effectively disables
|
123
|
streaming and will make streaming transports to behave like polling
|
124
|
transports. The default value is 128K.</dd>
|
125
|
|
126
|
<dt>websocket (boolean)</dt>
|
127
|
<dd>Some load balancers don't support websockets. This option can be used
|
128
|
to disable websockets support by the server. By default websockets are
|
129
|
enabled.</dd>
|
130
|
|
131
|
<dt>jsessionid (boolean or function)</dt>
|
132
|
<dd>Some hosting providers enable sticky sessions only to requests that
|
133
|
have JSESSIONID cookie set. This setting controls if the server should
|
134
|
set this cookie to a dummy value. By default setting JSESSIONID cookie
|
135
|
is disabled. More sophisticated behaviour can be achieved by supplying
|
136
|
a function.</dd>
|
137
|
|
138
|
<dt>log (function(severity, message))</dt>
|
139
|
<dd>It's quite useful, especially for debugging, to see some messages
|
140
|
printed by a SockJS-node library. This is done using this `log`
|
141
|
function, which is by default set to `console.log`. If this
|
142
|
behaviour annoys you for some reason, override `log` setting with a
|
143
|
custom handler. The following `severities` are used: `debug`
|
144
|
(miscellaneous logs), `info` (requests logs), `error` (serious
|
145
|
errors, consider filing an issue).</dd>
|
146
|
|
147
|
<dt>heartbeat_delay (milliseconds)</dt>
|
148
|
<dd>In order to keep proxies and load balancers from closing long
|
149
|
running http requests we need to pretend that the connection is
|
150
|
active and send a heartbeat packet once in a while. This setting
|
151
|
controls how often this is done. By default a heartbeat packet is
|
152
|
sent every 25 seconds. </dd>
|
153
|
|
154
|
<dt>disconnect_delay (milliseconds)</dt>
|
155
|
<dd>The server sends a `close` event when a client receiving
|
156
|
connection have not been seen for a while. This delay is configured
|
157
|
by this setting. By default the `close` event will be emitted when a
|
158
|
receiving connection wasn't seen for 5 seconds. </dd>
|
159
|
|
160
|
<dt>disable_cors (boolean)</dt>
|
161
|
<dd>Enabling this option will prevent
|
162
|
<a href="https://en.wikipedia.org/wiki/Cross-origin_resource_sharing">CORS</a>
|
163
|
headers from being included in the HTTP response. Can be used when the
|
164
|
sockjs client is known to be connecting from the same origin as the
|
165
|
sockjs server.</dd>
|
166
|
</dl>
|
167
|
|
168
|
|
169
|
### Server instance
|
170
|
|
171
|
Once you have create `Server` instance you can hook it to the
|
172
|
[http.Server instance](http://nodejs.org/docs/v0.5.8/api/http.html#http.createServer).
|
173
|
|
174
|
```javascript
|
175
|
var http_server = http.createServer();
|
176
|
sockjs_server.installHandlers(http_server, options);
|
177
|
http_server.listen(...);
|
178
|
```
|
179
|
|
180
|
Where `options` can overshadow options given when creating `Server`
|
181
|
instance.
|
182
|
|
183
|
`Server` instance is an
|
184
|
[EventEmitter](http://nodejs.org/docs/v0.4.10/api/events.html#events.EventEmitter),
|
185
|
and emits following event:
|
186
|
|
187
|
<dl>
|
188
|
<dt>Event: connection (connection)</dt>
|
189
|
<dd>A new connection has been successfully opened.</dd>
|
190
|
</dl>
|
191
|
|
192
|
All http requests that don't go under the path selected by `prefix`
|
193
|
will remain unanswered and will be passed to previously registered
|
194
|
handlers. You must install your custom http handlers before calling
|
195
|
`installHandlers`.
|
196
|
|
197
|
### Connection instance
|
198
|
|
199
|
A `Connection` instance supports
|
200
|
[Node Stream API](http://nodejs.org/docs/v0.5.8/api/streams.html) and
|
201
|
has following methods and properties:
|
202
|
|
203
|
<dl>
|
204
|
<dt>Property: readable (boolean)</dt>
|
205
|
<dd>Is the stream readable?</dd>
|
206
|
|
207
|
<dt>Property: writable (boolean)</dt>
|
208
|
<dd>Is the stream writable?</dd>
|
209
|
|
210
|
<dt>Property: remoteAddress (string)</dt>
|
211
|
<dd>Last known IP address of the client.</dd>
|
212
|
|
213
|
<dt>Property: remotePort (number)</dt>
|
214
|
<dd>Last known port number of the client.</dd>
|
215
|
|
216
|
<dt>Property: address (object)</dt>
|
217
|
<dd>Hash with 'address' and 'port' fields.</dd>
|
218
|
|
219
|
<dt>Property: headers (object)</dt>
|
220
|
<dd>Hash containing various headers copied from last receiving request
|
221
|
on that connection. Exposed headers include: `origin`, `referer`
|
222
|
and `x-forwarded-for` (and friends). We explicitly do not grant
|
223
|
access to `cookie` header, as using it may easily lead to security
|
224
|
issues (for details read the section "Authorisation").</dd>
|
225
|
|
226
|
<dt>Property: url (string)</dt>
|
227
|
<dd><a href="http://nodejs.org/docs/v0.4.10/api/http.html#request.url">Url</a>
|
228
|
property copied from last request.</dd>
|
229
|
|
230
|
<dt>Property: pathname (string)</dt>
|
231
|
<dd>`pathname` from parsed url, for convenience.</dd>
|
232
|
|
233
|
<dt>Property: prefix (string)</dt>
|
234
|
<dd>Prefix of the url on which the request was handled.</dd>
|
235
|
|
236
|
<dt>Property: protocol (string)</dt>
|
237
|
<dd>Protocol used by the connection. Keep in mind that some protocols
|
238
|
are indistinguishable - for example "xhr-polling" and "xdr-polling".</dd>
|
239
|
|
240
|
<dt>Property: readyState (integer)</dt>
|
241
|
<dd>Current state of the connection:
|
242
|
0-connecting, 1-open, 2-closing, 3-closed.</dd>
|
243
|
|
244
|
<dt>write(message)</dt>
|
245
|
<dd>Sends a message over opened connection. A message must be a
|
246
|
non-empty string. It's illegal to send a message after the connection was
|
247
|
closed (either after 'close' or 'end' method or 'close' event).</dd>
|
248
|
|
249
|
<dt>close([code], [reason])</dt>
|
250
|
<dd>Asks the remote client to disconnect. 'code' and 'reason'
|
251
|
parameters are optional and can be used to share the reason of
|
252
|
disconnection.</dd>
|
253
|
|
254
|
<dt>end()</dt>
|
255
|
<dd>Asks the remote client to disconnect with default 'code' and
|
256
|
'reason' values.</dd>
|
257
|
|
258
|
</dl>
|
259
|
|
260
|
A `Connection` instance emits the following events:
|
261
|
|
262
|
<dl>
|
263
|
<dt>Event: data (message)</dt>
|
264
|
<dd>A message arrived on the connection. Message is a unicode
|
265
|
string.</dd>
|
266
|
|
267
|
<dt>Event: close ()</dt>
|
268
|
<dd>Connection was closed. This event is triggered exactly once for
|
269
|
every connection.</dd>
|
270
|
</dl>
|
271
|
|
272
|
For example:
|
273
|
|
274
|
```javascript
|
275
|
sockjs_server.on('connection', function(conn) {
|
276
|
console.log('connection' + conn);
|
277
|
conn.on('close', function() {
|
278
|
console.log('close ' + conn);
|
279
|
});
|
280
|
conn.on('data', function(message) {
|
281
|
console.log('message ' + conn,
|
282
|
message);
|
283
|
});
|
284
|
});
|
285
|
```
|
286
|
|
287
|
### Footnote
|
288
|
|
289
|
A fully working echo server does need a bit more boilerplate (to
|
290
|
handle requests unanswered by SockJS), see the
|
291
|
[`echo` example](https://github.com/sockjs/sockjs-node/tree/master/examples/echo)
|
292
|
for a complete code.
|
293
|
|
294
|
### Examples
|
295
|
|
296
|
If you want to see samples of running code, take a look at:
|
297
|
|
298
|
* [./examples/echo](https://github.com/sockjs/sockjs-node/tree/master/examples/echo)
|
299
|
directory, which contains a full example of a echo server.
|
300
|
* [./examples/test_server](https://github.com/sockjs/sockjs-node/tree/master/examples/test_server) a standard SockJS test server.
|
301
|
|
302
|
|
303
|
Connecting to SockJS-node without the client
|
304
|
--------------------------------------------
|
305
|
|
306
|
Although the main point of SockJS it to enable browser-to-server
|
307
|
connectivity, it is possible to connect to SockJS from an external
|
308
|
application. Any SockJS server complying with 0.3 protocol does
|
309
|
support a raw WebSocket url. The raw WebSocket url for the test server
|
310
|
looks like:
|
311
|
|
312
|
* ws://localhost:8081/echo/websocket
|
313
|
|
314
|
You can connect any WebSocket RFC 6455 compliant WebSocket client to
|
315
|
this url. This can be a command line client, external application,
|
316
|
third party code or even a browser (though I don't know why you would
|
317
|
want to do so).
|
318
|
|
319
|
Note: This endpoint will *not send any heartbeat packets*.
|
320
|
|
321
|
|
322
|
Deployment and load balancing
|
323
|
-----------------------------
|
324
|
|
325
|
There are two issues that need to be considered when planning a
|
326
|
non-trivial SockJS-node deployment: WebSocket-compatible load balancer
|
327
|
and sticky sessions (aka session affinity).
|
328
|
|
329
|
### WebSocket compatible load balancer
|
330
|
|
331
|
Often WebSockets don't play nicely with proxies and load balancers.
|
332
|
Deploying a SockJS server behind Nginx or Apache could be painful.
|
333
|
|
334
|
Fortunately recent versions of an excellent load balancer
|
335
|
[HAProxy](http://haproxy.1wt.eu/) are able to proxy WebSocket
|
336
|
connections. We propose to put HAProxy as a front line load balancer
|
337
|
and use it to split SockJS traffic from normal HTTP data. Take a look
|
338
|
at the sample
|
339
|
[SockJS HAProxy configuration](https://github.com/sockjs/sockjs-node/blob/master/examples/haproxy.cfg).
|
340
|
|
341
|
The config also shows how to use HAproxy balancing to split traffic
|
342
|
between multiple Node.js servers. You can also do balancing using dns
|
343
|
names.
|
344
|
|
345
|
### Sticky sessions
|
346
|
|
347
|
If you plan deploying more than one SockJS server, you must make sure
|
348
|
that all HTTP requests for a single session will hit the same server.
|
349
|
SockJS has two mechanisms that can be useful to achieve that:
|
350
|
|
351
|
* Urls are prefixed with server and session id numbers, like:
|
352
|
`/resource/<server_number>/<session_id>/transport`. This is
|
353
|
useful for load balancers that support prefix-based affinity
|
354
|
(HAProxy does).
|
355
|
* `JSESSIONID` cookie is being set by SockJS-node. Many load
|
356
|
balancers turn on sticky sessions if that cookie is set. This
|
357
|
technique is derived from Java applications, where sticky sessions
|
358
|
are often necessary. HAProxy does support this method, as well as
|
359
|
some hosting providers, for example CloudFoundry. In order to
|
360
|
enable this method on the client side, please supply a
|
361
|
`cookie:true` option to SockJS constructor.
|
362
|
|
363
|
|
364
|
Development and testing
|
365
|
-----------------------
|
366
|
|
367
|
If you want to work on SockJS-node source code, you need to clone the
|
368
|
git repo and follow these steps. First you need to install
|
369
|
dependencies:
|
370
|
|
371
|
cd sockjs-node
|
372
|
npm install
|
373
|
npm install --dev
|
374
|
ln -s .. node_modules/sockjs
|
375
|
|
376
|
You're ready to compile CoffeeScript:
|
377
|
|
378
|
make build
|
379
|
|
380
|
If compilation succeeds you may want to test if your changes pass all
|
381
|
the tests. Currently, there are two separate test suites. For both of
|
382
|
them you need to start a SockJS-node test server (by default listening
|
383
|
on port 8081):
|
384
|
|
385
|
make test_server
|
386
|
|
387
|
### SockJS-protocol Python tests
|
388
|
|
389
|
To run it run something like:
|
390
|
|
391
|
cd sockjs-protocol
|
392
|
make test_deps
|
393
|
./venv/bin/python sockjs-protocol.py
|
394
|
|
395
|
For details see
|
396
|
[SockJS-protocol README](https://github.com/sockjs/sockjs-protocol#readme).
|
397
|
|
398
|
### SockJS-client QUnit tests
|
399
|
|
400
|
You need to start a second web server (by default listening on 8080)
|
401
|
that is serving various static html and javascript files:
|
402
|
|
403
|
cd sockjs-client
|
404
|
make test
|
405
|
|
406
|
At that point you should have two web servers running: sockjs-node on
|
407
|
8081 and sockjs-client on 8080. When you open the browser on
|
408
|
[http://localhost:8080/](http://localhost:8080/) you should be able
|
409
|
run the QUnit tests against your sockjs-node server.
|
410
|
|
411
|
For details see
|
412
|
[SockJS-client README](https://github.com/sockjs/sockjs-client#readme).
|
413
|
|
414
|
Additionally, if you're doing more serious development consider using
|
415
|
`make serve`, which will automatically the server when you modify the
|
416
|
source code.
|
417
|
|
418
|
|
419
|
Various issues and design considerations
|
420
|
----------------------------------------
|
421
|
|
422
|
### Authorisation
|
423
|
|
424
|
SockJS-node does not expose cookies to the application. This is done
|
425
|
deliberately as using cookie-based authorisation with SockJS simply
|
426
|
doesn't make sense and will lead to security issues.
|
427
|
|
428
|
Cookies are a contract between a browser and an http server, and are
|
429
|
identified by a domain name. If a browser has a cookie set for
|
430
|
particular domain, it will pass it as a part of all http requests to
|
431
|
the host. But to get various transports working, SockJS uses a middleman
|
432
|
- an iframe hosted from target SockJS domain. That means the server
|
433
|
will receive requests from the iframe, and not from the real
|
434
|
domain. The domain of an iframe is the same as the SockJS domain. The
|
435
|
problem is that any website can embed the iframe and communicate with
|
436
|
it - and request establishing SockJS connection. Using cookies for
|
437
|
authorisation in this scenario will result in granting full access to
|
438
|
SockJS communication with your website from any website. This is a
|
439
|
classic CSRF attack.
|
440
|
|
441
|
Basically - cookies are not suited for SockJS model. If you want to
|
442
|
authorise a session - provide a unique token on a page, send it as a
|
443
|
first thing over SockJS connection and validate it on the server
|
444
|
side. In essence, this is how cookies work.
|
445
|
|
446
|
|
447
|
### Deploying SockJS on Heroku
|
448
|
|
449
|
Long polling is known to cause problems on Heroku, but
|
450
|
[workaround for SockJS is available](https://github.com/sockjs/sockjs-node/issues/57#issuecomment-5242187).
|