Projekt

Obecné

Profil

Stáhnout (12.8 KB) Statistiky
| Větev: | Revize:
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.
(3-3/4)