Pre –

Before we begin, here’s an example:

const Koa = require('koa');
const app = new Koa();
app.use(async (ctx,next)=>{
  console.log('fn1');
  await next();
  console.log('end fn1');
})

app.use(async (ctx,next)=>{
  console.log('fn2')
  await next()
  console.log('end fn2')
}
app.use(async (ctx,next)=>{
  console.log('fn3')
}
app.listen(3000);
Copy the code

What does the code above print?

  • content

The middleware

The above code represents the most important part of Koa’s source code, the middleware, which is pushed one at a time

The printing order also mirrors Koa’s middleware model, the Onion ring model.

  • Each middleware execution encounters next and executes the next middleware until it reaches the last middleware, and then executes the rest of the code backwards.

  • Middleware code (recursive implementation)

/* file name: compose. Js */
function compose(middlewares) {  // Middlewares middleware list
    return function (ctx) {  // CTX: context of request and response
        // Define a dispatcher
        function dispatch(i) {
            // Get the current middleware
            const fn = middlewares[i]
            try {
                return Promise.resolve(
                    // Get the next middleware through I + 1, pass it to next
                    fn(ctx, function next(){  / / the next key
                    	return dispatch(i+1)}}))catch (err) {
                // fn does not have an automatic capture error
                return Promise.reject(err)
            }
        }
        // Start dispatching the first middleware
        return dispatch(0)}}module.exports = compose;
Copy the code

context, request, response

  • Define real CTX, REQ, RES for the prototype based on these
/* proto.js */
const Cookies = require('cookies');
const COOKIES = Symbol('context#cookies');

// Prototype base attribute definition
const request = {
  get header() {
    return this.req.headers;
  },
  set header(val) {
    this.req.headers = val;
  },
  get headers() {
    return this.req.headers;
  },
  set headers(val) {
    this.req.headers = val;
  },
  get url() {
    return this.req.url;
  },
  set url(val) {
    this.req.url = val;
  }
  /* Other content */
}

// Prototype base attribute definition
const response = {
  get header() {
    const { res } = this;
    return typeof res.getHeaders === 'function'
      ? res.getHeaders()
      : res._headers || {}; / / the Node < 7.7
  },
  get headers() {
    return this.header;
  },
  get status() {
    return this.res.statusCode;
  },  
  get header() {
    const { res } = this;
    return typeof res.getHeaders === 'function'
      ? res.getHeaders()
      : res._headers || {}; / / the Node < 7.7
  },
  get headers() {
    return this.header;
  },
  get status() {
    return this.res.statusCode;
  },
  get body() {return this._body
  },
  set body(val) {this._body = val
  }
  /* Other content */
}

const context = {
  toJSON() {
    return {
      request: this.request.toJSON(),
      response: this.response.toJSON(),
      app: this.app.toJSON(),
      originalUrl: this.originalUrl,
      req: '<original node req>'.res: '<original node res>'.socket: '<original node socket>'
    };
  },
  get cookies() {
    if (!this[COOKIES]) {
      this[COOKIES] = new Cookies(this.req, this.res, {
        keys: this.app.keys,
        secure: this.request.secure
      });
    }
    return this[COOKIES];
  },
  set cookies(_cookies) {
    this[COOKIES] = _cookies;
  }
  /* Other content */
};
module.exports = {
  request,
  response,
  context
}
Copy the code

Start building a real Koa build

const http = require('http');
const compose = require("./compose.js");
const {
  request,
  response,
  context
} = require("./proto.js")

class Application {
  constructor(){
    this.context = Object.create(context);
    this.request = Object.create(request);
    this.response = Object.create(response);
    this.middlewares = [];
  }
  use(fn){
    this.middlewares.push(fn);
  }
  listen(. args) {
    const server = http.createServer(this.callback());
    returnserver.listen(... args); }/* createServer content */
  callback() {
    const fn = compose(this.middleware);
    const handleRequest = (req, res) = > {
      const ctx = this.createContext(req, res);
      return this.handleRequest(ctx, fn);  // Fn (CTX) middleware
    };
    return handleRequest;
  }

  handleRequest(ctx, fnMiddleware) {
    /* Error handling mechanism... * /
    return fnMiddleware(ctx);
  }

  /* Create the actual context */
  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 = {};
    returncontext; }}module.exports = Application
Copy the code