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