Compare commits

...

7 Commits

9 changed files with 127 additions and 85 deletions
+2 -1
View File
@@ -4,7 +4,8 @@ node_js:
- "0.6"
- "0.8"
- "0.10"
- "0.11"
- "0.12"
- "iojs"
before_install:
- '[ "${TRAVIS_NODE_VERSION}" = "0.6" ] && npm conf set strict-ssl false || true'
+6
View File
@@ -1,3 +1,9 @@
### 0.5.2 / 2015-02-19
* Fix compatibility with the HTTP parser on io.js
* Use `websocket-extensions` to make sure messages and close frames are kept in order
* Don't emit multiple `error` events
### 0.5.1 / 2014-12-18
* Don't allow drivers to be created with unrecognized options
+42 -43
View File
@@ -1,14 +1,14 @@
# websocket-driver [![Build Status](https://travis-ci.org/faye/websocket-driver-node.svg)](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
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:
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
@@ -40,18 +40,18 @@ $ 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.
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.
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'),
@@ -150,8 +150,8 @@ driver.messages.on('data', function(message) {
});
```
Client drivers have two additional properties for reading the HTTP data that
was sent back by the server:
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
@@ -229,23 +229,23 @@ 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.
`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
* `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.
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.
@@ -254,8 +254,8 @@ 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.
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) {})`
@@ -318,8 +318,8 @@ 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.
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)`.
@@ -332,11 +332,11 @@ 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.
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`
@@ -346,30 +346,29 @@ Returns the WebSocket version in use as a string. Will either be `hixie-75`,
#### `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.
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
Copyright (c) 2010-2015 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:
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.
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.
+13 -6
View File
@@ -153,14 +153,17 @@ var instance = {
},
text: function(message) {
if (this.readyState > 1) return false;
return this.frame(message, 'text');
},
binary: function(message) {
if (this.readyState > 1) return false;
return this.frame(message, 'binary');
},
ping: function(message, callback) {
if (this.readyState > 1) return false;
message = message || '';
if (callback) this._pingCallbacks[message] = callback;
return this.frame(message, 'ping');
@@ -175,8 +178,8 @@ var instance = {
this.emit('close', new Base.CloseEvent(code, reason));
return true;
} else if (this.readyState === 1) {
this.frame(reason, 'close', code);
this.readyState = 2;
this._extensions.close(function() { this.frame(reason, 'close', code) }, this);
return true;
} else {
return false;
@@ -185,7 +188,7 @@ var instance = {
frame: function(data, type, code) {
if (this.readyState <= 0) return this._queue([data, type, code]);
if (this.readyState !== 1) return false;
if (this.readyState > 2) return false;
if (data instanceof Array) data = new Buffer(data);
@@ -288,16 +291,20 @@ var instance = {
},
_shutdown: function(code, reason) {
this.frame(reason, 'close', code);
delete this._frame;
delete this._message;
this.readyState = 3;
this.readyState = 2;
this._stage = 5;
this.emit('close', new Base.CloseEvent(code, reason));
this._extensions.close();
this._extensions.close(function() {
this.frame(reason, 'close', code);
this.readyState = 3;
this.emit('close', new Base.CloseEvent(code, reason));
}, this);
},
_fail: function(type, message) {
if (this.readyState > 1) return;
this.emit('error', new Error(message));
this._shutdown(this.ERRORS[type], message);
},
+39 -20
View File
@@ -3,7 +3,6 @@
var StreamReader = function() {
this._queue = [];
this._queueSize = 0;
this._cursor = 0;
};
StreamReader.prototype.put = function(buffer) {
@@ -15,29 +14,49 @@ StreamReader.prototype.put = function(buffer) {
StreamReader.prototype.read = function(length) {
if (length > this._queueSize) return null;
if (length === 0) return new Buffer(0);
var queue = this._queue,
first = queue[0],
buffer;
if (first.length >= length) {
this._queueSize -= length;
if (first.length === length) {
return queue.shift();
} else {
buffer = first.slice(0, length);
queue[0] = first.slice(length);
return buffer;
}
}
var remain = length, buffers;
for (var i=0, n = queue.length; i < n; i++) {
if (remain < queue[i].length) break;
remain -= queue[i].length;
}
buffers = queue.splice(0, i);
if (remain > 0 && queue.length > 0) {
buffers.push(queue[0].slice(0, remain));
queue[0] = queue[0].slice(remain);
}
this._queueSize -= length;
return this._concat(buffers, length);
};
StreamReader.prototype._concat = function(buffers, length) {
if (Buffer.concat) return Buffer.concat(buffers, length);
var buffer = new Buffer(length),
queue = this._queue,
remain = length,
n = queue.length,
i = 0,
chunk, size;
offset = 0;
while (remain > 0 && i < n) {
chunk = queue[i];
size = Math.min(remain, chunk.length - this._cursor);
chunk.copy(buffer, length - remain, this._cursor, this._cursor + size);
remain -= size;
this._queueSize -= size;
this._cursor = (this._cursor + size) % chunk.length;
i += 1;
for (var i = 0, n = buffers.length; i < n; i++) {
buffers[i].copy(buffer, offset);
offset += buffers[i].length;
}
queue.splice(0, this._cursor === 0 ? i : i - 1);
return buffer;
};
+19 -10
View File
@@ -1,13 +1,13 @@
'use strict';
var HTTPParser = process.binding('http_parser').HTTPParser,
version = HTTPParser.RESPONSE ? 6 : 4;
var NodeHTTPParser = process.binding('http_parser').HTTPParser,
version = NodeHTTPParser.RESPONSE ? 6 : 4;
var HttpParser = function(type) {
if (type === 'request')
this._parser = new HTTPParser(HTTPParser.REQUEST || 'request');
this._parser = new NodeHTTPParser(NodeHTTPParser.REQUEST || 'request');
else
this._parser = new HTTPParser(HTTPParser.RESPONSE || 'response');
this._parser = new NodeHTTPParser(NodeHTTPParser.RESPONSE || 'response');
this._type = type;
this._complete = false;
@@ -29,15 +29,24 @@ var HttpParser = function(type) {
self.headers[current] = value;
};
this._parser.onHeadersComplete = this._parser[HTTPParser.kOnHeadersComplete] = function(info) {
self.method = (typeof info.method === 'number') ? HttpParser.METHODS[info.method] : info.method;
self.statusCode = info.statusCode;
self.url = info.url;
this._parser.onHeadersComplete = this._parser[NodeHTTPParser.kOnHeadersComplete] =
function(majorVersion, minorVersion, headers, method, pathname, statusCode) {
var info = arguments[0];
if (typeof info === 'object') {
method = info.method;
pathname = info.url;
statusCode = info.statusCode;
headers = info.headers;
}
self.method = (typeof method === 'number') ? HttpParser.METHODS[method] : method;
self.statusCode = statusCode;
self.url = pathname;
var headers = info.headers, key, value;
if (!headers) return;
for (var i = 0, n = headers.length; i < n; i += 2) {
for (var i = 0, n = headers.length, key, value; i < n; i += 2) {
key = headers[i].toLowerCase();
value = headers[i+1];
if (self.headers.hasOwnProperty(key))
+2 -1
View File
@@ -126,7 +126,8 @@ Messages.prototype.resume = function() {
// the source whether to back off.
Messages.prototype.write = function(message) {
if (!this.writable) return false;
this._driver.frame(message);
if (typeof message === 'string') this._driver.text(message);
else this._driver.binary(message);
return !this._paused;
};
+2 -2
View File
@@ -5,10 +5,10 @@
, "keywords" : ["websocket"]
, "license" : "MIT"
, "version" : "0.5.1"
, "version" : "0.5.2"
, "engines" : {"node": ">=0.6.0"}
, "main" : "./lib/websocket/driver"
, "dependencies" : {"websocket-extensions": ">=0.1.0"}
, "dependencies" : {"websocket-extensions": ">=0.1.1"}
, "devDependencies" : {"jstest": "", "permessage-deflate": ""}
, "scripts" : {"test": "jstest spec/runner.js"}
+2 -2
View File
@@ -451,8 +451,8 @@ test.describe("Hybi", function() { with(this) {
driver().frame("dropped")
}})
it("returns false", function() { with(this) {
assertEqual( false, driver().frame("wut") )
it("returns true", function() { with(this) {
assertEqual( true, driver().frame("wut") )
}})
}})