mirror of
https://github.com/expressjs/express.git
synced 2026-02-27 11:09:29 +00:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9cee90efc | ||
|
|
175aa08500 | ||
|
|
c9ff6198d3 | ||
|
|
f026218c82 | ||
|
|
5bc86b9e29 | ||
|
|
5830ac9936 | ||
|
|
d7c6c9a9f9 | ||
|
|
9c87eed60e | ||
|
|
f15eb6d5ef | ||
|
|
5b33788359 | ||
|
|
11ec3ccd48 | ||
|
|
9d498ba3f1 | ||
|
|
15d4047180 | ||
|
|
44eae73843 | ||
|
|
d5b1c70731 | ||
|
|
e78dc18cfd | ||
|
|
4d122923e9 | ||
|
|
b1a7310263 | ||
|
|
d6ef90d98d | ||
|
|
85df59ac31 | ||
|
|
b789a28581 | ||
|
|
4068e7f444 | ||
|
|
80e9ffbf5d | ||
|
|
610fc92ca3 | ||
|
|
0f5dc9bdb2 | ||
|
|
c24b2faec5 | ||
|
|
c407f58dc2 | ||
|
|
380da0e202 | ||
|
|
9e5e7a1526 | ||
|
|
9016b5aaae |
19
History.md
19
History.md
@@ -1,4 +1,23 @@
|
||||
|
||||
2.3.9 / 2011-05-25
|
||||
==================
|
||||
|
||||
* Fixed bug-ish with `../' in `res.partial()` calls
|
||||
|
||||
2.3.8 / 2011-05-24
|
||||
==================
|
||||
|
||||
* Fixed `app.options()`
|
||||
|
||||
2.3.7 / 2011-05-23
|
||||
==================
|
||||
|
||||
* Added route `Collection`, ex: `app.get('/user/:id').remove();`
|
||||
* Added support for `app.param(fn)` to define param logic
|
||||
* Removed `app.param()` support for callback with return value
|
||||
* Removed module.parent check from express(1) generated app. Closes #670
|
||||
* Refactored router. Closes #639
|
||||
|
||||
2.3.6 / 2011-05-20
|
||||
==================
|
||||
|
||||
|
||||
3
Makefile
3
Makefile
@@ -1,12 +1,13 @@
|
||||
|
||||
DOCS = $(shell find docs/*.md)
|
||||
HTMLDOCS =$(DOCS:.md=.html)
|
||||
TESTS = $(shell find test/*.test.js)
|
||||
|
||||
test:
|
||||
@NODE_ENV=test ./node_modules/.bin/expresso \
|
||||
-I lib \
|
||||
$(TESTFLAGS) \
|
||||
test/*.test.js
|
||||
$(TESTS)
|
||||
|
||||
test-cov:
|
||||
@TESTFLAGS=--cov $(MAKE) test
|
||||
|
||||
@@ -62,6 +62,8 @@ The following are the major contributors of Express (in no specific order).
|
||||
* [express-configure](http://github.com/visionmedia/express-configuration) async configuration support
|
||||
* [express-messages](http://github.com/visionmedia/express-messages) flash notification rendering helper
|
||||
* [express-namespace](http://github.com/visionmedia/express-namespace) namespaced route support
|
||||
* [express-params](https://github.com/visionmedia/express-params) param pre-condition functions
|
||||
* [express-mongoose](https://github.com/LearnBoost/express-mongoose) plugin for easy rendering of Mongoose async Query results
|
||||
* Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) on twitter for updates
|
||||
* [Google Group](http://groups.google.com/group/express-js) for discussion
|
||||
* Visit the [Wiki](http://github.com/visionmedia/express/wiki)
|
||||
|
||||
10
bin/express
10
bin/express
@@ -11,7 +11,7 @@ var fs = require('fs')
|
||||
* Framework version.
|
||||
*/
|
||||
|
||||
var version = '2.3.6';
|
||||
var version = '2.3.9';
|
||||
|
||||
/**
|
||||
* Add session support.
|
||||
@@ -217,12 +217,8 @@ var app = [
|
||||
, ' });'
|
||||
, '});'
|
||||
, ''
|
||||
, '// Only listen on $ node app.js'
|
||||
, ''
|
||||
, 'if (!module.parent) {'
|
||||
, ' app.listen(3000);'
|
||||
, ' console.log("Express server listening on port %d", app.address().port);'
|
||||
, '}'
|
||||
, 'app.listen(3000);'
|
||||
, 'console.log("Express server listening on port %d", app.address().port);'
|
||||
, ''
|
||||
].join('\n');
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
### Installation
|
||||
|
||||
$ npm install express
|
||||
@@ -236,7 +235,7 @@ passed to _express.createServer()_ as you would with a regular Connect server. F
|
||||
|
||||
Alternatively we can _use()_ them which is useful when adding middleware within _configure()_ blocks, in a progressive manor.
|
||||
|
||||
app.use(express.logger({ format: ':method :uri' }));
|
||||
app.use(express.logger({ format: ':method :url' }));
|
||||
|
||||
Typically with connect middleware you would _require('connect')_ like so:
|
||||
|
||||
@@ -481,14 +480,6 @@ Doing so, as mentioned drastically improves our route readability, and allows us
|
||||
res.send('user ' + req.user.name);
|
||||
});
|
||||
|
||||
For simple cases such as route placeholder validation and coercion we can simple pass a callback which has an arity of 1 (accepts one argument). Any errors thrown will be passed to _next(err)_.
|
||||
|
||||
app.param('number', function(n){ return parseInt(n, 10); });
|
||||
|
||||
We may also apply the same callback to several placeholders, for example a route GET _/commits/:from-:to_ are both numbers, so we may define them as an array:
|
||||
|
||||
app.param(['from', 'to'], function(n){ return parseInt(n, 10); });
|
||||
|
||||
### View Rendering
|
||||
|
||||
View filenames take the form "<name>.<engine>", where <engine> is the name
|
||||
|
||||
@@ -28,7 +28,7 @@ var exports = module.exports = connect.middleware;
|
||||
* Framework version.
|
||||
*/
|
||||
|
||||
exports.version = '2.3.6';
|
||||
exports.version = '2.3.9';
|
||||
|
||||
/**
|
||||
* Shortcut for `new Server(...)`.
|
||||
|
||||
198
lib/http.js
198
lib/http.js
@@ -12,8 +12,10 @@
|
||||
var qs = require('qs')
|
||||
, connect = require('connect')
|
||||
, router = require('./router')
|
||||
, methods = router.methods.concat('del', 'all')
|
||||
, Router = require('./router')
|
||||
, view = require('./view')
|
||||
, toArray = require('./utils').toArray
|
||||
, methods = router.methods.concat('del', 'all')
|
||||
, url = require('url')
|
||||
, utils = connect.utils;
|
||||
|
||||
@@ -86,11 +88,11 @@ app.init = function(middleware){
|
||||
// apply middleware
|
||||
if (middleware) middleware.forEach(self.use.bind(self));
|
||||
|
||||
// use router, expose as app.get(), etc
|
||||
var fn = router(function(app){ self.routes = app; }, this);
|
||||
// router
|
||||
this.routes = new Router(this);
|
||||
this.__defineGetter__('router', function(){
|
||||
this.__usedRouter = true;
|
||||
return fn;
|
||||
return self.routes.middleware;
|
||||
});
|
||||
|
||||
// default locals
|
||||
@@ -113,38 +115,62 @@ app.init = function(middleware){
|
||||
// so that they disregard definition position.
|
||||
this.on('listening', this.registerErrorHandlers.bind(this));
|
||||
|
||||
// route lookup methods
|
||||
this.remove = function(url){
|
||||
return self.remove.all(url);
|
||||
};
|
||||
|
||||
this.match = function(url){
|
||||
return self.match.all(url);
|
||||
};
|
||||
|
||||
this.lookup = function(url){
|
||||
return self.lookup.all(url);
|
||||
};
|
||||
|
||||
// route manipulation methods
|
||||
methods.forEach(function(method){
|
||||
self.match[method] = function(url){
|
||||
return self.router.match(url, 'all' == method
|
||||
? null
|
||||
: method);
|
||||
};
|
||||
|
||||
self.remove[method] = function(url){
|
||||
return self.router.remove(url, 'all' == method
|
||||
? null
|
||||
: method);
|
||||
};
|
||||
|
||||
self.lookup[method] = function(path){
|
||||
return self.router.lookup(path, 'all' == method
|
||||
? null
|
||||
: method);
|
||||
return self.routes.lookup(method, path);
|
||||
};
|
||||
|
||||
self.match[method] = function(path){
|
||||
return self.routes.match(method, path);
|
||||
};
|
||||
|
||||
self.remove[method] = function(path){
|
||||
return self.routes.lookup(method, path).remove();
|
||||
};
|
||||
});
|
||||
|
||||
// del -> delete
|
||||
self.lookup.del = self.lookup.delete;
|
||||
self.match.del = self.match.delete;
|
||||
self.remove.del = self.remove.delete;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove routes matching the given `path`.
|
||||
*
|
||||
* @param {Route} path
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
app.remove = function(path){
|
||||
return this.routes.lookup('all', path).remove();
|
||||
};
|
||||
|
||||
/**
|
||||
* Lookup routes defined with a path
|
||||
* equivalent to `path`.
|
||||
*
|
||||
* @param {Stirng} path
|
||||
* @return {Array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
app.lookup = function(path){
|
||||
return this.routes.lookup('all', path);
|
||||
};
|
||||
|
||||
/**
|
||||
* Lookup routes matching the given `url`.
|
||||
*
|
||||
* @param {Stirng} url
|
||||
* @return {Array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
app.match = function(url){
|
||||
return this.routes.match('all', url);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -289,54 +315,44 @@ app.dynamicHelpers = function(obj){
|
||||
* Map the given param placeholder `name`(s) to the given callback `fn`.
|
||||
*
|
||||
* Param mapping is used to provide pre-conditions to routes
|
||||
* which us normalized placeholders. For example ":user_id" may
|
||||
* attempt to load the user from the database, where as ":num" may
|
||||
* pass the value through `parseInt(num, 10)`.
|
||||
* which us normalized placeholders. This callback has the same
|
||||
* signature as regular middleware, for example below when ":userId"
|
||||
* is used this function will be invoked in an attempt to load the user.
|
||||
*
|
||||
* When the callback function accepts only a single argument, the
|
||||
* value of placeholder is passed:
|
||||
* app.param('userId', function(req, res, next, id){
|
||||
* User.find(id, function(err, user){
|
||||
* if (err) {
|
||||
* next(err);
|
||||
* } else if (user) {
|
||||
* req.user = user;
|
||||
* next();
|
||||
* } else {
|
||||
* next(new Error('failed to load user'));
|
||||
* }
|
||||
* });
|
||||
* });
|
||||
*
|
||||
* app.param('page', function(n){ return parseInt(n, 10); });
|
||||
*
|
||||
* After which "/users/:page" would automatically provide us with
|
||||
* an integer for `req.params.page`. If desired we could use the callback
|
||||
* signature shown below, and immediately `next(new Error('invalid page'))`
|
||||
* when `parseInt` fails.
|
||||
*
|
||||
* Alternatively the callback may accept the request, response, next, and
|
||||
* the value, acting like middlware:
|
||||
*
|
||||
* app.param('userId', function(req, res, next, id){
|
||||
* User.find(id, function(err, user){
|
||||
* if (err) {
|
||||
* next(err);
|
||||
* } else if (user) {
|
||||
* req.user = user;
|
||||
* next();
|
||||
* } else {
|
||||
* next(new Error('failed to load user'));
|
||||
* }
|
||||
* });
|
||||
* });
|
||||
*
|
||||
* Now every time ":userId" is present, the associated user object
|
||||
* will be loaded and assigned before the route handler is invoked.
|
||||
*
|
||||
* @param {String|Array} name
|
||||
* @param {String|Array|Function} name
|
||||
* @param {Function} fn
|
||||
* @return {Server} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
app.param = function(name, fn){
|
||||
// array
|
||||
if (Array.isArray(name)) {
|
||||
name.forEach(function(name){
|
||||
this.param(name, fn);
|
||||
}, this);
|
||||
// param logic
|
||||
} else if ('function' == typeof name) {
|
||||
this.routes.param(name);
|
||||
// single
|
||||
} else {
|
||||
if (':' == name[0]) name = name.substr(1);
|
||||
this.routes.param(name, fn);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -464,37 +480,45 @@ app.redirect = function(key, url){
|
||||
*/
|
||||
|
||||
app.configure = function(env, fn){
|
||||
if ('function' == typeof env) {
|
||||
fn = env, env = 'all';
|
||||
}
|
||||
if ('all' == env || env == this.settings.env) {
|
||||
fn.call(this);
|
||||
}
|
||||
if ('function' == typeof env) fn = env, env = 'all';
|
||||
if ('all' == env || env == this.settings.env) fn.call(this);
|
||||
return this;
|
||||
};
|
||||
|
||||
// Generate routing methods
|
||||
/**
|
||||
* Delegate `.VERB(...)` calls to `.route(VERB, ...)`.
|
||||
*/
|
||||
|
||||
methods.forEach(function(method){
|
||||
app[method] = function(path){
|
||||
var self = this;
|
||||
|
||||
// Lookup
|
||||
if (1 == arguments.length) {
|
||||
return this.router.lookup(path, 'all' == method
|
||||
? null
|
||||
: method);
|
||||
}
|
||||
|
||||
// Ensure router is mounted
|
||||
if (1 == arguments.length) return this.routes.lookup(method, path);
|
||||
var args = [method].concat(toArray(arguments));
|
||||
if (!this.__usedRouter) this.use(this.router);
|
||||
|
||||
// Generate the route
|
||||
this.routes[method].apply(this, arguments);
|
||||
return this;
|
||||
};
|
||||
return this.routes._route.apply(this.routes, args);
|
||||
}
|
||||
});
|
||||
|
||||
// Alias delete as "del"
|
||||
/**
|
||||
* Special-cased "all" method, applying the given route `path`,
|
||||
* middleware, and callback to _every_ HTTP method.
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {Function} ...
|
||||
* @return {Server} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
app.all = function(path){
|
||||
var args = arguments;
|
||||
if (1 == args.length) return this.routes.lookup('all', path);
|
||||
methods.forEach(function(method){
|
||||
if ('all' == method) return;
|
||||
app[method].apply(this, args);
|
||||
}, this);
|
||||
return this;
|
||||
};
|
||||
|
||||
// del -> delete alias
|
||||
|
||||
app.del = app.delete;
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ res.send = function(body, headers, status){
|
||||
}
|
||||
|
||||
// strip irrelevant headers
|
||||
if (204 === status) {
|
||||
if (204 == status) {
|
||||
this.removeHeader('Content-Type');
|
||||
this.removeHeader('Content-Length');
|
||||
}
|
||||
|
||||
53
lib/router/collection.js
Normal file
53
lib/router/collection.js
Normal file
@@ -0,0 +1,53 @@
|
||||
|
||||
/*!
|
||||
* Express - router - Collection
|
||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
||||
* MIT Licensed
|
||||
*/
|
||||
|
||||
/**
|
||||
* Expose `Collection`.
|
||||
*/
|
||||
|
||||
module.exports = Collection;
|
||||
|
||||
/**
|
||||
* Initialize a new route `Collection`
|
||||
* with the given `router`.
|
||||
*
|
||||
* @param {Router} router
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function Collection(router) {
|
||||
Array.apply(this, arguments);
|
||||
this.router = router;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherit from `Array.prototype`.
|
||||
*/
|
||||
|
||||
Collection.prototype.__proto__ = Array.prototype;
|
||||
|
||||
/**
|
||||
* Remove the routes in this collection.
|
||||
*
|
||||
* @return {Collection} of routes removed
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Collection.prototype.remove = function(){
|
||||
var router = this.router
|
||||
, len = this.length
|
||||
, ret = new Collection(this.router);
|
||||
|
||||
for (var i = 0; i < len; ++i) {
|
||||
if (router.remove(this[i])) {
|
||||
ret.push(this[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
/*!
|
||||
* Express - router
|
||||
* Express - Router
|
||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
||||
* MIT Licensed
|
||||
*/
|
||||
@@ -9,312 +9,323 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('../utils')
|
||||
var Route = require('./route')
|
||||
, Collection = require('./collection')
|
||||
, utils = require('../utils')
|
||||
, parse = require('url').parse
|
||||
, _methods = require('./methods')
|
||||
, Route = require('./route');
|
||||
, toArray = utils.toArray;
|
||||
|
||||
/**
|
||||
* Expose router.
|
||||
* Expose `Router` constructor.
|
||||
*/
|
||||
|
||||
exports = module.exports = router;
|
||||
exports = module.exports = Router;
|
||||
|
||||
/**
|
||||
* Expose methods.
|
||||
* Expose HTTP methods.
|
||||
*/
|
||||
|
||||
exports.methods = _methods;
|
||||
var methods = exports.methods = require('./methods');
|
||||
|
||||
/**
|
||||
* Provides Sinatra-like routing capabilities.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Function}
|
||||
* Initialize a new `Router` with the given `app`.
|
||||
*
|
||||
* @param {express.HTTPServer} app
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function router(fn, app){
|
||||
var self = this
|
||||
, methods = {}
|
||||
, routes = {}
|
||||
, params = {};
|
||||
function Router(app) {
|
||||
var self = this;
|
||||
this.app = app;
|
||||
this.routes = {};
|
||||
this.params = {};
|
||||
this._params = [];
|
||||
|
||||
if (!fn) throw new Error('router provider requires a callback function');
|
||||
|
||||
// Generate method functions
|
||||
_methods.forEach(function(method){
|
||||
methods[method] = generateMethodFunction(method.toUpperCase());
|
||||
});
|
||||
|
||||
// Alias del -> delete
|
||||
methods.del = methods.delete;
|
||||
|
||||
// Apply callback to all methods
|
||||
methods.all = function(){
|
||||
var args = arguments;
|
||||
_methods.forEach(function(name){
|
||||
methods[name].apply(this, args);
|
||||
});
|
||||
return self;
|
||||
this.middleware = function(req, res, next){
|
||||
self._dispatch(req, res, next);
|
||||
};
|
||||
}
|
||||
|
||||
// Register param callback
|
||||
methods.param = function(name, fn){
|
||||
params[name] = fn;
|
||||
};
|
||||
|
||||
fn.call(this, methods);
|
||||
/**
|
||||
* Register a param callback `fn` for the given `name`.
|
||||
*
|
||||
* @param {String|Function} name
|
||||
* @param {Function} fn
|
||||
* @return {Router} for chaining
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function generateMethodFunction(name) {
|
||||
var localRoutes = routes[name] = routes[name] || [];
|
||||
return function(path, fn){
|
||||
var keys = []
|
||||
, middleware = [];
|
||||
|
||||
// slice middleware
|
||||
if (arguments.length > 2) {
|
||||
middleware = Array.prototype.slice.call(arguments, 1, arguments.length);
|
||||
fn = middleware.pop();
|
||||
middleware = utils.flatten(middleware);
|
||||
}
|
||||
|
||||
if (!path) throw new Error(name + ' route requires a path');
|
||||
if (!fn) throw new Error(name + ' route ' + path + ' requires a callback');
|
||||
|
||||
var options = { sensitive: app.enabled('case sensitive routes') };
|
||||
var route = new Route(name, path, fn, options);
|
||||
route.middleware = middleware;
|
||||
localRoutes.push(route);
|
||||
return self;
|
||||
};
|
||||
Router.prototype.param = function(name, fn){
|
||||
// param logic
|
||||
if ('function' == typeof name) {
|
||||
this._params.push(name);
|
||||
return;
|
||||
}
|
||||
|
||||
function router(req, res, next){
|
||||
var route
|
||||
, self = this;
|
||||
// apply param functions
|
||||
var params = this._params
|
||||
, len = params.length
|
||||
, ret;
|
||||
|
||||
(function pass(i){
|
||||
if (route = match(req, routes, i)) {
|
||||
var i = 0
|
||||
, keys = route.keys;
|
||||
|
||||
req.params = route.params;
|
||||
|
||||
// Param preconditions
|
||||
(function param(err) {
|
||||
try {
|
||||
var key = keys[i++]
|
||||
, val = req.params[key]
|
||||
, fn = params[key];
|
||||
|
||||
if ('route' == err) {
|
||||
pass(req._route_index + 1);
|
||||
// Error
|
||||
} else if (err) {
|
||||
next(err);
|
||||
// Param has callback
|
||||
} else if (fn) {
|
||||
// Return style
|
||||
if (1 == fn.length) {
|
||||
req.params[key] = fn(val);
|
||||
param();
|
||||
// Middleware style
|
||||
} else {
|
||||
fn(req, res, param, val);
|
||||
}
|
||||
// Finished processing params
|
||||
} else if (!key) {
|
||||
// route middleware
|
||||
i = 0;
|
||||
(function nextMiddleware(err){
|
||||
var fn = route.middleware[i++];
|
||||
if ('route' == err) {
|
||||
pass(req._route_index + 1);
|
||||
} else if (err) {
|
||||
next(err);
|
||||
} else if (fn) {
|
||||
fn(req, res, nextMiddleware);
|
||||
} else {
|
||||
route.callback.call(self, req, res, function(err){
|
||||
if (err) {
|
||||
next(err);
|
||||
} else {
|
||||
pass(req._route_index + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
// More params
|
||||
} else {
|
||||
param();
|
||||
}
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
})();
|
||||
} else if ('OPTIONS' == req.method) {
|
||||
options(req, res, routes);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
router.remove = function(path, method, ret){
|
||||
var ret = ret || []
|
||||
, route;
|
||||
|
||||
// method specific remove
|
||||
if (method) {
|
||||
method = method.toUpperCase();
|
||||
if (routes[method]) {
|
||||
for (var i = 0; i < routes[method].length; ++i) {
|
||||
route = routes[method][i];
|
||||
if (path == route.path) {
|
||||
route.index = i;
|
||||
routes[method].splice(i, 1);
|
||||
ret.push(route);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
// global remove
|
||||
} else {
|
||||
_methods.forEach(function(method){
|
||||
router.remove(path, method, ret);
|
||||
});
|
||||
for (var i = 0; i < len; ++i) {
|
||||
if (ret = params[i](name, fn)) {
|
||||
fn = ret;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
// ensure we end up with a
|
||||
// middleware function
|
||||
if ('function' != typeof fn) {
|
||||
throw new Error('invalid param() call for ' + name + ', got ' + fn);
|
||||
}
|
||||
|
||||
router.lookup = function(path, method, ret){
|
||||
ret = ret || [];
|
||||
|
||||
// method specific lookup
|
||||
if (method) {
|
||||
method = method.toUpperCase();
|
||||
if (routes[method]) {
|
||||
routes[method].forEach(function(route, i){
|
||||
if (path == route.path) {
|
||||
route.index = i;
|
||||
ret.push(route);
|
||||
}
|
||||
});
|
||||
}
|
||||
// global lookup
|
||||
} else {
|
||||
_methods.forEach(function(method){
|
||||
router.lookup(path, method, ret);
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
router.match = function(url, method, ret){
|
||||
var ret = ret || []
|
||||
, i = 0
|
||||
, route
|
||||
, req;
|
||||
|
||||
// method specific matches
|
||||
if (method) {
|
||||
method = method.toUpperCase();
|
||||
req = { url: url, method: method };
|
||||
while (route = match(req, routes, i)) {
|
||||
i = req._route_index + 1;
|
||||
route.index = i;
|
||||
ret.push(route);
|
||||
}
|
||||
// global matches
|
||||
} else {
|
||||
_methods.forEach(function(method){
|
||||
router.match(url, method, ret);
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
router.routes = routes;
|
||||
|
||||
return router;
|
||||
}
|
||||
this.params[name] = fn;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to OPTIONS.
|
||||
* Remove the given `route`, returns
|
||||
* a bool indicating if the route was present
|
||||
* or not.
|
||||
*
|
||||
* @param {ServerRequest} req
|
||||
* @param {ServerResponse} req
|
||||
* @param {Array} routes
|
||||
* @param {Route} route
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Router.prototype.remove = function(route){
|
||||
var routes = this.routes[route.method]
|
||||
, len = routes.length;
|
||||
|
||||
for (var i = 0; i < len; ++i) {
|
||||
if (route == routes[i]) {
|
||||
routes.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return routes with route paths matching `path`.
|
||||
*
|
||||
* @param {String} method
|
||||
* @param {String} path
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Router.prototype.lookup = function(method, path){
|
||||
return this.find(function(route){
|
||||
return path == route.path
|
||||
&& (route.method == method
|
||||
|| method == 'all');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return routes with regexps that match the given `url`.
|
||||
*
|
||||
* @param {String} method
|
||||
* @param {String} url
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Router.prototype.match = function(method, url){
|
||||
return this.find(function(route){
|
||||
return route.match(url)
|
||||
&& (route.method == method
|
||||
|| method == 'all');
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Find routes based on the return value of `fn`
|
||||
* which is invoked once per route.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Collection}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
Router.prototype.find = function(fn){
|
||||
var len = methods.length
|
||||
, ret = new Collection(this)
|
||||
, method
|
||||
, routes
|
||||
, route;
|
||||
|
||||
for (var i = 0; i < len; ++i) {
|
||||
method = methods[i];
|
||||
routes = this.routes[method];
|
||||
if (!routes) continue;
|
||||
for (var j = 0, jlen = routes.length; j < jlen; ++j) {
|
||||
route = routes[j];
|
||||
if (fn(route)) ret.push(route);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* Route dispatcher aka the route "middleware".
|
||||
*
|
||||
* @param {IncomingMessage} req
|
||||
* @param {ServerResponse} res
|
||||
* @param {Function} next
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function options(req, res, routes) {
|
||||
var pathname = parse(req.url).pathname
|
||||
, body = optionsFor(pathname, routes).join(',');
|
||||
res.send(body, { Allow: body });
|
||||
}
|
||||
Router.prototype._dispatch = function(req, res, next){
|
||||
var params = this.params
|
||||
, self = this;
|
||||
|
||||
// route dispatch
|
||||
(function pass(i){
|
||||
var route
|
||||
, keys
|
||||
, ret;
|
||||
|
||||
// match next route
|
||||
function nextRoute() {
|
||||
pass(req._route_index + 1);
|
||||
}
|
||||
|
||||
// match route
|
||||
route = self._match(req, i);
|
||||
|
||||
// implied OPTIONS
|
||||
if (!route && 'OPTIONS' == req.method) return self._options(req, res);
|
||||
|
||||
// no route
|
||||
if (!route) return next();
|
||||
|
||||
// we have a route
|
||||
// start at param 0
|
||||
req.params = route.params;
|
||||
keys = route.keys;
|
||||
i = 0;
|
||||
|
||||
(function param(err) {
|
||||
var key = keys[i++]
|
||||
, val = req.params[key]
|
||||
, fn = params[key]
|
||||
, ret;
|
||||
|
||||
try {
|
||||
if ('route' == err) {
|
||||
nextRoute();
|
||||
} else if (err) {
|
||||
next(err);
|
||||
} else if (fn) {
|
||||
fn(req, res, param, val);
|
||||
} else if (key) {
|
||||
param();
|
||||
} else {
|
||||
i = 0;
|
||||
middleware();
|
||||
}
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
})();
|
||||
|
||||
// invoke route middleware
|
||||
function middleware(err) {
|
||||
var fn = route.middleware[i++];
|
||||
if ('route' == err) {
|
||||
nextRoute();
|
||||
} else if (err) {
|
||||
next(err);
|
||||
} else if (fn) {
|
||||
fn(req, res, middleware);
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
// invoke middleware callback
|
||||
function done() {
|
||||
route.callback.call(self, req, res, function(err){
|
||||
if (err) return next(err);
|
||||
pass(req._route_index + 1);
|
||||
});
|
||||
}
|
||||
})(0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return OPTIONS array for the given `path`, matching `routes`.
|
||||
* Respond to __OPTIONS__ method.
|
||||
*
|
||||
* @param {IncomingMessage} req
|
||||
* @param {ServerResponse} res
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Router.prototype._options = function(req, res){
|
||||
var path = parse(req.url).pathname
|
||||
, body = this._optionsFor(path).join(',');
|
||||
res.send(body, { Allow: body });
|
||||
};
|
||||
|
||||
/**
|
||||
* Return an array of HTTP verbs or "options" for `path`.
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {Array} routes
|
||||
* @return {Array}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function optionsFor(path, routes) {
|
||||
return _methods.filter(function(method){
|
||||
var arr = routes[method.toUpperCase()];
|
||||
for (var i = 0, len = arr.length; i < len; ++i) {
|
||||
if (arr[i].regexp.test(path)) return true;
|
||||
Router.prototype._optionsFor = function(path){
|
||||
var self = this;
|
||||
return methods.filter(function(method){
|
||||
var routes = self.routes[method];
|
||||
if (!routes || 'options' == method) return;
|
||||
for (var i = 0, len = routes.length; i < len; ++i) {
|
||||
if (routes[i].match(path)) return true;
|
||||
}
|
||||
}).map(function(method){
|
||||
return method.toUpperCase();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Attempt to match the given request to
|
||||
* one of the routes. When successful
|
||||
* a route function is returned.
|
||||
* Attempt to match a route for `req`
|
||||
* starting from offset `i`.
|
||||
*
|
||||
* @param {ServerRequest} req
|
||||
* @param {Object} routes
|
||||
* @return {Function}
|
||||
* @param {IncomingMessage} req
|
||||
* @param {Number} i
|
||||
* @return {Route}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function match(req, routes, i) {
|
||||
var captures
|
||||
, method = req.method
|
||||
, i = i || 0;
|
||||
Router.prototype._match = function(req, i){
|
||||
var method = req.method.toLowerCase()
|
||||
, url = parse(req.url)
|
||||
, path = url.pathname
|
||||
, routes = this.routes
|
||||
, captures
|
||||
, route
|
||||
, keys;
|
||||
|
||||
// pass HEAD to GET routes
|
||||
if ('HEAD' == method) method = 'GET';
|
||||
if ('head' == method) method = 'get';
|
||||
|
||||
// routes for this method
|
||||
if (routes = routes[method]) {
|
||||
var url = parse(req.url)
|
||||
, pathname = url.pathname;
|
||||
|
||||
// matching routes
|
||||
for (var len = routes.length; i < len; ++i) {
|
||||
var route = routes[i]
|
||||
, fn = route.callback
|
||||
, path = route.regexp
|
||||
, keys = route.keys;
|
||||
|
||||
// match
|
||||
if (captures = path.exec(pathname)) {
|
||||
route = routes[i];
|
||||
if (captures = route.match(path)) {
|
||||
keys = route.keys;
|
||||
route.params = [];
|
||||
for (var j = 1, l = captures.length; j < l; ++j) {
|
||||
var key = keys[j-1],
|
||||
val = 'string' == typeof captures[j]
|
||||
|
||||
// params from capture groups
|
||||
for (var j = 1, jlen = captures.length; j < jlen; ++j) {
|
||||
var key = keys[j-1]
|
||||
, val = 'string' == typeof captures[j]
|
||||
? decodeURIComponent(captures[j])
|
||||
: captures[j];
|
||||
if (key) {
|
||||
@@ -323,9 +334,50 @@ function match(req, routes, i) {
|
||||
route.params.push(val);
|
||||
}
|
||||
}
|
||||
|
||||
// all done
|
||||
req._route_index = i;
|
||||
return route;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Route `method`, `path`, and optional middleware
|
||||
* to the callback `fn`.
|
||||
*
|
||||
* @param {String} method
|
||||
* @param {String} path
|
||||
* @param {Function} ...
|
||||
* @param {Function} fn
|
||||
* @return {Router} for chaining
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Router.prototype._route = function(method, path, fn){
|
||||
var app = this.app
|
||||
, middleware = [];
|
||||
|
||||
// slice middleware
|
||||
if (arguments.length > 3) {
|
||||
middleware = toArray(arguments, 2);
|
||||
fn = middleware.pop();
|
||||
middleware = utils.flatten(middleware);
|
||||
}
|
||||
|
||||
// ensure path and callback are given
|
||||
if (!path) throw new Error(method + 'route requires a path');
|
||||
if (!fn) throw new Error(method + ' route ' + path + ' requires a callback');
|
||||
|
||||
// create the route
|
||||
var route = new Route(method, path, fn, {
|
||||
sensitive: app.enabled('case sensitive routes')
|
||||
, middleware: middleware
|
||||
});
|
||||
|
||||
// add it
|
||||
(this.routes[method] = this.routes[method] || [])
|
||||
.push(route);
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ module.exports = Route;
|
||||
* Options:
|
||||
*
|
||||
* - `sensitive` enable case-sensitive routes
|
||||
* - `middleware` array of middleware
|
||||
*
|
||||
* @param {String} method
|
||||
* @param {String} path
|
||||
@@ -30,10 +31,23 @@ function Route(method, path, fn, options) {
|
||||
options = options || {};
|
||||
this.callback = fn;
|
||||
this.path = path;
|
||||
this.regexp = normalize(path, this.keys = [], options.sensitive);
|
||||
this.method = method;
|
||||
this.regexp = normalize(path, this.keys = [], options.sensitive);
|
||||
this.middleware = options.middleware;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this route matches `path` and return captures made.
|
||||
*
|
||||
* @param {String} path
|
||||
* @return {Array}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Route.prototype.match = function(path){
|
||||
return this.regexp.exec(path);
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalize the given path string,
|
||||
* returning a regular expression.
|
||||
|
||||
17
lib/utils.js
17
lib/utils.js
@@ -120,3 +120,20 @@ exports.parseRange = function(size, str){
|
||||
});
|
||||
return valid ? arr : undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fast alternative to `Array.prototype.slice.call()`.
|
||||
*
|
||||
* @param {Arguments} args
|
||||
* @param {Number} n
|
||||
* @return {Array}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.toArray = function(args, i){
|
||||
var arr = []
|
||||
, len = args.length
|
||||
, i = i || 0;
|
||||
for (; i < len; ++i) arr.push(args[i]);
|
||||
return arr;
|
||||
};
|
||||
|
||||
@@ -127,11 +127,6 @@ exports.lookup = function(view, options){
|
||||
function renderPartial(res, view, options, parentLocals, parent){
|
||||
var collection, object, locals;
|
||||
|
||||
// Inherit parent view extension when not present
|
||||
if (parent && !~view.indexOf('.')) {
|
||||
view += parent.extension;
|
||||
}
|
||||
|
||||
if (options) {
|
||||
// collection
|
||||
if (options.collection) {
|
||||
@@ -269,7 +264,7 @@ res.partial = function(view, options, fn){
|
||||
parent.dirname = app.set('views') || process.cwd() + '/views';
|
||||
|
||||
// utilize "view engine" option
|
||||
if (viewEngine) parent.extension = '.' + viewEngine;
|
||||
if (viewEngine) parent.engine = viewEngine;
|
||||
|
||||
// render the partial
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "express",
|
||||
"description": "Sinatra inspired web development framework",
|
||||
"version": "2.3.6",
|
||||
"version": "2.3.9",
|
||||
"author": "TJ Holowaychuk <tj@vision-media.ca>",
|
||||
"contributors": [
|
||||
{ "name": "TJ Holowaychuk", "email": "tj@vision-media.ca" },
|
||||
|
||||
4
test/fixtures/forum/thread.jade
vendored
4
test/fixtures/forum/thread.jade
vendored
@@ -1 +1,3 @@
|
||||
h1 Forum Thread
|
||||
h1 Forum Thread
|
||||
!= partial('../hello')
|
||||
!= partial('../hello.haml')
|
||||
@@ -81,6 +81,22 @@ module.exports = {
|
||||
res.send('user ' + req.params.id);
|
||||
});
|
||||
|
||||
app.post('/pin/save/:lat(\\d+.\\d+)/:long(\\d+.\\d+)', function(req, res){
|
||||
res.send(req.params.lat + ' ' + req.params.long);
|
||||
});
|
||||
|
||||
app.post('/pin/save2/:lat([0-9]+.[0-9]+)/:long([0-9]+.[0-9]+)', function(req, res){
|
||||
res.send(req.params.lat + ' ' + req.params.long);
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/pin/save/1.2/3.4', method: 'POST' },
|
||||
{ body: '1.2 3.4' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/pin/save2/1.2/3.4', method: 'POST' },
|
||||
{ body: '1.2 3.4' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/12' },
|
||||
{ body: 'user 12' });
|
||||
@@ -101,9 +117,6 @@ module.exports = {
|
||||
, { name: 'bandit' }
|
||||
];
|
||||
|
||||
function integer(n){ return parseInt(n, 10); };
|
||||
app.param(['to', 'from'], integer);
|
||||
|
||||
app.param('user', function(req, res, next, id){
|
||||
if (req.user = users[id]) {
|
||||
next();
|
||||
@@ -116,13 +129,6 @@ module.exports = {
|
||||
res.send('user ' + req.user.name);
|
||||
});
|
||||
|
||||
app.get('/users/:from-:to', function(req, res, next){
|
||||
var names = users.slice(req.params.from, req.params.to).map(function(user){
|
||||
return user.name;
|
||||
});
|
||||
res.send('users ' + names.join(', '));
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/user/0' },
|
||||
{ body: 'user tj' });
|
||||
@@ -130,10 +136,6 @@ module.exports = {
|
||||
assert.response(app,
|
||||
{ url: '/user/1' },
|
||||
{ body: 'user tobi' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/users/0-3' },
|
||||
{ body: 'users tj, tobi, loki' });
|
||||
},
|
||||
|
||||
'test OPTIONS': function(){
|
||||
@@ -165,8 +167,7 @@ module.exports = {
|
||||
route.callback.should.be.a('function');
|
||||
route.path.should.equal('/user/:id');
|
||||
route.regexp.should.be.an.instanceof(RegExp);
|
||||
route.method.should.equal('GET');
|
||||
route.index.should.equal(1);
|
||||
route.method.should.equal('get');
|
||||
route.keys.should.eql(['id']);
|
||||
|
||||
app.get('/user').should.have.length(1);
|
||||
@@ -228,10 +229,9 @@ module.exports = {
|
||||
route.callback.should.be.a('function');
|
||||
route.path.should.equal('/user/:id');
|
||||
route.regexp.should.be.an.instanceof(RegExp);
|
||||
route.method.should.equal('GET');
|
||||
route.index.should.equal(2);
|
||||
route.method.should.equal('get');
|
||||
route.keys.should.eql(['id']);
|
||||
route.params.id.should.equal('12');
|
||||
//route.params.id.should.equal('12');
|
||||
|
||||
app.match.get('/user').should.have.length(1);
|
||||
app.match.get('/user/12').should.have.length(2);
|
||||
@@ -242,6 +242,20 @@ module.exports = {
|
||||
app.match.all('/user/123').should.have.length(3);
|
||||
app.match('/user/123').should.have.length(3);
|
||||
},
|
||||
|
||||
'test Collection': function(){
|
||||
var app = express.createServer();
|
||||
app.get('/user', function(){});
|
||||
app.get('/user/:id', function(){});
|
||||
app.get('/user/:id/:op?', function(){});
|
||||
app.put('/user/:id', function(){});
|
||||
app.get('/user/:id/edit', function(){});
|
||||
|
||||
var ret = app.match.all('/user/12').remove();
|
||||
ret.should.have.length(3);
|
||||
app.match.all('/user/12').should.have.length(0);
|
||||
app.get('/user/:id').should.have.length(0);
|
||||
},
|
||||
|
||||
'test "case sensitive routes" setting': function(){
|
||||
var app = express.createServer();
|
||||
@@ -263,5 +277,26 @@ module.exports = {
|
||||
assert.response(app,
|
||||
{ url: '/Account' },
|
||||
{ body: 'Account' });
|
||||
},
|
||||
|
||||
'override OPTIONS default': function(){
|
||||
var app = express.createServer();
|
||||
|
||||
app.get('/', function(req, res, next){
|
||||
|
||||
});
|
||||
|
||||
app.options('/foo', function(req, res, next){
|
||||
res.header('Allow', 'GET')
|
||||
res.send('whatever');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/', method: 'OPTIONS' },
|
||||
{ body: 'GET', headers: { Allow: 'GET' }});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/foo', method: 'OPTIONS' },
|
||||
{ body: 'whatever', headers: { Allow: 'GET' }});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -668,6 +668,67 @@ module.exports = {
|
||||
{ body: '<p>two</p>' });
|
||||
},
|
||||
|
||||
'test #partial() relative lookup with "view engine"': function(){
|
||||
var app = create();
|
||||
app.set('view engine', 'jade');
|
||||
|
||||
app.get('/', function(req, res, next){
|
||||
res.render('forum/thread', { layout: false });
|
||||
});
|
||||
|
||||
app.get('/2', function(req, res, next){
|
||||
res.render('forum/../forum/thread', { layout: false });
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/2' },
|
||||
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
|
||||
},
|
||||
|
||||
'test #partial() relative lookup without "view engine"': function(){
|
||||
var app = create();
|
||||
|
||||
app.get('/', function(req, res, next){
|
||||
res.render('forum/thread.jade', { layout: false });
|
||||
});
|
||||
|
||||
app.get('/2', function(req, res, next){
|
||||
res.render('forum/../forum/thread.jade', { layout: false });
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/2' },
|
||||
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
|
||||
},
|
||||
|
||||
'test #partial() relative lookup': function(){
|
||||
var app = create();
|
||||
|
||||
app.get('/', function(req, res, next){
|
||||
res.partial('forum/thread.jade');
|
||||
});
|
||||
|
||||
app.get('/2', function(req, res, next){
|
||||
res.partial('forum/../forum/thread.jade');
|
||||
});
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/2' },
|
||||
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
|
||||
|
||||
assert.response(app,
|
||||
{ url: '/' },
|
||||
{ body: '<h1>Forum Thread</h1><p>:(</p>\n<p>Hello World</p>' });
|
||||
},
|
||||
|
||||
'test #partial() with several calls': function(){
|
||||
var app = create();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user