The code structure

├ ─ ─ the History. The md ├ ─ ─ LICENSE ├ ─ ─ the Readme. Md ├ ─ ─ dist │ └ ─ ─ koa. MJS ├ ─ ─ lib │ ├ ─ ─ application. Js │ ├ ─ ─ the context, js │ ├ ─ ─ ├ ─ garbage, ├ ─ download.txtCopy the code

application.js


'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 {boolean} [options.proxyIpHeader] proxy ip header, default to X-Forwarded-For
    * @param {boolean} [options.maxIpsCount] max ips read from proxy ip header, default to 0 (means infinity)
    *
    */

  constructor(options) {
    super(a); 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());
    returnserver.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 (typeoffn ! = ='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 || The '-');
    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() {
    returnApplication; }};/** * 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;

Copy the code

Among them, the most important thing is the call of a series of methods after the listen method is called. The call chain is mainly as follows:

  • Listen: listen -> callback -> compose -> returns a handleRequest
  • CreateContext -> construct the fufilled callback and reject callback -> pass the created context as a parameter to the compose fn execution -> call the fufilled or Reject method based on the promise state

In fact, the koa.js code is relatively simple, there are several main issues to pay attention to:

1. koa-compose (middleware: fn[]) => fn;

function compose (middleware) {
  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array! ')
  for (const fn of middleware) {
    if (typeoffn ! = ='function') throw new TypeError('Middleware must be composed of functions! ')}/ * * *@param {Object} context
   * @return {Promise}
   * @api public* /

  return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      if (i === middleware.length) fn = next
      if(! fn)return Promise.resolve()
      try {
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}
Copy the code

The above code clearly shows how to recurse, so look at it several times if you don’t understand it. The routing controller code needs to introduce the koA-Router package.

2. Construction of CTX

In this case, after the request comes in, a new CTX object is constructed, and then objects such as App res req and methods below RES req are mounted to CTX using createContext and Delegate.

The relevant codes are as follows:

3. What does the onFinished method do