PageRenderTime 669ms CodeModel.GetById 159ms app.highlight 188ms RepoModel.GetById 251ms app.codeStats 0ms

/js/lib/Socket.IO-node/lib/socket.io/transports/websocket/index.js

http://github.com/onedayitwillmake/RealtimeMultiplayerNodeJs
JavaScript | 203 lines | 125 code | 37 blank | 41 comment | 18 complexity | fc87814577db5783fdbb0589aa7f9f0f MD5 | raw file
  1
  2/*!
  3 * Socket.IO - transports - WebSocket
  4 * Copyright (c) 2010-2011 Guillermo Rauch <guillermo@learnboost.com>
  5 * MIT Licensed
  6 */
  7
  8var Client = require('../../client')
  9  , Stream = require('net').Stream
 10  , EventEmitter = require('events').EventEmitter
 11  , Parser = require('./parser')
 12  , crypto = require('crypto')
 13  , url = require('url');
 14
 15/**
 16 * Expose `WebSocket`.
 17 */
 18
 19module.exports = WebSocket;
 20
 21/**
 22 * Initialize a `WebSocket` client.
 23 *
 24 * @api private
 25 */
 26
 27function WebSocket(){
 28  Client.apply(this, arguments);
 29};
 30
 31/**
 32 * Inherit from `Client.prototype`.
 33 */
 34
 35WebSocket.prototype.__proto__ = Client.prototype;
 36
 37WebSocket.prototype._onConnect = function(req, socket){
 38  var self = this
 39    , headers = [];
 40  
 41  if (!req.connection.setTimeout){
 42    req.connection.end();
 43    return false;
 44  }
 45
 46  this.parser = new Parser();
 47  this.parser.on('data', self._onMessage.bind(this));
 48  this.parser.on('error', self._onClose.bind(this));
 49
 50  Client.prototype._onConnect.call(this, req);
 51    
 52  if (this.request.headers.upgrade !== 'WebSocket' || !this._verifyOrigin(this.request.headers.origin)){
 53    this.listener.log('WebSocket connection invalid or Origin not verified');
 54    this._onClose();
 55    return false;
 56  }
 57  
 58  var origin = this.request.headers.origin,
 59      location = (this.request.socket.encrypted ? 'wss' : 'ws')
 60               + '://' + this.request.headers.host + this.request.url;
 61  
 62  this.waitingForNonce = false;
 63  if ('sec-websocket-key1' in this.request.headers){
 64    /*  We need to send the 101 response immediately when using Draft 76 with
 65        a load balancing proxy, such as HAProxy.  In order to protect an
 66        unsuspecting non-websocket HTTP server, HAProxy will not send the
 67        8-byte nonce through the connection until the Upgrade: WebSocket
 68        request has been confirmed by the WebSocket server by a 101 response
 69        indicating that the server can handle the upgraded protocol.  We
 70        therefore must send the 101 response immediately, and then wait for
 71        the nonce to be forwarded to us afterward in order to finish the
 72        Draft 76 handshake.
 73      */
 74    
 75    // If we don't have the nonce yet, wait for it.
 76    if (!(this.upgradeHead && this.upgradeHead.length >= 8)) {
 77      this.waitingForNonce = true;
 78    }
 79    
 80    headers = [
 81        'HTTP/1.1 101 WebSocket Protocol Handshake'
 82      , 'Upgrade: WebSocket'
 83      , 'Connection: Upgrade'
 84      , 'Sec-WebSocket-Origin: ' + origin
 85      , 'Sec-WebSocket-Location: ' + location
 86    ];
 87    
 88    if ('sec-websocket-protocol' in this.request.headers){
 89      headers.push('Sec-WebSocket-Protocol: ' + this.request.headers['sec-websocket-protocol']);
 90    }
 91  } else {
 92    headers = [
 93        'HTTP/1.1 101 Web Socket Protocol Handshake'
 94      , 'Upgrade: WebSocket'
 95      , 'Connection: Upgrade'
 96      , 'WebSocket-Origin: ' + origin
 97      , 'WebSocket-Location: ' + location
 98    ];
 99  }
100
101  try {
102    this.connection.write(headers.concat('', '').join('\r\n'));
103    this.connection.setTimeout(0);
104    this.connection.setNoDelay(true);
105    this.connection.setEncoding('utf-8');
106  } catch(e){
107    this._onClose();
108    return;
109  }
110  
111  if (this.waitingForNonce) {
112    // Since we will be receiving the binary nonce through the normal HTTP
113    // data event, set the connection to 'binary' temporarily
114    this.connection.setEncoding('binary');
115    this._headers = headers;
116  }
117  else {
118    if (this._proveReception(headers)) this._payload();
119  }
120  
121  this.buffer = '';
122  
123  this.connection.on('data', function(data){
124    if (self.waitingForNonce) {
125      self.buffer += data;
126
127      if (self.buffer.length < 8) return;
128      // Restore the connection to utf8 encoding after receiving the nonce
129      self.connection.setEncoding('utf8');
130      self.waitingForNonce = false;
131      // Stuff the nonce into the location where it's expected to be
132      self.upgradeHead = self.buffer.substr(0,8);
133      self.buffer = '';
134      if (self._proveReception(self._headers)) self._payload();
135      return;
136    }
137
138    self.parser.write(data);
139  });
140};
141
142// http://www.whatwg.org/specs/web-apps/current-work/complete/network.html#opening-handshake
143WebSocket.prototype._proveReception = function(headers){
144  var self = this
145    , k1 = this.request.headers['sec-websocket-key1']
146    , k2 = this.request.headers['sec-websocket-key2'];
147  
148  if (k1 && k2){
149    var md5 = crypto.createHash('md5');
150
151    [k1, k2].forEach(function(k){
152      var n = parseInt(k.replace(/[^\d]/g, '')),
153          spaces = k.replace(/[^ ]/g, '').length;
154          
155      if (spaces === 0 || n % spaces !== 0){
156        self.listener.log('Invalid WebSocket key: "' + k + '". Dropping connection');
157        self._onClose();
158        return false;
159      }
160
161      n /= spaces;
162      
163      md5.update(String.fromCharCode(
164        n >> 24 & 0xff,
165        n >> 16 & 0xff,
166        n >> 8  & 0xff,
167        n       & 0xff));
168    });
169
170    md5.update(this.upgradeHead.toString('binary'));
171    
172    try {
173      this.connection.write(md5.digest('binary'), 'binary');
174    } catch(e){
175      this._onClose();
176    }
177  }
178  
179  return true;
180};
181
182/**
183 * Write implementation.
184 * 
185 * @param {String} message
186 * @api private
187 */
188
189WebSocket.prototype._write = function(message){
190  try {
191    this.connection.write('\u0000', 'binary');
192    this.connection.write(message, 'utf8');
193    this.connection.write('\uffff', 'binary');
194  } catch(e){
195    this._onClose();
196  }
197};
198
199/**
200 * Requires upgrade.
201 */
202
203WebSocket.httpUpgrade = true;