308 lines
11 KiB
Markdown
308 lines
11 KiB
Markdown
# websocket-driver [](https://travis-ci.org/faye/websocket-driver-node)
|
|
|
|
This module provides a complete implementation of the WebSocket protocols that
|
|
can be hooked up to any I/O stream. It aims to simplify things by decoupling
|
|
the protocol details from the I/O layer, such that users only need to implement
|
|
code to stream data in and out of it without needing to know anything about how
|
|
the protocol actually works. Think of it as a complete WebSocket system with
|
|
pluggable I/O.
|
|
|
|
Due to this design, you get a lot of things for free. In particular, if you
|
|
hook this module up to some I/O object, it will do all of this for you:
|
|
|
|
* Select the correct server-side driver to talk to the client
|
|
* Generate and send both server- and client-side handshakes
|
|
* Recognize when the handshake phase completes and the WS protocol begins
|
|
* Negotiate subprotocol selection based on `Sec-WebSocket-Protocol`
|
|
* Buffer sent messages until the handshake process is finished
|
|
* Deal with proxies that defer delivery of the draft-76 handshake body
|
|
* Notify you when the socket is open and closed and when messages arrive
|
|
* Recombine fragmented messages
|
|
* Dispatch text, binary, ping and close frames
|
|
* Manage the socket-closing handshake process
|
|
* Automatically reply to ping frames with a matching pong
|
|
* Apply masking to messages sent by the client
|
|
|
|
This library was originally extracted from the [Faye](http://faye.jcoglan.com)
|
|
project but now aims to provide simple WebSocket support for any Node-based
|
|
project.
|
|
|
|
|
|
## Installation
|
|
|
|
```
|
|
$ npm install websocket-driver
|
|
```
|
|
|
|
|
|
## Usage
|
|
|
|
This module provides protocol drivers that have the same interface on the
|
|
server and on the client. A WebSocket driver is an object with two duplex
|
|
streams attached; one for incoming/outgoing messages and one for managing the
|
|
wire protocol over an I/O stream. The full API is described below.
|
|
|
|
|
|
### Server-side with HTTP
|
|
|
|
A Node webserver emits a special event for 'upgrade' requests, and this is
|
|
where you should handle WebSockets. You first check whether the request is a
|
|
WebSocket, and if so you can create a driver and attach the request's I/O
|
|
stream to it.
|
|
|
|
```js
|
|
var http = require('http'),
|
|
websocket = require('websocket-driver');
|
|
|
|
var server = http.createServer();
|
|
|
|
server.on('upgrade', function(request, socket, body) {
|
|
if (!websocket.isWebSocket(request)) return;
|
|
|
|
var driver = websocket.http(request);
|
|
|
|
driver.io.write(body);
|
|
socket.pipe(driver.io).pipe(socket);
|
|
|
|
driver.messages.on('data', function(message) {
|
|
console.log('Got a message', message);
|
|
});
|
|
|
|
driver.start();
|
|
});
|
|
```
|
|
|
|
Note the line `driver.io.write(body)` - you must pass the `body` buffer to the
|
|
socket driver in order to make certain versions of the protocol work.
|
|
|
|
|
|
### Server-side with TCP
|
|
|
|
You can also handle WebSocket connections in a bare TCP server, if you're not
|
|
using an HTTP server and don't want to implement HTTP parsing yourself.
|
|
|
|
The driver will emit a `connect` event when a request is received, and at this
|
|
point you can detect whether it's a WebSocket and handle it as such. Here's an
|
|
example using the Node `net` module:
|
|
|
|
```js
|
|
var net = require('net'),
|
|
websocket = require('websocket-driver');
|
|
|
|
var server = net.createServer(function(connection) {
|
|
var driver = websocket.server();
|
|
|
|
driver.on('connect', function() {
|
|
if (websocket.isWebSocket(driver)) {
|
|
driver.start();
|
|
} else {
|
|
// handle other HTTP requests
|
|
}
|
|
});
|
|
|
|
driver.on('close', function() { connection.end() });
|
|
connection.on('error', function() {});
|
|
|
|
connection.pipe(driver.io).pipe(connection);
|
|
|
|
driver.messages.pipe(driver.messages);
|
|
});
|
|
|
|
server.listen(4180);
|
|
```
|
|
|
|
In the `connect` event, the driver gains several properties to describe the
|
|
request, similar to a Node request object, such as `method`, `url` and
|
|
`headers`. However you should remember it's not a real request object; you
|
|
cannot write data to it, it only tells you what request data we parsed from the
|
|
input.
|
|
|
|
If the request has a body, it will be in the `driver.body` buffer, but only as
|
|
much of the body as has been piped into the driver when the `connect` event
|
|
fires.
|
|
|
|
|
|
### Client-side
|
|
|
|
Similarly, to implement a WebSocket client you just need to make a driver by
|
|
passing in a URL. After this you use the driver API as described below to
|
|
process incoming data and send outgoing data.
|
|
|
|
|
|
```js
|
|
var net = require('net'),
|
|
websocket = require('websocket-driver');
|
|
|
|
var driver = websocket.client('ws://www.example.com/socket'),
|
|
tcp = net.createConnection(80, 'www.example.com');
|
|
|
|
tcp.pipe(driver.io).pipe(tcp);
|
|
|
|
driver.messages.on('data', function(message) {
|
|
console.log('Got a message', message);
|
|
});
|
|
|
|
tcp.on('connect', function() {
|
|
driver.start();
|
|
});
|
|
```
|
|
|
|
Client drivers have two additional properties for reading the HTTP data that
|
|
was sent back by the server:
|
|
|
|
* `driver.statusCode` - the integer value of the HTTP status code
|
|
* `driver.headers` - an object containing the response headers
|
|
|
|
|
|
### Driver API
|
|
|
|
Drivers are created using one of the following methods:
|
|
|
|
```js
|
|
driver = websocket.http(request, options)
|
|
driver = websocket.server(options)
|
|
driver = websocket.client(url, options)
|
|
```
|
|
|
|
The `http` method returns a driver chosen using the headers from a Node HTTP
|
|
request object. The `server` method returns a driver that will parse an HTTP
|
|
request and then decide which driver to use for it using the `http` method. The
|
|
`client` method always returns a driver for the RFC version of the protocol
|
|
with masking enabled on outgoing frames.
|
|
|
|
The `options` argument is optional, and is an object. It may contain the
|
|
following fields:
|
|
|
|
* `maxLength` - the maximum allowed size of incoming message frames, in bytes.
|
|
The default value is `2^26 - 1`, or 1 byte short of 64 MiB.
|
|
* `protocols` - an array of strings representing acceptable subprotocols for
|
|
use over the socket. The driver will negotiate one of these to use via the
|
|
`Sec-WebSocket-Protocol` header if supported by the other peer.
|
|
|
|
A driver has two duplex streams attached to it:
|
|
|
|
* <b>`driver.io`</b> - this stream should be attached to an I/O socket like a
|
|
TCP stream. Pipe incoming TCP chunks to this stream for them to be parsed,
|
|
and pipe this stream back into TCP to send outgoing frames.
|
|
* <b>`driver.messages`</b> - this stream emits messages received over the
|
|
WebSocket. Writing to it sends messages to the other peer by emitting frames
|
|
via the `driver.io` stream.
|
|
|
|
All drivers respond to the following API methods, but some of them are no-ops
|
|
depending on whether the client supports the behaviour.
|
|
|
|
Note that most of these methods are commands: if they produce data that should
|
|
be sent over the socket, they will give this to you by emitting `data` events
|
|
on the `driver.io` stream.
|
|
|
|
#### `driver.on('open', function(event) {})`
|
|
|
|
Sets the callback to execute when the socket becomes open.
|
|
|
|
#### `driver.on('message', function(event) {})`
|
|
|
|
Sets the callback to execute when a message is received. `event` will have a
|
|
`data` attribute containing either a string in the case of a text message or a
|
|
`Buffer` in the case of a binary message.
|
|
|
|
You can also listen for messages using the `driver.messages.on('data')` event,
|
|
which emits strings for text messages and buffers for binary messages.
|
|
|
|
#### `driver.on('error', function(event) {})`
|
|
|
|
Sets the callback to execute when a protocol error occurs due to the other peer
|
|
sending an invalid byte sequence. `event` will have a `message` attribute
|
|
describing the error.
|
|
|
|
#### `driver.on('close', function(event) {})`
|
|
|
|
Sets the callback to execute when the socket becomes closed. The `event` object
|
|
has `code` and `reason` attributes.
|
|
|
|
#### `driver.setHeader(name, value)`
|
|
|
|
Sets a custom header to be sent as part of the handshake response, either from
|
|
the server or from the client. Must be called before `start()`, since this is
|
|
when the headers are serialized and sent.
|
|
|
|
#### `driver.start()`
|
|
|
|
Initiates the protocol by sending the handshake - either the response for a
|
|
server-side driver or the request for a client-side one. This should be the
|
|
first method you invoke. Returns `true` iff a handshake was sent.
|
|
|
|
#### `driver.parse(string)`
|
|
|
|
Takes a string and parses it, potentially resulting in message events being
|
|
emitted (see `on('message')` above) or in data being sent to `driver.io`. You
|
|
should send all data you receive via I/O to this method by piping a stream into
|
|
`driver.io`.
|
|
|
|
#### `driver.text(string)`
|
|
|
|
Sends a text message over the socket. If the socket handshake is not yet
|
|
complete, the message will be queued until it is. Returns `true` if the message
|
|
was sent or queued, and `false` if the socket can no longer send messages.
|
|
|
|
This method is equivalent to `driver.messages.write(string)`.
|
|
|
|
#### `driver.binary(buffer)`
|
|
|
|
Takes a `Buffer` and sends it as a binary message. Will queue and return `true`
|
|
or `false` the same way as the `text` method. It will also return `false` if
|
|
the driver does not support binary messages.
|
|
|
|
This method is equivalent to `driver.messages.write(buffer)`.
|
|
|
|
#### `driver.ping(string = '', function() {})`
|
|
|
|
Sends a ping frame over the socket, queueing it if necessary. `string` and the
|
|
callback are both optional. If a callback is given, it will be invoked when the
|
|
socket receives a pong frame whose content matches `string`. Returns `false` if
|
|
frames can no longer be sent, or if the driver does not support ping/pong.
|
|
|
|
#### `driver.close()`
|
|
|
|
Initiates the closing handshake if the socket is still open. For drivers with
|
|
no closing handshake, this will result in the immediate execution of the
|
|
`on('close')` driver. For drivers with a closing handshake, this sends a
|
|
closing frame and `emit('close')` will execute when a response is received or a
|
|
protocol error occurs.
|
|
|
|
#### `driver.version`
|
|
|
|
Returns the WebSocket version in use as a string. Will either be `hixie-75`,
|
|
`hixie-76` or `hybi-$version`.
|
|
|
|
#### `driver.protocol`
|
|
|
|
Returns a string containing the selected subprotocol, if any was agreed upon
|
|
using the `Sec-WebSocket-Protocol` mechanism. This value becomes available
|
|
after `emit('open')` has fired.
|
|
|
|
|
|
## License
|
|
|
|
(The MIT License)
|
|
|
|
Copyright (c) 2010-2014 James Coglan
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
this software and associated documentation files (the 'Software'), to deal in
|
|
the Software without restriction, including without limitation the rights to
|
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
of the Software, and to permit persons to whom the Software is furnished to do
|
|
so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
|