diff --git a/lib/engine.io.js b/lib/engine.io.js index 888b8070..9b547c9f 100644 --- a/lib/engine.io.js +++ b/lib/engine.io.js @@ -6,6 +6,14 @@ var http = require('http') , debug = require('debug')('engine:core') +/** + * Server listeners. + * + * @api private + */ + +var serverListeners = []; + /** * Protocol revision number. * @@ -96,59 +104,140 @@ exports.listen = function (port, options, fn) { exports.attach = function (server, options) { var engine = new exports.Server(options) , options = options || {} - , path = (options.path || '/engine.io').replace(/\/$/, '') - , resource = options.resource || 'default' - // normalize path - path += '/' + resource + '/'; + function serverListener() { + for (var i=0; i=0.7 + for (var i = 0, l = listeners.length; i < l; i++) { + oldListeners[i] = listeners[i]; + } + + server.removeAllListeners('request'); - // copy the references onto a new array for node >=0.7 - for (var i = 0, l = listeners.length; i < l; i++) { - oldListeners[i] = listeners[i]; + server.on('close', function () { + for (var i = 0, l = instanceListeners.close.length; i < l; i++) { + instanceListeners.close[i](); + } + }); + + // add request handler + server.on('request', function (req, res) { + var fired = false; + for (var i = 0, l = instanceListeners.request.length; i < l; i++) { + if (instanceListeners.request[i](req, res)) { + fired = true; + break; + } + } + if (fired) return; + for (var i = 0, l = oldListeners.length; i < l; i++) { + oldListeners[i].call(server, req, res); + } + }); + + server.on('upgrade', function (req, socket, head) { + var fired = false; + for (var i = 0, l = instanceListeners.upgrade.length; i < l; i++) { + if (instanceListeners.upgrade[i](req, socket, head)) { + fired = true; + break; + } + } + if (fired) return; + // NOTE: `options.destroyUpgrade` must be equivalent for ALL `engine` instances using the same `server` instance! + if (false !== options.destroyUpgrade) { + socket.end(); + } + }); + + if (~engine.transports.indexOf('flashsocket') + && false !== options.policyFile) { + server.on('connection', function (socket) { + // NOTE: This only needs to be attached once to any `engine` instance as it does nothing + // specific to the instance. + engine.handleSocket(socket); + }); + } + + serverListeners.push([ + server, + instanceListeners + ]); + + return instanceListeners; } - server.removeAllListeners('request'); + var listener = serverListener(); - server.on('close', function () { + listener.close.push(function() { engine.close(); }); + var path = '/engine.io'; + if (typeof options.path === 'string') { + path = options.path; + } + var check = makePathCheck(path, options.resource); + // add request handler - server.on('request', function (req, res) { + listener.request.push(function (req, res) { if (check(req)) { - debug('intercepting request for path "%s"', path); engine.handleRequest(req, res); - } else { - for (var i = 0, l = oldListeners.length; i < l; i++) { - oldListeners[i].call(server, req, res); - } + return true; } + return false; }); if(~engine.transports.indexOf('websocket')) { - server.on('upgrade', function (req, socket, head) { + listener.upgrade.push(function (req, socket, head) { if (check(req)) { engine.handleUpgrade(req, socket, head); - } else if (false !== options.destroyUpgrade) { - socket.end(); + return true; } + return false; }); - } - - if (~engine.transports.indexOf('flashsocket') - && false !== options.policyFile) { - server.on('connection', function (socket) { - engine.handleSocket(socket); - }); - } + } return engine; }; + +/** + * Make function to check path/route. + * + * @param {Mixed} path + * @param {String} resource + * @return {Function} check function + * @api private + */ + +function makePathCheck(path, resource) { + if (typeof resource === 'function') { + return resource; + } else + if (typeof resource === 'object' && resource.test) { + return function (req) { + return resource.test(req.url.substring(path.length)); + }; + } else { + // normalize path + var route = path + '/' + (resource || 'default').replace(/^\/|\/$/g, '') + '/'; + return function (req) { + return route == req.url.substr(0, route.length); + } + } +} diff --git a/package.json b/package.json index 3e34e9ba..1e3475d3 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ , "author": "Guillermo Rauch " , "contributors": [ { "name": "Eugen Dueck", "web": "https://github.com/EugenDueck" }, - { "name": "Afshin Mehrabani", "web": "https://github.com/afshinm" } + { "name": "Afshin Mehrabani", "web": "https://github.com/afshinm" }, + { "name": "Christoph Dorn", "web": "https://github.com/cadorn" } ] , "dependencies": { "debug": "0.6.0" diff --git a/test/engine.io.js b/test/engine.io.js index 0c8d1df1..75cf2eaf 100644 --- a/test/engine.io.js +++ b/test/engine.io.js @@ -193,4 +193,91 @@ describe('engine', function () { }); }); + describe('attach() at resource', function () { + it('should attach at string resource', function (done) { + var server = http.createServer() + , engine = eio.attach(server, { + resource: "/custom/path" + }); + + server.listen(function () { + var uri = ('http://localhost:%d/engine.io/custom/path/').s(server.address().port); + request.get(uri, function (res) { + expect(res.status).to.be(500); + server.once('close', done); + server.close(); + }); + }); + }); + + it('should attach at regexp resource', function (done) { + var server = http.createServer() + , engine = eio.attach(server, { + resource: /^\/custom\/[^\/]+/ + }); + + server.listen(function () { + var uri = ('http://localhost:%d/engine.io/custom/path/').s(server.address().port); + request.get(uri, function (res) { + expect(res.status).to.be(500); + server.once('close', done); + server.close(); + }); + }); + }); + + it('should attach at check function resource', function (done) { + var server = http.createServer() + , engine = eio.attach(server, { + resource: function(req) { + var path = '/engine.io/custom/path'; + return path == req.url.substr(0, path.length); + } + }); + + server.listen(function () { + var uri = ('http://localhost:%d/engine.io/custom/path/').s(server.address().port); + request.get(uri, function (res) { + expect(res.status).to.be(500); + server.once('close', done); + server.close(); + }); + }); + }); + + it('should work with many instances', function (done) { + // TODO: Capture `warning: possible EventEmitter memory leak detected. 11 listeners added.` and fail test. + var server = http.createServer(); + var tests = []; + function attach(i) { + var engine = eio.attach(server, { + resource: "/custom/path-" + i + }); + tests.push(function(done) { + var uri = ('http://localhost:%d/engine.io/custom/path-' + i + '/').s(server.address().port); + request.get(uri, function (res) { + expect(res.status).to.be(500); + done(); + }); + }); + } + for (var i=0; i<100; i++) { + attach(i); + } + server.listen(function () { + var counter = 0; + tests.forEach(function(test) { + counter += 1; + test(function() { + counter -= 1; + if (counter === 0) { + server.once('close', done); + server.close(); + } + }); + }); + }); + }); + }); + });