What is Koa

Koa is an HTTP middleware framework based on node implementation, which is built from the original Express framework.

So why Koa is more popular (at least I prefer Koa), it’s more elegant and concise than Express, and it’s more expressive and free.

Second, Koa experience

Node implements the underlying HTTP server

  const http = require('http');
  
  const server = http.createServer((req, res) = > {
    // When we need to do req processing, we need to do a lot of processing here, and it is not modular
    res.writeHead(200);
    res.end('hi jinguo');
  });
  
  server.listen(3000, () = > {console.log('Listening port 3000');
  });
Copy the code

Koa implements the underlying HTTP server

  const koa = require('koa');
  cosnt app = new koa();
  
  // Modularization/simplification
  app.use(ctx= > {
    ctx.body = 'hi jinguo';
  })
  
  app.listen(3000);
Copy the code

Koa’s goal is to implement the callback part in a more simplified, streamlined, and modular way

  app.use(async (ctx, next) => {
    ctx.state = 'hi jinguo';
    await next();
  });
  
  app.use(ctx= > {
    ctx.body = ctx.state;
  });
Copy the code

Koa source code analysis

Source directory structure

Lib ├ ─ ─ application. Js / / koa entry documents ├ ─ ─ context. The js / / application context of koa CTX ├ ─ ─ request. Js / / encapsulates the processing HTTP requests └ ─ ─ the response. The js / / Encapsulates processing HTTP responsesCopy the code

application.js

  // Inherits events, which gives the ability to listen to events and fire events
  module.exports = class Application extends Emitter {
    constructor(options) {
      super(a);this.middleware = []; // This array holds all middleware functions introduced through the use function
      // Create context, request, and response.
      this.context = Object.create(context);
      this.request = Object.create(request);
      this.response = Object.create(response);
    }
  
    // There is a wrapper around http.createserverlisten(... args) { debug('listen');
      // The important thing is that the callback passed in this function includes middleware merging, context handling, and special handling of res.
      const server = http.createServer(this.callback());
      returnserver.listen(... args); }Use is to register middleware, putting multiple middleware into a cache queue
    use(fn) {
      this.middleware.push(fn);
      return this;
    }
  	
    // Returns a function like (req, res) => {}
    callback() {
      Compose compose compose compose compose compose compose compose compose compose compose compose compose will be explained below for implementing KOA on its own
      const fn = compose(this.middleware);
  
      if (!this.listenerCount('error')) this.on('error'.this.onerror);

      const handleRequest = (req, res) = > {
        // Encapsulate the CTX required by the middleware according to REQ and RES.
        const ctx = this.createContext(req, res);
        return this.handleRequest(ctx, fn);
      };
  
      return handleRequest;
    }
  
    // Encapsulates a powerful CTX
    createContext(req, res) {
      // Created 3 simple objects and specified their prototypes as corresponding objects in our app. The native REq and RES are then assigned to the corresponding attributes
      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;
      return context;
    }
  
    handleRequest(ctx, fnMiddleware) {
      const res = ctx.res;
      res.statusCode = 404;
  
      // Call context.js's onerror function
      const onerror = err= > ctx.onerror(err);
  
      // Process the response content
      const handleResponse = (a)= > respond(ctx);
  
      // Ensure that a stream executes the response callback when it closes, completes, and reports an error
      onFinished(res, onerror);
  
      // Middleware implementation, unified error handling mechanism key
      returnfnMiddleware(ctx).then(handleResponse).catch(onerror); }}Copy the code

Application.js does four things

  1. Start the framework
  2. Implement the Onion model middleware mechanism
  3. Encapsulate a highly cohesive context
  4. Implement uniform error handling mechanism for asynchronous functions

context.js

  const proto = module.exports = {
    onerror(err) {
      // Raises an application instance error event
      this.app.emit('error', err, this); }}// The following two delegates allow context objects to delegate properties and methods of request and response
  delegate(proto, 'response')
    .method('attachment')
    .method('redirect')
    .method('remove')... delegate(proto,'request')
    .method('acceptsLanguages')
    .method('acceptsEncodings')
    .method('acceptsCharsets')...Copy the code

Context.js does two things

  1. Error event handling
  2. Proxy partial properties and methods of response and Request objects

request.js

  module.exports = {
    // Request objects encapsulate many convenient properties and methods based on REq
    get header() {
      return this.req.headers;
    },
  
    set header(val) {
      this.req.headers = val;
    },
  
    get url() {
      return this.req.url;
    },
  
    set url(val) {
      this.req.url = val;
    },
    // omits a number of similar tool attributes and methods. };Copy the code

When you access ctx.request. XXX, you are actually accessing the setter and getter on the Request object

response.js

  module.exports = {
    get header() {
      const { res } = this;
      return typeof res.getHeaders === 'function'
        ? res.getHeaders()
        : res._headers || {}; / / the Node < 7.7
    },
    get body() {
      return this._body;
    },
  
    set body(val) {
      this._body = val;
    },
    // omits a number of similar tool attributes and methods. }Copy the code

A Response object is similar to a Request object

Four, simple implementation of Koa

  // JKoa.js
  const http = require("http");
  const request = require("./request");
  const response = require("./response");
  
  class JKoa {
    constructor() {
      this.middlewares = []; } listen(... args) {const server = http.createServer(async (req, res) => {
        // Create a context object
        const ctx = this.createContext(req, res);
        // Combine the middlewares, the compose function is implemented below
        const fn = this.compose(this.middlewares);
        await fn(ctx)
        // Return data to the userres.end(ctx.body); }); server.listen(... args); } use(middlewares) {this.middlewares.push(middlewares);
    }
  
    createContext(req, res) {
      const ctx = Object.create(context);
      ctx.request = Object.create(request);
      ctx.response = Object.create(response); ctx.req = ctx.request.req = req; ctx.res = ctx.response.res = res; }}module.exports = JKoa;
Copy the code

context

In order to simplify the API, KOA introduces the concept of context, encapsulates and mounts the original request object REq and the corresponding object RES into the context, and sets the getter and setter on the context to simplify operations.

  // context.js
  module.exports = {
    get url() {
      retrun this.request.url;
    },
    get body() {
      return this.response.body;
    },
    set body(val) {
      this.response.body = val; }}// request.js
  module.exports = {
    get url() {
      return this.req.url; }}// response.js
  module.exports = {
    get body() {
      return this._body;
    },
    set body(val) {
      this._body = val; }}Copy the code

The middleware

Let’s start with the following example to understand the concept of function composition

  function add(x, y) {
    return x + y;
  }
  
  function square(z) {
    return z * z
  }
  
  // Common mode
  const result = square(add(1.2));
  
  // Function combination
  function compose(middlewares) {
    return middlewares.reduce((prev, next) = >(... args) => next(prev(... args))); }const middlewares = [add, square];
  const resultFn = compose(middlewares);
  const result = resultFn(1.2);
Copy the code

The above example composition function is synchronous and can be executed one by one. If it is asynchronous, we need middleware that supports async + await

  function compose(middlewares) {
    return function(ctx) {
      // execute 0
      return dispatch(0);
      function dispatch(i) {
        let fn = middlewares[i];
        if(! fn) {return Promise.resolve();
        }
        return Promise.resolve(
          fn(ctx, function next() {
            // Promise completes before the next one
            return dispatch(i + 1); })); }}}Copy the code

The Koa middleware mechanism is the concept of function composition, which combines a set of functions that need to be executed sequentially into a single function. The arguments of the outer function are actually the return values of the inner function. The onion ring model is a visual representation of this mechanism, which is the essence and difficulty of the Koa source code.

Five, the summary

Koa is a middleware onion model mechanism based on async/await that enables request and Response objects based on node’s native REQ and RES and encapsulates them into a context.

I hope you can gain something after reading my article. Thank you for reading my article!