preface

Now that you’ve seen how the KOA2 framework works, there are a few questions:

  • Where does the CTX parameter for each middleware come from
  • What is the next function of each middleware
  • I decided to dig into the source code to find out why calling next() would give execution control to the next middleware.

The main idea

Koa2 is a Node.js Web development framework. Koa defines an Application class that provides properties and methods. After instantiating an Application class object app, the app.ues method loads a set of middleware, and app.listen is called to create the service (essentially using the native HTTP package, and callback as a callback function for http.createserver). The main logic in the callback method is:

  • Compose (this.Middleware), which uses the compose method of the KOa-compose module to combine all middleware into one large middleware FN
  • (req, res) => {const CTX = this.createcontext (req, res)}, each time a new request is heard, a new CTX is created (encapsulating the native REq and RES)
  • Fn (CTX), middleware execution mechanism, in order, each middleware gets CTX processing corresponding function logic

Each request creates a CTX that takes the request information and manipulates the CTX object to respond to the user. It encapsulates the native HTTP request and response objects through the model definition in context.js, request.js, response.js, attribute proxy, etc.

The overall architecture

There are only 4 core files to download from the KOA official website, in the lib folder:

  1. Application.js – entry file to the KOA framework
  2. Context.js – Creates the context object for the network request
  3. Request.js – Used to wrap the REQUEST object for KOA
  4. Response.js – Used to wrap koA’s Response object

There are Settings in the main configuration item of the package.json file of the KOA package

"main": "lib/application.js".Copy the code

Application. js is the core file of KOA. Context. js, request.js and Response. js encapsulate native HTTP request and response objects through model definition, attribute proxy and other methods.

This will focus on the logic in application.js.

application.js

The Application class is defined in Application. js, which inherits the Emitter class

class Application extends Emitter { //... }
Copy the code

There are a set of properties and six core methods.

  • The constructor for the Application class is simple
Constructor (options) {// Since it inherits from EventEmitter, we need to call super super(); options = options || {}; // Proxy Settings, totrueSaid to get the real client IP address this. The proxy = options. The proxy | |false;
    this.subdomainOffset = options.subdomainOffset || 2;
    this.proxyIpHeader = options.proxyIpHeader || 'X-Forwarded-For'; this.maxIpsCount = options.maxIpsCount || 0; / / this environment variable. The env = options. Env | | process. The env. NODE_ENV | |'development';
    if(options.keys) this.keys = options.keys; // Array that stores all middleware functions, all app.use(fn) fn(middleware) is pushed into this.middleware = []; This.context = object.create (context); this.request = Object.create(request); this.response = Object.create(response);if(util.inspect.custom) { this[util.inspect.custom] = this.inspect; }}Copy the code

Middleware – KoA’s middleware functions are concatenated through an array of context – the context object Request – which encapsulates an HTTP request response – which encapsulates an HTTP response

  • use(fn)
 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 || The '-'); // Store middleware functions in the middleware array of instance objects this.middleware.push(fn);return this;
  }
Copy the code

The use method takes a function. Use checks whether fn is function and if so, pushes it directly into the this.middleware array. If not, use a generator function to wrap the parameters into a Promise and push the fn into the this.middleware array.

  • listen(… args)
listen(... args) { debug('listen');
    const server = http.createServer(this.callback());
    returnserver.listen(... args); }Copy the code

Create an HTTP Server based on the native built-in HTTP module in Node, pass in the callback function, and then start listening on the port. Here the callback function this.callback() is the focus. If you implement an HTTP server program using only HTTP modules, you can write this

'use strict'; Var HTTP = require('http'); // Create the HTTP server and pass in the callback function: var server = http.createserver (function(request, response) {// The callback function receives request and response objects, and // gets the method and URL of the HTTP request: console.log(request.method +)':'+ request.url); // Write the HTTP response 200 to response and set content-type: text/ HTML: response.writeHead(200, {'Content-Type': 'text/html'}); // Write the HTML content of the HTTP response to response: response.end('

Hello world!

'
); }); // Let the server listen on port 8080: server.listen(8080);Copy the code

By contrast, we can see that the return value of this.callback() should be a function that receives request and Response objects.

  • callback()
callback() {
    // Merge all middleware functions in the this.middleware array into one large function
    const fn = compose(this.middleware);

   // If no error event is being listened for, bind error event listening processing
    if (!this.listenerCount('error')) this.on('error'.this.onerror);
   // The handleRequest function, which is the http.createserver callback in listen, is this.callback().
   // There are two parameters, req and res, representing the native request,response object
    const handleRequest = (req, res) = > {
      // The server listens and generates a new context every time it receives a new request
      // Create a context object to establish the relationship between the koA context, request, and Response properties and the native HTTP object
      // The created context object is then carried into a series of middleware functions
      const ctx = this.createContext(req, res);
      return this.handleRequest(ctx, fn);
    };

    return handleRequest;
  }
Copy the code

The compose method is imported from the NPM package, the KOa-compose module

const compose = require('koa-compose');
Copy the code

Multiple middleware functions can be combined into one large middleware function, and then the large middleware function can be called to execute the middleware functions in turn, performing a series of tasks. The execution sequence is first in first.

  • createContext(req, res)
  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;
  }
Copy the code

Create a context object and make connections between the context, request, and Response properties in KOA

  • handleRequest(ctx, fnMiddleware)
handleRequest(ctx, fnMiddleware) { const res = ctx.res; // the default statusCode is 404 res.statuscode = 404; const onerror = err => ctx.onerror(err); Const handleResponse = () => respond(CTX); // Call the onError method in context.js for error handling // Respond to the respond method onFinished(res, onError); What does the middleware do based on CTX? What does the middleware do based on CTXreturn fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }
Copy the code

HandleRequest simply executes a set of middleware functions.

FnMiddleware takes CTX, processes some logic, and returns the result

  • response
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 the HEAD method is used
  if ('HEAD' === ctx.method) {
    // The headersSent property is on Node's native response object and is used to check whether the HTTP response header has already been sent
    // If not, add the length header
    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 body is null
  if (null == body) {
  // httpVersionMajor is an attribute on node's native object Response that returns the current version of HTTP. Here is a compatibility for http2 versions and above
    if (ctx.req.httpVersionMajor >= 2) {
      body = String(code);
    } else {
      body = ctx.message || String(code);
    }
    // If the response header has not yet been sent, add a length attribute to CTX. The length attribute records the length of the body of the current message
    if(! res.headersSent) { ctx.type ='text';
      ctx.length = Buffer.byteLength(body);
    }
    return res.end(body);
  }

  // responses
  // If body is not null, process according to the body type and return the result
  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
  // Finally, string processing is done for the JSON-formatted body, converting it to a string
  // Add the length header as well
  body = JSON.stringify(body);
  if(! res.headersSent) { ctx.length = Buffer.byteLength(body); } res.end(body); }Copy the code

Based on the attributes of the KOA context object, the content of the final response object is set through the end method in the Response object of the Node native HTTP module.

request.js

Request. js encapsulates request.js objects in HTTP modules

response.js

Response.js, like Request. js, mainly encapsulates the Response object in the HTTP module

context.js

Context.js proxies properties and methods from the Request and Response objects into the context object. The amount of code in context.js is small and easy to read

Koa-compose source analysis

The koa-compose package consists of very little code, which defines a compose method and exports it through module.export

module.exports = compose
functioncompose (middleware) {... }Copy the code

Compose the source

function compose (middleware) {
  Make sure middleware is an array and that each element in the array is a method
  // Middleware is not an array
  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array! ')
  for (const fn of middleware) {
    // Middleware children are not methods, error reported
    if (typeoffn ! = ='function') throw new TypeError('Middleware must be composed of functions! ')}// Return the Promise object
  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) {
        // Error handling
        return Promise.reject(err)
      }
    }
  }
}
Copy the code
  1. The parameters passed in must be arrays: middleware arrays

  2. The elements of an array must be functions

  3. Returns a closure function that is the result of merging an array of middleware elements into one large middleware element, taking two parameters: context and next

This is understood in conjunction with the call in koA source code

const fn = compose(this.middleware); // Merge into large middleware
fnMiddleware(ctx)
Copy the code

The large middleware function fn is executed. The first argument is CTX, which is created for each request, and the second argument is next, which is null. So the CTX parameters of each middleware are actually taken from here

The internal process of fnMiddleware(CTX) :

  1. dispatch(0)

    index = 0
    let fn = middleware[0]
    return Promise.resolve(fn(context, dispatch(1)))
    Copy the code

    Middleware [0] means to start from the first middleware, with context = CTX of fnMiddleware and next = dispatch(1). So the next parameter is the next middleware function to the current middleware function

  2. When the next function, dispatch(1), is called, middleware[1] starts executing, thus starting the recursive Dispatch method. So calling next() gives execution control to the next middleware

  3. When the last middleware is executed, the next function of its middleware = fnMiddleware’s next function = null, so whether the last middleware’s next function is called or not is the same. When the next function of the current middleware is executed, the logic after the next function of the current middleware continues to be executed, and the execution control is handed back layer by layer after the execution is completed.

    if (i === middleware.length) fn = next
    if(! fn)return Promise.resolve()
    Copy the code

conclusion

  • The source code for KOA is minimal, with only four files
  • The CTX parameter for each middleware is
  • The CTX object reference created each time the request is made, and the next argument to each middleware function, via the large intermediate function fn, returned by the compose method, is the next middleware function
  • The compose method of the KOA-compose module implements koA’s middleware execution mechanism

Reference documentation

Koa website: koa.bootcss.com/ source address: github.com/koajs/koa koa2 source code parsing: www.jianshu.com/p/bc0e1f46f… chenshenhai.github.io/koa2-note/