| /*! |
| * Express - HTTPServer |
| * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> |
| * MIT Licensed |
| */ |
| |
| /** |
| * Module dependencies. |
| */ |
| |
| var qs = require('qs') |
| , connect = require('connect') |
| , router = require('./router') |
| , Router = require('./router') |
| , view = require('./view') |
| , toArray = require('./utils').toArray |
| , methods = router.methods.concat('del', 'all') |
| , url = require('url') |
| , utils = connect.utils; |
| |
| /** |
| * Expose `HTTPServer`. |
| */ |
| |
| exports = module.exports = HTTPServer; |
| |
| /** |
| * Server proto. |
| */ |
| |
| var app = HTTPServer.prototype; |
| |
| /** |
| * Initialize a new `HTTPServer` with optional `middleware`. |
| * |
| * @param {Array} middleware |
| * @api public |
| */ |
| |
| function HTTPServer(middleware){ |
| connect.HTTPServer.call(this, []); |
| this.init(middleware); |
| }; |
| |
| /** |
| * Inherit from `connect.HTTPServer`. |
| */ |
| |
| app.__proto__ = connect.HTTPServer.prototype; |
| |
| /** |
| * Initialize the server. |
| * |
| * @param {Array} middleware |
| * @api private |
| */ |
| |
| app.init = function(middleware){ |
| var self = this; |
| this.cache = {}; |
| this.settings = {}; |
| this.redirects = {}; |
| this.isCallbacks = {}; |
| this._locals = {}; |
| this.dynamicViewHelpers = {}; |
| this.errorHandlers = []; |
| |
| this.set('env', process.env.NODE_ENV || 'development'); |
| |
| // expose objects to each other |
| this.use(function(req, res, next){ |
| req.query = req.query || {}; |
| res.setHeader('X-Powered-By', 'Express'); |
| req.app = res.app = self; |
| req.res = res; |
| res.req = req; |
| req.next = next; |
| // assign req.query |
| if (req.url.indexOf('?') > 0) { |
| var query = url.parse(req.url).query; |
| req.query = qs.parse(query); |
| } |
| 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 |
| , app: this |
| }); |
| |
| // default development configuration |
| this.configure('development', function(){ |
| this.enable('hints'); |
| }); |
| |
| // default production configuration |
| this.configure('production', function(){ |
| this.enable('view cache'); |
| }); |
| |
| // register error handlers on "listening" |
| // so that they disregard definition position. |
| this.on('listening', this.registerErrorHandlers.bind(this)); |
| |
| // 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); |
| }; |
| |
| /** |
| * When using the vhost() middleware register error handlers. |
| */ |
| |
| app.onvhost = function(){ |
| this.registerErrorHandlers(); |
| }; |
| |
| /** |
| * Register error handlers. |
| * |
| * @return {Server} for chaining |
| * @api public |
| */ |
| |
| app.registerErrorHandlers = function(){ |
| this.errorHandlers.forEach(function(fn){ |
| this.use(function(err, req, res, next){ |
| fn.apply(this, arguments); |
| }); |
| }, this); |
| return this; |
| }; |
| |
| /** |
| * 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, base, 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.HTTPServer.prototype.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; |
| }; |
| |
| /** |
| * See: view.register. |
| * |
| * @return {Server} for chaining |
| * @api public |
| */ |
| |
| app.register = function(){ |
| view.register.apply(this, arguments); |
| return this; |
| }; |
| |
| /** |
| * 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.helpers = |
| app.locals = function(obj){ |
| utils.merge(this._locals, obj); |
| return this; |
| }; |
| |
| /** |
| * Register the given dynamic view helpers `obj`. This method |
| * can be called several times to apply additional helpers. |
| * |
| * @param {Object} obj |
| * @return {Server} for chaining |
| * @api public |
| */ |
| |
| app.dynamicHelpers = function(obj){ |
| utils.merge(this.dynamicViewHelpers, 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; |
| }; |
| |
| /** |
| * Assign a custom exception handler callback `fn`. |
| * These handlers are always _last_ in the middleware stack. |
| * |
| * @param {Function} fn |
| * @return {Server} for chaining |
| * @api public |
| */ |
| |
| app.error = function(fn){ |
| this.errorHandlers.push(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 || 'del' == method) return; |
| app[method].apply(this, args); |
| }, this); |
| return this; |
| }; |
| |
| // del -> delete alias |
| |
| app.del = app.delete; |
| |