mirror of
https://github.com/zebrajr/express.git
synced 2025-12-06 12:19:51 +01:00
596 lines
13 KiB
JavaScript
596 lines
13 KiB
JavaScript
|
|
/*!
|
|
* Express - proto
|
|
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
|
* MIT Licensed
|
|
*/
|
|
|
|
/**
|
|
* Module dependencies.
|
|
*/
|
|
|
|
var connect = require('connect')
|
|
, Router = require('./router')
|
|
, view = require('./view')
|
|
, toArray = require('./utils').toArray
|
|
, methods = Router.methods.concat('del', 'all')
|
|
, res = require('./response')
|
|
, union = require('./utils').union
|
|
, url = require('url')
|
|
, utils = connect.utils
|
|
, path = require('path')
|
|
, extname = path.extname
|
|
, join = path.join
|
|
, fs = require('fs')
|
|
, qs = require('qs');
|
|
|
|
/**
|
|
* Expose `HTTPServer`.
|
|
*/
|
|
|
|
var app = exports = module.exports = {};
|
|
|
|
/**
|
|
* Initialize the server.
|
|
*
|
|
* @param {Array} middleware
|
|
* @api private
|
|
*/
|
|
|
|
app.init = function(middleware){
|
|
var self = this;
|
|
this.settings = {};
|
|
this.engines = {};
|
|
this.redirects = {};
|
|
this.isCallbacks = {};
|
|
|
|
this.set('home', '/');
|
|
this.set('env', process.env.NODE_ENV || 'development');
|
|
this.use(connect.query());
|
|
|
|
/**
|
|
* Assign several locals with the given `obj`,
|
|
* or return the locals.
|
|
*
|
|
* @param {Object} obj
|
|
* @return {Object|HTTPServer}
|
|
* @api public
|
|
*/
|
|
|
|
this.locals = function(obj){
|
|
for (var key in obj) {
|
|
self.locals[key] = obj[key];
|
|
}
|
|
return self;
|
|
};
|
|
|
|
// expose objects to each other
|
|
this.use(function(req, res, next){
|
|
var charset;
|
|
req.query = req.query || {};
|
|
res.setHeader('X-Powered-By', 'Express');
|
|
req.app = res.app = self;
|
|
req.res = res;
|
|
res.req = req;
|
|
req.next = next;
|
|
|
|
// charset
|
|
if (charset = self.set('charset')) res.charset = charset;
|
|
|
|
/**
|
|
* Assign several locals with the given `obj`,
|
|
* or return the locals.
|
|
*
|
|
* @param {Object} obj
|
|
* @return {Object|ServerResponse}
|
|
* @api public
|
|
*/
|
|
|
|
res.locals = function(obj){
|
|
for (var key in obj) {
|
|
res.locals[key] = obj[key];
|
|
}
|
|
return res;
|
|
};
|
|
|
|
next();
|
|
});
|
|
|
|
// apply middleware
|
|
if (middleware) middleware.forEach(self.use.bind(self));
|
|
|
|
// router
|
|
this.routes = new Router(this);
|
|
this.__defineGetter__('router', function(){
|
|
this.__usedRouter = true;
|
|
return self.routes.middleware;
|
|
});
|
|
|
|
// default locals
|
|
this.locals.settings = this.settings;
|
|
|
|
// default development configuration
|
|
this.configure('development', function(){
|
|
this.enable('hints');
|
|
});
|
|
|
|
// default production configuration
|
|
this.configure('production', function(){
|
|
this.enable('view cache');
|
|
});
|
|
|
|
// route manipulation methods
|
|
methods.forEach(function(method){
|
|
self.lookup[method] = function(path){
|
|
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);
|
|
};
|
|
|
|
/**
|
|
* Proxy `connect.HTTPServer#use()` to apply settings to
|
|
* mounted applications.
|
|
*
|
|
* @param {String|Function|Server} route
|
|
* @param {Function|Server} middleware
|
|
* @return {Server} for chaining
|
|
* @api public
|
|
*/
|
|
|
|
app.use = function(route, middleware){
|
|
var app, home, handle;
|
|
|
|
if ('string' != typeof route) {
|
|
middleware = route, route = '/';
|
|
}
|
|
|
|
// express app
|
|
if (middleware.handle && middleware.set) app = middleware;
|
|
|
|
// restore .app property on req and res
|
|
if (app) {
|
|
app.route = route;
|
|
middleware = function(req, res, next) {
|
|
var orig = req.app;
|
|
app.handle(req, res, function(err){
|
|
req.app = res.app = orig;
|
|
next(err);
|
|
});
|
|
};
|
|
}
|
|
|
|
connect.proto.use.call(this, route, middleware);
|
|
|
|
// mounted an app, invoke the hook
|
|
// and adjust some settings
|
|
if (app) {
|
|
base = this.set('basepath') || this.route;
|
|
if ('/' == base) base = '';
|
|
base = base + (app.set('basepath') || app.route);
|
|
app.set('basepath', base);
|
|
app.parent = this;
|
|
if (app.__mounted) app.__mounted.call(app, this);
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Assign a callback `fn` which is called
|
|
* when this `Server` is passed to `Server#use()`.
|
|
*
|
|
* Examples:
|
|
*
|
|
* var app = express.createServer()
|
|
* , blog = express.createServer();
|
|
*
|
|
* blog.mounted(function(parent){
|
|
* // parent is app
|
|
* // "this" is blog
|
|
* });
|
|
*
|
|
* app.use(blog);
|
|
*
|
|
* @param {Function} fn
|
|
* @return {Server} for chaining
|
|
* @api public
|
|
*/
|
|
|
|
app.mounted = function(fn){
|
|
this.__mounted = fn;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Register the given template engine callback `fn`
|
|
* as `ext`. For example we may wish to map ".html"
|
|
* files to ejs rather than using the ".ejs" extension.
|
|
*
|
|
* app.register('.html', require('ejs').render);
|
|
*
|
|
* or
|
|
*
|
|
* app.register('html', require('ejs').render);
|
|
*
|
|
* @param {String} ext
|
|
* @param {Function} fn
|
|
* @return {Server} for chaining
|
|
* @api public
|
|
*/
|
|
|
|
app.register = function(ext, fn){
|
|
if ('.' != ext[0]) ext = '.' + ext;
|
|
this.engines[ext] = fn;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Map the given param placeholder `name`(s) to the given callback `fn`.
|
|
* Register the given view helpers `obj`. This method
|
|
* can be called several times to apply additional helpers.
|
|
*
|
|
* @param {Object} obj
|
|
* @return {Server} for chaining
|
|
* @api public
|
|
*/
|
|
|
|
app.locals = function(obj){
|
|
utils.merge(this._locals, obj);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Map the given param placeholder `name`(s) to the given callback(s).
|
|
*
|
|
* Param mapping is used to provide pre-conditions to routes
|
|
* 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.
|
|
*
|
|
* 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'));
|
|
* }
|
|
* });
|
|
* });
|
|
*
|
|
* Passing a single function allows you to map logic
|
|
* to the values passed to `app.param()`, for example
|
|
* this is useful to provide coercion support in a concise manner.
|
|
*
|
|
* The following example maps regular expressions to param values
|
|
* ensuring that they match, otherwise passing control to the next
|
|
* route:
|
|
*
|
|
* app.param(function(name, regexp){
|
|
* if (regexp instanceof RegExp) {
|
|
* return function(req, res, next, val){
|
|
* var captures;
|
|
* if (captures = regexp.exec(String(val))) {
|
|
* req.params[name] = captures;
|
|
* next();
|
|
* } else {
|
|
* next('route');
|
|
* }
|
|
* }
|
|
* }
|
|
* });
|
|
*
|
|
* We can now use it as shown below, where "/commit/:commit" expects
|
|
* that the value for ":commit" is at 5 or more digits. The capture
|
|
* groups are then available as `req.params.commit` as we defined
|
|
* in the function above.
|
|
*
|
|
* app.param('commit', /^\d{5,}$/);
|
|
*
|
|
* For more of this useful functionality take a look
|
|
* at [express-params](http://github.com/visionmedia/express-params).
|
|
*
|
|
* @param {String|Array|Function} name
|
|
* @param {Function} fn
|
|
* @return {Server} for chaining
|
|
* @api public
|
|
*/
|
|
|
|
app.param = function(name, fn){
|
|
var self = this
|
|
, fns = [].slice.call(arguments, 1);
|
|
|
|
// array
|
|
if (Array.isArray(name)) {
|
|
name.forEach(function(name){
|
|
fns.forEach(function(fn){
|
|
self.param(name, fn);
|
|
});
|
|
});
|
|
// param logic
|
|
} else if ('function' == typeof name) {
|
|
this.routes.param(name);
|
|
// single
|
|
} else {
|
|
if (':' == name[0]) name = name.substr(1);
|
|
fns.forEach(function(fn){
|
|
self.routes.param(name, fn);
|
|
});
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Register the given callback `fn` for the given `type`.
|
|
*
|
|
* @param {String} type
|
|
* @param {Function} fn
|
|
* @return {Server} for chaining
|
|
* @api public
|
|
*/
|
|
|
|
app.is = function(type, fn){
|
|
if (!fn) return this.isCallbacks[type];
|
|
this.isCallbacks[type] = fn;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Assign `setting` to `val`, or return `setting`'s value.
|
|
* Mounted servers inherit their parent server's settings.
|
|
*
|
|
* @param {String} setting
|
|
* @param {String} val
|
|
* @return {Server|Mixed} for chaining, or the setting value
|
|
* @api public
|
|
*/
|
|
|
|
app.set = function(setting, val){
|
|
if (val === undefined) {
|
|
if (this.settings.hasOwnProperty(setting)) {
|
|
return this.settings[setting];
|
|
} else if (this.parent) {
|
|
return this.parent.set(setting);
|
|
}
|
|
} else {
|
|
this.settings[setting] = val;
|
|
return this;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Check if `setting` is enabled.
|
|
*
|
|
* @param {String} setting
|
|
* @return {Boolean}
|
|
* @api public
|
|
*/
|
|
|
|
app.enabled = function(setting){
|
|
return !!this.set(setting);
|
|
};
|
|
|
|
/**
|
|
* Check if `setting` is disabled.
|
|
*
|
|
* @param {String} setting
|
|
* @return {Boolean}
|
|
* @api public
|
|
*/
|
|
|
|
app.disabled = function(setting){
|
|
return !this.set(setting);
|
|
};
|
|
|
|
/**
|
|
* Enable `setting`.
|
|
*
|
|
* @param {String} setting
|
|
* @return {Server} for chaining
|
|
* @api public
|
|
*/
|
|
|
|
app.enable = function(setting){
|
|
return this.set(setting, true);
|
|
};
|
|
|
|
/**
|
|
* Disable `setting`.
|
|
*
|
|
* @param {String} setting
|
|
* @return {Server} for chaining
|
|
* @api public
|
|
*/
|
|
|
|
app.disable = function(setting){
|
|
return this.set(setting, false);
|
|
};
|
|
|
|
/**
|
|
* Redirect `key` to `url`.
|
|
*
|
|
* @param {String} key
|
|
* @param {String} url
|
|
* @return {Server} for chaining
|
|
* @api public
|
|
*/
|
|
|
|
app.redirect = function(key, url){
|
|
this.redirects[key] = url;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Configure callback for zero or more envs,
|
|
* when no env is specified that callback will
|
|
* be invoked for all environments. Any combination
|
|
* can be used multiple times, in any order desired.
|
|
*
|
|
* Examples:
|
|
*
|
|
* app.configure(function(){
|
|
* // executed for all envs
|
|
* });
|
|
*
|
|
* app.configure('stage', function(){
|
|
* // executed staging env
|
|
* });
|
|
*
|
|
* app.configure('stage', 'production', function(){
|
|
* // executed for stage and production
|
|
* });
|
|
*
|
|
* @param {String} env...
|
|
* @param {Function} fn
|
|
* @return {Server} for chaining
|
|
* @api public
|
|
*/
|
|
|
|
app.configure = function(env, fn){
|
|
var envs = 'all'
|
|
, args = toArray(arguments);
|
|
fn = args.pop();
|
|
if (args.length) envs = args;
|
|
if ('all' == envs || ~envs.indexOf(this.settings.env)) fn.call(this);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Delegate `.VERB(...)` calls to `.route(VERB, ...)`.
|
|
*/
|
|
|
|
methods.forEach(function(method){
|
|
app[method] = function(path){
|
|
if (1 == arguments.length) return this.routes.lookup(method, path);
|
|
var args = [method].concat(toArray(arguments));
|
|
if (!this.__usedRouter) this.use(this.router);
|
|
return this.routes._route.apply(this.routes, args);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 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;
|
|
|
|
/**
|
|
* Render the given `view` name with `opts`
|
|
* and a callback accepting an error and the
|
|
* rendered template string.
|
|
*
|
|
* @param {String} view
|
|
* @param {String|Function} opts or fn
|
|
* @param {Function} fn
|
|
* @api public
|
|
*/
|
|
|
|
app.render = function(view, opts, fn){
|
|
var self = this
|
|
, options = {}
|
|
, engines = this.engines
|
|
, root = this.set('views') || process.cwd() + '/views';
|
|
|
|
// support callback function as second arg
|
|
if ('function' == typeof opts) {
|
|
fn = opts, opts = null;
|
|
}
|
|
|
|
// merge app.locals
|
|
union(options, this.locals);
|
|
|
|
// merge render() options
|
|
if (opts) utils.merge(options, opts);
|
|
|
|
// join "view engine" if necessary
|
|
var ext = extname(view);
|
|
if (!ext) view += '.' + (ext = this.set('view engine'));
|
|
|
|
// pass .cache to the engine
|
|
options.cache = this.enabled('view cache');
|
|
|
|
// when no extension nor "view engine" is given
|
|
if (!ext) return fn(new Error('failed to find view "' + view + '"'));
|
|
|
|
// render
|
|
try {
|
|
var engine = engines[ext] = engines[ext] || require(ext);
|
|
options.filename = join(root, view);
|
|
view = fs.readFileSync(options.filename, 'utf8');
|
|
engine.render(view, options, fn);
|
|
} catch (err) {
|
|
fn(err);
|
|
}
|
|
};
|