Skip to content

Commit 5ad93b8

Browse files
committed
Implement client-side protocol negotiation
Related issues: #24, #29, #39
1 parent a72946f commit 5ad93b8

File tree

3 files changed

+46
-18
lines changed

3 files changed

+46
-18
lines changed

Connection.js

+37-9
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,18 @@ var util = require('util'),
1111
frame = require('./frame'),
1212
Server = require('./Server')
1313

14+
/**
15+
* @typedef {Object} Connection~Options
16+
* @param {string} path
17+
* @param {string} host
18+
* @param {?Object<string>} extraHeaders
19+
* @param {?Array<string>} protocols
20+
*/
21+
1422
/**
1523
* @class
1624
* @param {(net.Socket|tls.CleartextStream)} socket a net or tls socket
17-
* @param {(Server|{path:string,host:string})} parentOrUrl parent in case of server-side connection, url object in case of client-side
25+
* @param {(Server|Connection~Options)} parentOrOptions parent in case of server-side connection, object in case of client-side
1826
* @param {Function} [callback] will be added as a listener to 'connect'
1927
* @inherits EventEmitter
2028
* @event close the numeric code and string reason will be passed
@@ -24,25 +32,26 @@ var util = require('util'),
2432
* @event pong a string is passed
2533
* @event connect
2634
*/
27-
function Connection(socket, parentOrUrl, callback) {
35+
function Connection(socket, parentOrOptions, callback) {
2836
var that = this,
2937
connectEvent
3038

31-
if (parentOrUrl instanceof Server) {
39+
if (parentOrOptions instanceof Server) {
3240
// Server-side connection
33-
this.server = parentOrUrl
41+
this.server = parentOrOptions
3442
this.path = null
3543
this.host = null
3644
this.extraHeaders = null
45+
this.protocols = []
3746
} else {
3847
// Client-side
3948
this.server = null
40-
this.path = parentOrUrl.path
41-
this.host = parentOrUrl.host
42-
this.extraHeaders = parentOrUrl.extraHeaders
49+
this.path = parentOrOptions.path
50+
this.host = parentOrOptions.host
51+
this.extraHeaders = parentOrOptions.extraHeaders
52+
this.protocols = parentOrOptions.protocols || []
4353
}
4454

45-
this.protocols = []
4655
this.protocol = undefined
4756
this.socket = socket
4857
this.readyState = this.CONNECTING
@@ -268,6 +277,10 @@ Connection.prototype.startHandshake = function () {
268277
'Sec-WebSocket-Version': '13'
269278
}
270279

280+
if (this.protocols && this.protocols.length) {
281+
headers['Sec-WebSocket-Protocol'] = this.protocols.join(', ')
282+
}
283+
271284
for (header in this.extraHeaders) {
272285
headers[header] = this.extraHeaders[header]
273286
}
@@ -342,7 +355,7 @@ Connection.prototype.readHeaders = function (lines) {
342355
* @private
343356
*/
344357
Connection.prototype.checkHandshake = function (lines) {
345-
var key, sha1
358+
var key, sha1, protocol
346359

347360
// First line
348361
if (lines.length < 4) {
@@ -367,6 +380,21 @@ Connection.prototype.checkHandshake = function (lines) {
367380
}
368381
key = this.headers['sec-websocket-accept']
369382

383+
// Check protocol negotiation
384+
protocol = this.headers['sec-websocket-protocol']
385+
if (this.protocols && this.protocols.length) {
386+
// The server must choose one from our list
387+
if (!protocol || this.protocols.indexOf(protocol) === -1) {
388+
return false
389+
}
390+
} else {
391+
// The server must not choose a protocol
392+
if (protocol) {
393+
return false
394+
}
395+
}
396+
this.protocol = protocol
397+
370398
// Check the key
371399
sha1 = crypto.createHash('sha1')
372400
sha1.end(this.key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ Returns a new `Connection` object, representing a websocket client connection
4747

4848
`options` is an object that will be passed to net.connect() (or tls.connect() if the protocol is "wss:").
4949
The properties "host" and "port" will be read from the `URL`.
50-
The property `extraHeaders` will be used to add more headers to the HTTP handshake request.
50+
The optional property `extraHeaders` will be used to add more headers to the HTTP handshake request. If present, it must be an object, like `{'X-My-Header': 'value'}`.
51+
The optional property `protocols` will be used in the handshake (as "Sec-WebSocket-Protocol" header) to allow the server to choose one of those values. If present, it must be an array of strings.
5152

5253
`callback` will be added as "connect" listener
5354

index.js

+7-8
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,20 @@ exports.connect = function (URL, options, callback) {
3535
}
3636
options = options || {}
3737

38-
URL = parseWSURL(URL)
39-
options.port = URL.port
40-
options.host = URL.host
38+
var connectionOptions = parseWSURL(URL)
39+
options.port = connectionOptions.port
40+
options.host = connectionOptions.host
4141

42-
if (options.hasOwnProperty('extraHeaders')) {
43-
URL.extraHeaders = options.extraHeaders
44-
}
42+
connectionOptions.extraHeaders = options.extraHeaders
43+
connectionOptions.protocols = options.protocols
4544

46-
if (URL.secure) {
45+
if (connectionOptions.secure) {
4746
socket = tls.connect(options)
4847
} else {
4948
socket = net.connect(options)
5049
}
5150

52-
return new Connection(socket, URL, callback)
51+
return new Connection(socket, connectionOptions, callback)
5352
}
5453

5554
/**

0 commit comments

Comments
 (0)