[6a3a178] | 1 | # faye-websocket
|
---|
| 2 |
|
---|
| 3 | This is a general-purpose WebSocket implementation extracted from the
|
---|
| 4 | [Faye](http://faye.jcoglan.com) project. It provides classes for easily building
|
---|
| 5 | WebSocket servers and clients in Node. It does not provide a server itself, but
|
---|
| 6 | rather makes it easy to handle WebSocket connections within an existing
|
---|
| 7 | [Node](https://nodejs.org/) application. It does not provide any abstraction
|
---|
| 8 | other than the standard [WebSocket
|
---|
| 9 | API](https://html.spec.whatwg.org/multipage/comms.html#network).
|
---|
| 10 |
|
---|
| 11 | It also provides an abstraction for handling
|
---|
| 12 | [EventSource](https://html.spec.whatwg.org/multipage/comms.html#server-sent-events)
|
---|
| 13 | connections, which are one-way connections that allow the server to push data to
|
---|
| 14 | the client. They are based on streaming HTTP responses and can be easier to access
|
---|
| 15 | via proxies than WebSockets.
|
---|
| 16 |
|
---|
| 17 |
|
---|
| 18 | ## Installation
|
---|
| 19 |
|
---|
| 20 | ```
|
---|
| 21 | $ npm install faye-websocket
|
---|
| 22 | ```
|
---|
| 23 |
|
---|
| 24 |
|
---|
| 25 | ## Handling WebSocket connections in Node
|
---|
| 26 |
|
---|
| 27 | You can handle WebSockets on the server side by listening for HTTP Upgrade
|
---|
| 28 | requests, and creating a new socket for the request. This socket object exposes
|
---|
| 29 | the usual WebSocket methods for receiving and sending messages. For example this
|
---|
| 30 | is how you'd implement an echo server:
|
---|
| 31 |
|
---|
| 32 | ```js
|
---|
| 33 | var WebSocket = require('faye-websocket'),
|
---|
| 34 | http = require('http');
|
---|
| 35 |
|
---|
| 36 | var server = http.createServer();
|
---|
| 37 |
|
---|
| 38 | server.on('upgrade', function(request, socket, body) {
|
---|
| 39 | if (WebSocket.isWebSocket(request)) {
|
---|
| 40 | var ws = new WebSocket(request, socket, body);
|
---|
| 41 |
|
---|
| 42 | ws.on('message', function(event) {
|
---|
| 43 | ws.send(event.data);
|
---|
| 44 | });
|
---|
| 45 |
|
---|
| 46 | ws.on('close', function(event) {
|
---|
| 47 | console.log('close', event.code, event.reason);
|
---|
| 48 | ws = null;
|
---|
| 49 | });
|
---|
| 50 | }
|
---|
| 51 | });
|
---|
| 52 |
|
---|
| 53 | server.listen(8000);
|
---|
| 54 | ```
|
---|
| 55 |
|
---|
| 56 | `WebSocket` objects are also duplex streams, so you could replace the
|
---|
| 57 | `ws.on('message', ...)` line with:
|
---|
| 58 |
|
---|
| 59 | ```js
|
---|
| 60 | ws.pipe(ws);
|
---|
| 61 | ```
|
---|
| 62 |
|
---|
| 63 | Note that under certain circumstances (notably a draft-76 client connecting
|
---|
| 64 | through an HTTP proxy), the WebSocket handshake will not be complete after you
|
---|
| 65 | call `new WebSocket()` because the server will not have received the entire
|
---|
| 66 | handshake from the client yet. In this case, calls to `ws.send()` will buffer
|
---|
| 67 | the message in memory until the handshake is complete, at which point any
|
---|
| 68 | buffered messages will be sent to the client.
|
---|
| 69 |
|
---|
| 70 | If you need to detect when the WebSocket handshake is complete, you can use the
|
---|
| 71 | `onopen` event.
|
---|
| 72 |
|
---|
| 73 | If the connection's protocol version supports it, you can call `ws.ping()` to
|
---|
| 74 | send a ping message and wait for the client's response. This method takes a
|
---|
| 75 | message string, and an optional callback that fires when a matching pong message
|
---|
| 76 | is received. It returns `true` if and only if a ping message was sent. If the
|
---|
| 77 | client does not support ping/pong, this method sends no data and returns
|
---|
| 78 | `false`.
|
---|
| 79 |
|
---|
| 80 | ```js
|
---|
| 81 | ws.ping('Mic check, one, two', function() {
|
---|
| 82 | // fires when pong is received
|
---|
| 83 | });
|
---|
| 84 | ```
|
---|
| 85 |
|
---|
| 86 |
|
---|
| 87 | ## Using the WebSocket client
|
---|
| 88 |
|
---|
| 89 | The client supports both the plain-text `ws` protocol and the encrypted `wss`
|
---|
| 90 | protocol, and has exactly the same interface as a socket you would use in a web
|
---|
| 91 | browser. On the wire it identifies itself as `hybi-13`.
|
---|
| 92 |
|
---|
| 93 | ```js
|
---|
| 94 | var WebSocket = require('faye-websocket'),
|
---|
| 95 | ws = new WebSocket.Client('ws://www.example.com/');
|
---|
| 96 |
|
---|
| 97 | ws.on('open', function(event) {
|
---|
| 98 | console.log('open');
|
---|
| 99 | ws.send('Hello, world!');
|
---|
| 100 | });
|
---|
| 101 |
|
---|
| 102 | ws.on('message', function(event) {
|
---|
| 103 | console.log('message', event.data);
|
---|
| 104 | });
|
---|
| 105 |
|
---|
| 106 | ws.on('close', function(event) {
|
---|
| 107 | console.log('close', event.code, event.reason);
|
---|
| 108 | ws = null;
|
---|
| 109 | });
|
---|
| 110 | ```
|
---|
| 111 |
|
---|
| 112 | The WebSocket client also lets you inspect the status and headers of the
|
---|
| 113 | handshake response via its `statusCode` and `headers` properties.
|
---|
| 114 |
|
---|
| 115 | To connect via a proxy, set the `proxy` option to the HTTP origin of the proxy,
|
---|
| 116 | including any authorization information, custom headers and TLS config you
|
---|
| 117 | require. Only the `origin` setting is required.
|
---|
| 118 |
|
---|
| 119 | ```js
|
---|
| 120 | var ws = new WebSocket.Client('ws://www.example.com/', [], {
|
---|
| 121 | proxy: {
|
---|
| 122 | origin: 'http://username:password@proxy.example.com',
|
---|
| 123 | headers: { 'User-Agent': 'node' },
|
---|
| 124 | tls: { cert: fs.readFileSync('client.crt') }
|
---|
| 125 | }
|
---|
| 126 | });
|
---|
| 127 | ```
|
---|
| 128 |
|
---|
| 129 | The `tls` value is an object that will be passed to
|
---|
| 130 | [`tls.connect()`](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback).
|
---|
| 131 |
|
---|
| 132 |
|
---|
| 133 | ## Subprotocol negotiation
|
---|
| 134 |
|
---|
| 135 | The WebSocket protocol allows peers to select and identify the application
|
---|
| 136 | protocol to use over the connection. On the client side, you can set which
|
---|
| 137 | protocols the client accepts by passing a list of protocol names when you
|
---|
| 138 | construct the socket:
|
---|
| 139 |
|
---|
| 140 | ```js
|
---|
| 141 | var ws = new WebSocket.Client('ws://www.example.com/', ['irc', 'amqp']);
|
---|
| 142 | ```
|
---|
| 143 |
|
---|
| 144 | On the server side, you can likewise pass in the list of protocols the server
|
---|
| 145 | supports after the other constructor arguments:
|
---|
| 146 |
|
---|
| 147 | ```js
|
---|
| 148 | var ws = new WebSocket(request, socket, body, ['irc', 'amqp']);
|
---|
| 149 | ```
|
---|
| 150 |
|
---|
| 151 | If the client and server agree on a protocol, both the client- and server-side
|
---|
| 152 | socket objects expose the selected protocol through the `ws.protocol` property.
|
---|
| 153 |
|
---|
| 154 |
|
---|
| 155 | ## Protocol extensions
|
---|
| 156 |
|
---|
| 157 | faye-websocket is based on the
|
---|
| 158 | [websocket-extensions](https://github.com/faye/websocket-extensions-node)
|
---|
| 159 | framework that allows extensions to be negotiated via the
|
---|
| 160 | `Sec-WebSocket-Extensions` header. To add extensions to a connection, pass an
|
---|
| 161 | array of extensions to the `:extensions` option. For example, to add
|
---|
| 162 | [permessage-deflate](https://github.com/faye/permessage-deflate-node):
|
---|
| 163 |
|
---|
| 164 | ```js
|
---|
| 165 | var deflate = require('permessage-deflate');
|
---|
| 166 |
|
---|
| 167 | var ws = new WebSocket(request, socket, body, [], { extensions: [deflate] });
|
---|
| 168 | ```
|
---|
| 169 |
|
---|
| 170 |
|
---|
| 171 | ## Initialization options
|
---|
| 172 |
|
---|
| 173 | Both the server- and client-side classes allow an options object to be passed in
|
---|
| 174 | at initialization time, for example:
|
---|
| 175 |
|
---|
| 176 | ```js
|
---|
| 177 | var ws = new WebSocket(request, socket, body, protocols, options);
|
---|
| 178 | var ws = new WebSocket.Client(url, protocols, options);
|
---|
| 179 | ```
|
---|
| 180 |
|
---|
| 181 | `protocols` is an array of subprotocols as described above, or `null`.
|
---|
| 182 | `options` is an optional object containing any of these fields:
|
---|
| 183 |
|
---|
| 184 | - `extensions` - an array of
|
---|
| 185 | [websocket-extensions](https://github.com/faye/websocket-extensions-node)
|
---|
| 186 | compatible extensions, as described above
|
---|
| 187 | - `headers` - an object containing key-value pairs representing HTTP headers to
|
---|
| 188 | be sent during the handshake process
|
---|
| 189 | - `maxLength` - the maximum allowed size of incoming message frames, in bytes.
|
---|
| 190 | The default value is `2^26 - 1`, or 1 byte short of 64 MiB.
|
---|
| 191 | - `ping` - an integer that sets how often the WebSocket should send ping frames,
|
---|
| 192 | measured in seconds
|
---|
| 193 |
|
---|
| 194 | The client accepts some additional options:
|
---|
| 195 |
|
---|
| 196 | - `proxy` - settings for a proxy as described above
|
---|
| 197 | - `net` - an object containing settings for the origin server that will be
|
---|
| 198 | passed to
|
---|
| 199 | [`net.connect()`](https://nodejs.org/api/net.html#net_socket_connect_options_connectlistener)
|
---|
| 200 | - `tls` - an object containing TLS settings for the origin server, this will be
|
---|
| 201 | passed to
|
---|
| 202 | [`tls.connect()`](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback)
|
---|
| 203 | - `ca` - (legacy) a shorthand for passing `{ tls: { ca: value } }`
|
---|
| 204 |
|
---|
| 205 |
|
---|
| 206 | ## WebSocket API
|
---|
| 207 |
|
---|
| 208 | Both server- and client-side `WebSocket` objects support the following API.
|
---|
| 209 |
|
---|
| 210 | - **`on('open', function(event) {})`** fires when the socket connection is
|
---|
| 211 | established. Event has no attributes.
|
---|
| 212 | - **`on('message', function(event) {})`** fires when the socket receives a
|
---|
| 213 | message. Event has one attribute, **`data`**, which is either a `String` (for
|
---|
| 214 | text frames) or a `Buffer` (for binary frames).
|
---|
| 215 | - **`on('error', function(event) {})`** fires when there is a protocol error due
|
---|
| 216 | to bad data sent by the other peer. This event is purely informational, you do
|
---|
| 217 | not need to implement error recover.
|
---|
| 218 | - **`on('close', function(event) {})`** fires when either the client or the
|
---|
| 219 | server closes the connection. Event has two optional attributes, **`code`**
|
---|
| 220 | and **`reason`**, that expose the status code and message sent by the peer
|
---|
| 221 | that closed the connection.
|
---|
| 222 | - **`send(message)`** accepts either a `String` or a `Buffer` and sends a text
|
---|
| 223 | or binary message over the connection to the other peer.
|
---|
| 224 | - **`ping(message, function() {})`** sends a ping frame with an optional message
|
---|
| 225 | and fires the callback when a matching pong is received.
|
---|
| 226 | - **`close(code, reason)`** closes the connection, sending the given status code
|
---|
| 227 | and reason text, both of which are optional.
|
---|
| 228 | - **`version`** is a string containing the version of the `WebSocket` protocol
|
---|
| 229 | the connection is using.
|
---|
| 230 | - **`protocol`** is a string (which may be empty) identifying the subprotocol
|
---|
| 231 | the socket is using.
|
---|
| 232 |
|
---|
| 233 |
|
---|
| 234 | ## Handling EventSource connections in Node
|
---|
| 235 |
|
---|
| 236 | EventSource connections provide a very similar interface, although because they
|
---|
| 237 | only allow the server to send data to the client, there is no `onmessage` API.
|
---|
| 238 | EventSource allows the server to push text messages to the client, where each
|
---|
| 239 | message has an optional event-type and ID.
|
---|
| 240 |
|
---|
| 241 | ```js
|
---|
| 242 | var WebSocket = require('faye-websocket'),
|
---|
| 243 | EventSource = WebSocket.EventSource,
|
---|
| 244 | http = require('http');
|
---|
| 245 |
|
---|
| 246 | var server = http.createServer();
|
---|
| 247 |
|
---|
| 248 | server.on('request', function(request, response) {
|
---|
| 249 | if (EventSource.isEventSource(request)) {
|
---|
| 250 | var es = new EventSource(request, response);
|
---|
| 251 | console.log('open', es.url, es.lastEventId);
|
---|
| 252 |
|
---|
| 253 | // Periodically send messages
|
---|
| 254 | var loop = setInterval(function() { es.send('Hello') }, 1000);
|
---|
| 255 |
|
---|
| 256 | es.on('close', function() {
|
---|
| 257 | clearInterval(loop);
|
---|
| 258 | es = null;
|
---|
| 259 | });
|
---|
| 260 |
|
---|
| 261 | } else {
|
---|
| 262 | // Normal HTTP request
|
---|
| 263 | response.writeHead(200, { 'Content-Type': 'text/plain' });
|
---|
| 264 | response.end('Hello');
|
---|
| 265 | }
|
---|
| 266 | });
|
---|
| 267 |
|
---|
| 268 | server.listen(8000);
|
---|
| 269 | ```
|
---|
| 270 |
|
---|
| 271 | The `send` method takes two optional parameters, `event` and `id`. The default
|
---|
| 272 | event-type is `'message'` with no ID. For example, to send a `notification`
|
---|
| 273 | event with ID `99`:
|
---|
| 274 |
|
---|
| 275 | ```js
|
---|
| 276 | es.send('Breaking News!', { event: 'notification', id: '99' });
|
---|
| 277 | ```
|
---|
| 278 |
|
---|
| 279 | The `EventSource` object exposes the following properties:
|
---|
| 280 |
|
---|
| 281 | - **`url`** is a string containing the URL the client used to create the
|
---|
| 282 | EventSource.
|
---|
| 283 | - **`lastEventId`** is a string containing the last event ID received by the
|
---|
| 284 | client. You can use this when the client reconnects after a dropped connection
|
---|
| 285 | to determine which messages need resending.
|
---|
| 286 |
|
---|
| 287 | When you initialize an EventSource with ` new EventSource()`, you can pass
|
---|
| 288 | configuration options after the `response` parameter. Available options are:
|
---|
| 289 |
|
---|
| 290 | - **`headers`** is an object containing custom headers to be set on the
|
---|
| 291 | EventSource response.
|
---|
| 292 | - **`retry`** is a number that tells the client how long (in seconds) it should
|
---|
| 293 | wait after a dropped connection before attempting to reconnect.
|
---|
| 294 | - **`ping`** is a number that tells the server how often (in seconds) to send
|
---|
| 295 | 'ping' packets to the client to keep the connection open, to defeat timeouts
|
---|
| 296 | set by proxies. The client will ignore these messages.
|
---|
| 297 |
|
---|
| 298 | For example, this creates a connection that allows access from any origin, pings
|
---|
| 299 | every 15 seconds and is retryable every 10 seconds if the connection is broken:
|
---|
| 300 |
|
---|
| 301 | ```js
|
---|
| 302 | var es = new EventSource(request, response, {
|
---|
| 303 | headers: { 'Access-Control-Allow-Origin': '*' },
|
---|
| 304 | ping: 15,
|
---|
| 305 | retry: 10
|
---|
| 306 | });
|
---|
| 307 | ```
|
---|
| 308 |
|
---|
| 309 | You can send a ping message at any time by calling `es.ping()`. Unlike
|
---|
| 310 | WebSocket, the client does not send a response to this; it is merely to send
|
---|
| 311 | some data over the wire to keep the connection alive.
|
---|