UNPKG

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