|
|
# websocket-driver [](https://travis-ci.org/faye/websocket-driver-node)
This module provides a complete implementation of the WebSocket protocols thatcan be hooked up to any I/O stream. It aims to simplify things by decoupling theprotocol details from the I/O layer, such that users only need to implement codeto stream data in and out of it without needing to know anything about how theprotocol actually works. Think of it as a complete WebSocket system withpluggable I/O.
Due to this design, you get a lot of things for free. In particular, if you hookthis 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`- Negotiate and use extensions via the [websocket-extensions](https://github.com/faye/websocket-extensions-node) module- 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, pong 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-basedproject.
## Installation
```$ npm install websocket-driver```
## Usage
This module provides protocol drivers that have the same interface on the serverand on the client. A WebSocket driver is an object with two duplex streamsattached; one for incoming/outgoing messages and one for managing the wireprotocol 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 whereyou should handle WebSockets. You first check whether the request is aWebSocket, and if so you can create a driver and attach the request's I/O streamto it.
```jsvar 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 thesocket 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 notusing 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 thispoint you can detect whether it's a WebSocket and handle it as such. Here's anexample using the Node `net` module:
```jsvar 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 therequest, similar to a Node request object, such as `method`, `url` and`headers`. However you should remember it's not a real request object; youcannot write data to it, it only tells you what request data we parsed from theinput.
If the request has a body, it will be in the `driver.body` buffer, but only asmuch of the body as has been piped into the driver when the `connect` eventfires.
### Client-side
Similarly, to implement a WebSocket client you just need to make a driver bypassing in a URL. After this you use the driver API as described below toprocess incoming data and send outgoing data.
```jsvar net = require('net'), websocket = require('websocket-driver');
var driver = websocket.client('ws://www.example.com/socket'), tcp = net.connect(80, 'www.example.com');
tcp.pipe(driver.io).pipe(tcp);
tcp.on('connect', function() { driver.start();});
driver.messages.on('data', function(message) { console.log('Got a message', message);});```
Client drivers have two additional properties for reading the HTTP data that wassent back by the server:
- `driver.statusCode` - the integer value of the HTTP status code- `driver.headers` - an object containing the response headers
### HTTP Proxies
The client driver supports connections via HTTP proxies using the `CONNECT`method. Instead of sending the WebSocket handshake immediately, it will send a`CONNECT` request, wait for a `200` response, and then proceed as normal.
To use this feature, call `driver.proxy(url)` where `url` is the origin of theproxy, including a username and password if required. This produces a duplexstream that you should pipe in and out of your TCP connection to the proxyserver. When the proxy emits `connect`, you can then pipe `driver.io` to yourTCP stream and call `driver.start()`.
```jsvar net = require('net'), websocket = require('websocket-driver');
var driver = websocket.client('ws://www.example.com/socket'), proxy = driver.proxy('http://username:password@proxy.example.com'), tcp = net.connect(80, 'proxy.example.com');
tcp.pipe(proxy).pipe(tcp, { end: false });
tcp.on('connect', function() { proxy.start();});
proxy.on('connect', function() { driver.io.pipe(tcp).pipe(driver.io); driver.start();});
driver.messages.on('data', function(message) { console.log('Got a message', message);});```
The proxy's `connect` event is also where you should perform a TLS handshake onyour TCP stream, if you are connecting to a `wss:` endpoint.
In the event that proxy connection fails, `proxy` will emit an `error`. You caninspect the proxy's response via `proxy.statusCode` and `proxy.headers`.
```jsproxy.on('error', function(error) { console.error(error.message); console.log(proxy.statusCode); console.log(proxy.headers);});```
Before calling `proxy.start()` you can set custom headers using`proxy.setHeader()`:
```jsproxy.setHeader('User-Agent', 'node');proxy.start();```
### Driver API
Drivers are created using one of the following methods:
```jsdriver = 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 HTTPrequest object. The `server` method returns a driver that will parse an HTTPrequest 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 withmasking enabled on outgoing frames.
The `options` argument is optional, and is an object. It may contain thefollowing 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:
- **`driver.io`** - 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.- **`driver.messages`** - 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-opsdepending on whether the client supports the behaviour.
Note that most of these methods are commands: if they produce data that shouldbe sent over the socket, they will give this to you by emitting `data` events onthe `driver.io` stream.
#### `driver.on('open', function(event) {})`
Adds a callback to execute when the socket becomes open.
#### `driver.on('message', function(event) {})`
Adds a 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) {})`
Adds a callback to execute when a protocol error occurs due to the other peersending an invalid byte sequence. `event` will have a `message` attributedescribing the error.
#### `driver.on('close', function(event) {})`
Adds a callback to execute when the socket becomes closed. The `event` objecthas `code` and `reason` attributes.
#### `driver.on('ping', function(event) {})`
Adds a callback block to execute when a ping is received. You do not need tohandle this by sending a pong frame yourself; the driver handles this for you.
#### `driver.on('pong', function(event) {})`
Adds a callback block to execute when a pong is received. If this was inresponse to a ping you sent, you can also handle this event via the`driver.ping(message, function() { ... })` callback.
#### `driver.addExtension(extension)`
Registers a protocol extension whose operation will be negotiated via the`Sec-WebSocket-Extensions` header. `extension` is any extension compatible withthe [websocket-extensions](https://github.com/faye/websocket-extensions-node)framework.
#### `driver.setHeader(name, value)`
Sets a custom header to be sent as part of the handshake response, either fromthe server or from the client. Must be called before `start()`, since this iswhen the headers are serialized and sent.
#### `driver.start()`
Initiates the protocol by sending the handshake - either the response for aserver-side driver or the request for a client-side one. This should be thefirst method you invoke. Returns `true` if and only if a handshake was sent.
#### `driver.parse(string)`
Takes a string and parses it, potentially resulting in message events beingemitted (see `on('message')` above) or in data being sent to `driver.io`. Youshould 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 yetcomplete, the message will be queued until it is. Returns `true` if the messagewas 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 thedriver 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 thecallback are both optional. If a callback is given, it will be invoked when thesocket receives a pong frame whose content matches `string`. Returns `false` ifframes can no longer be sent, or if the driver does not support ping/pong.
#### `driver.pong(string = '')`
Sends a pong frame over the socket, queueing it if necessary. `string` isoptional. Returns `false` if frames can no longer be sent, or if the driver doesnot support ping/pong.
You don't need to call this when a ping frame is received; pings are replied toautomatically by the driver. This method is for sending unsolicited pongs.
#### `driver.close()`
Initiates the closing handshake if the socket is still open. For drivers with noclosing handshake, this will result in the immediate execution of the`on('close')` driver. For drivers with a closing handshake, this sends a closingframe and `emit('close')` will execute when a response is received or a protocolerror 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 uponusing the `Sec-WebSocket-Protocol` mechanism. This value becomes available after`emit('open')` has fired.
|