index.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. /*!
  2. * connect
  3. * Copyright(c) 2010 Sencha Inc.
  4. * Copyright(c) 2011 TJ Holowaychuk
  5. * Copyright(c) 2015 Douglas Christopher Wilson
  6. * MIT Licensed
  7. */
  8. 'use strict';
  9. /**
  10. * Module dependencies.
  11. * @private
  12. */
  13. var debug = require('debug')('connect:dispatcher');
  14. var EventEmitter = require('events').EventEmitter;
  15. var finalhandler = require('finalhandler');
  16. var http = require('http');
  17. var merge = require('utils-merge');
  18. var parseUrl = require('parseurl');
  19. /**
  20. * Module exports.
  21. * @public
  22. */
  23. module.exports = createServer;
  24. /**
  25. * Module variables.
  26. * @private
  27. */
  28. var env = process.env.NODE_ENV || 'development';
  29. var proto = {};
  30. /* istanbul ignore next */
  31. var defer = typeof setImmediate === 'function'
  32. ? setImmediate
  33. : function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) }
  34. /**
  35. * Create a new connect server.
  36. *
  37. * @return {function}
  38. * @public
  39. */
  40. function createServer() {
  41. function app(req, res, next){ app.handle(req, res, next); }
  42. merge(app, proto);
  43. merge(app, EventEmitter.prototype);
  44. app.route = '/';
  45. app.stack = [];
  46. return app;
  47. }
  48. /**
  49. * Utilize the given middleware `handle` to the given `route`,
  50. * defaulting to _/_. This "route" is the mount-point for the
  51. * middleware, when given a value other than _/_ the middleware
  52. * is only effective when that segment is present in the request's
  53. * pathname.
  54. *
  55. * For example if we were to mount a function at _/admin_, it would
  56. * be invoked on _/admin_, and _/admin/settings_, however it would
  57. * not be invoked for _/_, or _/posts_.
  58. *
  59. * @param {String|Function|Server} route, callback or server
  60. * @param {Function|Server} callback or server
  61. * @return {Server} for chaining
  62. * @public
  63. */
  64. proto.use = function use(route, fn) {
  65. var handle = fn;
  66. var path = route;
  67. // default route to '/'
  68. if (typeof route !== 'string') {
  69. handle = route;
  70. path = '/';
  71. }
  72. // wrap sub-apps
  73. if (typeof handle.handle === 'function') {
  74. var server = handle;
  75. server.route = path;
  76. handle = function (req, res, next) {
  77. server.handle(req, res, next);
  78. };
  79. }
  80. // wrap vanilla http.Servers
  81. if (handle instanceof http.Server) {
  82. handle = handle.listeners('request')[0];
  83. }
  84. // strip trailing slash
  85. if (path[path.length - 1] === '/') {
  86. path = path.slice(0, -1);
  87. }
  88. // add the middleware
  89. debug('use %s %s', path || '/', handle.name || 'anonymous');
  90. this.stack.push({ route: path, handle: handle });
  91. return this;
  92. };
  93. /**
  94. * Handle server requests, punting them down
  95. * the middleware stack.
  96. *
  97. * @private
  98. */
  99. proto.handle = function handle(req, res, out) {
  100. var index = 0;
  101. var protohost = getProtohost(req.url) || '';
  102. var removed = '';
  103. var slashAdded = false;
  104. var stack = this.stack;
  105. // final function handler
  106. var done = out || finalhandler(req, res, {
  107. env: env,
  108. onerror: logerror
  109. });
  110. // store the original URL
  111. req.originalUrl = req.originalUrl || req.url;
  112. function next(err) {
  113. if (slashAdded) {
  114. req.url = req.url.substr(1);
  115. slashAdded = false;
  116. }
  117. if (removed.length !== 0) {
  118. req.url = protohost + removed + req.url.substr(protohost.length);
  119. removed = '';
  120. }
  121. // next callback
  122. var layer = stack[index++];
  123. // all done
  124. if (!layer) {
  125. defer(done, err);
  126. return;
  127. }
  128. // route data
  129. var path = parseUrl(req).pathname || '/';
  130. var route = layer.route;
  131. // skip this layer if the route doesn't match
  132. if (path.toLowerCase().substr(0, route.length) !== route.toLowerCase()) {
  133. return next(err);
  134. }
  135. // skip if route match does not border "/", ".", or end
  136. var c = path.length > route.length && path[route.length];
  137. if (c && c !== '/' && c !== '.') {
  138. return next(err);
  139. }
  140. // trim off the part of the url that matches the route
  141. if (route.length !== 0 && route !== '/') {
  142. removed = route;
  143. req.url = protohost + req.url.substr(protohost.length + removed.length);
  144. // ensure leading slash
  145. if (!protohost && req.url[0] !== '/') {
  146. req.url = '/' + req.url;
  147. slashAdded = true;
  148. }
  149. }
  150. // call the layer handle
  151. call(layer.handle, route, err, req, res, next);
  152. }
  153. next();
  154. };
  155. /**
  156. * Listen for connections.
  157. *
  158. * This method takes the same arguments
  159. * as node's `http.Server#listen()`.
  160. *
  161. * HTTP and HTTPS:
  162. *
  163. * If you run your application both as HTTP
  164. * and HTTPS you may wrap them individually,
  165. * since your Connect "server" is really just
  166. * a JavaScript `Function`.
  167. *
  168. * var connect = require('connect')
  169. * , http = require('http')
  170. * , https = require('https');
  171. *
  172. * var app = connect();
  173. *
  174. * http.createServer(app).listen(80);
  175. * https.createServer(options, app).listen(443);
  176. *
  177. * @return {http.Server}
  178. * @api public
  179. */
  180. proto.listen = function listen() {
  181. var server = http.createServer(this);
  182. return server.listen.apply(server, arguments);
  183. };
  184. /**
  185. * Invoke a route handle.
  186. * @private
  187. */
  188. function call(handle, route, err, req, res, next) {
  189. var arity = handle.length;
  190. var error = err;
  191. var hasError = Boolean(err);
  192. debug('%s %s : %s', handle.name || '<anonymous>', route, req.originalUrl);
  193. try {
  194. if (hasError && arity === 4) {
  195. // error-handling middleware
  196. handle(err, req, res, next);
  197. return;
  198. } else if (!hasError && arity < 4) {
  199. // request-handling middleware
  200. handle(req, res, next);
  201. return;
  202. }
  203. } catch (e) {
  204. // replace the error
  205. error = e;
  206. }
  207. // continue
  208. next(error);
  209. }
  210. /**
  211. * Log error using console.error.
  212. *
  213. * @param {Error} err
  214. * @private
  215. */
  216. function logerror(err) {
  217. if (env !== 'test') console.error(err.stack || err.toString());
  218. }
  219. /**
  220. * Get get protocol + host for a URL.
  221. *
  222. * @param {string} url
  223. * @private
  224. */
  225. function getProtohost(url) {
  226. if (url.length === 0 || url[0] === '/') {
  227. return undefined;
  228. }
  229. var fqdnIndex = url.indexOf('://')
  230. return fqdnIndex !== -1 && url.lastIndexOf('?', fqdnIndex) === -1
  231. ? url.substr(0, url.indexOf('/', 3 + fqdnIndex))
  232. : undefined;
  233. }