Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Regular expression & check function support for checking routes #76

Closed
wants to merge 9 commits into from
Closed
153 changes: 121 additions & 32 deletions lib/engine.io.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@
var http = require('http')
, debug = require('debug')('engine:core')

/**
* Server listeners.
*
* @api private
*/

var serverListeners = [];

/**
* Protocol revision number.
*
Expand Down Expand Up @@ -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<serverListeners.length; i++) {
if (serverListeners[i][0] === server) {
return serverListeners[i][1];
}
}

var instanceListeners = {
close: [],
request: [],
upgrade: []
};

function check (req) {
return path == req.url.substr(0, path.length);
}
// cache and clean up listeners
var listeners = server.listeners('request')
, oldListeners = []

// cache and clean up listeners
var listeners = server.listeners('request')
, oldListeners = []
// 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.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);
}
}
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
, "author": "Guillermo Rauch <guillermo@learnboost.com>"
, "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"
Expand Down
87 changes: 87 additions & 0 deletions test/engine.io.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
});
});
});
});
});

});