Compare commits

...

44 Commits
0.8.5 ... 0.8.6

Author SHA1 Message Date
Guillermo Rauch
abd0326b06 Release 0.8.6 2011-10-27 20:10:18 +09:00
Guillermo Rauch
3e0b4488f8 Merge pull request #599 from 3rd-Eden/jsonptests
The server expects JSON.stringify'd packages
2011-10-27 02:44:50 -07:00
Arnout Kazemier
86908c3b4d The server expects JSON.stringify'd packages 2011-10-27 09:10:27 +02:00
Guillermo Rauch
5491c2798e Merge branch 'master' of github.com:LearnBoost/socket.io 2011-10-27 12:54:03 +08:00
Guillermo Rauch
a9def6e209 Merge pull request #598 from 3rd-Eden/utf8
charset=UTF8
2011-10-26 21:53:46 -07:00
Guillermo Rauch
c7a2dc45c8 Merge pull request #591 from einaros/master
Resubmission of: Minor potential issues corrected in websocket transports
2011-10-26 21:51:52 -07:00
Guillermo Rauch
cf76b13145 Added JSON decoding on jsonp-polling transport.
This is due to browser's buggy handling of outgoing \n
2011-10-27 12:45:19 +08:00
Arnout Kazemier
db2a17f279 charset=UTF8 2011-10-26 11:48:41 +02:00
einaros
67495ad8a9 case-insensitive match for websocket upgrade, in all websocket transports 2011-10-22 10:21:10 +02:00
einaros
bee1efb11c fixes #555 2011-10-22 10:16:48 +02:00
Guillermo Rauch
120924f626 Merge pull request #582 from chees/patch-1
Changed sockets.emit to io.sockets.emit to get the example working.
2011-10-16 10:00:20 -07:00
Christiaan
acdbacb25e Changed sockets.emit to io.sockets.emit to get the example working. 2011-10-16 14:59:14 +03:00
TJ Holowaychuk
cde6a38218 Merge pull request #580 from 3rd-Eden/performance
Added a benchmark runner + profile option
2011-10-15 12:12:14 -07:00
Guillermo Rauch
e69c185e17 Merge pull request #581 from dshaw/patch/520
Fixes #520. Updates to latest node_redis.
2011-10-15 11:47:41 -07:00
Daniel Shaw
8cab86af1c Fixes #520. Updates to latest node_redis. 2011-10-15 11:43:48 -07:00
Arnout Kazemier
00557f663a Added error and heartbeat decoding 2011-10-15 15:30:23 +02:00
Arnout Kazemier
54c22aea96 Added a benchmark runner + profile option 2011-10-15 15:10:35 +02:00
Guillermo Rauch
a9929c916f Merge pull request #578 from 3rd-Eden/performance
Use benchmark.js instead, so we can benchmark 0.5.9
2011-10-13 13:42:19 -07:00
Arnout Kazemier
1d66b6b5da Some tiny optimizations 2011-10-13 22:31:52 +02:00
Arnout Kazemier
a75670c1c2 Use benchmark.js instead, so we can benchmark 0.5.9 2011-10-13 21:00:59 +02:00
Guillermo Rauch
373c729e66 Merge pull request #573 from 3rd-Eden/performance
Parser performance boost
2011-10-12 15:10:19 -07:00
Arnout Kazemier
7800003c5e Returned the switch for the decoder, optimized the switch for the encoder 2011-10-12 22:03:46 +02:00
Arnout Kazemier
b662f2e14e Optimized the loop, so the most commen packets are checked first 2011-10-12 21:53:38 +02:00
Arnout Kazemier
709c172444 http://cl.ly/402t2C133B2a1P0g1K0h <--- before http://cl.ly/363W080c2j261l3A1m3y <--- after ;o 2011-10-12 21:44:09 +02:00
Tj Holowaychuk
6d5ffa0d33 remove vbench dev dep
so people dont need node-canvas
2011-10-11 21:38:31 -07:00
Tj Holowaychuk
f8c7ff2782 Added decode/encode benchmarks 2011-10-11 16:00:15 -07:00
Guillermo Rauch
175fe8573b Merge pull request #563 from 3rd-Eden/logger
Logger
2011-10-11 08:18:16 -07:00
Guillermo Rauch
0224e4ac5f Merge pull request #569 from 3rd-Eden/blacklist
Blacklist events
2011-10-11 08:13:55 -07:00
Arnout Kazemier
ecd20b0e1f Added support for blacklisting events that are emitted from the client side.
Currently it's possible for a client do .emit('disconnect') and this will trigger
the disconnect event on the server.. Which can lead to major issues.

We should black list that by default. You can override or add more events by adding
them to the `blacklist` setting
2011-10-11 10:40:22 +02:00
Arnout Kazemier
b8f6dc7810 Inital stab at blacklisting client side events 2011-10-11 09:53:26 +02:00
Guillermo Rauch
0339e745fd Merge pull request #564 from 3rd-Eden/bug/538
fixes #538
2011-10-10 13:23:51 -07:00
Arnout Kazemier
b3740e9ab6 fixes #538 2011-10-10 21:52:16 +02:00
Arnout Kazemier
0b7ed64082 Fixed logging options, closes #540 2011-10-10 21:32:54 +02:00
Arnout Kazemier
07b84f4400 Added a test to ensure that etags do no mess up far future headers 2011-10-10 21:06:58 +02:00
Guillermo Rauch
59e4c3b46c Merge pull request #562 from 3rd-Eden/static
Hardcore caching for pro's
2011-10-10 11:20:38 -07:00
Arnout Kazemier
0e3bbd0e16 Whoops 2011-10-10 20:02:37 +02:00
Arnout Kazemier
763fdd1c4e Added more hardcore caching fixes #558
Added tests against it
Added vary header for gzip
2011-10-10 20:01:28 +02:00
Guillermo Rauch
61bd23f0f9 Merge pull request #561 from einaros/master
Proper websocket test fixes, after patching node-websocket-client
2011-10-09 01:33:13 -07:00
Arnout Kazemier
8107c1a1e2 Added support for HEAD requests, closes #557 2011-10-08 15:46:23 +02:00
Arnout Kazemier
fa5b518110 Merge branch 'master' of github.com:LearnBoost/socket.io into static 2011-10-08 15:42:21 +02:00
einaros
b3df2836e9 properly cleaned up async websocket / flashsocket tests, after patching node-websocket-client 2011-10-08 12:02:31 +02:00
einaros
08568ee49e patched to properly shut down when a finishClose call is made during connection establishment 2011-10-08 12:01:46 +02:00
einaros
aba2d5e0ef removed empty console.log 2011-10-08 12:00:55 +02:00
Arnout Kazemier
5573f7fcdf First stab of adding Expires + cache control headers for #558 2011-10-05 20:16:20 +02:00
27 changed files with 668 additions and 161 deletions

1
.gitignore vendored
View File

@@ -6,4 +6,5 @@ lib-cov
*.dat
*.out
*.pid
benchmarks/*.png
node_modules

View File

@@ -1,4 +1,21 @@
0.8.6 / 2011-10-27
==================
* Added JSON decoding on jsonp-polling transport.
* Fixed README example.
* Major speed optimizations [3rd-Eden] [einaros] [visionmedia]
* Added decode/encode benchmarks [visionmedia]
* Added support for black-listing client sent events.
* Fixed logging options, closes #540 [3rd-Eden]
* Added vary header for gzip [3rd-Eden]
* Properly cleaned up async websocket / flashsocket tests, after patching node-websocket-client
* Patched to properly shut down when a finishClose call is made during connection establishment
* Added support for socket.io version on url and far-future Expires [3rd-Eden] [getify]
* Began IE10 compatibility [einaros] [tbranyen]
* Misc WebSocket fixes [einaros]
* Added UTF8 to respone headers for htmlfile [3rd-Eden]
0.8.5 / 2011-10-07
==================

View File

@@ -1,5 +1,6 @@
ALL_TESTS = $(shell find test/ -name '*.test.js')
ALL_BENCH = $(shell find benchmarks -name '*.bench.js')
run-tests:
@./node_modules/.bin/expresso \
@@ -19,4 +20,13 @@ test-cov:
test-leaks:
@ls test/leaks/* | xargs node --expose_debug_as=debug --expose_gc
.PHONY: test
run-bench:
@node $(PROFILEFLAGS) benchmarks/runner.js
bench:
@$(MAKE) BENCHMARKS="$(ALL_BENCH)" run-bench
profile:
@PROFILEFLAGS='--prof --trace-opt --trace-bailout --trace-deopt' $(MAKE) bench
.PHONY: test bench profile

View File

@@ -67,7 +67,7 @@ io.sockets.on('connection', function (socket) {
});
socket.on('disconnect', function () {
sockets.emit('user disconnected');
io.sockets.emit('user disconnected');
});
});
```

View File

@@ -0,0 +1,64 @@
/**
* Module dependencies.
*/
var benchmark = require('benchmark')
, colors = require('colors')
, io = require('../')
, parser = io.parser
, suite = new benchmark.Suite('Decode packet');
suite.add('string', function () {
parser.decodePacket('4:::"2"');
});
suite.add('event', function () {
parser.decodePacket('5:::{"name":"woot"}');
});
suite.add('event+ack', function () {
parser.decodePacket('5:1+::{"name":"tobi"}');
});
suite.add('event+data', function () {
parser.decodePacket('5:::{"name":"edwald","args":[{"a": "b"},2,"3"]}');
});
suite.add('heartbeat', function () {
parser.decodePacket('2:::');
});
suite.add('error', function () {
parser.decodePacket('7:::2+0');
});
var payload = parser.encodePayload([
parser.encodePacket({ type: 'message', data: '5', endpoint: '' })
, parser.encodePacket({ type: 'message', data: '53d', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbazfoobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
]);
suite.add('payload', function () {
parser.decodePayload(payload);
});
suite.on('cycle', function (bench, details) {
console.log('\n' + suite.name.grey, details.name.white.bold);
console.log([
details.hz.toFixed(2).cyan + ' ops/sec'.grey
, details.count.toString().white + ' times executed'.grey
, 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey
,
].join(', '.grey));
});
if (!module.parent) {
suite.run();
} else {
module.exports = suite;
}

View File

@@ -0,0 +1,90 @@
/**
* Module dependencies.
*/
var benchmark = require('benchmark')
, colors = require('colors')
, io = require('../')
, parser = io.parser
, suite = new benchmark.Suite('Encode packet');
suite.add('string', function () {
parser.encodePacket({
type: 'json'
, endpoint: ''
, data: '2'
});
});
suite.add('event', function () {
parser.encodePacket({
type: 'event'
, name: 'woot'
, endpoint: ''
, args: []
});
});
suite.add('event+ack', function () {
parser.encodePacket({
type: 'json'
, id: 1
, ack: 'data'
, endpoint: ''
, data: { a: 'b' }
});
});
suite.add('event+data', function () {
parser.encodePacket({
type: 'event'
, name: 'edwald'
, endpoint: ''
, args: [{a: 'b'}, 2, '3']
});
});
suite.add('heartbeat', function () {
parser.encodePacket({
type: 'heartbeat'
, endpoint: ''
})
});
suite.add('error', function () {
parser.encodePacket({
type: 'error'
, reason: 'unauthorized'
, advice: 'reconnect'
, endpoint: ''
})
})
suite.add('payload', function () {
parser.encodePayload([
parser.encodePacket({ type: 'message', data: '5', endpoint: '' })
, parser.encodePacket({ type: 'message', data: '53d', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbazfoobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobarbaz', endpoint: '' })
, parser.encodePacket({ type: 'message', data: 'foobar', endpoint: '' })
]);
});
suite.on('cycle', function (bench, details) {
console.log('\n' + suite.name.grey, details.name.white.bold);
console.log([
details.hz.toFixed(2).cyan + ' ops/sec'.grey
, details.count.toString().white + ' times executed'.grey
, 'benchmark took '.grey + details.times.elapsed.toString().white + ' sec.'.grey
,
].join(', '.grey));
});
if (!module.parent) {
suite.run();
} else {
module.exports = suite;
}

55
benchmarks/runner.js Normal file
View File

@@ -0,0 +1,55 @@
/**
* Benchmark runner dependencies
*/
var colors = require('colors')
, path = require('path');
/**
* Find all the benchmarks
*/
var benchmarks_files = process.env.BENCHMARKS.split(' ')
, all = [].concat(benchmarks_files)
, first = all.shift()
, benchmarks = {};
// find the benchmarks and load them all in our obj
benchmarks_files.forEach(function (file) {
benchmarks[file] = require(path.join(__dirname, '..', file));
});
// setup the complete listeners
benchmarks_files.forEach(function (file) {
var benchmark = benchmarks[file]
, next_file = all.shift()
, next = benchmarks[next_file];
/**
* Generate a oncomplete function for the tests, either we are done or we
* have more benchmarks to process.
*/
function complete () {
if (!next) {
console.log(
'\n\nBenchmark completed in'.grey
, (Date.now() - start).toString().green + ' ms'.grey
);
} else {
console.log('\nStarting benchmark '.grey + next_file.yellow);
next.run();
}
}
// attach the listener
benchmark.on('complete', complete);
});
/**
* Start the benchmark
*/
var start = Date.now();
console.log('Starting benchmark '.grey + first.yellow);
benchmarks[first].run();

View File

@@ -60,6 +60,7 @@ var Logger = module.exports = function (opts) {
opts = opts || {}
this.colors = false !== opts.colors;
this.level = 3;
this.enabled = true;
};
/**
@@ -71,7 +72,7 @@ var Logger = module.exports = function (opts) {
Logger.prototype.log = function (type) {
var index = levels.indexOf(type);
if (index > this.level)
if (index > this.level || !this.enabled)
return this;
console.log.apply(

View File

@@ -67,7 +67,9 @@ function Manager (server, options) {
, resource: '/socket.io'
, transports: defaultTransports
, authorization: false
, blacklist: ['disconnect']
, 'log level': 3
, 'log colors': true
, 'close timeout': 25
, 'heartbeat timeout': 15
, 'heartbeat interval': 20
@@ -79,6 +81,7 @@ function Manager (server, options) {
, 'browser client cache': true
, 'browser client minification': false
, 'browser client etag': false
, 'browser client expires': 315360000
, 'browser client gzip': false
, 'browser client handler': false
, 'client store expiration': 15
@@ -146,10 +149,11 @@ Manager.prototype.__defineGetter__('store', function () {
*/
Manager.prototype.__defineGetter__('log', function () {
if (this.disabled('log')) return;
var logger = this.get('logger');
logger.level = this.get('log level') || -1;
logger.colors = this.get('log colors');
logger.enabled = this.enabled('log');
return logger;
});

View File

@@ -34,7 +34,7 @@ function SocketNamespace (mgr, name) {
SocketNamespace.prototype.__proto__ = EventEmitter.prototype;
/**
* Copies emit since we override it
* Copies emit since we override it.
*
* @api private
*/
@@ -103,7 +103,7 @@ SocketNamespace.prototype.__defineGetter__('volatile', function () {
});
/**
* Overrides the room to relay messages to (flag)
* Overrides the room to relay messages to (flag).
*
* @api public
*/
@@ -114,7 +114,7 @@ SocketNamespace.prototype.in = SocketNamespace.prototype.to = function (room) {
};
/**
* Adds a session id we should prevent relaying messages to (flag)
* Adds a session id we should prevent relaying messages to (flag).
*
* @api public
*/
@@ -139,7 +139,7 @@ SocketNamespace.prototype.setFlags = function () {
};
/**
* Sends out a packet
* Sends out a packet.
*
* @api private
*/
@@ -175,9 +175,9 @@ SocketNamespace.prototype.send = function (data) {
};
/**
* Emits to everyone (override)
* Emits to everyone (override).
*
* @api private
* @api public
*/
SocketNamespace.prototype.emit = function (name) {
@@ -196,7 +196,7 @@ SocketNamespace.prototype.emit = function (name) {
* Retrieves or creates a write-only socket for a client, unless specified.
*
* @param {Boolean} whether the socket will be readable when initialized
* @api private
* @api public
*/
SocketNamespace.prototype.socket = function (sid, readable) {
@@ -208,7 +208,7 @@ SocketNamespace.prototype.socket = function (sid, readable) {
};
/**
* Sets authorization for this namespace
* Sets authorization for this namespace.
*
* @api public
*/
@@ -264,6 +264,7 @@ SocketNamespace.prototype.authorize = function (data, fn) {
SocketNamespace.prototype.handlePacket = function (sessid, packet) {
var socket = this.socket(sessid)
, dataAck = packet.ack == 'data'
, manager = this.manager
, self = this;
function ack () {
@@ -296,8 +297,7 @@ SocketNamespace.prototype.handlePacket = function (sessid, packet) {
if (packet.endpoint == '') {
connect();
} else {
var manager = this.manager
, handshakeData = manager.handshaken[sessid];
var handshakeData = manager.handshaken[sessid];
this.authorize(handshakeData, function (err, authorized, newData) {
if (err) return error(err);
@@ -322,13 +322,18 @@ SocketNamespace.prototype.handlePacket = function (sessid, packet) {
break;
case 'event':
var params = [packet.name].concat(packet.args);
// check if the emitted event is not blacklisted
if (-~manager.get('blacklist').indexOf(packet.name)) {
this.log.debug('ignoring blacklisted event `' + packet.name + '`');
} else {
var params = [packet.name].concat(packet.args);
if (dataAck) {
params.push(ack);
if (dataAck) {
params.push(ack);
}
socket.$emit.apply(socket, params);
}
socket.$emit.apply(socket, params);
break;
case 'disconnect':

View File

@@ -13,35 +13,38 @@
* Packet types.
*/
var packets = exports.packets = [
'disconnect'
, 'connect'
, 'heartbeat'
, 'message'
, 'json'
, 'event'
, 'ack'
, 'error'
, 'noop'
];
var packets = exports.packets = {
'disconnect': 0
, 'connect': 1
, 'heartbeat': 2
, 'message': 3
, 'json': 4
, 'event': 5
, 'ack': 6
, 'error': 7
, 'noop': 8
}
, packetslist = Object.keys(packets);
/**
* Errors reasons.
*/
var reasons = exports.reasons = [
'transport not supported'
, 'client not handshaken'
, 'unauthorized'
];
var reasons = exports.reasons = {
'transport not supported': 0
, 'client not handshaken': 1
, 'unauthorized': 2
}
, reasonslist = Object.keys(reasons);
/**
* Errors advice.
*/
var advice = exports.advice = [
'reconnect'
];
var advice = exports.advice = {
'reconnect': 0
}
, advicelist = Object.keys(advice);
/**
* Encodes a packet.
@@ -50,22 +53,13 @@ var advice = exports.advice = [
*/
exports.encodePacket = function (packet) {
var type = packets.indexOf(packet.type)
var type = packets[packet.type]
, id = packet.id || ''
, endpoint = packet.endpoint || ''
, ack = packet.ack
, data = null;
switch (packet.type) {
case 'error':
var reason = packet.reason ? reasons.indexOf(packet.reason) : ''
, adv = packet.advice ? advice.indexOf(packet.advice) : ''
if (reason !== '' || adv !== '')
data = reason + (adv !== '' ? ('+' + adv) : '')
break;
case 'message':
if (packet.data !== '')
data = packet.data;
@@ -85,30 +79,35 @@ exports.encodePacket = function (packet) {
data = JSON.stringify(packet.data);
break;
case 'connect':
if (packet.qs)
data = packet.qs;
break;
case 'ack':
data = packet.ackId
+ (packet.args && packet.args.length
? '+' + JSON.stringify(packet.args) : '');
break;
case 'connect':
if (packet.qs)
data = packet.qs;
break;
case 'error':
var reason = packet.reason ? reasons[packet.reason] : ''
, adv = packet.advice ? advice[packet.advice] : ''
if (reason !== '' || adv !== '')
data = reason + (adv !== '' ? ('+' + adv) : '')
break;
}
// construct packet with required fragments
var encoded = [
type
, id + (ack == 'data' ? '+' : '')
, endpoint
];
var encoded = type + ':' + id + (ack == 'data' ? '+' : '') + ':' + endpoint;
// data fragment is optional
if (data !== null && data !== undefined)
encoded.push(data);
encoded += ':' + data;
return encoded.join(':');
return encoded;
};
/**
@@ -140,6 +139,18 @@ exports.encodePayload = function (packets) {
var regexp = /([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/;
/**
* Wrap the JSON.parse in a seperate function the crankshaft optimizer will
* only punish this function for the usage for try catch
*
* @api private
*/
function parse (data) {
try { return JSON.parse(data) }
catch (e) { return false }
}
exports.decodePacket = function (data) {
var pieces = data.match(regexp);
@@ -148,7 +159,7 @@ exports.decodePacket = function (data) {
var id = pieces[2] || ''
, data = pieces[5] || ''
, packet = {
type: packets[pieces[1]]
type: packetslist[pieces[1]]
, endpoint: pieces[4] || ''
};
@@ -163,30 +174,22 @@ exports.decodePacket = function (data) {
// handle different packet types
switch (packet.type) {
case 'error':
var pieces = data.split('+');
packet.reason = reasons[pieces[0]] || '';
packet.advice = advice[pieces[1]] || '';
break;
case 'message':
packet.data = data || '';
break;
case 'event':
try {
var opts = JSON.parse(data);
packet.name = opts.name;
packet.args = opts.args;
} catch (e) { }
pieces = parse(data);
if (pieces) {
packet.name = pieces.name;
packet.args = pieces.args;
}
packet.args = packet.args || [];
break;
case 'json':
try {
packet.data = JSON.parse(data);
} catch (e) { }
packet.data = parse(data);
break;
case 'connect':
@@ -194,23 +197,22 @@ exports.decodePacket = function (data) {
break;
case 'ack':
var pieces = data.match(/^([0-9]+)(\+)?(.*)/);
pieces = data.match(/^([0-9]+)(\+)?(.*)/);
if (pieces) {
packet.ackId = pieces[1];
packet.args = [];
if (pieces[3]) {
try {
packet.args = pieces[3] ? JSON.parse(pieces[3]) : [];
} catch (e) { }
packet.args = parse(pieces[3]) || [];
}
}
break;
case 'disconnect':
case 'heartbeat':
break;
};
case 'error':
pieces = data.split('+');
packet.reason = reasonslist[pieces[0]] || '';
packet.advice = advicelist[pieces[1]] || '';
}
return packet;
};

View File

@@ -15,7 +15,7 @@ var client = require('socket.io-client');
* Version.
*/
exports.version = '0.8.5';
exports.version = '0.8.6';
/**
* Supported protocol version.

View File

@@ -42,7 +42,8 @@ var mime = {
* @api private
*/
var bundle = /\+((?:\+)?[\w\-]+)*(?:\.js)$/g;
var bundle = /\+((?:\+)?[\w\-]+)*(?:\.v\d+\.\d+\.\d+)?(?:\.js)$/
, versioning = /\.v\d+\.\d+\.\d+(?:\.js)$/;
/**
* Export the constructor
@@ -120,10 +121,14 @@ Static.prototype.init = function () {
build(self.manager.get('transports'), callback);
});
this.add('/socket.io.v', { mime: mime.js }, function (path, callback) {
build(self.manager.get('transports'), callback);
});
// allow custom builds based on url paths
this.add('/socket.io+', { mime: mime.js }, function (path, callback) {
var available = self.manager.get('transports')
, matches = bundle.exec(path)
, matches = path.match(bundle)
, transports = [];
if (!matches) return callback('No valid transports');
@@ -217,7 +222,7 @@ Static.prototype.has = function (path) {
, i = keys.length;
while (i--) {
if (!!~path.indexOf(keys[i])) return this.paths[keys[i]];
if (-~path.indexOf(keys[i])) return this.paths[keys[i]];
}
return false;
@@ -271,7 +276,13 @@ Static.prototype.write = function (path, req, res) {
function write (status, headers, content, encoding) {
try {
res.writeHead(status, headers || undefined);
res.end(content || '', encoding || undefined);
// only write content if it's not a HEAD request and we actually have
// some content to write (304's doesn't have content).
res.end(
req.method !== 'HEAD' && content ? content : ''
, encoding || undefined
);
} catch (e) {}
}
@@ -291,19 +302,28 @@ Static.prototype.write = function (path, req, res) {
var accept = req.headers['accept-encoding'] || ''
, gzip = !!~accept.toLowerCase().indexOf('gzip')
, mime = reply.mime
, versioned = reply.versioned
, headers = {
'Content-Type': mime.type
};
// check if we can add a etag
if (self.manager.enabled('browser client etag') && reply.etag) {
if (self.manager.enabled('browser client etag') && reply.etag && !versioned) {
headers['Etag'] = reply.etag;
}
// check if we can send gzip data
// see if we need to set Expire headers because the path is versioned
if (versioned) {
var expires = self.manager.get('browser client expires');
headers['Cache-Control'] = 'private, x-gzip-ok="", max-age=' + expires;
headers['Date'] = new Date().toUTCString();
headers['Expires'] = new Date(Date.now() + (expires * 1000)).toUTCString();
}
if (gzip && reply.gzip) {
headers['Content-Length'] = reply.gzip.length;
headers['Content-Encoding'] = 'gzip';
headers['Vary'] = 'Accept-Encoding';
write(200, headers, reply.gzip.content, mime.encoding);
} else {
headers['Content-Length'] = reply.length;
@@ -342,6 +362,7 @@ Static.prototype.write = function (path, req, res) {
, length: content.length
, mime: details.mime
, etag: etag || client.version
, versioned: versioning.test(path)
};
// check if gzip is enabled

View File

@@ -84,6 +84,9 @@ function Redis (opts) {
}
Store.call(this, opts);
this.sub.setMaxListeners(0);
this.setMaxListeners(0);
};
/**

View File

@@ -52,7 +52,7 @@ HTMLFile.prototype.handleRequest = function (req) {
if (req.method == 'GET') {
req.res.writeHead(200, {
'Content-Type': 'text/html'
'Content-Type': 'text/html; charset=UTF-8'
, 'Connection': 'keep-alive'
, 'Transfer-Encoding': 'chunked'
});

View File

@@ -54,6 +54,24 @@ JSONPPolling.prototype.name = 'jsonppolling';
JSONPPolling.prototype.postEncoded = true;
/**
* Handles incoming data.
* Due to a bug in \n handling by browsers, we expect a JSONified string.
*
* @api private
*/
JSONPPolling.prototype.onData = function (data) {
try {
data = JSON.parse(data);
} catch (e) {
this.error('parse', 'reconnect');
return;
}
HTTPPolling.prototype.onData.call(this, data);
};
/**
* Performs the write.
*

View File

@@ -41,7 +41,13 @@ function WebSocket (mng, data, req) {
});
this.parser.on('ping', function () {
// version 8 ping => pong
self.socket.write('\u008a\u0000');
try {
self.socket.write('\u008a\u0000');
}
catch (e) {
self.end();
return;
}
});
this.parser.on('close', function () {
self.end();
@@ -85,7 +91,7 @@ WebSocket.prototype.protocolVersion = '07-12';
WebSocket.prototype.onSocketConnect = function () {
var self = this;
if (this.req.headers.upgrade !== 'websocket') {
if (this.req.headers.upgrade.toLowerCase() !== 'websocket') {
this.log.warn(this.name + ' connection invalid');
this.end();
return;
@@ -177,7 +183,13 @@ WebSocket.prototype.verifyOrigin = function (origin) {
WebSocket.prototype.write = function (data) {
if (this.open) {
var buf = this.frame(0x81, data);
this.socket.write(buf, 'binary');
try {
this.socket.write(buf, 'binary');
}
catch (e) {
this.end();
return;
}
this.log.debug(this.name + ' writing', data);
}
};

View File

@@ -41,7 +41,13 @@ function WebSocket (mng, data, req) {
});
this.parser.on('ping', function () {
// version 8 ping => pong
self.socket.write('\u008a\u0000');
try {
self.socket.write('\u008a\u0000');
}
catch (e) {
self.end();
return;
}
});
this.parser.on('close', function () {
self.end();
@@ -85,7 +91,7 @@ WebSocket.prototype.protocolVersion = '16';
WebSocket.prototype.onSocketConnect = function () {
var self = this;
if (this.req.headers.upgrade !== 'websocket') {
if (this.req.headers.upgrade.toLowerCase() !== 'websocket') {
this.log.warn(this.name + ' connection invalid');
this.end();
return;
@@ -177,7 +183,13 @@ WebSocket.prototype.verifyOrigin = function (origin) {
WebSocket.prototype.write = function (data) {
if (this.open) {
var buf = this.frame(0x81, data);
this.socket.write(buf, 'binary');
try {
this.socket.write(buf, 'binary');
}
catch (e) {
this.end();
return;
}
this.log.debug(this.name + ' writing', data);
}
};

View File

@@ -1,6 +1,6 @@
{
"name": "socket.io"
, "version": "0.8.5"
, "version": "0.8.6"
, "description": "Real-time apps made cross-browser & easy with a WebSocket-like API"
, "homepage": "http://socket.io"
, "keywords": ["websocket", "socket", "realtime", "socket.io", "comet", "ajax"]
@@ -16,14 +16,17 @@
, "url": "https://github.com/LearnBoost/socket.io.git"
}
, "dependencies": {
"socket.io-client": "0.8.5"
"socket.io-client": "0.8.6"
, "policyfile": "0.0.4"
, "redis": "0.6.6"
, "redis": "0.6.7"
}
, "devDependencies": {
"expresso": "0.7.7"
, "should": "0.0.4"
, "assertvanish": "0.0.3-1"
, "benchmark": "0.2.2"
, "microtime": "0.1.3-1"
, "colors": "0.5.1"
}
, "main": "index"
, "engines": { "node": ">= 0.4.0" }

View File

@@ -494,6 +494,13 @@ var WebSocket = function(url, proto, opts) {
return function(req, s, head) {
stream = s;
if (readyState == CLOSED) {
stream.end();
stream.destroy();
stream = undefined;
return;
}
stream.on('data', function(d) {
if (d.length <= 0) {
return;

View File

@@ -147,6 +147,24 @@ HTTPClient.prototype.post = function (path, data, opts, fn) {
return this.request(path, opts, fn);
};
/**
* Issue a HEAD request
*
* @api private
*/
HTTPClient.prototype.head = function (path, opts, fn) {
if ('function' == typeof opts) {
fn = opts;
opts = {};
}
opts = opts || {};
opts.method = 'HEAD';
return this.request(path, opts, fn);
};
/**
* Performs a handshake (GET) request
*
@@ -179,7 +197,6 @@ client = function (port) {
*/
create = function (cl) {
console.log('');
var manager = io.listen(cl.port);
manager.set('client store expiration', 0);
return manager;

View File

@@ -515,6 +515,49 @@ module.exports = {
io.get('resource').should.equal('/my resource');
io.get('custom').should.equal('opt');
io.server.close();
done();
},
'test disabling the log': function (done) {
var port = ++ports
, io = sio.listen(port, { log: false })
, _console = console.log
, calls = 0;
// the logger uses console.log to output data, override it to see if get's
// used
console.log = function () { ++calls };
io.log.debug('test');
io.log.log('testing');
console.log = _console;
calls.should.equal(0);
io.server.close();
done();
},
'test disabling logging with colors': function (done) {
var port = ++ports
, io = sio.listen(port, { 'log colors': false })
, _console = console.log
, calls = 0;
// the logger uses console.log to output data, override it to see if get's
// used
console.log = function (data) {
++calls;
data.indexOf('\033').should.equal(-1);
};
io.log.debug('test');
io.log.log('testing');
console.log = _console;
calls.should.equal(2);
io.server.close();
done();
}

View File

@@ -243,5 +243,44 @@ module.exports = {
}
});
})
},
'ignoring blacklisted events': function (done) {
var cl = client(++ports)
, io = create(cl)
, calls = 0
, ws;
io.set('heartbeat interval', 1);
io.set('blacklist', ['foobar']);
io.sockets.on('connection', function (socket) {
socket.on('foobar', function () {
calls++;
});
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
ws.on('open', function (){
ws.packet({
type: 'event'
, name: 'foobar'
, endpoint: ''
});
});
ws.on('message', function (data) {
if (data.type === 'heartbeat') {
cl.end();
ws.finishClose();
io.server.close();
calls.should.equal(0);
done();
}
});
});
}
};

View File

@@ -24,7 +24,9 @@ module.exports = {
, io = sio.listen(port);
(!!io.static.has('/socket.io.js')).should.be.true;
(!!io.static.has('/socket.io+')).should.be.true;
(!!io.static.has('/socket.io.v1.0.0.js')).should.be.true;
(!!io.static.has('/socket.io+xhr-polling.js')).should.be.true;
(!!io.static.has('/socket.io+xhr-polling.v1.0.0.js')).should.be.true;
(!!io.static.has('/static/flashsocket/WebSocketMain.swf')).should.be.true;
(!!io.static.has('/static/flashsocket/WebSocketMainInsecure.swf')).should.be.true;
@@ -455,6 +457,89 @@ module.exports = {
io.server.close();
done();
});
}
},
'test that HEAD requests work': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.head('/socket.io/socket.io.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
data.should.eql('');
cl.end();
io.server.close()
done();
});
},
'test that a versioned client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
cl.get('/socket.io/socket.io.v0.8.9.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers['cache-control']
.indexOf(io.get('browser client expires')).should.be.above(-1);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
},
'test that a custom versioned build client is served': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.set('browser client expires', 1337);
cl.get('/socket.io/socket.io+websocket.v0.8.10.js', function (res, data) {
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers['cache-control']
.indexOf(io.get('browser client expires')).should.be.above(-1);
data.should.match(/XMLHttpRequest/);
data.should.match(/WS\.prototype\.name/);
data.should.not.match(/Flashsocket\.prototype\.name/);
data.should.not.match(/HTMLFile\.prototype\.name/);
data.should.not.match(/JSONPPolling\.prototype\.name/);
data.should.not.match(/XHRPolling\.prototype\.name/);
cl.end();
io.server.close();
done();
});
},
'test that etags are ignored for versioned requests': function (done) {
var port = ++ports
, io = sio.listen(port)
, cl = client(port);
io.enable('browser client etag');
cl.get('/socket.io/socket.io.v0.8.9.js', function (res, data) {
should.strictEqual(res.headers.etag, undefined);
res.headers['content-type'].should.eql('application/javascript');
res.headers['content-length'].should.match(/([0-9]+)/);
res.headers['cache-control']
.indexOf(io.get('browser client expires')).should.be.above(-1);
data.should.match(/XMLHttpRequest/);
cl.end();
io.server.close();
done();
});
},
};

View File

@@ -168,13 +168,18 @@ module.exports = {
'flashsocket identifies as flashsocket': function (done) {
var cl = client(++ports)
, io = create(cl)
, messages = 0
, ws;
io.set('transports', ['flashsocket']);
io.sockets.on('connection', function (socket) {
socket.manager.transports[socket.id].name.should.equal('flashsocket');
ws.finishClose();
cl.end();
io.flashPolicyServer.close();
io.server.close();
done();
});
cl.handshake(function (sid) {
ws = websocket(cl, sid, 'flashsocket');
});

View File

@@ -323,7 +323,7 @@ module.exports = {
io.sockets.on('connection', function (client) {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'disconnect' })
, JSON.stringify(parser.encodePacket({ type: 'disconnect' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
@@ -390,11 +390,11 @@ module.exports = {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePayload([
, JSON.stringify(parser.encodePayload([
parser.encodePacket({ type: 'message', data: 'a' })
, parser.encodePacket({ type: 'message', data: 'b' })
, parser.encodePacket({ type: 'disconnect' })
])
]))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
@@ -504,7 +504,7 @@ module.exports = {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/woot' })
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/woot' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
@@ -585,21 +585,21 @@ module.exports = {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function () {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/woot' })
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/woot' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'disconnect', endpoint: '/woot' })
, JSON.stringify(parser.encodePacket({ type: 'disconnect', endpoint: '/woot' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'message', data: 'ferret' })
, JSON.stringify(parser.encodePacket({ type: 'message', data: 'ferret' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
@@ -656,7 +656,7 @@ module.exports = {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/a' })
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/a' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
@@ -665,7 +665,7 @@ module.exports = {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/b' })
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/b' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
@@ -721,7 +721,7 @@ module.exports = {
cl.get('/socket.io/{protocol}/jsonp-polling/' + sid, function (res, data) {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'message', data: '' })
, JSON.stringify(parser.encodePacket({ type: 'message', data: '' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
@@ -730,14 +730,14 @@ module.exports = {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/a' })
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/a' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'message', endpoint: '/a', data: 'a' })
, JSON.stringify(parser.encodePacket({ type: 'message', endpoint: '/a', data: 'a' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
@@ -748,14 +748,14 @@ module.exports = {
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'connect', endpoint: '/b' })
, JSON.stringify(parser.encodePacket({ type: 'connect', endpoint: '/b' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');
cl.post(
'/socket.io/{protocol}/jsonp-polling/' + sid
, parser.encodePacket({ type: 'message', endpoint: '/b', data: 'b' })
, JSON.stringify(parser.encodePacket({ type: 'message', endpoint: '/b', data: 'b' }))
, function (res, data) {
res.statusCode.should.eql(200);
data.should.eql('1');

View File

@@ -19,7 +19,24 @@ var sio = require('socket.io')
*/
module.exports = {
'websocket identifies as websocket': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
io.set('transports', ['websocket']);
io.sockets.on('connection', function (socket) {
socket.manager.transports[socket.id].name.should.equal('websocket');
ws.finishClose();
cl.end();
io.server.close();
done();
});
cl.handshake(function (sid) {
ws = websocket(cl, sid);
});
},
'default websocket draft parser is used for unknown sec-websocket-version': function (done) {
var cl = client(++ports)
, io = create(cl)
@@ -28,17 +45,10 @@ module.exports = {
io.set('transports', ['websocket']);
io.sockets.on('connection', function (socket) {
socket.manager.transports[socket.id].protocolVersion.should.equal('hixie-76');
socket.on('disconnect', function () {
setTimeout(function () {
ws.finishClose();
cl.end();
io.server.close();
done();
}, 10);
});
socket.disconnect();
ws.finishClose();
cl.end();
io.server.close();
done();
});
cl.handshake(function (sid) {
@@ -48,22 +58,14 @@ module.exports = {
'hybi-07-12 websocket draft parser is used for sec-websocket-version: 8': function (done) {
var cl = client(++ports)
, io = create(cl)
, ws;
, io = create(cl);
io.set('transports', ['websocket']);
io.sockets.on('connection', function (socket) {
socket.manager.transports[socket.id].protocolVersion.should.equal('07-12');
socket.on('disconnect', function () {
setTimeout(function () {
cl.end();
io.server.close();
done();
}, 10);
});
socket.disconnect();
cl.end();
io.server.close();
done();
});
var headers = {
@@ -87,16 +89,9 @@ module.exports = {
io.sockets.on('connection', function (socket) {
socket.manager.transports[socket.id].protocolVersion.should.equal('16');
socket.on('disconnect', function () {
setTimeout(function () {
cl.end();
io.server.close();
done();
}, 10);
});
socket.disconnect();
cl.end();
io.server.close();
done();
});
var headers = {
@@ -131,10 +126,9 @@ module.exports = {
var sid = data.split(':')[0];
var url = '/socket.io/' + sio.protocol + '/websocket/' + sid;
cl.get(url, {headers: headers}, function (res, data) {});
res.client.onend = function() {
io.server.close();
done();
}
cl.end();
io.server.close();
done();
});
},
@@ -157,10 +151,9 @@ module.exports = {
var sid = data.split(':')[0];
var url = '/socket.io/' + sio.protocol + '/websocket/' + sid;
cl.get(url, {headers: headers}, function (res, data) {});
res.client.onend = function() {
io.server.close();
done();
}
cl.end();
io.server.close();
done();
});
},