|
|
'use strict';
var Buffer = require('safe-buffer').Buffer, crypto = require('crypto'), url = require('url'), util = require('util'), HttpParser = require('../http_parser'), Base = require('./base'), Hybi = require('./hybi'), Proxy = require('./proxy');
var Client = function(_url, options) { this.version = 'hybi-' + Hybi.VERSION; Hybi.call(this, null, _url, options);
this.readyState = -1; this._key = Client.generateKey(); this._accept = Hybi.generateAccept(this._key); this._http = new HttpParser('response');
var uri = url.parse(this.url), auth = uri.auth && Buffer.from(uri.auth, 'utf8').toString('base64');
if (this.VALID_PROTOCOLS.indexOf(uri.protocol) < 0) throw new Error(this.url + ' is not a valid WebSocket URL');
this._pathname = (uri.pathname || '/') + (uri.search || '');
this._headers.set('Host', uri.host); this._headers.set('Upgrade', 'websocket'); this._headers.set('Connection', 'Upgrade'); this._headers.set('Sec-WebSocket-Key', this._key); this._headers.set('Sec-WebSocket-Version', Hybi.VERSION);
if (this._protocols.length > 0) this._headers.set('Sec-WebSocket-Protocol', this._protocols.join(', '));
if (auth) this._headers.set('Authorization', 'Basic ' + auth); }; util.inherits(Client, Hybi);
Client.generateKey = function() { return crypto.randomBytes(16).toString('base64'); };
var instance = { VALID_PROTOCOLS: ['ws:', 'wss:'],
proxy: function(origin, options) { return new Proxy(this, origin, options); },
start: function() { if (this.readyState !== -1) return false; this._write(this._handshakeRequest()); this.readyState = 0; return true; },
parse: function(chunk) { if (this.readyState === 3) return; if (this.readyState > 0) return Hybi.prototype.parse.call(this, chunk);
this._http.parse(chunk); if (!this._http.isComplete()) return;
this._validateHandshake(); if (this.readyState === 3) return;
this._open(); this.parse(this._http.body); },
_handshakeRequest: function() { var extensions = this._extensions.generateOffer(); if (extensions) this._headers.set('Sec-WebSocket-Extensions', extensions);
var start = 'GET ' + this._pathname + ' HTTP/1.1', headers = [start, this._headers.toString(), ''];
return Buffer.from(headers.join('\r\n'), 'utf8'); },
_failHandshake: function(message) { message = 'Error during WebSocket handshake: ' + message; this.readyState = 3; this.emit('error', new Error(message)); this.emit('close', new Base.CloseEvent(this.ERRORS.protocol_error, message)); },
_validateHandshake: function() { 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);
var headers = this._http.headers, upgrade = headers['upgrade'] || '', connection = headers['connection'] || '', accept = headers['sec-websocket-accept'] || '', protocol = headers['sec-websocket-protocol'] || '';
if (upgrade === '') return this._failHandshake("'Upgrade' header is missing"); if (upgrade.toLowerCase() !== 'websocket') return this._failHandshake("'Upgrade' header value is not 'WebSocket'");
if (connection === '') return this._failHandshake("'Connection' header is missing"); if (connection.toLowerCase() !== 'upgrade') return this._failHandshake("'Connection' header value is not 'Upgrade'");
if (accept !== this._accept) return this._failHandshake('Sec-WebSocket-Accept mismatch');
this.protocol = null;
if (protocol !== '') { if (this._protocols.indexOf(protocol) < 0) return this._failHandshake('Sec-WebSocket-Protocol mismatch'); else this.protocol = protocol; }
try { this._extensions.activate(this.headers['sec-websocket-extensions']); } catch (e) { return this._failHandshake(e.message); } } };
for (var key in instance) Client.prototype[key] = instance[key];
module.exports = Client;
|