|
|
/*! * connect * Copyright(c) 2010 Sencha Inc. * Copyright(c) 2011 TJ Holowaychuk * Copyright(c) 2015 Douglas Christopher Wilson * MIT Licensed */
'use strict';
/** * Module dependencies. * @private */
var debug = require('debug')('connect:dispatcher'); var EventEmitter = require('events').EventEmitter; var finalhandler = require('finalhandler'); var http = require('http'); var merge = require('utils-merge'); var parseUrl = require('parseurl');
/** * Module exports. * @public */
module.exports = createServer;
/** * Module variables. * @private */
var env = process.env.NODE_ENV || 'development'; var proto = {};
/* istanbul ignore next */ var defer = typeof setImmediate === 'function' ? setImmediate : function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) }
/** * Create a new connect server. * * @return {function} * @public */
function createServer() { function app(req, res, next){ app.handle(req, res, next); } merge(app, proto); merge(app, EventEmitter.prototype); app.route = '/'; app.stack = []; return app; }
/** * 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_. * * @param {String|Function|Server} route, callback or server * @param {Function|Server} callback or server * @return {Server} for chaining * @public */
proto.use = function use(route, fn) { var handle = fn; var path = route;
// default route to '/'
if (typeof route !== 'string') { handle = route; path = '/'; }
// wrap sub-apps
if (typeof handle.handle === 'function') { var server = handle; server.route = path; handle = function (req, res, next) { server.handle(req, res, next); }; }
// wrap vanilla http.Servers
if (handle instanceof http.Server) { handle = handle.listeners('request')[0]; }
// strip trailing slash
if (path[path.length - 1] === '/') { path = path.slice(0, -1); }
// add the middleware
debug('use %s %s', path || '/', handle.name || 'anonymous'); this.stack.push({ route: path, handle: handle });
return this; };
/** * Handle server requests, punting them down * the middleware stack. * * @private */
proto.handle = function handle(req, res, out) { var index = 0; var protohost = getProtohost(req.url) || ''; var removed = ''; var slashAdded = false; var stack = this.stack;
// final function handler
var done = out || finalhandler(req, res, { env: env, onerror: logerror });
// store the original URL
req.originalUrl = req.originalUrl || req.url;
function next(err) { if (slashAdded) { req.url = req.url.substr(1); slashAdded = false; }
if (removed.length !== 0) { req.url = protohost + removed + req.url.substr(protohost.length); removed = ''; }
// next callback
var layer = stack[index++];
// all done
if (!layer) { defer(done, err); return; }
// route data
var path = parseUrl(req).pathname || '/'; var route = layer.route;
// skip this layer if the route doesn't match
if (path.toLowerCase().substr(0, route.length) !== route.toLowerCase()) { return next(err); }
// skip if route match does not border "/", ".", or end
var c = path.length > route.length && path[route.length]; if (c && c !== '/' && c !== '.') { return next(err); }
// trim off the part of the url that matches the route
if (route.length !== 0 && route !== '/') { removed = route; req.url = protohost + req.url.substr(protohost.length + removed.length);
// ensure leading slash
if (!protohost && req.url[0] !== '/') { req.url = '/' + req.url; slashAdded = true; } }
// call the layer handle
call(layer.handle, route, err, req, res, next); }
next(); };
/** * Listen for connections. * * This method takes the same arguments * as node's `http.Server#listen()`. * * HTTP and HTTPS: * * If you run your application both as HTTP * and HTTPS you may wrap them individually, * since your Connect "server" is really just * a JavaScript `Function`. * * var connect = require('connect') * , http = require('http') * , https = require('https'); * * var app = connect(); * * http.createServer(app).listen(80); * https.createServer(options, app).listen(443); * * @return {http.Server} * @api public */
proto.listen = function listen() { var server = http.createServer(this); return server.listen.apply(server, arguments); };
/** * Invoke a route handle. * @private */
function call(handle, route, err, req, res, next) { var arity = handle.length; var error = err; var hasError = Boolean(err);
debug('%s %s : %s', handle.name || '<anonymous>', route, req.originalUrl);
try { if (hasError && arity === 4) { // error-handling middleware
handle(err, req, res, next); return; } else if (!hasError && arity < 4) { // request-handling middleware
handle(req, res, next); return; } } catch (e) { // replace the error
error = e; }
// continue
next(error); }
/** * Log error using console.error. * * @param {Error} err * @private */
function logerror(err) { if (env !== 'test') console.error(err.stack || err.toString()); }
/** * Get get protocol + host for a URL. * * @param {string} url * @private */
function getProtohost(url) { if (url.length === 0 || url[0] === '/') { return undefined; }
var fqdnIndex = url.indexOf('://')
return fqdnIndex !== -1 && url.lastIndexOf('?', fqdnIndex) === -1 ? url.substr(0, url.indexOf('/', 3 + fqdnIndex)) : undefined; }
|