123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- 'use strict';
- /**
- * Module dependencies.
- */
- const isGeneratorFunction = require('is-generator-function');
- const debug = require('debug')('koa:application');
- const onFinished = require('on-finished');
- const response = require('./response');
- const compose = require('koa-compose');
- const context = require('./context');
- const request = require('./request');
- const statuses = require('statuses');
- const Emitter = require('events');
- const util = require('util');
- const Stream = require('stream');
- const http = require('http');
- const only = require('only');
- const convert = require('koa-convert');
- const deprecate = require('depd')('koa');
- const { HttpError } = require('http-errors');
- /**
- * Expose `Application` class.
- * Inherits from `Emitter.prototype`.
- */
- module.exports = class Application extends Emitter {
- /**
- * Initialize a new `Application`.
- *
- * @api public
- */
- /**
- *
- * @param {object} [options] Application options
- * @param {string} [options.env='development'] Environment
- * @param {string[]} [options.keys] Signed cookie keys
- * @param {boolean} [options.proxy] Trust proxy headers
- * @param {number} [options.subdomainOffset] Subdomain offset
- * @param {string} [options.proxyIpHeader] Proxy IP header, defaults to X-Forwarded-For
- * @param {number} [options.maxIpsCount] Max IPs read from proxy IP header, default to 0 (means infinity)
- *
- */
- constructor(options) {
- super();
- options = options || {};
- this.proxy = options.proxy || false;
- this.subdomainOffset = options.subdomainOffset || 2;
- this.proxyIpHeader = options.proxyIpHeader || 'X-Forwarded-For';
- this.maxIpsCount = options.maxIpsCount || 0;
- this.env = options.env || process.env.NODE_ENV || 'development';
- if (options.keys) this.keys = options.keys;
- this.middleware = [];
- this.context = Object.create(context);
- this.request = Object.create(request);
- this.response = Object.create(response);
- // util.inspect.custom support for node 6+
- /* istanbul ignore else */
- if (util.inspect.custom) {
- this[util.inspect.custom] = this.inspect;
- }
- }
- /**
- * Shorthand for:
- *
- * http.createServer(app.callback()).listen(...)
- *
- * @param {Mixed} ...
- * @return {Server}
- * @api public
- */
- listen(...args) {
- debug('listen');
- const server = http.createServer(this.callback());
- return server.listen(...args);
- }
- /**
- * Return JSON representation.
- * We only bother showing settings.
- *
- * @return {Object}
- * @api public
- */
- toJSON() {
- return only(this, [
- 'subdomainOffset',
- 'proxy',
- 'env'
- ]);
- }
- /**
- * Inspect implementation.
- *
- * @return {Object}
- * @api public
- */
- inspect() {
- return this.toJSON();
- }
- /**
- * Use the given middleware `fn`.
- *
- * Old-style middleware will be converted.
- *
- * @param {Function} fn
- * @return {Application} self
- * @api public
- */
- use(fn) {
- if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
- if (isGeneratorFunction(fn)) {
- deprecate('Support for generators will be removed in v3. ' +
- 'See the documentation for examples of how to convert old middleware ' +
- 'https://github.com/koajs/koa/blob/master/docs/migration.md');
- fn = convert(fn);
- }
- debug('use %s', fn._name || fn.name || '-');
- this.middleware.push(fn);
- return this;
- }
- /**
- * Return a request handler callback
- * for node's native http server.
- *
- * @return {Function}
- * @api public
- */
- callback() {
- const fn = compose(this.middleware);
- if (!this.listenerCount('error')) this.on('error', this.onerror);
- const handleRequest = (req, res) => {
- const ctx = this.createContext(req, res);
- return this.handleRequest(ctx, fn);
- };
- return handleRequest;
- }
- /**
- * Handle request in callback.
- *
- * @api private
- */
- handleRequest(ctx, fnMiddleware) {
- const res = ctx.res;
- res.statusCode = 404;
- const onerror = err => ctx.onerror(err);
- const handleResponse = () => respond(ctx);
- onFinished(res, onerror);
- return fnMiddleware(ctx).then(handleResponse).catch(onerror);
- }
- /**
- * Initialize a new context.
- *
- * @api private
- */
- createContext(req, res) {
- const context = Object.create(this.context);
- const request = context.request = Object.create(this.request);
- const response = context.response = Object.create(this.response);
- context.app = request.app = response.app = this;
- context.req = request.req = response.req = req;
- context.res = request.res = response.res = res;
- request.ctx = response.ctx = context;
- request.response = response;
- response.request = request;
- context.originalUrl = request.originalUrl = req.url;
- context.state = {};
- return context;
- }
- /**
- * Default error handler.
- *
- * @param {Error} err
- * @api private
- */
- onerror(err) {
- // When dealing with cross-globals a normal `instanceof` check doesn't work properly.
- // See https://github.com/koajs/koa/issues/1466
- // We can probably remove it once jest fixes https://github.com/facebook/jest/issues/2549.
- const isNativeError =
- Object.prototype.toString.call(err) === '[object Error]' ||
- err instanceof Error;
- if (!isNativeError) throw new TypeError(util.format('non-error thrown: %j', err));
- if (404 === err.status || err.expose) return;
- if (this.silent) return;
- const msg = err.stack || err.toString();
- console.error(`\n${msg.replace(/^/gm, ' ')}\n`);
- }
- /**
- * Help TS users comply to CommonJS, ESM, bundler mismatch.
- * @see https://github.com/koajs/koa/issues/1513
- */
- static get default() {
- return Application;
- }
- };
- /**
- * Response helper.
- */
- function respond(ctx) {
- // allow bypassing koa
- if (false === ctx.respond) return;
- if (!ctx.writable) return;
- const res = ctx.res;
- let body = ctx.body;
- const code = ctx.status;
- // ignore body
- if (statuses.empty[code]) {
- // strip headers
- ctx.body = null;
- return res.end();
- }
- if ('HEAD' === ctx.method) {
- if (!res.headersSent && !ctx.response.has('Content-Length')) {
- const { length } = ctx.response;
- if (Number.isInteger(length)) ctx.length = length;
- }
- return res.end();
- }
- // status body
- if (null == body) {
- if (ctx.response._explicitNullBody) {
- ctx.response.remove('Content-Type');
- ctx.response.remove('Transfer-Encoding');
- return res.end();
- }
- if (ctx.req.httpVersionMajor >= 2) {
- body = String(code);
- } else {
- body = ctx.message || String(code);
- }
- if (!res.headersSent) {
- ctx.type = 'text';
- ctx.length = Buffer.byteLength(body);
- }
- return res.end(body);
- }
- // responses
- if (Buffer.isBuffer(body)) return res.end(body);
- if ('string' === typeof body) return res.end(body);
- if (body instanceof Stream) return body.pipe(res);
- // body: json
- body = JSON.stringify(body);
- if (!res.headersSent) {
- ctx.length = Buffer.byteLength(body);
- }
- res.end(body);
- }
- /**
- * Make HttpError available to consumers of the library so that consumers don't
- * have a direct dependency upon `http-errors`
- */
- module.exports.HttpError = HttpError;
|