application.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. 'use strict';
  2. /**
  3. * Module dependencies.
  4. */
  5. const isGeneratorFunction = require('is-generator-function');
  6. const debug = require('debug')('koa:application');
  7. const onFinished = require('on-finished');
  8. const response = require('./response');
  9. const compose = require('koa-compose');
  10. const isJSON = require('koa-is-json');
  11. const context = require('./context');
  12. const request = require('./request');
  13. const statuses = require('statuses');
  14. const Cookies = require('cookies');
  15. const accepts = require('accepts');
  16. const Emitter = require('events');
  17. const assert = require('assert');
  18. const Stream = require('stream');
  19. const http = require('http');
  20. const only = require('only');
  21. const convert = require('koa-convert');
  22. const deprecate = require('depd')('koa');
  23. /**
  24. * Expose `Application` class.
  25. * Inherits from `Emitter.prototype`.
  26. */
  27. module.exports = class Application extends Emitter {
  28. /**
  29. * Initialize a new `Application`.
  30. *
  31. * @api public
  32. */
  33. constructor() {
  34. super();
  35. this.proxy = false;
  36. this.middleware = [];
  37. this.subdomainOffset = 2;
  38. this.env = process.env.NODE_ENV || 'development';
  39. this.context = Object.create(context);
  40. this.request = Object.create(request);
  41. this.response = Object.create(response);
  42. }
  43. /**
  44. * Shorthand for:
  45. *
  46. * http.createServer(app.callback()).listen(...)
  47. *
  48. * @param {Mixed} ...
  49. * @return {Server}
  50. * @api public
  51. */
  52. listen(...args) {
  53. debug('listen');
  54. const server = http.createServer(this.callback());
  55. return server.listen(...args);
  56. }
  57. /**
  58. * Return JSON representation.
  59. * We only bother showing settings.
  60. *
  61. * @return {Object}
  62. * @api public
  63. */
  64. toJSON() {
  65. return only(this, [
  66. 'subdomainOffset',
  67. 'proxy',
  68. 'env'
  69. ]);
  70. }
  71. /**
  72. * Inspect implementation.
  73. *
  74. * @return {Object}
  75. * @api public
  76. */
  77. inspect() {
  78. return this.toJSON();
  79. }
  80. /**
  81. * Use the given middleware `fn`.
  82. *
  83. * Old-style middleware will be converted.
  84. *
  85. * @param {Function} fn
  86. * @return {Application} self
  87. * @api public
  88. */
  89. use(fn) {
  90. if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
  91. if (isGeneratorFunction(fn)) {
  92. deprecate('Support for generators will be removed in v3. ' +
  93. 'See the documentation for examples of how to convert old middleware ' +
  94. 'https://github.com/koajs/koa/blob/master/docs/migration.md');
  95. fn = convert(fn);
  96. }
  97. debug('use %s', fn._name || fn.name || '-');
  98. this.middleware.push(fn);
  99. return this;
  100. }
  101. /**
  102. * Return a request handler callback
  103. * for node's native http server.
  104. *
  105. * @return {Function}
  106. * @api public
  107. */
  108. callback() {
  109. const fn = compose(this.middleware);
  110. if (!this.listeners('error').length) this.on('error', this.onerror);
  111. const handleRequest = (req, res) => {
  112. res.statusCode = 404;
  113. const ctx = this.createContext(req, res);
  114. const onerror = err => ctx.onerror(err);
  115. const handleResponse = () => respond(ctx);
  116. onFinished(res, onerror);
  117. return fn(ctx).then(handleResponse).catch(onerror);
  118. };
  119. return handleRequest;
  120. }
  121. /**
  122. * Initialize a new context.
  123. *
  124. * @api private
  125. */
  126. createContext(req, res) {
  127. const context = Object.create(this.context);
  128. const request = context.request = Object.create(this.request);
  129. const response = context.response = Object.create(this.response);
  130. context.app = request.app = response.app = this;
  131. context.req = request.req = response.req = req;
  132. context.res = request.res = response.res = res;
  133. request.ctx = response.ctx = context;
  134. request.response = response;
  135. response.request = request;
  136. context.originalUrl = request.originalUrl = req.url;
  137. context.cookies = new Cookies(req, res, {
  138. keys: this.keys,
  139. secure: request.secure
  140. });
  141. request.ip = request.ips[0] || req.socket.remoteAddress || '';
  142. context.accept = request.accept = accepts(req);
  143. context.state = {};
  144. return context;
  145. }
  146. /**
  147. * Default error handler.
  148. *
  149. * @param {Error} err
  150. * @api private
  151. */
  152. onerror(err) {
  153. assert(err instanceof Error, `non-error thrown: ${err}`);
  154. if (404 == err.status || err.expose) return;
  155. if (this.silent) return;
  156. const msg = err.stack || err.toString();
  157. console.error();
  158. console.error(msg.replace(/^/gm, ' '));
  159. console.error();
  160. }
  161. };
  162. /**
  163. * Response helper.
  164. */
  165. function respond(ctx) {
  166. // allow bypassing koa
  167. if (false === ctx.respond) return;
  168. const res = ctx.res;
  169. if (!ctx.writable) return;
  170. let body = ctx.body;
  171. const code = ctx.status;
  172. // ignore body
  173. if (statuses.empty[code]) {
  174. // strip headers
  175. ctx.body = null;
  176. return res.end();
  177. }
  178. if ('HEAD' == ctx.method) {
  179. if (!res.headersSent && isJSON(body)) {
  180. ctx.length = Buffer.byteLength(JSON.stringify(body));
  181. }
  182. return res.end();
  183. }
  184. // status body
  185. if (null == body) {
  186. body = ctx.message || String(code);
  187. if (!res.headersSent) {
  188. ctx.type = 'text';
  189. ctx.length = Buffer.byteLength(body);
  190. }
  191. return res.end(body);
  192. }
  193. // responses
  194. if (Buffer.isBuffer(body)) return res.end(body);
  195. if ('string' == typeof body) return res.end(body);
  196. if (body instanceof Stream) return body.pipe(res);
  197. // body: json
  198. body = JSON.stringify(body);
  199. if (!res.headersSent) {
  200. ctx.length = Buffer.byteLength(body);
  201. }
  202. res.end(body);
  203. }