[6a3a178] | 1 | # websocket-driver [![Build Status](https://travis-ci.org/faye/websocket-driver-node.svg)](https://travis-ci.org/faye/websocket-driver-node)
|
---|
| 2 |
|
---|
| 3 | This module provides a complete implementation of the WebSocket protocols that
|
---|
| 4 | can be hooked up to any I/O stream. It aims to simplify things by decoupling the
|
---|
| 5 | protocol details from the I/O layer, such that users only need to implement code
|
---|
| 6 | to stream data in and out of it without needing to know anything about how the
|
---|
| 7 | protocol actually works. Think of it as a complete WebSocket system with
|
---|
| 8 | pluggable I/O.
|
---|
| 9 |
|
---|
| 10 | Due to this design, you get a lot of things for free. In particular, if you hook
|
---|
| 11 | this module up to some I/O object, it will do all of this for you:
|
---|
| 12 |
|
---|
| 13 | - Select the correct server-side driver to talk to the client
|
---|
| 14 | - Generate and send both server- and client-side handshakes
|
---|
| 15 | - Recognize when the handshake phase completes and the WS protocol begins
|
---|
| 16 | - Negotiate subprotocol selection based on `Sec-WebSocket-Protocol`
|
---|
| 17 | - Negotiate and use extensions via the
|
---|
| 18 | [websocket-extensions](https://github.com/faye/websocket-extensions-node)
|
---|
| 19 | module
|
---|
| 20 | - Buffer sent messages until the handshake process is finished
|
---|
| 21 | - Deal with proxies that defer delivery of the draft-76 handshake body
|
---|
| 22 | - Notify you when the socket is open and closed and when messages arrive
|
---|
| 23 | - Recombine fragmented messages
|
---|
| 24 | - Dispatch text, binary, ping, pong and close frames
|
---|
| 25 | - Manage the socket-closing handshake process
|
---|
| 26 | - Automatically reply to ping frames with a matching pong
|
---|
| 27 | - Apply masking to messages sent by the client
|
---|
| 28 |
|
---|
| 29 | This library was originally extracted from the [Faye](http://faye.jcoglan.com)
|
---|
| 30 | project but now aims to provide simple WebSocket support for any Node-based
|
---|
| 31 | project.
|
---|
| 32 |
|
---|
| 33 |
|
---|
| 34 | ## Installation
|
---|
| 35 |
|
---|
| 36 | ```
|
---|
| 37 | $ npm install websocket-driver
|
---|
| 38 | ```
|
---|
| 39 |
|
---|
| 40 |
|
---|
| 41 | ## Usage
|
---|
| 42 |
|
---|
| 43 | This module provides protocol drivers that have the same interface on the server
|
---|
| 44 | and on the client. A WebSocket driver is an object with two duplex streams
|
---|
| 45 | attached; one for incoming/outgoing messages and one for managing the wire
|
---|
| 46 | protocol over an I/O stream. The full API is described below.
|
---|
| 47 |
|
---|
| 48 |
|
---|
| 49 | ### Server-side with HTTP
|
---|
| 50 |
|
---|
| 51 | A Node webserver emits a special event for 'upgrade' requests, and this is where
|
---|
| 52 | you should handle WebSockets. You first check whether the request is a
|
---|
| 53 | WebSocket, and if so you can create a driver and attach the request's I/O stream
|
---|
| 54 | to it.
|
---|
| 55 |
|
---|
| 56 | ```js
|
---|
| 57 | var http = require('http'),
|
---|
| 58 | websocket = require('websocket-driver');
|
---|
| 59 |
|
---|
| 60 | var server = http.createServer();
|
---|
| 61 |
|
---|
| 62 | server.on('upgrade', function(request, socket, body) {
|
---|
| 63 | if (!websocket.isWebSocket(request)) return;
|
---|
| 64 |
|
---|
| 65 | var driver = websocket.http(request);
|
---|
| 66 |
|
---|
| 67 | driver.io.write(body);
|
---|
| 68 | socket.pipe(driver.io).pipe(socket);
|
---|
| 69 |
|
---|
| 70 | driver.messages.on('data', function(message) {
|
---|
| 71 | console.log('Got a message', message);
|
---|
| 72 | });
|
---|
| 73 |
|
---|
| 74 | driver.start();
|
---|
| 75 | });
|
---|
| 76 | ```
|
---|
| 77 |
|
---|
| 78 | Note the line `driver.io.write(body)` - you must pass the `body` buffer to the
|
---|
| 79 | socket driver in order to make certain versions of the protocol work.
|
---|
| 80 |
|
---|
| 81 |
|
---|
| 82 | ### Server-side with TCP
|
---|
| 83 |
|
---|
| 84 | You can also handle WebSocket connections in a bare TCP server, if you're not
|
---|
| 85 | using an HTTP server and don't want to implement HTTP parsing yourself.
|
---|
| 86 |
|
---|
| 87 | The driver will emit a `connect` event when a request is received, and at this
|
---|
| 88 | point you can detect whether it's a WebSocket and handle it as such. Here's an
|
---|
| 89 | example using the Node `net` module:
|
---|
| 90 |
|
---|
| 91 | ```js
|
---|
| 92 | var net = require('net'),
|
---|
| 93 | websocket = require('websocket-driver');
|
---|
| 94 |
|
---|
| 95 | var server = net.createServer(function(connection) {
|
---|
| 96 | var driver = websocket.server();
|
---|
| 97 |
|
---|
| 98 | driver.on('connect', function() {
|
---|
| 99 | if (websocket.isWebSocket(driver)) {
|
---|
| 100 | driver.start();
|
---|
| 101 | } else {
|
---|
| 102 | // handle other HTTP requests
|
---|
| 103 | }
|
---|
| 104 | });
|
---|
| 105 |
|
---|
| 106 | driver.on('close', function() { connection.end() });
|
---|
| 107 | connection.on('error', function() {});
|
---|
| 108 |
|
---|
| 109 | connection.pipe(driver.io).pipe(connection);
|
---|
| 110 |
|
---|
| 111 | driver.messages.pipe(driver.messages);
|
---|
| 112 | });
|
---|
| 113 |
|
---|
| 114 | server.listen(4180);
|
---|
| 115 | ```
|
---|
| 116 |
|
---|
| 117 | In the `connect` event, the driver gains several properties to describe the
|
---|
| 118 | request, similar to a Node request object, such as `method`, `url` and
|
---|
| 119 | `headers`. However you should remember it's not a real request object; you
|
---|
| 120 | cannot write data to it, it only tells you what request data we parsed from the
|
---|
| 121 | input.
|
---|
| 122 |
|
---|
| 123 | If the request has a body, it will be in the `driver.body` buffer, but only as
|
---|
| 124 | much of the body as has been piped into the driver when the `connect` event
|
---|
| 125 | fires.
|
---|
| 126 |
|
---|
| 127 |
|
---|
| 128 | ### Client-side
|
---|
| 129 |
|
---|
| 130 | Similarly, to implement a WebSocket client you just need to make a driver by
|
---|
| 131 | passing in a URL. After this you use the driver API as described below to
|
---|
| 132 | process incoming data and send outgoing data.
|
---|
| 133 |
|
---|
| 134 |
|
---|
| 135 | ```js
|
---|
| 136 | var net = require('net'),
|
---|
| 137 | websocket = require('websocket-driver');
|
---|
| 138 |
|
---|
| 139 | var driver = websocket.client('ws://www.example.com/socket'),
|
---|
| 140 | tcp = net.connect(80, 'www.example.com');
|
---|
| 141 |
|
---|
| 142 | tcp.pipe(driver.io).pipe(tcp);
|
---|
| 143 |
|
---|
| 144 | tcp.on('connect', function() {
|
---|
| 145 | driver.start();
|
---|
| 146 | });
|
---|
| 147 |
|
---|
| 148 | driver.messages.on('data', function(message) {
|
---|
| 149 | console.log('Got a message', message);
|
---|
| 150 | });
|
---|
| 151 | ```
|
---|
| 152 |
|
---|
| 153 | Client drivers have two additional properties for reading the HTTP data that was
|
---|
| 154 | sent back by the server:
|
---|
| 155 |
|
---|
| 156 | - `driver.statusCode` - the integer value of the HTTP status code
|
---|
| 157 | - `driver.headers` - an object containing the response headers
|
---|
| 158 |
|
---|
| 159 |
|
---|
| 160 | ### HTTP Proxies
|
---|
| 161 |
|
---|
| 162 | The client driver supports connections via HTTP proxies using the `CONNECT`
|
---|
| 163 | method. Instead of sending the WebSocket handshake immediately, it will send a
|
---|
| 164 | `CONNECT` request, wait for a `200` response, and then proceed as normal.
|
---|
| 165 |
|
---|
| 166 | To use this feature, call `driver.proxy(url)` where `url` is the origin of the
|
---|
| 167 | proxy, including a username and password if required. This produces a duplex
|
---|
| 168 | stream that you should pipe in and out of your TCP connection to the proxy
|
---|
| 169 | server. When the proxy emits `connect`, you can then pipe `driver.io` to your
|
---|
| 170 | TCP stream and call `driver.start()`.
|
---|
| 171 |
|
---|
| 172 | ```js
|
---|
| 173 | var net = require('net'),
|
---|
| 174 | websocket = require('websocket-driver');
|
---|
| 175 |
|
---|
| 176 | var driver = websocket.client('ws://www.example.com/socket'),
|
---|
| 177 | proxy = driver.proxy('http://username:password@proxy.example.com'),
|
---|
| 178 | tcp = net.connect(80, 'proxy.example.com');
|
---|
| 179 |
|
---|
| 180 | tcp.pipe(proxy).pipe(tcp, { end: false });
|
---|
| 181 |
|
---|
| 182 | tcp.on('connect', function() {
|
---|
| 183 | proxy.start();
|
---|
| 184 | });
|
---|
| 185 |
|
---|
| 186 | proxy.on('connect', function() {
|
---|
| 187 | driver.io.pipe(tcp).pipe(driver.io);
|
---|
| 188 | driver.start();
|
---|
| 189 | });
|
---|
| 190 |
|
---|
| 191 | driver.messages.on('data', function(message) {
|
---|
| 192 | console.log('Got a message', message);
|
---|
| 193 | });
|
---|
| 194 | ```
|
---|
| 195 |
|
---|
| 196 | The proxy's `connect` event is also where you should perform a TLS handshake on
|
---|
| 197 | your TCP stream, if you are connecting to a `wss:` endpoint.
|
---|
| 198 |
|
---|
| 199 | In the event that proxy connection fails, `proxy` will emit an `error`. You can
|
---|
| 200 | inspect the proxy's response via `proxy.statusCode` and `proxy.headers`.
|
---|
| 201 |
|
---|
| 202 | ```js
|
---|
| 203 | proxy.on('error', function(error) {
|
---|
| 204 | console.error(error.message);
|
---|
| 205 | console.log(proxy.statusCode);
|
---|
| 206 | console.log(proxy.headers);
|
---|
| 207 | });
|
---|
| 208 | ```
|
---|
| 209 |
|
---|
| 210 | Before calling `proxy.start()` you can set custom headers using
|
---|
| 211 | `proxy.setHeader()`:
|
---|
| 212 |
|
---|
| 213 | ```js
|
---|
| 214 | proxy.setHeader('User-Agent', 'node');
|
---|
| 215 | proxy.start();
|
---|
| 216 | ```
|
---|
| 217 |
|
---|
| 218 |
|
---|
| 219 | ### Driver API
|
---|
| 220 |
|
---|
| 221 | Drivers are created using one of the following methods:
|
---|
| 222 |
|
---|
| 223 | ```js
|
---|
| 224 | driver = websocket.http(request, options)
|
---|
| 225 | driver = websocket.server(options)
|
---|
| 226 | driver = websocket.client(url, options)
|
---|
| 227 | ```
|
---|
| 228 |
|
---|
| 229 | The `http` method returns a driver chosen using the headers from a Node HTTP
|
---|
| 230 | request object. The `server` method returns a driver that will parse an HTTP
|
---|
| 231 | request and then decide which driver to use for it using the `http` method. The
|
---|
| 232 | `client` method always returns a driver for the RFC version of the protocol with
|
---|
| 233 | masking enabled on outgoing frames.
|
---|
| 234 |
|
---|
| 235 | The `options` argument is optional, and is an object. It may contain the
|
---|
| 236 | following fields:
|
---|
| 237 |
|
---|
| 238 | - `maxLength` - the maximum allowed size of incoming message frames, in bytes.
|
---|
| 239 | The default value is `2^26 - 1`, or 1 byte short of 64 MiB.
|
---|
| 240 | - `protocols` - an array of strings representing acceptable subprotocols for use
|
---|
| 241 | over the socket. The driver will negotiate one of these to use via the
|
---|
| 242 | `Sec-WebSocket-Protocol` header if supported by the other peer.
|
---|
| 243 |
|
---|
| 244 | A driver has two duplex streams attached to it:
|
---|
| 245 |
|
---|
| 246 | - **`driver.io`** - this stream should be attached to an I/O socket like a TCP
|
---|
| 247 | stream. Pipe incoming TCP chunks to this stream for them to be parsed, and
|
---|
| 248 | pipe this stream back into TCP to send outgoing frames.
|
---|
| 249 | - **`driver.messages`** - this stream emits messages received over the
|
---|
| 250 | WebSocket. Writing to it sends messages to the other peer by emitting frames
|
---|
| 251 | via the `driver.io` stream.
|
---|
| 252 |
|
---|
| 253 | All drivers respond to the following API methods, but some of them are no-ops
|
---|
| 254 | depending on whether the client supports the behaviour.
|
---|
| 255 |
|
---|
| 256 | Note that most of these methods are commands: if they produce data that should
|
---|
| 257 | be sent over the socket, they will give this to you by emitting `data` events on
|
---|
| 258 | the `driver.io` stream.
|
---|
| 259 |
|
---|
| 260 | #### `driver.on('open', function(event) {})`
|
---|
| 261 |
|
---|
| 262 | Adds a callback to execute when the socket becomes open.
|
---|
| 263 |
|
---|
| 264 | #### `driver.on('message', function(event) {})`
|
---|
| 265 |
|
---|
| 266 | Adds a callback to execute when a message is received. `event` will have a
|
---|
| 267 | `data` attribute containing either a string in the case of a text message or a
|
---|
| 268 | `Buffer` in the case of a binary message.
|
---|
| 269 |
|
---|
| 270 | You can also listen for messages using the `driver.messages.on('data')` event,
|
---|
| 271 | which emits strings for text messages and buffers for binary messages.
|
---|
| 272 |
|
---|
| 273 | #### `driver.on('error', function(event) {})`
|
---|
| 274 |
|
---|
| 275 | Adds a callback to execute when a protocol error occurs due to the other peer
|
---|
| 276 | sending an invalid byte sequence. `event` will have a `message` attribute
|
---|
| 277 | describing the error.
|
---|
| 278 |
|
---|
| 279 | #### `driver.on('close', function(event) {})`
|
---|
| 280 |
|
---|
| 281 | Adds a callback to execute when the socket becomes closed. The `event` object
|
---|
| 282 | has `code` and `reason` attributes.
|
---|
| 283 |
|
---|
| 284 | #### `driver.on('ping', function(event) {})`
|
---|
| 285 |
|
---|
| 286 | Adds a callback block to execute when a ping is received. You do not need to
|
---|
| 287 | handle this by sending a pong frame yourself; the driver handles this for you.
|
---|
| 288 |
|
---|
| 289 | #### `driver.on('pong', function(event) {})`
|
---|
| 290 |
|
---|
| 291 | Adds a callback block to execute when a pong is received. If this was in
|
---|
| 292 | response to a ping you sent, you can also handle this event via the
|
---|
| 293 | `driver.ping(message, function() { ... })` callback.
|
---|
| 294 |
|
---|
| 295 | #### `driver.addExtension(extension)`
|
---|
| 296 |
|
---|
| 297 | Registers a protocol extension whose operation will be negotiated via the
|
---|
| 298 | `Sec-WebSocket-Extensions` header. `extension` is any extension compatible with
|
---|
| 299 | the [websocket-extensions](https://github.com/faye/websocket-extensions-node)
|
---|
| 300 | framework.
|
---|
| 301 |
|
---|
| 302 | #### `driver.setHeader(name, value)`
|
---|
| 303 |
|
---|
| 304 | Sets a custom header to be sent as part of the handshake response, either from
|
---|
| 305 | the server or from the client. Must be called before `start()`, since this is
|
---|
| 306 | when the headers are serialized and sent.
|
---|
| 307 |
|
---|
| 308 | #### `driver.start()`
|
---|
| 309 |
|
---|
| 310 | Initiates the protocol by sending the handshake - either the response for a
|
---|
| 311 | server-side driver or the request for a client-side one. This should be the
|
---|
| 312 | first method you invoke. Returns `true` if and only if a handshake was sent.
|
---|
| 313 |
|
---|
| 314 | #### `driver.parse(string)`
|
---|
| 315 |
|
---|
| 316 | Takes a string and parses it, potentially resulting in message events being
|
---|
| 317 | emitted (see `on('message')` above) or in data being sent to `driver.io`. You
|
---|
| 318 | should send all data you receive via I/O to this method by piping a stream into
|
---|
| 319 | `driver.io`.
|
---|
| 320 |
|
---|
| 321 | #### `driver.text(string)`
|
---|
| 322 |
|
---|
| 323 | Sends a text message over the socket. If the socket handshake is not yet
|
---|
| 324 | complete, the message will be queued until it is. Returns `true` if the message
|
---|
| 325 | was sent or queued, and `false` if the socket can no longer send messages.
|
---|
| 326 |
|
---|
| 327 | This method is equivalent to `driver.messages.write(string)`.
|
---|
| 328 |
|
---|
| 329 | #### `driver.binary(buffer)`
|
---|
| 330 |
|
---|
| 331 | Takes a `Buffer` and sends it as a binary message. Will queue and return `true`
|
---|
| 332 | or `false` the same way as the `text` method. It will also return `false` if the
|
---|
| 333 | driver does not support binary messages.
|
---|
| 334 |
|
---|
| 335 | This method is equivalent to `driver.messages.write(buffer)`.
|
---|
| 336 |
|
---|
| 337 | #### `driver.ping(string = '', function() {})`
|
---|
| 338 |
|
---|
| 339 | Sends a ping frame over the socket, queueing it if necessary. `string` and the
|
---|
| 340 | callback are both optional. If a callback is given, it will be invoked when the
|
---|
| 341 | socket receives a pong frame whose content matches `string`. Returns `false` if
|
---|
| 342 | frames can no longer be sent, or if the driver does not support ping/pong.
|
---|
| 343 |
|
---|
| 344 | #### `driver.pong(string = '')`
|
---|
| 345 |
|
---|
| 346 | Sends a pong frame over the socket, queueing it if necessary. `string` is
|
---|
| 347 | optional. Returns `false` if frames can no longer be sent, or if the driver does
|
---|
| 348 | not support ping/pong.
|
---|
| 349 |
|
---|
| 350 | You don't need to call this when a ping frame is received; pings are replied to
|
---|
| 351 | automatically by the driver. This method is for sending unsolicited pongs.
|
---|
| 352 |
|
---|
| 353 | #### `driver.close()`
|
---|
| 354 |
|
---|
| 355 | Initiates the closing handshake if the socket is still open. For drivers with no
|
---|
| 356 | closing handshake, this will result in the immediate execution of the
|
---|
| 357 | `on('close')` driver. For drivers with a closing handshake, this sends a closing
|
---|
| 358 | frame and `emit('close')` will execute when a response is received or a protocol
|
---|
| 359 | error occurs.
|
---|
| 360 |
|
---|
| 361 | #### `driver.version`
|
---|
| 362 |
|
---|
| 363 | Returns the WebSocket version in use as a string. Will either be `hixie-75`,
|
---|
| 364 | `hixie-76` or `hybi-$version`.
|
---|
| 365 |
|
---|
| 366 | #### `driver.protocol`
|
---|
| 367 |
|
---|
| 368 | Returns a string containing the selected subprotocol, if any was agreed upon
|
---|
| 369 | using the `Sec-WebSocket-Protocol` mechanism. This value becomes available after
|
---|
| 370 | `emit('open')` has fired.
|
---|