Permalink
Browse files

Merge branch 'master' of http://github.com/LearnBoost/Socket.IO-node

Conflicts:
	lib/socket.io/client.js
	lib/socket.io/listener.js
	lib/socket.io/transports/websocket.js
	lib/socket.io/transports/xhr-multipart.js
	lib/socket.io/transports/xhr-polling.js

Conflicts Resolved
  • Loading branch information...
christiaan committed Jun 1, 2010
2 parents c1fca0c + f3c28e2 commit 31d119740c0e9522c8d1b77a4ae1762d22486d45
View
@@ -1,18 +1,20 @@
-var options = require('./util/options').options;
+var options = require('./util/options').options, urlparse = require('url').parse;
exports.Client = Class({
include: [options],
options: {
- closeTimeout: 0
+ closeTimeout: 0,
+ heartbeatInterval: 5000
},
- init: function(listener, req, res, options){
+ init: function(listener, req, res, options, head){
this.listener = listener;
this.setOptions(options);
this.connections = 0;
this.connected = false;
+ this.upgradeHead = head;
this._onConnect(req, res);
},
@@ -82,6 +84,7 @@ exports.Client = Class({
_onClose: function(){
var self = this;
+ if (this._heartbeatInterval) clearInterval(this._heartbeatInterval);
this.connected = false;
this._disconnectTimeout = setTimeout(function(){
self._onDisconnect();
@@ -113,6 +116,14 @@ exports.Client = Class({
}
this.sessionId = Math.random().toString().substr(2);
return this;
+ },
+
+ _verifyOrigin: function(origin){
+ var parts = urlparse(origin);
+ return this.listener.options.origins.indexOf('*:*') !== -1
+ || this.listener.options.origins.indexOf(parts.host + ':' + parts.port) !== -1
+ || this.listener.options.origins.indexOf(parts.host + ':*') !== -1
+ || this.listener.options.origins.indexOf('*:' + parts.port) !== -1;
}
});
View
@@ -1,5 +1,4 @@
var url = require('url'),
- sys = require('sys'),
options = require('./util/options').options,
Client = require('./client').Client,
transports = {};
@@ -14,7 +13,7 @@ var Listener = exports.Listener = Class({
transports: ['websocket', 'flashsocket', 'htmlfile', 'xhr-multipart', 'xhr-polling'],
transportOptions: {},
log: function(message){
- sys.log(message);
+ require('sys').log(message);
}
},
@@ -37,7 +36,7 @@ var Listener = exports.Listener = Class({
});
this.server.addListener('upgrade', function(req, socket, head){
- if (!self.check(req, socket, true)){
+ if (!self.check(req, socket, true, head)){
socket.destroy();
}
});
@@ -61,7 +60,7 @@ var Listener = exports.Listener = Class({
return this;
},
- check: function(req, res, httpUpgrade){
+ check: function(req, res, httpUpgrade, head){
var path = url.parse(req.url).pathname, parts, cn;
if (path.indexOf('/' + this.options.resource) === 0){
parts = path.substr(1).split('/');
@@ -71,10 +70,10 @@ var Listener = exports.Listener = Class({
cn._onConnect(req, res);
} else {
req.connection.end();
- sys.log('Couldnt find client with session id "' + parts[2] + '"');
+ this.options.log('Couldnt find client with session id "' + parts[2] + '"');
}
} else {
- this._onConnection(parts[1], req, res, httpUpgrade);
+ this._onConnection(parts[1], req, res, httpUpgrade, head);
}
return true;
}
@@ -87,12 +86,12 @@ var Listener = exports.Listener = Class({
_onClientConnect: function(client){
if (!(client instanceof Client) || !client.sessionId){
- return sys.log('Invalid client');
+ return this.options.log('Invalid client');
}
client.i = this.clients.length;
this.clients.push(client);
this.clientsIndex[client.sessionId] = client;
- sys.log('Client '+ client.sessionId +' connected');
+ this.options.log('Client '+ client.sessionId +' connected');
this.emit('clientConnect', client);
},
@@ -103,18 +102,18 @@ var Listener = exports.Listener = Class({
_onClientDisconnect: function(client){
this.clientsIndex[client.sessionId] = null;
this.clients[client.i] = null;
- sys.log('Client '+ client.sessionId +' disconnected');
+ this.options.log('Client '+ client.sessionId +' disconnected');
this.emit('clientDisconnect', client);
},
// new connections (no session id)
- _onConnection: function(transport, req, res, httpUpgrade){
+ _onConnection: function(transport, req, res, httpUpgrade, head){
if (this.options.transports.indexOf(transport) === -1 || (httpUpgrade && !transports[transport].httpUpgrade)){
httpUpgrade ? res.destroy() : req.connection.destroy();
- return sys.log('Illegal transport "'+ transport +'"');
+ return this.options.log('Illegal transport "'+ transport +'"');
}
- sys.log('Initializing client with transport "'+ transport +'"');
- new transports[transport](this, req, res, this.options.transportOptions[transport]);
+ this.options.log('Initializing client with transport "'+ transport +'"');
+ new transports[transport](this, req, res, this.options.transportOptions[transport], head);
}
-});
+});
@@ -19,10 +19,10 @@ exports.htmlfile = Client.extend({
this.response.write('<html><body>' + new Array(244).join(' '));
this.response.flush();
this._payload();
- setInterval(function(){
+ this._heartbeatInterval = setInterval(function(){
self.response.write('<!-- heartbeat -->');
self.response.flush();
- }, 1000);
+ }, this.options.heartbeatInterval);
break;
case 'POST':
@@ -1,10 +1,12 @@
var Client = require('../client').Client,
- url = require('url');
+ url = require('url'),
+ Buffer = require('buffer').Buffer,
+ crypto = require('crypto');
exports.websocket = Client.extend({
_onConnect: function(req, socket){
- var self = this;
+ var self = this, headers = [];
this.request = req;
this.connection = socket;
this.data = '';
@@ -17,17 +19,26 @@ exports.websocket = Client.extend({
this.connection.setTimeout(0);
this.connection.setEncoding('utf8');
this.connection.setNoDelay(true);
- this.connection.write([
+
+ headers = [
'HTTP/1.1 101 Web Socket Protocol Handshake',
'Upgrade: WebSocket',
'Connection: Upgrade',
'WebSocket-Origin: ' + this.request.headers.origin,
- 'WebSocket-Location: ws://' + this.request.headers.host + this.request.url,
- '', ''
- ].join('\r\n'));
+ 'WebSocket-Location: ws://' + this.request.headers.host + this.request.url
+ ];
+
+ if ('sec-websocket-key1' in this.request.headers){
+ headers.push(
+ 'Sec-WebSocket-Origin: ' + this.request.headers.origin,
+ 'Sec-WebSocket-Location: ws://' + this.request.headers.host + this.request.url
+ );
+ }
+
+ this.connection.write(headers.concat('', '').join('\r\n'));
this.connection.addListener('end', function(){ self._onClose(); });
this.connection.addListener('data', function(data){ self._handle(data); });
- this._payload();
+ if (this._proveReception()) this._payload();
},
_handle: function(data){
@@ -46,17 +57,39 @@ exports.websocket = Client.extend({
}
this.data = chunks[chunks.length - 1];
},
-
- _verifyOrigin: function(origin){
- var parts = url.parse(origin),
- origins = this.listener.options.origins;
-
- return origins.indexOf('*:*') !== -1 ||
- origins.indexOf(parts.host + ':' + parts.port) !== -1 ||
- origins.indexOf(parts.host + ':*') !== -1 ||
- origins.indexOf('*:' + parts.port) !== -1;
+
+ // http://www.whatwg.org/specs/web-apps/current-work/complete/network.html#opening-handshake
+ _proveReception: function(){
+ var k1 = this.request.headers['sec-websocket-key1'],
+ k2 = this.request.headers['sec-websocket-key2'];
+ if (k1 && k2) {
+ var md5 = crypto.createHash('md5');
+
+ [k1, k2].forEach(function(k) {
+ var n = k.replace(/[^\d]/g, ''),
+ spaces = k.replace(/[^ ]/g, '').length,
+ buf = new Buffer(4);
+ if (spaces === 0) {
+ this.listener.options.log('Invalid WebSocket key: "' + k + '". Dropping connection');
+ this.connection.destroy();
+ return false;
+ }
+
+ n /= spaces;
+ buf[3] = n & 0xff;
+ buf[2] = (n >>= 8) & 0xff;
+ buf[1] = (n >>= 8) & 0xff;
+ buf[0] = (n >>= 8) & 0xff;
+
+ md5.update(buf.toString('binary'));
+ });
+
+ md5.update(this.upgradeHead.toString('binary'));
+ this.connection.write(md5.digest('binary'), 'binary');
+ }
+ return true;
},
-
+
_write: function(message){
try {
this.connection.write('\u0000', 'binary');
@@ -2,43 +2,65 @@ var Client = require('../client').Client,
qs = require('querystring');
exports['xhr-multipart'] = Client.extend({
+
+ options: {
+ pingInterval: 7000
+ },
+ _pingInterval: null,
+
_onConnect: function(req, res){
- var self = this, body = '';
+ var self = this, body = '', headers = {};
+ // https://developer.mozilla.org/En/HTTP_Access_Control
+ if (req.headers['origin'] && this._verifyOrigin(req.headers['origin'])) {
+ headers['Access-Control-Allow-Origin'] = req.headers['origin'];
+ headers['Access-Control-Allow-Credentials'] = 'true';
+ }
+ if (typeof req.headers['access-control-request-method'] != 'undefined') {
+ // CORS preflight message
+ headers['Access-Control-Allow-Methods'] = req.headers['access-control-request-method'];
+ res.writeHead(200, headers);
+ res.write('ok');
+ res.end();
+ return;
+ }
switch (req.method){
case 'GET':
this.__super__(req, res);
+ headers['Content-Type'] = 'multipart/x-mixed-replace;boundary="socketio"';
+ headers['Connection'] = 'keep-alive';
this.request.connection.addListener('end', function(){ self._onClose(); });
this.response.useChunkedEncodingByDefault = false;
this.response.shouldKeepAlive = true;
- this.response.writeHead(200, {
- 'Content-Type': 'multipart/x-mixed-replace;boundary=socketio',
- 'Connection': 'keep-alive'
- });
+ this.response.writeHead(200, headers);
this.response.write("--socketio\n");
this.response.flush();
this._payload();
+ this._heartbeatInterval = setInterval(function(){
+ self._write(String.fromCharCode(6));
+ }, this.options.heartbeatInterval);
break;
case 'POST':
req.addListener('data', function(message){
- body += message;
+ body += message.toString();
});
req.addListener('end', function(){
try {
var msg = qs.parse(body);
self._onMessage(msg.data);
- } catch(e){}
- res.writeHead(200);
+ } catch(e){}
+ res.writeHead(200, headers);
res.write('ok');
res.end();
+ body = '';
});
break;
}
},
_write: function(message){
- this.response.write("Content-Type: text/plain\n\n");
+ this.response.write("Content-Type: text/plain" + (message.length == 1 && message.charCodeAt(0) == 6 ? "; charset=us-ascii" : "") + "\n\n");
this.response.write(message + "\n");
this.response.write("--socketio\n");
this.response.flush();
@@ -1,6 +1,5 @@
var Client = require('../client').Client,
- qs = require('querystring'),
- sys = require('sys');
+ qs = require('querystring');
exports['xhr-polling'] = Client.extend({
@@ -28,7 +27,7 @@ exports['xhr-polling'] = Client.extend({
try {
var msg = qs.parse(body);
self._onMessage(msg.data);
- } catch(e){}
+ } catch(e){}
res.writeHead(200);
res.write('ok');
res.end();
@@ -41,7 +40,14 @@ exports['xhr-polling'] = Client.extend({
if (this._closeTimeout) {
clearTimeout(this._closeTimeout);
}
- this.response.writeHead(200, {'Content-Type': 'text/plain', 'Content-Length': message.length});
+ var headers = {'Content-Type': 'text/plain', 'Content-Length': message.length};
+ // https://developer.mozilla.org/En/HTTP_Access_Control
+ if (this.request.headers['origin'] && this._verifyOrigin(this.request.headers['origin'])) {
+ headers['Access-Control-Allow-Origin'] = this.request.headers['origin'];
+ if (this.request.headers['cookie'])
+ headers['Access-Control-Allow-Credentials'] = 'true';
+ }
+ this.response.writeHead(200, headers);
this.response.write(message);
this.response.end();
this._onClose();

0 comments on commit 31d1197

Please sign in to comment.