Koa infrastructure

First, let’s look at the positioning of the KOA framework — KOA is a stripped-down Node framework:

  • Based on node’s native REq and RES, it encapsulates custom Request and Response objects, and encapsulates them into a unified context object.
  • It implements the middleware mechanism based on the Onion model of Async /await (Generator).

The core catalog of the KOA framework is as follows:

─ ─ lib ├ ─ ─ application. Js ├ ─ ─ the context, js ├ ─ ─ request. Js └ ─ ─ response. Js// The specific function of each file─ ─ lib ├ ─ ─newKoa () | | CTX. App ├ ─ ─ CTX ├ ─ ─ CTX. The req | | CTX. Request └ ─ ─ CTX. Res | | CTX. The responseCopy the code

Koa source code base skeleton

Application. Js Application. Js is the main entry and the core part of KOA. It mainly does the following things:

  1. Initialization of the KOA instance is complete and the server is started
  2. Middleware mechanism of Onion model is implemented
  3. Encapsulates a highly cohesive context object
  4. The unified error handling mechanism of asynchronous function is realized

Context.js does two things:

  1. Error event handling is complete
  2. Some properties and methods of response and Request objects are proxied

The request.js request object encapsulates a set of convenience properties and methods based on node’s native REQ that can be invoked when processing a request. So when you access ctx.request. XXX, you’re actually accessing the setter and getter on the request object.

The Response.js Response object encapsulates a set of convenience properties and methods based on node’s native RES that can be called when processing a request. So when you access ctx.Response. XXX, you’re actually accessing the setter and getter on the Response object.

The code structure of the four files is as follows:

Koa workflow

The Koa process can be divided into three steps:

  1. Initialization phase

New initializes an instance, including creating an array of middleware, creating a context/ Request/Response object, adding middleware to the middleware array using use(FN), and finally synthesizing middleware fnMiddleware with Listen. Execute the middleware in turn according to the Onion model, return a callback function to HTTP. createServer, start the server, and wait for the HTTP request. The structure diagram is as follows:

  1. The request phase

On each request, createContext generates a new CTX, which is passed to fnMiddleware, triggering the entire flow of the middleware. 3. Response Phase When the middleware is complete, it calls the respond method, processes the request and returns the response to the client.

Koa middleware mechanism and implementation

The middleware mechanism is implemented using koa-compose. The compose function takes an array of middleware parameters. Each of the middleware objects is an async function and returns a function that takes context and next as inputs. Call it fnMiddleware and call the last line of this.Handlerequest externally, running middleware: fnMiddleware(CTX).then(handleResponse).catch(onError);

The following is akoa-composeCore functions in the library:

The question is: What exactly is Next in middleware? Why does executing next lead to the next middleware? The middleware stack is shown below, where Next is a function with a Dispatch method. When the first middleware executes next, it executes dispatch(2) and enters the process flow of the next middleware. Since dispatch returns all Promise objects, the NTH middleware await next(), entering the NTH +1 middleware, and the NTH +1 middleware can return when the execution of the NTH +1 middleware completes. But if we don’t write next() in a certain middleware, we don’t execute any middleware after it. The operation mechanism is shown in the figure below:

Koa – convert parsing

The koA-convert library was introduced in KOA2, and the convert method is used when using the use function (showing only the core code) :

const convert = require('koa-convert');

module.exports = class Application extends Emitter {
    use(fn) {
        if (typeoffn ! = ='function') throw new TypeError('middleware must be a function! ');
        if (isGeneratorFunction(fn)) {
            deprecate('Support for generators will be removed';
            fn = convert(fn);
        }
        debug('use %s', fn._name || fn.name || The '-');
        this.middleware.push(fn);
        return this; }}Copy the code

The KOA2 framework is compatible with the KOA1 version. Middleware functions that are generator functions are converted to “async functions” using koA-convert. First we must understand the difference between Generator and Async: The async function executes automatically, whereas the generator calls the next function every time, so we need to find a way to make the next() function last forever by specifying the yield value in the generator as a Promise object. Here is the core code in KOA-convert:

const co = require('co')
const compose = require('koa-compose')

module.exports = convert

function convert (mw) {
  if (typeofmw ! = ='function') {
    throw new TypeError('middleware must be a function')}if(mw.constructor.name ! = ='GeneratorFunction') {
    return mw
  }
  const converted = function (ctx, next) {
    return co.call(ctx, mw.call(ctx, createGenerator(next)))
  }
  converted._name = mw._name || mw.name
  return converted
}
Copy the code

First, verify the passed parameter MW, throw an exception if it is not a function, return if it is not a generator function, and use co if it is a generator function. The core code of CO is as follows:

function co(gen) {
  var ctx = this;
  var args = slice.call(arguments.1);
  
  return new Promise(function(resolve, reject) {
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    if(! gen ||typeofgen.next ! = ='function') return resolve(gen);

    onFulfilled();
    
    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
      return null;
    }

    function onRejected(err) {
      var ret;
      try {
        ret = gen.throw(err);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }

    function next(ret) {
      if (ret.done) return resolve(ret.value);
      var value = toPromise.call(ctx, ret.value);
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
        + 'but the following object was passed: "' + String(ret.value) + '"')); }}); }Copy the code

As can be seen from the above code, the processing in co is as follows:

  1. Put ageneratorEncapsulated in aPromiseIn the object
  2. thisPromiseThe object again puts itsgen.next()Also encapsulatePromiseObject, which corresponds to this childPromiseThe object is also called repeatedly when it is finishedgen.next()
  3. When all iterations are complete, call the parentPromiseThe object ofresolve

Once this is done, a class async function is formed.

Uniform error handling for asynchronous functions

In the KOA framework, there are two error handling mechanisms:

  1. Middleware capture
  2. Framework to capture

Middleware capture is an error-handling response to middleware, such as fnMiddleware(CTX).then(handleResponse).Catch (onError). Onerror listener function will be started when middleware fails to run. This.app. Emit (‘error’, err, this) is a reference to application. When context.js calls onError, The application class inherits from the EventEmitter class, and therefore has the ability to handle asynchronous events. You can use the error-handling methods in EventEmitter for asynchronous functions.

Why is KOA able to achieve uniform error handling for asynchronous functions? Since async returns a Promise object, if an exception is thrown inside the async function, the Promise object changes to reject and the exception is caught by the catch callback (onError). If the Promise object after await becomes reject, reject’s argument can also be caught by the catch callback (onerror).

Application of delegation pattern in KOA

The delegates Library, written by the famous TJ, allows us to easily and quickly use the delegate pattern of design patterns, in which the exposed object delegates requests to another object inside.

Delegates the essential use of delegates is to bound the variable or function of the interior object to the variable exposed in the outer cell, delegates are directly engaged in the following essential delegate method:

  • Getter: An external object can directly access the value of an internal object
  • Setter: External objects can modify the values of internal objects directly
  • Access: includes getters and setters
  • Method: External objects can call functions of internal objects directly

The delegates principle is __defineGetter__ and __defineSetter__. In application. CreateContext function, created the context object will mount based on request. The js implementation request object and based on the response. The js implementation of the response object. The following two delegates allow context to delegate some properties and methods of request and response:

After doing that, many of the properties of context.request are delegated to the context, many of the methods of context.response are delegated to the context, Therefore, we can not only use this.ctx.request. Xx and this.ctx.response. Xx to fetch the corresponding properties, but also use this.ctx.xx to fetch xx methods mounted under this.ctx.request or this.ctx.response.

Response.js and request.js use get set proxies, while context.js uses delegate proxies. Because the delegate method is relatively simple, it only proxies properties; But using the set and GET methods adds some additional logic. In context.js, you only need the proxy property, which can be done using the delegate method. In Response.js and request.js, you need to handle other logic, such as formatting the query:

get query() {
  const str = this.querystring;
  const c = this._querycache = this._querycache || {};
  return c[str] || (c[str] = qs.parse(str));
}
Copy the code

At this point, I believe you have a deeper understanding of the implementation of koA2 principle?