engine.io
Advanced tools
Comparing version 0.5.0 to 0.6.0
0.6.0 / 2013-05-31 | ||
================== | ||
* socket: clear timer after sending one noop packet (fixes #174) | ||
* clear all timers on socket close | ||
* sending error on transport creation upon a bad request | ||
* added test for client-side buffer cleanup | ||
* changed flushComplete to flush | ||
* ended support for node 0.6 | ||
0.5.0 / 2013-03-16 | ||
@@ -3,0 +13,0 @@ ================== |
@@ -55,3 +55,4 @@ | ||
UNKNOWN_SID: 1, | ||
BAD_HANDSHAKE_METHOD: 2 | ||
BAD_HANDSHAKE_METHOD: 2, | ||
BAD_REQUEST: 3 | ||
}; | ||
@@ -62,3 +63,4 @@ | ||
1: 'Session ID unknown', | ||
2: 'Bad handshake method' | ||
2: 'Bad handshake method', | ||
3: 'Bad request' | ||
}; | ||
@@ -163,7 +165,3 @@ | ||
if (code !== true) { | ||
res.writeHead(400, { 'Content-Type': 'application/json' }); | ||
res.end(JSON.stringify({ | ||
code: code, | ||
message: Server.errorMessages[code] | ||
})); | ||
sendErrorMessage(res, code); | ||
return this; | ||
@@ -183,2 +181,18 @@ } | ||
/** | ||
* Sends an Engine.IO Error Message | ||
* | ||
* @param {http.ServerResponse} response | ||
* @param {code} error code | ||
* @api private | ||
*/ | ||
function sendErrorMessage(res, code) { | ||
res.writeHead(400, { 'Content-Type': 'application/json' }); | ||
res.end(JSON.stringify({ | ||
code: code, | ||
message: Server.errorMessages[code] | ||
})); | ||
} | ||
/** | ||
* Handshakes a new client. | ||
@@ -196,3 +210,9 @@ * | ||
var transport = new transports[transport](req); | ||
try { | ||
var transport = new transports[transport](req); | ||
} | ||
catch (e) { | ||
sendErrorMessage(req.res, Server.errors.BAD_REQUEST); | ||
return; | ||
} | ||
var socket = new Socket(id, this, transport); | ||
@@ -199,0 +219,0 @@ var self = this; |
@@ -5,4 +5,4 @@ /** | ||
var EventEmitter = require('events').EventEmitter | ||
, debug = require('debug')('engine:socket') | ||
var EventEmitter = require('events').EventEmitter; | ||
var debug = require('debug')('engine:socket'); | ||
@@ -165,7 +165,8 @@ /** | ||
var self = this; | ||
// set transport upgrade timer | ||
var checkInterval; | ||
var upgradeTimeout = setTimeout(function () { | ||
self.upgradeTimeoutTimer = setTimeout(function () { | ||
debug('client did not complete upgrade - closing transport'); | ||
clearInterval(checkInterval); | ||
clearInterval(self.checkIntervalTimer); | ||
if ('open' == transport.readyState) { | ||
@@ -176,3 +177,2 @@ transport.close(); | ||
var self = this; | ||
function onPacket (packet) { | ||
@@ -183,10 +183,9 @@ if ('ping' == packet.type && 'probe' == packet.data) { | ||
// we force a polling cycle to ensure a fast upgrade | ||
function check () { | ||
self.checkIntervalTimer = setInterval(function(){ | ||
if ('polling' == self.transport.name && self.transport.writable) { | ||
debug('writing a noop packet to polling for fast upgrade'); | ||
self.transport.send([{ type: 'noop' }]); | ||
clearInterval(self.checkIntervalTimer); | ||
} | ||
} | ||
checkInterval = setInterval(check, 100); | ||
}, 100); | ||
} else if ('upgrade' == packet.type && self.readyState == 'open') { | ||
@@ -200,4 +199,4 @@ debug('got upgrade packet - upgrading'); | ||
self.flush(); | ||
clearInterval(checkInterval); | ||
clearTimeout(upgradeTimeout); | ||
clearInterval(self.checkIntervalTimer); | ||
clearTimeout(self.upgradeTimeoutTimer); | ||
transport.removeListener('packet', onPacket); | ||
@@ -234,2 +233,5 @@ } else { | ||
if ('closed' != this.readyState) { | ||
clearTimeout(this.pingTimeoutTimer); | ||
clearInterval(this.checkIntervalTimer); | ||
clearTimeout(this.upgradeTimeoutTimer); | ||
var self = this; | ||
@@ -257,4 +259,4 @@ // clean writeBuffer in next tick, so developers can still | ||
var self = this; | ||
//the message was sent successfully, execute the callback | ||
this.transport.on('drain', function() { | ||
//the message was sent successfully, execute the callback | ||
this.transport.on('drain', function() { | ||
if (self.sentCallbackFn.length > 0) { | ||
@@ -334,3 +336,3 @@ var seqFn = self.sentCallbackFn.splice(0,1)[0]; | ||
if (!this.transport.supportsFraming) { | ||
this.sentCallbackFn.push(this.packetsFn) | ||
this.sentCallbackFn.push(this.packetsFn); | ||
} else { | ||
@@ -373,2 +375,5 @@ this.sentCallbackFn.push.apply(this.sentCallbackFn, this.packetsFn); | ||
if ('open' == this.readyState) { | ||
clearTimeout(this.pingTimeoutTimer); | ||
clearInterval(this.checkIntervalTimer); | ||
clearTimeout(this.upgradeTimeoutTimer); | ||
this.readyState = 'closing'; | ||
@@ -375,0 +380,0 @@ var self = this; |
{ | ||
"name": "engine.io" | ||
, "version": "0.5.0" | ||
, "version": "0.6.0" | ||
, "description": "The realtime engine behind Socket.IO. Provides the foundation of a bidirectional connection between client and server" | ||
@@ -27,3 +27,3 @@ , "main": "./lib/engine.io" | ||
, "superagent": "*" | ||
, "engine.io-client": "0.5.0" | ||
, "engine.io-client": "git://github.com/LearnBoost/engine.io-client#68780141ef" | ||
, "s": "*" | ||
@@ -30,0 +30,0 @@ } |
# Engine.IO: the realtime engine | ||
[![Build Status](https://secure.travis-ci.org/LearnBoost/engine.io.png)](http://travis-ci.org/LearnBoost/engine.io) | ||
[![NPM version](https://badge.fury.io/js/engine.io.png)](http://badge.fury.io/js/engine.io) | ||
@@ -131,3 +132,3 @@ `Engine` is the implementation of transport-based cross-browser/cross-device | ||
- Captures `upgrade` requests for a `http.Server`. In other words, makes | ||
a regular http.Server websocket-compatible. | ||
a regular http.Server WebSocket-compatible. | ||
- **Parameters** | ||
@@ -159,3 +160,3 @@ - `http.Server`: server to attach to. | ||
**Important**: if you plan to use engine.io in a scalable way, please | ||
**Important**: if you plan to use Engine.IO in a scalable way, please | ||
keep in mind the properties below will only reflect the clients connected | ||
@@ -180,3 +181,3 @@ to a single process. | ||
to (`['polling', 'websocket', 'flashsocket']`) | ||
- `allowUpgrades` (`Boolean`): whether to allow tranport upgrades | ||
- `allowUpgrades` (`Boolean`): whether to allow transport upgrades | ||
(`true`) | ||
@@ -225,3 +226,3 @@ - `cookie` (`String|Boolean`): name of the HTTP cookie that | ||
- **Arguments** | ||
- `String`: unicode string | ||
- `String`: Unicode string | ||
- `error` | ||
@@ -238,3 +239,3 @@ - Fired when an error occurs. | ||
- `packet` | ||
- Called when a socket reseived a packet (`message`, `ping`) | ||
- Called when a socket received a packet (`message`, `ping`) | ||
- **Arguments** | ||
@@ -282,3 +283,3 @@ - `type`: packet type | ||
Engine.IO is powered by [debug](http://github.com/visionmedia/debug). | ||
In order to see all the debug output, run your app with the env variable | ||
In order to see all the debug output, run your app with the environment variable | ||
`DEBUG` including the desired scope. | ||
@@ -362,7 +363,7 @@ | ||
The main goal of `Engine` is ensuring the most reliable realtime communication. | ||
Unlike the previous socket.io core, it always establishes a long-polling | ||
Unlike the previous Socket.IO core, it always establishes a long-polling | ||
connection first, then tries to upgrade to better transports that are "tested" on | ||
the side. | ||
During the lifetime of the socket.io projects, we've found countless drawbacks | ||
During the lifetime of the Socket.IO projects, we've found countless drawbacks | ||
to relying on `HTML5 WebSocket` or `Flash Socket` as the first connection | ||
@@ -415,3 +416,3 @@ mechanisms. | ||
As a result of our research, we've found that at least 3 personal security | ||
applications block websocket traffic. | ||
applications block WebSocket traffic. | ||
@@ -422,3 +423,3 @@ 3. **Cloud application platforms**<br> | ||
inevitably using long polling, but the seamless installation experience of | ||
socket.io we strive for (_"require() it and it just works"_) disappears. | ||
Socket.IO we strive for (_"require() it and it just works"_) disappears. | ||
@@ -425,0 +426,0 @@ Some of these problems have solutions. In the case of proxies and personal programs, |
@@ -226,10 +226,24 @@ /*global eio,eioc,listen,request,expect*/ | ||
}); | ||
it('should disallow bad requests', function (done) { | ||
var engine = listen(function (port) { | ||
request.get('http://localhost:%d/engine.io/default/'.s(port)) | ||
.query({ transport: 'websocket' }) | ||
.end(function (res) { | ||
expect(res.status).to.be(400); | ||
expect(res.body.code).to.be(3); | ||
expect(res.body.message).to.be('Bad request'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('close', function () { | ||
it('should be able to access non-empty writeBuffer at closing', function(done) { | ||
var opts = {allowUpgrades: false, pingInterval: 10, pingTimeout: 10 }; | ||
it('should be able to access non-empty writeBuffer at closing (server)', function(done) { | ||
var opts = {allowUpgrades: false}; | ||
var engine = listen(opts, function (port) { | ||
var socket = new eioc.Socket('http://localhost:%d'.s(port)); | ||
socket.sendPacket = function (){}; | ||
engine.on('connection', function (conn) { | ||
@@ -249,2 +263,23 @@ conn.on('close', function (reason) { | ||
it('should be able to access non-empty writeBuffer at closing (client)', function(done) { | ||
var opts = {allowUpgrades: false}; | ||
var engine = listen(opts, function (port) { | ||
var socket = new eioc.Socket('http://localhost:%d'.s(port)); | ||
socket.on('open', function() { | ||
socket.on('close', function (reason) { | ||
expect(socket.writeBuffer.length).to.be(1); | ||
expect(socket.callbackBuffer.length).to.be(1); | ||
setTimeout(function() { | ||
expect(socket.writeBuffer.length).to.be(0); | ||
expect(socket.callbackBuffer.length).to.be(0); | ||
}, 10); | ||
done(); | ||
}); | ||
socket.writeBuffer.push({ type: 'message', data: 'foo'}); | ||
socket.callbackBuffer.push(function() {}); | ||
socket.onError(''); | ||
}); | ||
}); | ||
}); | ||
it('should trigger on server if the client does not pong', function (done) { | ||
@@ -402,2 +437,29 @@ var opts = { allowUpgrades: false, pingInterval: 5, pingTimeout: 5 }; | ||
it('should trigger when calling socket.close() in payload', function (done) { | ||
var engine = listen({ allowUpgrades: false }, function (port) { | ||
var socket = new eioc.Socket('ws://localhost:%d'.s(port)) | ||
engine.on('connection', function (conn) { | ||
conn.send(null, function () {socket.close();}); | ||
conn.send('this should not be handled'); | ||
conn.on('close', function (reason) { | ||
expect(reason).to.be('transport close'); | ||
done(); | ||
}); | ||
}); | ||
socket.on('open', function () { | ||
socket.on('message', function (msg) { | ||
expect(msg).to.not.be('this should not be handled'); | ||
}); | ||
socket.on('close', function (reason) { | ||
expect(reason).to.be('forced close'); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('should abort upgrade if socket is closed (GH-35)', function (done) { | ||
@@ -809,3 +871,181 @@ var engine = listen({ allowUpgrades: true }, function (port) { | ||
describe('send', function() { | ||
describe('writeBuffer', function() { | ||
it('should not empty until `drain` event (polling)', function (done) { | ||
var engine = listen({ allowUpgrades: false }, function (port) { | ||
var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['polling'] }); | ||
var totalEvents = 2; | ||
socket.on('open', function() { | ||
socket.send('a'); | ||
socket.send('b'); | ||
// writeBuffer should be nonempty, with 'a' still in it | ||
expect(socket.writeBuffer.length).to.eql(2); | ||
}); | ||
socket.transport.on('drain', function() { | ||
expect(socket.writeBuffer.length).to.eql(--totalEvents); | ||
totalEvents || done(); | ||
}); | ||
}); | ||
}); | ||
it('should not empty until `drain` event (websocket)', function (done) { | ||
var engine = listen({ allowUpgrades: false }, function (port) { | ||
var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] }); | ||
var totalEvents = 2; | ||
socket.on('open', function() { | ||
socket.send('a'); | ||
socket.send('b'); | ||
// writeBuffer should be nonempty, with 'a' still in it | ||
expect(socket.writeBuffer.length).to.eql(2); | ||
}); | ||
socket.transport.on('drain', function() { | ||
expect(socket.writeBuffer.length).to.eql(--totalEvents); | ||
totalEvents || done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('callback', function() { | ||
it('should execute in order when message sent (client) (polling)', function (done) { | ||
var engine = listen({ allowUpgrades: false }, function (port) { | ||
var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['polling'] }); | ||
var i = 0; | ||
var j = 0; | ||
engine.on('connection', function(conn) { | ||
conn.on('message', function(msg) { | ||
conn.send(msg); | ||
}); | ||
}); | ||
socket.on('open', function () { | ||
socket.on('message', function(msg) { | ||
// send another packet until we've sent 3 total | ||
if (++i < 3) { | ||
expect(i).to.eql(j); | ||
sendFn(); | ||
} else { | ||
done(); | ||
} | ||
}); | ||
function sendFn() { | ||
socket.send(j, (function(value) { | ||
j++; | ||
})(j)); | ||
} | ||
sendFn(); | ||
}); | ||
}); | ||
}); | ||
it('should execute in order when message sent (client) (websocket)', function (done) { | ||
var engine = listen({ allowUpgrades: false }, function (port) { | ||
var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] }); | ||
var i = 0; | ||
var j = 0; | ||
engine.on('connection', function(conn) { | ||
conn.on('message', function(msg) { | ||
conn.send(msg); | ||
}); | ||
}); | ||
socket.on('open', function () { | ||
socket.on('message', function(msg) { | ||
// send another packet until we've sent 3 total | ||
if (++i < 3) { | ||
expect(i).to.eql(j); | ||
sendFn(); | ||
} else { | ||
done(); | ||
} | ||
}); | ||
function sendFn() { | ||
socket.send(j, (function(value) { | ||
j++; | ||
})(j)); | ||
} | ||
sendFn(); | ||
}); | ||
}); | ||
}); | ||
it('should execute in order with payloads (client) (polling)', function (done) { | ||
var engine = listen({ allowUpgrades: false }, function (port) { | ||
var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['polling'] }); | ||
var i = 0; | ||
var lastCbFired = 0; | ||
engine.on('connection', function(conn) { | ||
conn.on('message', function(msg) { | ||
conn.send(msg); | ||
}); | ||
}); | ||
socket.on('open', function () { | ||
socket.on('message', function(msg) { | ||
expect(msg).to.eql(i + 1); | ||
i++; | ||
}); | ||
function cb(value) { | ||
expect(value).to.eql(lastCbFired + 1); | ||
lastCbFired = value; | ||
if (value == 3) { | ||
done(); | ||
} | ||
} | ||
// 2 and 3 will be in the same payload | ||
socket.once('flush', function() { | ||
socket.send(2, function() { cb(2); }); | ||
socket.send(3, function() { cb(3); }); | ||
}); | ||
socket.send(1, function() { cb(1); }); | ||
}); | ||
}); | ||
}); | ||
it('should execute in order with payloads (client) (websocket)', function (done) { | ||
var engine = listen({ allowUpgrades: false }, function (port) { | ||
var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] }); | ||
var i = 0; | ||
var lastCbFired = 0; | ||
engine.on('connection', function(conn) { | ||
conn.on('message', function(msg) { | ||
conn.send(msg); | ||
}); | ||
}); | ||
socket.on('open', function () { | ||
socket.on('message', function(msg) { | ||
expect(msg).to.eql(i + 1); | ||
i++; | ||
}); | ||
function cb(value) { | ||
expect(value).to.eql(lastCbFired + 1); | ||
lastCbFired = value; | ||
if (value == 3) { | ||
done(); | ||
} | ||
} | ||
// 2 and 3 will be in the same payload | ||
socket.once('flush', function() { | ||
socket.send(2, function() { cb(2); }); | ||
socket.send(3, function() { cb(3); }); | ||
}); | ||
socket.send(1, function() { cb(1); }); | ||
}); | ||
}); | ||
}); | ||
it('should execute when message sent (polling)', function (done) { | ||
@@ -1159,162 +1399,2 @@ var engine = listen({ allowUpgrades: false }, function (port) { | ||
describe('JSONP', function () { | ||
before(function () { | ||
///we have to override the browser's functionality for JSONP | ||
document = { | ||
body: { | ||
appendChild: function () {} | ||
, removeChild: function () {} | ||
} | ||
} | ||
document.createElement = function (name) { | ||
var self = this; | ||
if('script' == name) { | ||
var script = {}; | ||
script.__defineGetter__('parentNode', function () { | ||
return document.body; | ||
}); | ||
script.__defineSetter__('src', function (uri) { | ||
request.get(uri).end(function(res) { | ||
eval(res.text); | ||
}); | ||
}); | ||
return script; | ||
} | ||
else if ('form' == name) { | ||
var form = { | ||
style: {} | ||
, action: '' | ||
, parentNode: { removeChild: function () {} } | ||
, removeChild: function () {} | ||
, setAttribute: function () {} | ||
, appendChild: function (elem) { area: elem; } | ||
, submit: function () { | ||
request.post(this.action).type('form').send({ d: self.areaValue }).end(function (res) {}); | ||
} | ||
} | ||
return form; | ||
} | ||
else if ('textarea' == name) { | ||
var textarea = {}; | ||
//a hack to be able to access the area data when form is sent | ||
textarea.__defineSetter__('value', function (data) { | ||
self.areaValue = data; | ||
}); | ||
return textarea; | ||
} else { | ||
return {} | ||
} | ||
} | ||
document.getElementsByTagName = function (name) { | ||
return [{ | ||
parentNode: { | ||
insertBefore: function () {} | ||
} | ||
}] | ||
} | ||
}); | ||
after(function () { | ||
delete document.getElementsByTagName | ||
, document.createElement | ||
, document; | ||
}); | ||
describe('handshake', function () { | ||
it('should open with polling JSONP when requested', function (done) { | ||
var engine = listen( { allowUpgrades: false, transports: ['polling'] }, function (port) { | ||
var socket = new eioc.Socket('ws://localhost:' + port | ||
, { transports: ['polling'], forceJSONP: true, upgrade: false }); | ||
engine.on('connection', function (socket) { | ||
expect(socket.transport.name).to.be('polling'); | ||
expect(socket.transport.head).to.be('___eio[0]('); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('messages', function () { | ||
it('should arrive from client to server and back (pollingJSONP)', function (done) { | ||
var engine = listen( { allowUpgrades: false, transports: ['polling'] }, function (port) { | ||
var socket = new eioc.Socket('ws://localhost:' + port | ||
, { transports: ['polling'], forceJSONP: true, upgrade: false }); | ||
engine.on('connection', function (conn) { | ||
conn.on('message', function (msg) { | ||
conn.send('a'); | ||
}); | ||
}); | ||
socket.on('open', function () { | ||
socket.send('a'); | ||
socket.on('message', function (msg) { | ||
expect(socket.transport.query.j).to.not.be(undefined); | ||
expect(msg).to.be('a'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('close', function () { | ||
it('should trigger when server closes a client', function (done) { | ||
var engine = listen( { allowUpgrades: false, transports: ['polling'] }, function (port) { | ||
var socket = new eioc.Socket('ws://localhost:' + port | ||
, { transports: ['polling'], forceJSONP: true, upgrade: false }) | ||
, total = 2; | ||
engine.on('connection', function (conn) { | ||
conn.on('close', function (reason) { | ||
expect(reason).to.be('forced close'); | ||
--total || done(); | ||
}); | ||
setTimeout(function () { | ||
conn.close(); | ||
}, 10); | ||
}); | ||
socket.on('open', function () { | ||
socket.on('close', function (reason) { | ||
expect(reason).to.be('transport close'); | ||
--total || done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
it('should trigger when client closes', function (done) { | ||
var engine = listen( { allowUpgrades: false, transports: ['polling'] }, function (port) { | ||
var socket = new eioc.Socket('ws://localhost:' + port | ||
, { transports: ['polling'], forceJSONP: true, upgrade: false }) | ||
, total = 2; | ||
engine.on('connection', function (conn) { | ||
conn.on('close', function (reason) { | ||
expect(reason).to.be('transport close'); | ||
--total || done(); | ||
}); | ||
}); | ||
socket.on('open', function () { | ||
socket.send('a'); | ||
socket.on('close', function (reason) { | ||
expect(reason).to.be('forced close'); | ||
--total || done(); | ||
}); | ||
setTimeout(function () { | ||
socket.close(); | ||
}, 10); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
182766
30
5404
509
9