218 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			218 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
 | 
						|
/*!
 | 
						|
 * 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();
 | 
						|
}; |