| /*! |
| * 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; |
| } |