Comparar commits

..

15 Commits

Autor SHA1 Mensagem Data
Roman Shtylman f8b954bcd9 make express.Router() return a Router function instance
Similar to how express() returns an express `app` instance which is also
a function, express.Router() returns the Router instance which is also a
function and can be easily used via another router or the app.

app.use(express.Router());
2014-02-26 20:22:11 -05:00
Roman Shtylman caa25b506d Merge pull request #1935 from visionmedia/router-params-middleware
Router: add parameter handling to middleware
2014-02-25 12:35:13 -05:00
Roman Shtylman 6911815171 Router: add parameter handling to middleware
Middleware (.use) can now specify parameter arguments to trigger
Router.param loading. This is handy if you want to `.use` additional
routers but need to load certain objects before the mounted middleware
runs.
2014-02-23 19:21:13 -05:00
Roman Shtylman 0719e5f402 implement app.route() 2014-02-23 11:31:43 -05:00
Roman Shtylman 07b731add0 bump cookie parser dependency to 1.0.1 2014-02-22 09:26:30 -05:00
Roman Shtylman d42d8f5b07 move support for multiple res.cookie calls to lib/response
Patch.js is simpler and follows upstream node.js closer as a result.
2014-02-22 09:26:30 -05:00
Roman Shtylman 143e72dd85 remove support for node 0.8 2014-02-22 09:26:30 -05:00
Roman Shtylman 6835289564 remove ServerResonse.headerSent monkey patch
node.js ServerResponse contains a headersSent field. Use that instead of
our patched misnamed version.
2014-02-22 09:26:29 -05:00
Roman Shtylman 1396e0855d remove last pieces of connect dependency
- copy over patch.js to shim ServerResponse
- bundle `static` middleware
2014-02-22 09:26:29 -05:00
Roman Shtylman 6a7363e4ae use local copy of parseUrl 2014-02-22 09:26:29 -05:00
Roman Shtylman 9bc63d92a0 move connect.query() into our repo 2014-02-22 09:26:29 -05:00
TJ Holowaychuk 6b05f60bad update node-fresh 2014-02-19 15:29:39 -08:00
Jonathan Ong 25e6629bcc update history 2014-02-08 11:40:48 -08:00
Jonathan Ong 0796c1d2d2 test app.router: ignore connect method
so tests pass in 0.11. 0.11 client seems to throw errors more often, so
this is not an issue with express or node’s servers.
2014-02-08 11:39:26 -08:00
Jonathan Ong aac1d52c4f res.location: remove resolving relative urls
closes #1804

this is an unnecessary maintenance burden (see the number of removed
tests), especially when supporting mounting. browsers handle relative
locations, and so should all clients.

a regression could be absolute locations on a mounted app, but 1. we
can fix that later when someone complains and 2) code-smell
2014-02-08 11:37:43 -08:00
16 arquivos alterados com 261 adições e 417 exclusões
+5 -4
Ver Arquivo
@@ -3,17 +3,18 @@
* remove:
- express(1) - moved to [express-generator](https://github.com/expressjs/generator)
- `req.accepted*` - use `req.accepts*()` instead
- `app.configure` - use logic in your own app code
- `express.createServer()` - it has been deprecated for a long time. Use `express()`
- `app.configure` - use logic in your own app code
- `app.router` - is removed
- `req.accepted*` - use `req.accepts*()` instead
- `res.location` - relative URL resolution is removed
- all bundled middleware except `static`
* change:
- `app.route` -> `app.mountpath` when mounting an express app in another express app
- `json spaces` no longer enabled by default in development
- `req.accepts*` -> `req.accepts*s` - i.e. `req.acceptsEncoding` -> `req.acceptsEncodings`
- `req.params` is now an object instead of an array
- `json spaces` no longer enabled by default in development
- `res.locals` is no longer a function. It is a plain js object. Treat it as such.
- `app.route` -> `app.mountpath` when mounting an express app in another express app
- `res.headerSent` -> `res.headersSent` to match node.js ServerResponse object
* refactor:
- `req.accepts*` with [accepts](https://github.com/expressjs/accepts)
+2
Ver Arquivo
@@ -214,6 +214,8 @@ app.use = function(route, fn){
*/
app.route = function(path){
this.lazyrouter();
return this._router.route(path);
};
/**
+2 -31
Ver Arquivo
@@ -623,47 +623,18 @@ res.cookie = function(name, val, options){
*
* res.location('/foo/bar').;
* res.location('http://example.com');
* res.location('../login'); // /blog/post/1 -> /blog/login
*
* Mounting:
*
* When an application is mounted and `res.location()`
* is given a path that does _not_ lead with "/" it becomes
* relative to the mount-point. For example if the application
* is mounted at "/blog", the following would become "/blog/login".
*
* res.location('login');
*
* While the leading slash would result in a location of "/login":
*
* res.location('/login');
* res.location('../login');
*
* @param {String} url
* @api public
*/
res.location = function(url){
var app = this.app
, req = this.req
, path;
var req = this.req;
// "back" is an alias for the referrer
if ('back' == url) url = req.get('Referrer') || '/';
// relative
if (!~url.indexOf('://') && 0 != url.indexOf('//')) {
// relative to path
if ('.' == url[0]) {
path = req.originalUrl.split('?')[0];
path = path + ('/' == path[path.length - 1] ? '' : '/');
url = resolve(path, url);
// relative to mount-point
} else if ('/' != url[0]) {
path = app.path();
url = path + '/' + url;
}
}
// Respond
this.set('Location', url);
return this;
+97 -68
Ver Arquivo
@@ -3,36 +3,38 @@
*/
var Route = require('./route')
, Layer = require('./layer')
, utils = require('../utils')
, methods = require('methods')
, debug = require('debug')('express:router')
, parseUrl = utils.parseUrl;
/**
* Expose `Router` constructor.
*/
exports = module.exports = Router;
/**
* Initialize a new `Router` with the given `options`.
*
* @param {Object} options
* @api private
* @return {Router} which is an callable function
* @api public
*/
function Router(options) {
var proto = module.exports = function(options) {
options = options || {};
var self = this;
self.params = {};
self._params = [];
self.caseSensitive = options.caseSensitive;
self.strict = options.strict;
self.stack = [];
function router(req, res, next) {
router.handle(req, res, next);
};
self.middleware = self.handle.bind(self);
}
// mixin Router class functions
router.__proto__ = proto;
router.params = {};
router._params = [];
router.caseSensitive = options.caseSensitive;
router.strict = options.strict;
router.stack = [];
return router;
};
/**
* Map the given param placeholder `name`(s) to the given callback.
@@ -68,7 +70,7 @@ function Router(options) {
* @api public
*/
Router.prototype.param = function(name, fn){
proto.param = function(name, fn){
// param logic
if ('function' == typeof name) {
this._params.push(name);
@@ -106,7 +108,7 @@ Router.prototype.param = function(name, fn){
* @api private
*/
Router.prototype.handle = function(req, res, done) {
proto.handle = function(req, res, done) {
var self = this;
debug('dispatching %s %s', req.method, req.url);
@@ -126,7 +128,7 @@ Router.prototype.handle = function(req, res, done) {
var options = [];
// middleware and routes
var stack = this.stack;
var stack = self.stack;
// for options requests, respond with a default if nothing else responds
if (method === 'options') {
@@ -162,62 +164,72 @@ Router.prototype.handle = function(req, res, done) {
var path = parseUrl(req).pathname;
if (undefined == path) path = '/';
if (!layer.match(path)) return next(err);
// route object and not middleware
var route = layer.route;
// handle route
// if final route, then we support options
if (route) {
// we don't run any routs with error first
if (err || !route.match(path)) {
if (err) {
return next(err);
}
req.params = route.params;
req.route = route;
// we can now dispatch to the route
if (method === 'options' && !route.methods['options']) {
options.push.apply(options, route._options());
}
}
return self.process_params(route, req, res, function(err) {
if (err) {
return next(err);
req.params = layer.params;
// this should be done for the layer
return self.process_params(layer, req, res, function(err) {
if (err) {
return next(err);
}
if (route) {
return layer.handle(req, res, next);
}
trim_prefix();
});
return next(err);
function trim_prefix() {
var c = path[layer.path.length];
if (c && '/' != c && '.' != c) return next(err);
// Trim off the part of the url that matches the route
// middleware (.use stuff) needs to have the path stripped
debug('trim prefix (%s) from url %s', removed, req.url);
removed = layer.path;
req.url = protohost + req.url.substr(protohost.length + removed.length);
// Ensure leading slash
if (!fqdn && '/' != req.url[0]) {
req.url = '/' + req.url;
slashAdded = true;
}
debug('%s %s : %s', layer.handle.name || 'anonymous', layer.path, req.originalUrl);
var arity = layer.handle.length;
if (err) {
if (arity === 4) {
layer.handle(err, req, res, next);
} else {
next(err);
}
route.dispatch(req, res, next);
});
}
// skip this layer if the path doesn't match.
if (0 != path.toLowerCase().indexOf(layer.path.toLowerCase())) return next(err);
var c = path[layer.path.length];
if (c && '/' != c && '.' != c) return next(err);
// Trim off the part of the url that matches the route
// middleware (.use stuff) needs to have the path stripped
debug('trim prefix (%s) from url %s', removed, req.url);
removed = layer.path;
req.url = protohost + req.url.substr(protohost.length + removed.length);
// Ensure leading slash
if (!fqdn && '/' != req.url[0]) {
req.url = '/' + req.url;
slashAdded = true;
}
debug('%s %s : %s', layer.handle.name || 'anonymous', layer.path, req.originalUrl);
var arity = layer.handle.length;
if (err) {
if (arity === 4) {
layer.handle(err, req, res, next);
} else if (arity < 4) {
layer.handle(req, res, next);
} else {
next(err);
}
} else if (arity < 4) {
layer.handle(req, res, next);
} else {
next(err);
}
} catch (err) {
next(err);
@@ -231,13 +243,18 @@ Router.prototype.handle = function(req, res, done) {
* @api private
*/
Router.prototype.process_params = function(route, req, res, done) {
proto.process_params = function(route, req, res, done) {
var self = this;
var params = this.params;
// captured parameters from the route, keys and values
var keys = route.keys || [];
// fast track
if (keys.length === 0) {
return done();
}
var i = 0;
var paramIndex = 0;
var key;
@@ -301,7 +318,7 @@ Router.prototype.process_params = function(route, req, res, done) {
* @api public
*/
Router.prototype.use = function(route, fn){
proto.use = function(route, fn){
// default route to '/'
if ('string' != typeof route) {
@@ -314,10 +331,16 @@ Router.prototype.use = function(route, fn){
route = route.slice(0, -1);
}
var layer = Layer(route, {
sensitive: this.caseSensitive,
strict: this.strict,
end: false
}, fn);
// add the middleware
debug('use %s %s', route || '/', fn.name || 'anonymous');
this.stack.push({ path: route, handle: fn });
this.stack.push(layer);
return this;
};
@@ -334,13 +357,18 @@ Router.prototype.use = function(route, fn){
* @api public
*/
Router.prototype.route = function(path){
var route = new Route(path, {
sensitive: this.caseSensitive,
strict: this.strict
});
proto.route = function(path){
var route = new Route(path);
this.stack.push({ path: path, route: route });
var layer = Layer(path, {
sensitive: this.caseSensitive,
strict: this.strict,
end: true
}, route.dispatch.bind(route));
layer.route = route;
this.stack.push(layer);
return route;
};
@@ -354,7 +382,7 @@ Router.prototype.route = function(path){
* @api public
*/
Router.prototype.all = function(path, fn) {
proto.all = function(path, fn) {
var route = this.route(path);
methods.forEach(function(method){
route[method](fn);
@@ -363,9 +391,10 @@ Router.prototype.all = function(path, fn) {
// create Router#VERB functions
methods.forEach(function(method){
Router.prototype[method] = function(path, fn){
proto[method] = function(path, fn){
var self = this;
self.route(path)[method](fn);
return self;
};
});
+61
Ver Arquivo
@@ -0,0 +1,61 @@
var utils = require('../utils')
, debug = require('debug')('express:router:layer')
function Layer(path, options, fn) {
if (!(this instanceof Layer)) {
return new Layer(path, options, fn);
}
debug('new %s', path);
options = options || {};
this.path = path;
this.params = {};
this.regexp = utils.pathRegexp(path
, this.keys = []
, options.sensitive
, options.strict
, options.end);
this.handle = fn;
}
/**
* Check if this route matches `path`, if so
* populate `.params`.
*
* @param {String} path
* @return {Boolean}
* @api private
*/
Layer.prototype.match = function(path){
var keys = this.keys
, params = this.params = {}
, m = this.regexp.exec(path)
, n = 0;
if (!m) return false;
for (var i = 1, len = m.length; i < len; ++i) {
var key = keys[i - 1];
try {
var val = 'string' == typeof m[i]
? decodeURIComponent(m[i])
: m[i];
} catch(e) {
var err = new Error("Failed to decode param '" + m[i] + "'");
err.status = 400;
throw err;
}
if (key) {
params[key.name] = val;
} else {
params[n++] = val;
}
}
return true;
};
module.exports = Layer;
+3 -58
Ver Arquivo
@@ -3,8 +3,7 @@
* Module dependencies.
*/
var utils = require('../utils')
, debug = require('debug')('express:router:route')
var debug = require('debug')('express:router:route')
, methods = require('methods')
/**
@@ -14,75 +13,21 @@ var utils = require('../utils')
module.exports = Route;
/**
* Initialize `Route` with the given HTTP `method`, `path`,
* and an array of `callbacks` and `options`.
*
* Options:
*
* - `sensitive` enable case-sensitive routes
* - `strict` enable strict matching for trailing slashes
* Initialize `Route` with the given `path`,
*
* @param {String} path
* @param {Object} options.
* @api private
*/
function Route(path, options) {
function Route(path) {
debug('new %s', path);
options = options || {};
this.path = path;
this.params = {};
this.regexp = utils.pathRegexp(path
, this.keys = []
, options.sensitive
, options.strict);
this.stack = undefined;
// route handlers for various http methods
this.methods = {};
}
/**
* Check if this route matches `path`, if so
* populate `.params`.
*
* @param {String} path
* @return {Boolean}
* @api private
*/
Route.prototype.match = function(path){
var keys = this.keys
, params = this.params = {}
, m = this.regexp.exec(path)
, n = 0;
if (!m) return false;
for (var i = 1, len = m.length; i < len; ++i) {
var key = keys[i - 1];
try {
var val = 'string' == typeof m[i]
? decodeURIComponent(m[i])
: m[i];
} catch(e) {
var err = new Error("Failed to decode param '" + m[i] + "'");
err.status = 400;
throw err;
}
if (key) {
params[key.name] = val;
} else {
params[n++] = val;
}
}
return true;
};
/**
* @return {Array} supported HTTP methods
* @api private
+3 -2
Ver Arquivo
@@ -132,11 +132,12 @@ function acceptParams(str, index) {
* @param {Array} keys
* @param {Boolean} sensitive
* @param {Boolean} strict
* @param {Boolean} end (whether to append $ to regex)
* @return {RegExp}
* @api private
*/
exports.pathRegexp = function(path, keys, sensitive, strict) {
exports.pathRegexp = function(path, keys, sensitive, strict, end) {
if (toString.call(path) == '[object RegExp]') return path;
if (Array.isArray(path)) path = '(' + path.join('|') + ')';
path = path
@@ -155,7 +156,7 @@ exports.pathRegexp = function(path, keys, sensitive, strict) {
})
.replace(/([\/.])/g, '\\$1')
.replace(/\*/g, '(.*)');
return new RegExp('^' + path + '$', sensitive ? '' : 'i');
return new RegExp('^' + path + ((end) ? '$' : ''), sensitive ? '' : 'i');
}
/**
+1 -1
Ver Arquivo
@@ -27,7 +27,7 @@
"range-parser": "1.0.0",
"cookie": "0.1.0",
"buffer-crc32": "0.2.1",
"fresh": "0.2.1",
"fresh": "0.2.2",
"methods": "0.1.0",
"send": "0.2.0",
"cookie-signature": "1.0.3",
-9
Ver Arquivo
@@ -6,15 +6,6 @@ var express = require('../')
describe('Route', function(){
describe('.match', function(){
it('should match', function(){
var route = new Route('/foo/bar');
assert(route.match('/foo/bar'));
assert(!route.match('/foo/baz'));
})
})
describe('.all', function(){
it('should add handler', function(done){
var route = new Route('/foo');
+60 -1
Ver Arquivo
@@ -6,7 +6,31 @@ var express = require('../')
describe('Router', function(){
describe('.middleware', function(){
it('should return a function with router methods', function() {
var router = Router();
assert(typeof router == 'function');
var router = new Router();
assert(typeof router == 'function');
assert(typeof router.get == 'function');
assert(typeof router.handle == 'function');
assert(typeof router.use == 'function');
});
it('should support .use of other routers', function(done) {
var router = Router();
var another = Router();
another.get('/bar', function(req, res) {
res.done();
});
router.use('/foo', another);
router.handle({ url: '/foo/bar', method: 'GET' }, { done: done });
});
describe('.handle', function(){
it('should dispatch', function(done){
var router = new Router();
@@ -94,4 +118,39 @@ describe('Router', function(){
done();
})
})
describe('.param', function() {
it('should call param function when routing VERBS', function(done) {
var router = new Router();
router.param('id', function(req, res, next, id) {
assert.equal(id, '123');
next();
});
router.get('/foo/:id/bar', function(req, res, next) {
assert.equal(req.params.id, '123');
next();
});
router.handle({ url: '/foo/123/bar', method: 'get' }, {}, done);
});
it('should call param function when routing middleware', function(done) {
var router = new Router();
router.param('id', function(req, res, next, id) {
assert.equal(id, '123');
next();
});
router.use('/foo/:id/bar', function(req, res, next) {
assert.equal(req.params.id, '123');
assert.equal(req.url, '/baz');
next();
});
router.handle({ url: '/foo/123/bar/baz', method: 'get' }, {}, done);
});
});
})
+3 -3
Ver Arquivo
@@ -18,7 +18,7 @@ describe('auth', function(){
it('should redirect to /login', function(done){
request(app)
.get('/')
.end(redirects(/\/login$/, done))
.end(redirects(/login$/, done))
})
})
@@ -26,7 +26,7 @@ describe('auth', function(){
it('should redirect to /login', function(done){
request(app)
.get('/restricted')
.end(redirects(/\/login$/,done))
.end(redirects(/login$/,done))
})
})
@@ -36,7 +36,7 @@ describe('auth', function(){
.post('/login')
.type('urlencoded')
.send('username=not-tj&password=foobar')
.end(redirects(/\/login$/, done))
.end(redirects(/login$/, done))
})
})
})
+1 -1
Ver Arquivo
@@ -31,7 +31,7 @@ describe('OPTIONS', function(){
var router = new express.Router();
router.get('/users', function(req, res){});
app.use(router.middleware);
app.use(router);
app.get('/other', function(req, res){});
request(app)
+20
Ver Arquivo
@@ -0,0 +1,20 @@
var express = require('../')
, request = require('./support/http')
describe('app.route', function(){
it('should return a new route', function(done){
var app = express();
app.route('/foo')
.get(function(req, res) {
res.send('get');
})
.post(function(req, res) {
res.send('post');
});
request(app)
.post('/foo')
.expect('post', done);
});
});
+2
Ver Arquivo
@@ -7,6 +7,8 @@ var express = require('../')
describe('app.router', function(){
describe('methods supported', function(){
methods.forEach(function(method){
if (method === 'connect') return;
it('should include ' + method.toUpperCase(), function(done){
if (method == 'delete') method = 'del';
var app = express();
-166
Ver Arquivo
@@ -18,171 +18,5 @@ describe('res', function(){
done();
})
})
describe('with leading //', function(){
it('should pass through scheme-relative urls', function(done){
var app = express();
app.use(function(req, res){
res.location('//cuteoverload.com').end();
});
request(app)
.get('/')
.end(function(err, res){
res.headers.should.have.property('location', '//cuteoverload.com');
done();
})
})
})
describe('with leading /', function(){
it('should construct scheme-relative urls', function(done){
var app = express();
app.use(function(req, res){
res.location('/login').end();
});
request(app)
.get('/')
.end(function(err, res){
res.headers.should.have.property('location', '/login');
done();
})
})
})
describe('with leading ./', function(){
it('should construct path-relative urls', function(done){
var app = express();
app.use(function(req, res){
res.location('./edit').end();
});
request(app)
.get('/post/1')
.end(function(err, res){
res.headers.should.have.property('location', '/post/1/edit');
done();
})
})
})
describe('with leading ../', function(){
it('should construct path-relative urls', function(done){
var app = express();
app.use(function(req, res){
res.location('../new').end();
});
request(app)
.get('/post/1')
.end(function(err, res){
res.headers.should.have.property('location', '/post/new');
done();
})
})
})
describe('with leading ./ and containing ..', function(){
it('should construct path-relative urls', function(done){
var app = express();
app.use(function(req, res){
res.location('./skip/../../new').end();
});
request(app)
.get('/post/1')
.end(function(err, res){
res.headers.should.have.property('location', '/post/new');
done();
})
})
})
describe('without leading /', function(){
it('should construct mount-point relative urls', function(done){
var app = express();
app.use(function(req, res){
res.location('login').end();
});
request(app)
.get('/')
.end(function(err, res){
res.headers.should.have.property('location', '/login');
done();
})
})
})
describe('when mounted', function(){
describe('deeply', function(){
it('should respect the mount-point', function(done){
var app = express()
, blog = express()
, admin = express();
admin.use(function(req, res){
res.location('login').end();
});
app.use('/blog', blog);
blog.use('/admin', admin);
request(app)
.get('/blog/admin')
.end(function(err, res){
res.headers.should.have.property('location', '/blog/admin/login');
done();
})
})
})
describe('omitting leading /', function(){
it('should respect the mount-point', function(done){
var app = express()
, admin = express();
admin.use(function(req, res){
res.location('admin/login').end();
});
app.use('/blog', admin);
request(app)
.get('/blog')
.end(function(err, res){
res.headers.should.have.property('location', '/blog/admin/login');
done();
})
})
})
describe('providing leading /', function(){
it('should ignore mount-point', function(done){
var app = express()
, admin = express();
admin.use(function(req, res){
res.location('/admin/login').end();
});
app.use('/blog', admin);
request(app)
.get('/blog')
.end(function(err, res){
res.headers.should.have.property('location', '/admin/login');
done();
})
})
})
})
})
})
+1 -73
Ver Arquivo
@@ -105,7 +105,7 @@ describe('res', function(){
.set('Host', 'http://example.com')
.set('Accept', 'text/html')
.end(function(err, res){
res.text.should.equal('<p>Moved Temporarily. Redirecting to <a href="/&lt;lame&gt;">/&lt;lame&gt;</a></p>');
res.text.should.equal('<p>Moved Temporarily. Redirecting to <a href="&lt;lame&gt;">&lt;lame&gt;</a></p>');
done();
})
})
@@ -169,76 +169,4 @@ describe('res', function(){
})
})
})
describe('responses redirected to relative paths', function(){
function create(depth, parent) {
var app = express();
if (parent) {
parent.use('/depth' + depth, app);
}
app.get('/', function(req, res){
res.redirect('./index');
});
app.get('/index', function(req, res){
res.json({ depth: depth, content: 'index' });
});
return app;
}
var root = create(0);
var depth1 = create(1, root);
var depth2 = create(2, depth1);
var depth3 = create(3, depth2);
root.use('/depth2', depth2);
root.use('/depth3', depth3);
it('should not contain redundant leading slashes in the location header', function(done){
request(root)
.get('/')
.end(function(err, res){
res.headers.location.search(/^\/{2}/).should.equal(-1);
done();
})
})
it('should preserve context when redirecting nested applications at any depth', function(done){
request(root)
.get('/depth1')
.end(function(err, res){
res.headers.should.have.property('location', '/depth1/index');
request(root)
.get('/depth1/depth2')
.end(function(err, res){
res.headers.should.have.property('location', '/depth1/depth2/index');
request(root)
.get('/depth1/depth2/depth3')
.end(function(err, res){
res.headers.should.have.property('location', '/depth1/depth2/depth3/index');
done();
})
})
});
})
it('should redirect correctly for nested applications that have been remounted', function(done){
request(root)
.get('/depth2')
.end(function(err, res){
res.headers.should.have.property('location', '/depth2/index');
request(root)
.get('/depth3')
.end(function(err, res){
res.headers.should.have.property('location', '/depth3/index');
done();
})
})
})
})
})