Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 569ec1d62e | |||
| 8128bec1e0 | |||
| 7be31c44e6 | |||
| 09476b8ed8 | |||
| d3396ace77 | |||
| 1967d6bbb8 | |||
| 1325828a9e | |||
| b82b7fa39a | |||
| c5c97bb30b | |||
| 9a16e7aa27 | |||
| ed1ce79915 | |||
| a1f70fb7f7 | |||
| 751c77aa1d | |||
| b76f4e27d0 | |||
| c4494ff88a | |||
| cfb2a86838 | |||
| 6a92cacf62 |
@@ -1,6 +0,0 @@
|
||||
.git
|
||||
.gitignore
|
||||
.npmignore
|
||||
.travis.yml
|
||||
node_modules
|
||||
spec
|
||||
+4
-5
@@ -2,15 +2,14 @@ sudo: false
|
||||
language: node_js
|
||||
|
||||
node_js:
|
||||
- "0.6"
|
||||
- "0.8"
|
||||
- "0.10"
|
||||
- "0.12"
|
||||
- "iojs-1"
|
||||
- "iojs-2"
|
||||
- "iojs-3"
|
||||
- "4"
|
||||
- "5"
|
||||
- "6"
|
||||
- "7"
|
||||
- "8"
|
||||
|
||||
before_install:
|
||||
- '[ "${TRAVIS_NODE_VERSION}" = "0.6" ] && npm conf set strict-ssl false || true'
|
||||
- '[ "${TRAVIS_NODE_VERSION}" = "0.8" ] && npm install -g npm@~1.4.0 || true'
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
### 0.7.0 / 2017-09-11
|
||||
|
||||
* Add `ping` and `pong` to the set of events users can listen to
|
||||
* Replace the bindings to Node's HTTP parser with `http-parser-js`
|
||||
|
||||
### 0.6.5 / 2016-05-20
|
||||
|
||||
* Don't mutate buffers passed in by the application when masking
|
||||
|
||||
### 0.6.4 / 2016-01-07
|
||||
|
||||
* If a number is given as input for a frame payload, send it as a string
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
# The MIT License
|
||||
|
||||
Copyright (c) 2010-2017 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.
|
||||
@@ -281,6 +281,17 @@ describing the error.
|
||||
Adds a callback to execute when the socket becomes closed. The `event` object
|
||||
has `code` and `reason` attributes.
|
||||
|
||||
#### `driver.on('ping', function(event) {})`
|
||||
|
||||
Adds a callback block to execute when a ping is received. You do not need to
|
||||
handle 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 in
|
||||
response 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
|
||||
@@ -357,27 +368,3 @@ Returns the WebSocket version in use as a string. Will either be `hixie-75`,
|
||||
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-2016 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.
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
var net = require('net'),
|
||||
url = require('url'),
|
||||
websocket = require('..'),
|
||||
deflate = require('permessage-deflate');
|
||||
|
||||
var DEFAULT_PORTS = {'ws:': 80, 'wss:': 443};
|
||||
|
||||
var uri = url.parse(process.argv[2]),
|
||||
port = uri.port || DEFAULT_PORTS[uri.protocol],
|
||||
conn = net.connect({host: uri.hostname, port: port});
|
||||
|
||||
var driver = websocket.client(uri.href);
|
||||
driver.addExtension(deflate);
|
||||
|
||||
driver.on('open', function() {
|
||||
driver.text('Hello, world');
|
||||
});
|
||||
|
||||
driver.on('message', function(event) {
|
||||
console.log(['message', event.data]);
|
||||
});
|
||||
|
||||
driver.on('close', function(event) {
|
||||
console.log(['close', event.code, event.reason]);
|
||||
conn.end();
|
||||
});
|
||||
|
||||
conn.pipe(driver.io);
|
||||
driver.io.pipe(conn);
|
||||
|
||||
driver.start();
|
||||
@@ -1,3 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
var net = require('net'),
|
||||
websocket = require('..'),
|
||||
deflate = require('permessage-deflate');
|
||||
+1
-12
@@ -27,19 +27,8 @@ var Driver = {
|
||||
return Server.http.apply(Server, arguments);
|
||||
},
|
||||
|
||||
isSecureRequest: function(request) {
|
||||
return Server.isSecureRequest(request);
|
||||
},
|
||||
|
||||
isWebSocket: function(request) {
|
||||
if (request.method !== 'GET') return false;
|
||||
|
||||
var connection = request.headers.connection || '',
|
||||
upgrade = request.headers.upgrade || '';
|
||||
|
||||
return request.method === 'GET' &&
|
||||
connection.toLowerCase().split(/ *, */).indexOf('upgrade') >= 0 &&
|
||||
upgrade.toLowerCase() === 'websocket';
|
||||
return Server.isWebSocket(request);
|
||||
},
|
||||
|
||||
validateOptions: function(options, validKeys) {
|
||||
|
||||
@@ -144,4 +144,12 @@ Base.MessageEvent = function(data) {
|
||||
this.data = data;
|
||||
};
|
||||
|
||||
Base.PingEvent = function(data) {
|
||||
this.data = data;
|
||||
};
|
||||
|
||||
Base.PongEvent = function(data) {
|
||||
this.data = data;
|
||||
};
|
||||
|
||||
module.exports = Base;
|
||||
|
||||
@@ -93,6 +93,9 @@ var instance = {
|
||||
this.statusCode = this._http.statusCode;
|
||||
this.headers = this._http.headers;
|
||||
|
||||
if (this._http.error)
|
||||
return this._failHandshake(this._http.error.message);
|
||||
|
||||
if (this._http.statusCode !== 101)
|
||||
return this._failHandshake('Unexpected response code: ' + this._http.statusCode);
|
||||
|
||||
|
||||
@@ -264,11 +264,11 @@ var instance = {
|
||||
buffer.writeUInt32BE(length % 0x100000000, 6);
|
||||
}
|
||||
|
||||
frame.payload.copy(buffer, offset);
|
||||
|
||||
if (frame.masked) {
|
||||
frame.maskingKey.copy(buffer, header);
|
||||
Hybi.mask(frame.payload, frame.maskingKey).copy(buffer, offset);
|
||||
} else {
|
||||
frame.payload.copy(buffer, offset);
|
||||
Hybi.mask(buffer, frame.maskingKey, offset);
|
||||
}
|
||||
|
||||
this._write(buffer);
|
||||
@@ -421,6 +421,7 @@ var instance = {
|
||||
|
||||
if (opcode === this.OPCODES.ping) {
|
||||
this.frame(payload, 'pong');
|
||||
this.emit('ping', new Base.PingEvent(payload.toString()))
|
||||
}
|
||||
|
||||
if (opcode === this.OPCODES.pong) {
|
||||
@@ -430,6 +431,8 @@ var instance = {
|
||||
|
||||
delete callbacks[message];
|
||||
if (callback) callback()
|
||||
|
||||
this.emit('pong', new Base.PongEvent(payload.toString()))
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -71,6 +71,54 @@ var instance = {
|
||||
for (var key in instance)
|
||||
Server.prototype[key] = instance[key];
|
||||
|
||||
Server.http = function(request, options) {
|
||||
options = options || {};
|
||||
if (options.requireMasking === undefined) options.requireMasking = true;
|
||||
|
||||
var klass = this.getDriverClass(request),
|
||||
url = this.determineUrl(request);
|
||||
|
||||
return klass && new klass(request, url, options);
|
||||
};
|
||||
|
||||
Server.isWebSocket = function(request) {
|
||||
var klass = this.getDriverClass(request);
|
||||
return !!klass;
|
||||
};
|
||||
|
||||
Server.getDriverClass = function(request) {
|
||||
var headers = request.headers;
|
||||
|
||||
var connection = headers['connection'] || '',
|
||||
key = headers['sec-websocket-key'],
|
||||
key1 = headers['sec-websocket-key1'],
|
||||
key2 = headers['sec-websocket-key2'],
|
||||
origin = headers['origin'],
|
||||
upgrade = headers['upgrade'] || '',
|
||||
version = headers['sec-websocket-version'];
|
||||
|
||||
if (request.method !== 'GET' ||
|
||||
connection.toLowerCase().split(/ *, */).indexOf('upgrade') < 0 ||
|
||||
upgrade.toLowerCase() !== 'websocket')
|
||||
return null;
|
||||
|
||||
if (typeof version === 'string' || typeof key === 'string')
|
||||
return (version === '13' && key.length > 0) ? Hybi : null;
|
||||
|
||||
if (typeof origin !== 'string' || origin.length === 0)
|
||||
return null;
|
||||
|
||||
if (typeof key1 === 'string' && typeof key2 === 'string')
|
||||
return Draft76;
|
||||
|
||||
return Draft75;
|
||||
};
|
||||
|
||||
Server.determineUrl = function(request) {
|
||||
var scheme = this.isSecureRequest(request) ? 'wss:' : 'ws:';
|
||||
return scheme + '//' + request.headers.host + request.url;
|
||||
};
|
||||
|
||||
Server.isSecureRequest = function(request) {
|
||||
if (request.connection && request.connection.authorized !== undefined) return true;
|
||||
if (request.socket && request.socket.secure) return true;
|
||||
@@ -85,24 +133,4 @@ Server.isSecureRequest = function(request) {
|
||||
return false;
|
||||
};
|
||||
|
||||
Server.determineUrl = function(request) {
|
||||
var scheme = this.isSecureRequest(request) ? 'wss:' : 'ws:';
|
||||
return scheme + '//' + request.headers.host + request.url;
|
||||
};
|
||||
|
||||
Server.http = function(request, options) {
|
||||
options = options || {};
|
||||
if (options.requireMasking === undefined) options.requireMasking = true;
|
||||
|
||||
var headers = request.headers,
|
||||
url = this.determineUrl(request);
|
||||
|
||||
if (headers['sec-websocket-version'])
|
||||
return new Hybi(request, url, options);
|
||||
else if (headers['sec-websocket-key1'])
|
||||
return new Draft76(request, url, options);
|
||||
else
|
||||
return new Draft75(request, url, options);
|
||||
};
|
||||
|
||||
module.exports = Server;
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
var NodeHTTPParser = process.binding('http_parser').HTTPParser,
|
||||
version = NodeHTTPParser.RESPONSE ? 6 : 4;
|
||||
var NodeHTTPParser = require('http-parser-js').HTTPParser;
|
||||
|
||||
var VERSION = process.version.match(/[0-9]+/g).map(function(n) { return parseInt(n, 10) });
|
||||
|
||||
var TYPES = {
|
||||
request: NodeHTTPParser.REQUEST || 'request',
|
||||
response: NodeHTTPParser.RESPONSE || 'response'
|
||||
};
|
||||
|
||||
var HttpParser = function(type) {
|
||||
if (type === 'request')
|
||||
this._parser = new NodeHTTPParser(NodeHTTPParser.REQUEST || 'request');
|
||||
else
|
||||
this._parser = new NodeHTTPParser(NodeHTTPParser.RESPONSE || 'response');
|
||||
|
||||
this._type = type;
|
||||
this._parser = new NodeHTTPParser(TYPES[type]);
|
||||
this._complete = false;
|
||||
this.headers = {};
|
||||
|
||||
@@ -76,20 +78,52 @@ HttpParser.METHODS = {
|
||||
13: 'PROPPATCH',
|
||||
14: 'SEARCH',
|
||||
15: 'UNLOCK',
|
||||
16: 'REPORT',
|
||||
17: 'MKACTIVITY',
|
||||
18: 'CHECKOUT',
|
||||
19: 'MERGE',
|
||||
24: 'PATCH'
|
||||
16: 'BIND',
|
||||
17: 'REBIND',
|
||||
18: 'UNBIND',
|
||||
19: 'ACL',
|
||||
20: 'REPORT',
|
||||
21: 'MKACTIVITY',
|
||||
22: 'CHECKOUT',
|
||||
23: 'MERGE',
|
||||
24: 'M-SEARCH',
|
||||
25: 'NOTIFY',
|
||||
26: 'SUBSCRIBE',
|
||||
27: 'UNSUBSCRIBE',
|
||||
28: 'PATCH',
|
||||
29: 'PURGE',
|
||||
30: 'MKCALENDAR',
|
||||
31: 'LINK',
|
||||
32: 'UNLINK'
|
||||
};
|
||||
|
||||
if (VERSION[0] === 0 && VERSION[1] === 12) {
|
||||
HttpParser.METHODS[16] = 'REPORT';
|
||||
HttpParser.METHODS[17] = 'MKACTIVITY';
|
||||
HttpParser.METHODS[18] = 'CHECKOUT';
|
||||
HttpParser.METHODS[19] = 'MERGE';
|
||||
HttpParser.METHODS[20] = 'M-SEARCH';
|
||||
HttpParser.METHODS[21] = 'NOTIFY';
|
||||
HttpParser.METHODS[22] = 'SUBSCRIBE';
|
||||
HttpParser.METHODS[23] = 'UNSUBSCRIBE';
|
||||
HttpParser.METHODS[24] = 'PATCH';
|
||||
HttpParser.METHODS[25] = 'PURGE';
|
||||
}
|
||||
|
||||
HttpParser.prototype.isComplete = function() {
|
||||
return this._complete;
|
||||
};
|
||||
|
||||
HttpParser.prototype.parse = function(chunk) {
|
||||
var offset = (version < 6) ? 1 : 0,
|
||||
consumed = this._parser.execute(chunk, 0, chunk.length) + offset;
|
||||
var consumed = this._parser.execute(chunk, 0, chunk.length);
|
||||
|
||||
if (typeof consumed !== 'number') {
|
||||
this.error = consumed;
|
||||
this._complete = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (VERSION[0] === 0 && VERSION[1] < 6) consumed += 1;
|
||||
|
||||
if (this._complete)
|
||||
this.body = (consumed < chunk.length)
|
||||
|
||||
+11
-5
@@ -5,13 +5,19 @@
|
||||
, "keywords" : ["websocket"]
|
||||
, "license" : "MIT"
|
||||
|
||||
, "version" : "0.6.4"
|
||||
, "engines" : {"node": ">=0.6.0"}
|
||||
, "version" : "0.7.0"
|
||||
, "engines" : { "node": ">=0.8.0" }
|
||||
, "files" : ["lib"]
|
||||
, "main" : "./lib/websocket/driver"
|
||||
, "dependencies" : {"websocket-extensions": ">=0.1.1"}
|
||||
, "devDependencies" : {"jstest": "", "permessage-deflate": ""}
|
||||
|
||||
, "scripts" : {"test": "jstest spec/runner.js"}
|
||||
, "dependencies" : { "http-parser-js": ">=0.4.0"
|
||||
, "websocket-extensions": ">=0.1.1"
|
||||
}
|
||||
, "devDependencies" : { "jstest": "*"
|
||||
, "permessage-deflate": "*"
|
||||
}
|
||||
|
||||
, "scripts" : { "test": "jstest spec/runner.js" }
|
||||
|
||||
, "repository" : { "type" : "git"
|
||||
, "url" : "git://github.com/faye/websocket-driver-node.git"
|
||||
|
||||
@@ -257,8 +257,8 @@ test.describe("Client", function() { with(this) {
|
||||
|
||||
it("changes the state to closed", function() { with(this) {
|
||||
assertEqual( false, open )
|
||||
assertEqual( "Error during WebSocket handshake: Unexpected response code: 4", error.message )
|
||||
assertEqual( [1002, "Error during WebSocket handshake: Unexpected response code: 4"], close )
|
||||
assertEqual( "Error during WebSocket handshake: Parse Error", error.message )
|
||||
assertEqual( [1002, "Error during WebSocket handshake: Parse Error"], close )
|
||||
assertEqual( "closed", driver().getState() )
|
||||
}})
|
||||
}})
|
||||
|
||||
@@ -386,6 +386,17 @@ test.describe("Hybi", function() { with(this) {
|
||||
assertEqual( [0x8a, 0x04, 0x4f, 0x48, 0x41, 0x49], collector().bytes )
|
||||
}})
|
||||
|
||||
it("triggers the onping event when a ping arrives", function() { with(this) {
|
||||
var ping, pong
|
||||
driver().on("ping", function(event) { ping = event })
|
||||
driver().on("pong", function(event) { pong = event })
|
||||
|
||||
driver().parse([0x89, 0x04, 0x4f, 0x48, 0x41, 0x49])
|
||||
|
||||
assertEqual( "OHAI", ping.data )
|
||||
assertEqual( undefined, pong )
|
||||
}})
|
||||
|
||||
describe("when a message listener throws an error", function() { with(this) {
|
||||
before(function() { with(this) {
|
||||
this.messages = []
|
||||
@@ -473,6 +484,17 @@ test.describe("Hybi", function() { with(this) {
|
||||
assert( reply )
|
||||
}})
|
||||
|
||||
it("triggers the onpong event when a pong arrives", function() { with(this) {
|
||||
var ping, pong
|
||||
driver().on("ping", function(event) { ping = event })
|
||||
driver().on("pong", function(event) { pong = event })
|
||||
|
||||
driver().parse([0x8a, 0x02, 72, 105])
|
||||
|
||||
assertEqual( undefined, ping )
|
||||
assertEqual( "Hi", pong.data )
|
||||
}})
|
||||
|
||||
it("does not run the callback on non-matching pong", function() { with(this) {
|
||||
var reply = null
|
||||
driver().ping("Hi", function() { reply = true })
|
||||
|
||||
Reference in New Issue
Block a user