You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

230 lines
5.6 KiB

  1. /*!
  2. * Connect - HTTPServer
  3. * Copyright(c) 2010 Sencha Inc.
  4. * Copyright(c) 2011 TJ Holowaychuk
  5. * MIT Licensed
  6. */
  7. /**
  8. * Module dependencies.
  9. */
  10. var http = require('http')
  11. , utils = require('./utils')
  12. , debug = require('debug')('connect:dispatcher');
  13. // prototype
  14. var app = module.exports = {};
  15. // environment
  16. var env = process.env.NODE_ENV || 'development';
  17. /**
  18. * Utilize the given middleware `handle` to the given `route`,
  19. * defaulting to _/_. This "route" is the mount-point for the
  20. * middleware, when given a value other than _/_ the middleware
  21. * is only effective when that segment is present in the request's
  22. * pathname.
  23. *
  24. * For example if we were to mount a function at _/admin_, it would
  25. * be invoked on _/admin_, and _/admin/settings_, however it would
  26. * not be invoked for _/_, or _/posts_.
  27. *
  28. * Examples:
  29. *
  30. * var app = connect();
  31. * app.use(connect.favicon());
  32. * app.use(connect.logger());
  33. * app.use(connect.static(__dirname + '/public'));
  34. *
  35. * If we wanted to prefix static files with _/public_, we could
  36. * "mount" the `static()` middleware:
  37. *
  38. * app.use('/public', connect.static(__dirname + '/public'));
  39. *
  40. * This api is chainable, so the following is valid:
  41. *
  42. * connect()
  43. * .use(connect.favicon())
  44. * .use(connect.logger())
  45. * .use(connect.static(__dirname + '/public'))
  46. * .listen(3000);
  47. *
  48. * @param {String|Function|Server} route, callback or server
  49. * @param {Function|Server} callback or server
  50. * @return {Server} for chaining
  51. * @api public
  52. */
  53. app.use = function(route, fn){
  54. // default route to '/'
  55. if ('string' != typeof route) {
  56. fn = route;
  57. route = '/';
  58. }
  59. // wrap sub-apps
  60. if ('function' == typeof fn.handle) {
  61. var server = fn;
  62. fn.route = route;
  63. fn = function(req, res, next){
  64. server.handle(req, res, next);
  65. };
  66. }
  67. // wrap vanilla http.Servers
  68. if (fn instanceof http.Server) {
  69. fn = fn.listeners('request')[0];
  70. }
  71. // strip trailing slash
  72. if ('/' == route[route.length - 1]) {
  73. route = route.slice(0, -1);
  74. }
  75. // add the middleware
  76. debug('use %s %s', route || '/', fn.name || 'anonymous');
  77. this.stack.push({ route: route, handle: fn });
  78. return this;
  79. };
  80. /**
  81. * Handle server requests, punting them down
  82. * the middleware stack.
  83. *
  84. * @api private
  85. */
  86. app.handle = function(req, res, out) {
  87. var stack = this.stack
  88. , fqdn = ~req.url.indexOf('://')
  89. , removed = ''
  90. , slashAdded = false
  91. , index = 0;
  92. function next(err) {
  93. var layer, path, status, c;
  94. if (slashAdded) {
  95. req.url = req.url.substr(1);
  96. slashAdded = false;
  97. }
  98. req.url = removed + req.url;
  99. req.originalUrl = req.originalUrl || req.url;
  100. removed = '';
  101. // next callback
  102. layer = stack[index++];
  103. // all done
  104. if (!layer || res.headerSent) {
  105. // delegate to parent
  106. if (out) return out(err);
  107. // unhandled error
  108. if (err) {
  109. // default to 500
  110. if (res.statusCode < 400) res.statusCode = 500;
  111. debug('default %s', res.statusCode);
  112. // respect err.status
  113. if (err.status) res.statusCode = err.status;
  114. // production gets a basic error message
  115. var msg = 'production' == env
  116. ? http.STATUS_CODES[res.statusCode]
  117. : err.stack || err.toString();
  118. // log to stderr in a non-test env
  119. if ('test' != env) console.error(err.stack || err.toString());
  120. if (res.headerSent) return req.socket.destroy();
  121. res.setHeader('Content-Type', 'text/plain');
  122. res.setHeader('Content-Length', Buffer.byteLength(msg));
  123. if ('HEAD' == req.method) return res.end();
  124. res.end(msg);
  125. } else {
  126. debug('default 404');
  127. res.statusCode = 404;
  128. res.setHeader('Content-Type', 'text/plain');
  129. if ('HEAD' == req.method) return res.end();
  130. res.end('Cannot ' + req.method + ' ' + utils.escape(req.originalUrl));
  131. }
  132. return;
  133. }
  134. try {
  135. path = utils.parseUrl(req).pathname;
  136. if (undefined == path) path = '/';
  137. // skip this layer if the route doesn't match.
  138. if (0 != path.toLowerCase().indexOf(layer.route.toLowerCase())) return next(err);
  139. c = path[layer.route.length];
  140. if (c && '/' != c && '.' != c) return next(err);
  141. // Call the layer handler
  142. // Trim off the part of the url that matches the route
  143. removed = layer.route;
  144. req.url = req.url.substr(removed.length);
  145. // Ensure leading slash
  146. if (!fqdn && '/' != req.url[0]) {
  147. req.url = '/' + req.url;
  148. slashAdded = true;
  149. }
  150. debug('%s', layer.handle.name || 'anonymous');
  151. var arity = layer.handle.length;
  152. if (err) {
  153. if (arity === 4) {
  154. layer.handle(err, req, res, next);
  155. } else {
  156. next(err);
  157. }
  158. } else if (arity < 4) {
  159. layer.handle(req, res, next);
  160. } else {
  161. next();
  162. }
  163. } catch (e) {
  164. next(e);
  165. }
  166. }
  167. next();
  168. };
  169. /**
  170. * Listen for connections.
  171. *
  172. * This method takes the same arguments
  173. * as node's `http.Server#listen()`.
  174. *
  175. * HTTP and HTTPS:
  176. *
  177. * If you run your application both as HTTP
  178. * and HTTPS you may wrap them individually,
  179. * since your Connect "server" is really just
  180. * a JavaScript `Function`.
  181. *
  182. * var connect = require('connect')
  183. * , http = require('http')
  184. * , https = require('https');
  185. *
  186. * var app = connect();
  187. *
  188. * http.createServer(app).listen(80);
  189. * https.createServer(options, app).listen(443);
  190. *
  191. * @return {http.Server}
  192. * @api public
  193. */
  194. app.listen = function(){
  195. var server = http.createServer(this);
  196. return server.listen.apply(server, arguments);
  197. };