4 * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
12 var qs = require('qs')
13 , connect = require('connect')
14 , router = require('./router')
15 , methods = router.methods.concat(['del', 'all'])
16 , view = require('./view')
17 , url = require('url')
18 , utils = connect.utils;
21 * Expose `HTTPServer`.
24 exports = module.exports = HTTPServer;
30 var app = HTTPServer.prototype;
33 * Initialize a new `HTTPServer` with optional `middleware`.
35 * @param {Array} middleware
39 function HTTPServer(middleware){
40 connect.HTTPServer.call(this, []);
41 this.init(middleware);
45 * Inherit from `connect.HTTPServer`.
48 app.__proto__ = connect.HTTPServer.prototype;
51 * Initialize the server.
53 * @param {Array} middleware
57 app.init = function(middleware){
62 this.isCallbacks = {};
64 this.dynamicViewHelpers = {};
65 this.errorHandlers = [];
67 this.set('home', '/');
68 this.set('env', process.env.NODE_ENV || 'development');
70 // expose objects to each other
71 this.use(function(req, res, next){
72 req.query = req.query || {};
73 res.setHeader('X-Powered-By', 'Express');
74 req.app = res.app = self;
79 if (req.url.indexOf('?') > 0) {
80 var query = url.parse(req.url).query;
81 req.query = qs.parse(query);
87 if (middleware) middleware.forEach(self.use.bind(self));
89 // use router, expose as app.get(), etc
90 var fn = router(function(app){ self.routes = app; }, this);
91 this.__defineGetter__('router', function(){
92 this.__usedRouter = true;
98 settings: this.settings
102 // default development configuration
103 this.configure('development', function(){
104 this.enable('hints');
107 // default production configuration
108 this.configure('production', function(){
109 this.enable('view cache');
112 // register error handlers on "listening"
113 // so that they disregard definition position.
114 this.on('listening', this.registerErrorHandlers.bind(this));
116 // route lookup methods
117 this.remove = function(url){
118 return self.remove.all(url);
121 this.match = function(url){
122 return self.match.all(url);
125 this.lookup = function(url){
126 return self.lookup.all(url);
129 methods.forEach(function(method){
130 self.match[method] = function(url){
131 return self.router.match(url, 'all' == method
136 self.remove[method] = function(url){
137 return self.router.remove(url, 'all' == method
142 self.lookup[method] = function(path){
143 return self.router.lookup(path, 'all' == method
151 * When using the vhost() middleware register error handlers.
154 app.onvhost = function(){
155 this.registerErrorHandlers();
159 * Register error handlers.
161 * @return {Server} for chaining
165 app.registerErrorHandlers = function(){
166 this.errorHandlers.forEach(function(fn){
167 this.use(function(err, req, res, next){
168 fn.apply(this, arguments);
175 * Proxy `connect.HTTPServer#use()` to apply settings to
176 * mounted applications.
178 * @param {String|Function|Server} route
179 * @param {Function|Server} middleware
180 * @return {Server} for chaining
184 app.use = function(route, middleware){
185 var app, home, handle;
187 if ('string' != typeof route) {
188 middleware = route, route = '/';
192 if (middleware.handle && middleware.set) app = middleware;
194 // restore .app property on req and res
197 middleware = function(req, res, next) {
199 app.handle(req, res, function(err){
200 req.app = res.app = orig;
206 connect.HTTPServer.prototype.use.call(this, route, middleware);
208 // mounted an app, invoke the hook
209 // and adjust some settings
211 home = app.set('home');
212 if ('/' == home) home = '';
213 app.set('home', app.route + home);
215 if (app.__mounted) app.__mounted.call(app, this);
222 * Assign a callback `fn` which is called
223 * when this `Server` is passed to `Server#use()`.
227 * var app = express.createServer()
228 * , blog = express.createServer();
230 * blog.mounted(function(parent){
237 * @param {Function} fn
238 * @return {Server} for chaining
242 app.mounted = function(fn){
248 * See: view.register.
250 * @return {Server} for chaining
254 app.register = function(){
255 view.register.apply(this, arguments);
260 * Register the given view helpers `obj`. This method
261 * can be called several times to apply additional helpers.
263 * @param {Object} obj
264 * @return {Server} for chaining
269 app.locals = function(obj){
270 utils.merge(this._locals, obj);
275 * Register the given dynamic view helpers `obj`. This method
276 * can be called several times to apply additional helpers.
278 * @param {Object} obj
279 * @return {Server} for chaining
283 app.dynamicHelpers = function(obj){
284 utils.merge(this.dynamicViewHelpers, obj);
289 * Map the given param placeholder `name`(s) to the given callback `fn`.
291 * Param mapping is used to provide pre-conditions to routes
292 * which us normalized placeholders. For example ":user_id" may
293 * attempt to load the user from the database, where as ":num" may
294 * pass the value through `parseInt(num, 10)`.
296 * When the callback function accepts only a single argument, the
297 * value of placeholder is passed:
299 * app.param('page', function(n){ return parseInt(n, 10); });
301 * After which "/users/:page" would automatically provide us with
302 * an integer for `req.params.page`. If desired we could use the callback
303 * signature shown below, and immediately `next(new Error('invalid page'))`
304 * when `parseInt` fails.
306 * Alternatively the callback may accept the request, response, next, and
307 * the value, acting like middlware:
309 * app.param('userId', function(req, res, next, id){
310 * User.find(id, function(err, user){
317 * next(new Error('failed to load user'));
322 * Now every time ":userId" is present, the associated user object
323 * will be loaded and assigned before the route handler is invoked.
325 * @param {String|Array} name
326 * @param {Function} fn
327 * @return {Server} for chaining
331 app.param = function(name, fn){
332 if (Array.isArray(name)) {
333 name.forEach(function(name){
334 this.param(name, fn);
337 if (':' == name[0]) name = name.substr(1);
338 this.routes.param(name, fn);
344 * Assign a custom exception handler callback `fn`.
345 * These handlers are always _last_ in the middleware stack.
347 * @param {Function} fn
348 * @return {Server} for chaining
352 app.error = function(fn){
353 this.errorHandlers.push(fn);
358 * Register the given callback `fn` for the given `type`.
360 * @param {String} type
361 * @param {Function} fn
362 * @return {Server} for chaining
366 app.is = function(type, fn){
367 if (!fn) return this.isCallbacks[type];
368 this.isCallbacks[type] = fn;
373 * Assign `setting` to `val`, or return `setting`'s value.
374 * Mounted servers inherit their parent server's settings.
376 * @param {String} setting
377 * @param {String} val
378 * @return {Server|Mixed} for chaining, or the setting value
382 app.set = function(setting, val){
383 if (val === undefined) {
384 if (this.settings.hasOwnProperty(setting)) {
385 return this.settings[setting];
386 } else if (this.parent) {
387 return this.parent.set(setting);
390 this.settings[setting] = val;
396 * Check if `setting` is enabled.
398 * @param {String} setting
403 app.enabled = function(setting){
404 return !!this.set(setting);
408 * Check if `setting` is disabled.
410 * @param {String} setting
415 app.disabled = function(setting){
416 return !this.set(setting);
422 * @param {String} setting
423 * @return {Server} for chaining
427 app.enable = function(setting){
428 return this.set(setting, true);
434 * @param {String} setting
435 * @return {Server} for chaining
439 app.disable = function(setting){
440 return this.set(setting, false);
444 * Redirect `key` to `url`.
446 * @param {String} key
447 * @param {String} url
448 * @return {Server} for chaining
452 app.redirect = function(key, url){
453 this.redirects[key] = url;
458 * Configure callback for the given `env`.
460 * @param {String} env
461 * @param {Function} fn
462 * @return {Server} for chaining
466 app.configure = function(env, fn){
467 if ('function' == typeof env) {
468 fn = env, env = 'all';
470 if ('all' == env || env == this.settings.env) {
476 // Generate routing methods
478 function generateMethod(method){
479 app[method] = function(path){
483 if (1 == arguments.length) {
484 return this.router.lookup(path, 'all' == method
489 // Ensure router is mounted
490 if (!this.__usedRouter) this.use(this.router);
492 // Generate the route
493 this.routes[method].apply(this, arguments);
496 return arguments.callee;
499 methods.forEach(generateMethod);
501 // Alias delete as "del"
503 app.del = app.delete;