| |
| /*! |
| * Connect - HTTPServer |
| * Copyright(c) 2010 Sencha Inc. |
| * Copyright(c) 2011 TJ Holowaychuk |
| * MIT Licensed |
| */ |
| |
| /** |
| * Module dependencies. |
| */ |
| |
| var http = require('http') |
| , parse = require('url').parse |
| , assert = require('assert') |
| , utils = require('./utils'); |
| |
| // environment |
| |
| var env = process.env.NODE_ENV || 'development'; |
| |
| /** |
| * Initialize a new `Server` with the given `middleware`. |
| * |
| * Examples: |
| * |
| * var server = connect.createServer( |
| * connect.favicon() |
| * , connect.logger() |
| * , connect.static(__dirname + '/public') |
| * ); |
| * |
| * @params {Array} middleware |
| * @return {Server} |
| * @api public |
| */ |
| |
| var Server = exports.Server = function HTTPServer(middleware) { |
| this.stack = []; |
| middleware.forEach(function(fn){ |
| this.use(fn); |
| }, this); |
| http.Server.call(this, this.handle); |
| }; |
| |
| /** |
| * Inherit from `http.Server.prototype`. |
| */ |
| |
| Server.prototype.__proto__ = http.Server.prototype; |
| |
| /** |
| * Utilize the given middleware `handle` to the given `route`, |
| * defaulting to _/_. This "route" is the mount-point for the |
| * middleware, when given a value other than _/_ the middleware |
| * is only effective when that segment is present in the request's |
| * pathname. |
| * |
| * For example if we were to mount a function at _/admin_, it would |
| * be invoked on _/admin_, and _/admin/settings_, however it would |
| * not be invoked for _/_, or _/posts_. |
| * |
| * This is effectively the same as passing middleware to `connect.createServer()`, |
| * however provides a progressive api. |
| * |
| * Examples: |
| * |
| * var server = connect.createServer(); |
| * server.use(connect.favicon()); |
| * server.use(connect.logger()); |
| * server.use(connect.static(__dirname + '/public')); |
| * |
| * If we wanted to prefix static files with _/public_, we could |
| * "mount" the `static()` middleware: |
| * |
| * server.use('/public', connect.static(__dirname + '/public')); |
| * |
| * This api is chainable, meaning the following is valid: |
| * |
| * connect.createServer() |
| * .use(connect.favicon()) |
| * .use(connect.logger()) |
| * .use(connect.static(__dirname + '/public')) |
| * .listen(3000); |
| * |
| * @param {String|Function} route or handle |
| * @param {Function} handle |
| * @return {Server} |
| * @api public |
| */ |
| |
| Server.prototype.use = function(route, handle){ |
| this.route = '/'; |
| |
| // default route to '/' |
| if ('string' != typeof route) { |
| handle = route; |
| route = '/'; |
| } |
| |
| // wrap sub-apps |
| if ('function' == typeof handle.handle) { |
| var server = handle; |
| server.route = route; |
| handle = function(req, res, next) { |
| server.handle(req, res, next); |
| }; |
| } |
| |
| // wrap vanilla http.Servers |
| if (handle instanceof http.Server) { |
| handle = handle.listeners('request')[0]; |
| } |
| |
| // normalize route to not trail with slash |
| if ('/' == route[route.length - 1]) { |
| route = route.substr(0, route.length - 1); |
| } |
| |
| // add the middleware |
| this.stack.push({ route: route, handle: handle }); |
| |
| // allow chaining |
| return this; |
| }; |
| |
| /** |
| * Handle server requests, punting them down |
| * the middleware stack. |
| * |
| * @api private |
| */ |
| |
| Server.prototype.handle = function(req, res, out) { |
| var writeHead = res.writeHead |
| , stack = this.stack |
| , removed = '' |
| , index = 0; |
| |
| function next(err) { |
| var layer, path, c; |
| req.url = removed + req.url; |
| req.originalUrl = req.originalUrl || req.url; |
| removed = ''; |
| |
| layer = stack[index++]; |
| |
| // all done |
| if (!layer || res.headerSent) { |
| // but wait! we have a parent |
| if (out) return out(err); |
| |
| // error |
| if (err) { |
| var msg = 'production' == env |
| ? 'Internal Server Error' |
| : err.stack || err.toString(); |
| |
| // output to stderr in a non-test env |
| if ('test' != env) console.error(err.stack || err.toString()); |
| |
| // unable to respond |
| if (res.headerSent) return req.socket.destroy(); |
| |
| res.statusCode = 500; |
| res.setHeader('Content-Type', 'text/plain'); |
| if ('HEAD' == req.method) return res.end(); |
| res.end(msg); |
| } else { |
| res.statusCode = 404; |
| res.setHeader('Content-Type', 'text/plain'); |
| if ('HEAD' == req.method) return res.end(); |
| res.end('Cannot ' + req.method + ' ' + utils.escape(req.originalUrl)); |
| } |
| return; |
| } |
| |
| try { |
| path = parse(req.url).pathname; |
| if (undefined == path) path = '/'; |
| |
| // skip this layer if the route doesn't match. |
| if (0 != path.indexOf(layer.route)) return next(err); |
| |
| c = path[layer.route.length]; |
| if (c && '/' != c && '.' != c) return next(err); |
| |
| // Call the layer handler |
| // Trim off the part of the url that matches the route |
| removed = layer.route; |
| req.url = req.url.substr(removed.length); |
| |
| // Ensure leading slash |
| if ('/' != req.url[0]) req.url = '/' + req.url; |
| |
| var arity = layer.handle.length; |
| if (err) { |
| if (arity === 4) { |
| layer.handle(err, req, res, next); |
| } else { |
| next(err); |
| } |
| } else if (arity < 4) { |
| layer.handle(req, res, next); |
| } else { |
| next(); |
| } |
| } catch (e) { |
| if (e instanceof assert.AssertionError) { |
| console.error(e.stack + '\n'); |
| next(e); |
| } else { |
| next(e); |
| } |
| } |
| } |
| next(); |
| }; |