UNPKG

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