4 * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
12 var Transport = require('../transport')
13 , EventEmitter = process.EventEmitter
14 , crypto = require('crypto')
15 , parser = require('../parser');
18 * Export the constructor.
21 exports = module.exports = WebSocket;
24 * HTTP interface constructor. Interface compatible with all transports that
25 * depend on request-response cycles.
30 function WebSocket (mng, data, req) {
34 this.parser = new Parser();
35 this.parser.on('data', function (packet) {
36 self.log.debug(self.name + ' received data packet', packet);
37 self.onMessage(parser.decodePacket(packet));
39 this.parser.on('close', function () {
42 this.parser.on('error', function () {
46 Transport.call(this, mng, data, req);
50 * Inherits from Transport.
53 WebSocket.prototype.__proto__ = Transport.prototype;
61 WebSocket.prototype.name = 'websocket';
64 * Called when the socket connects.
69 WebSocket.prototype.onSocketConnect = function () {
72 this.socket.setNoDelay(true);
77 if (this.req.headers.upgrade !== 'WebSocket') {
78 this.log.warn(this.name + ' connection invalid');
83 var origin = this.req.headers.origin
84 , location = (this.socket.encrypted ? 'wss' : 'ws')
85 + '://' + this.req.headers.host + this.req.url
86 , waitingForNonce = false;
88 if (this.req.headers['sec-websocket-key1']) {
89 // If we don't have the nonce yet, wait for it (HAProxy compatibility).
90 if (! (this.req.head && this.req.head.length >= 8)) {
91 waitingForNonce = true;
95 'HTTP/1.1 101 WebSocket Protocol Handshake'
96 , 'Upgrade: WebSocket'
97 , 'Connection: Upgrade'
98 , 'Sec-WebSocket-Origin: ' + origin
99 , 'Sec-WebSocket-Location: ' + location
102 if (this.req.headers['sec-websocket-protocol']){
103 headers.push('Sec-WebSocket-Protocol: '
104 + this.req.headers['sec-websocket-protocol']);
108 'HTTP/1.1 101 Web Socket Protocol Handshake'
109 , 'Upgrade: WebSocket'
110 , 'Connection: Upgrade'
111 , 'WebSocket-Origin: ' + origin
112 , 'WebSocket-Location: ' + location
117 this.socket.write(headers.concat('', '').join('\r\n'));
118 this.socket.setTimeout(0);
119 this.socket.setNoDelay(true);
120 this.socket.setEncoding('utf8');
126 if (waitingForNonce) {
127 this.socket.setEncoding('binary');
128 } else if (this.proveReception(headers)) {
134 this.socket.on('data', function (data) {
135 if (waitingForNonce) {
138 if (headBuffer.length < 8) {
142 // Restore the connection to utf8 encoding after receiving the nonce
143 self.socket.setEncoding('utf8');
144 waitingForNonce = false;
146 // Stuff the nonce into the location where it's expected to be
147 self.req.head = headBuffer.substr(0, 8);
150 if (self.proveReception(headers)) {
157 self.parser.add(data);
162 * Writes to the socket.
167 WebSocket.prototype.write = function (data) {
169 this.drained = false;
172 this.buffered.push(data);
176 var length = Buffer.byteLength(data)
177 , buffer = new Buffer(2 + length);
179 buffer.write('\u0000', 'binary');
180 buffer.write(data, 1, 'utf8');
181 buffer.write('\uffff', 1 + length, 'binary');
184 if (this.socket.write(buffer)) {
191 this.log.debug(this.name + ' writing', data);
196 * Flushes the internal buffer
201 WebSocket.prototype.flush = function () {
204 for (var i = 0, l = this.buffered.length; i < l; i++) {
205 this.write(this.buffered.splice(0, 1)[0]);
210 * Finishes the handshake.
215 WebSocket.prototype.proveReception = function (headers) {
217 , k1 = this.req.headers['sec-websocket-key1']
218 , k2 = this.req.headers['sec-websocket-key2'];
221 var md5 = crypto.createHash('md5');
223 [k1, k2].forEach(function (k) {
224 var n = parseInt(k.replace(/[^\d]/g, ''))
225 , spaces = k.replace(/[^ ]/g, '').length;
227 if (spaces === 0 || n % spaces !== 0){
228 self.log.warn('Invalid ' + self.name + ' key: "' + k + '".');
235 md5.update(String.fromCharCode(
242 md5.update(this.req.head.toString('binary'));
245 this.socket.write(md5.digest('binary'), 'binary');
260 WebSocket.prototype.payload = function (msgs) {
261 for (var i = 0, l = msgs.length; i < l; i++) {
269 * Closes the connection.
274 WebSocket.prototype.doClose = function () {
290 * Inherits from EventEmitter.
293 Parser.prototype.__proto__ = EventEmitter.prototype;
296 * Adds data to the buffer.
301 Parser.prototype.add = function (data) {
312 Parser.prototype.parse = function () {
313 for (var i = this.i, chr, l = this.buffer.length; i < l; i++){
314 chr = this.buffer[i];
316 if (this.buffer.length == 2 && this.buffer[1] == '\u0000') {
325 this.error('Bad framing. Expected null byte as first frame');
330 if (chr == '\ufffd'){
331 this.emit('data', this.buffer.substr(1, i - 1));
332 this.buffer = this.buffer.substr(i + 1);
345 Parser.prototype.error = function (reason) {
348 this.emit('error', reason);