Accept multiple callbacks to app.use()

fixes #2224
This commit is contained in:
Douglas Christopher Wilson 2014-07-09 20:04:24 -04:00
parent a01326adac
commit 997a558a73
4 changed files with 115 additions and 42 deletions

View File

@ -1,6 +1,7 @@
unreleased
==========
* accept multiple callbacks to `app.use()`
* add explicit "Rosetta Flash JSONP abuse" protection
- previous versions are not vulnerable; this is just explicit protection
* catch errors in multiple `req.param(name, fn)` handlers

View File

@ -147,41 +147,49 @@ app.handle = function(req, res, done) {
* @api public
*/
app.use = function use(path, fn){
app.use = function use(path, fn) {
var mount_app;
var mount_path;
// default path to '/'
if (arguments.length < 2) {
fn = path;
path = '/';
// check for .use(path, app) or .use(app) signature
if (arguments.length <= 2) {
mount_path = typeof path === 'string'
? path
: '/';
mount_app = typeof path === 'function'
? path
: fn;
}
// express app
if (fn.handle && fn.set) mount_app = fn;
// setup router
this.lazyrouter();
var router = this._router;
// restore .app property on req and res
if (mount_app) {
debug('.use app under %s', path);
// express app
if (mount_app && mount_app.handle && mount_app.set) {
debug('.use app under %s', mount_path);
mount_app.mountpath = path;
fn = function(req, res, next) {
mount_app.parent = this;
// restore .app property on req and res
router.use(mount_path, function mounted_app(req, res, next) {
var orig = req.app;
mount_app.handle(req, res, function(err) {
req.__proto__ = orig.request;
res.__proto__ = orig.response;
next(err);
});
};
}
});
this.lazyrouter();
this._router.use(path, fn);
// mounted an app
if (mount_app) {
mount_app.parent = this;
// mounted an app
mount_app.emit('mount', this);
return this;
}
// pass-through use
router.use.apply(router, arguments);
return this;
};

View File

@ -9,6 +9,8 @@ var mixin = require('utils-merge');
var debug = require('debug')('express:router');
var parseUrl = require('parseurl');
var slice = Array.prototype.slice;
var toString = Object.prototype.toString;
var utils = require('../utils');
/**
* Initialize a new `Router` with the given `options`.
@ -394,37 +396,47 @@ proto.process_params = function(layer, called, req, res, done) {
* handlers can operate without any code changes regardless of the "prefix"
* pathname.
*
* @param {String|Function} route
* @param {Function} fn
* @return {app} for chaining
* @api public
*/
proto.use = function(path, fn){
proto.use = function use(fn) {
var offset = 0;
var path = '/';
var self = this;
// default path to '/'
if (arguments.length < 2) {
fn = path;
path = '/';
}
if (typeof fn !== 'function') {
var type = {}.toString.call(fn);
var msg = 'Router.use() requires callback functions but got a ' + type;
throw new Error(msg);
offset = 1;
path = fn;
}
var layer = new Layer(path, {
sensitive: this.caseSensitive,
strict: false,
end: false
}, fn);
var callbacks = utils.flatten(slice.call(arguments, offset));
layer.route = undefined;
if (callbacks.length === 0) {
throw new TypeError('Router.use() requires callback function');
}
// add the middleware
debug('use %s %s', path || '/', fn.name || 'anonymous');
callbacks.forEach(function (fn) {
if (typeof fn !== 'function') {
var type = toString.call(fn);
var msg = 'Router.use() requires callback function but got a ' + type;
throw new TypeError(msg);
}
// add the middleware
debug('use %s %s', path, fn.name || '<anonymous>');
var layer = new Layer(path, {
sensitive: self.caseSensitive,
strict: false,
end: false
}, fn);
layer.route = undefined;
self.stack.push(layer);
});
this.stack.push(layer);
return this;
};

View File

@ -16,9 +16,9 @@ describe('app', function(){
app.use(blog);
})
it('should reject numbers', function(){
it('should reject missing functions', function(){
var app = express();
app.use.bind(app, 3).should.throw(/Number/);
app.use.bind(app, 3).should.throw(/requires callback function/);
})
describe('.use(app)', function(){
@ -87,6 +87,32 @@ describe('app', function(){
})
describe('.use(middleware)', function(){
it('should accept multiple arguments', function (done) {
var app = express();
function fn1(req, res, next) {
res.setHeader('x-fn-1', 'hit');
next();
}
function fn2(req, res, next) {
res.setHeader('x-fn-2', 'hit');
next();
}
app.use(fn1, fn2, function fn3(req, res) {
res.setHeader('x-fn-3', 'hit');
res.end();
});
request(app)
.get('/')
.expect('x-fn-1', 'hit')
.expect('x-fn-2', 'hit')
.expect('x-fn-3', 'hit')
.expect(200, done);
})
it('should invoke middleware for all requests', function (done) {
var app = express();
var cb = after(3, done);
@ -122,6 +148,32 @@ describe('app', function(){
.expect(200, 'saw GET /bar', done);
})
it('should accept multiple arguments', function (done) {
var app = express();
function fn1(req, res, next) {
res.setHeader('x-fn-1', 'hit');
next();
}
function fn2(req, res, next) {
res.setHeader('x-fn-2', 'hit');
next();
}
app.use('/foo', fn1, fn2, function fn3(req, res) {
res.setHeader('x-fn-3', 'hit');
res.end();
});
request(app)
.get('/foo')
.expect('x-fn-1', 'hit')
.expect('x-fn-2', 'hit')
.expect('x-fn-3', 'hit')
.expect(200, done);
})
it('should invoke middleware for all requests starting with path', function (done) {
var app = express();
var cb = after(3, done);