Objective: To achieve a simple KOA-Router core code, easy to understand the PRINCIPLE of KOA-Router. It will also lay a little foundation for the future of Express

Analyze the KOA-Router briefly

Let’s start with the basic code

const Koa = require('koa');
const Router = require('koa-router');

const app = new Koa();
const router = new Router();

router.get('/'.async (ctx, next) => {
  ctx.body = 'hello world 111';
  next();
})
router.get('/'.async (ctx, next) => {
  ctx.body = 'hello world 222';
  next();
})

app.use(router.routes());

app.listen(3000);
Copy the code

This is the simplest route, and the information you can get from it is (other uses aside)

  1. Router is a constructor
  2. The Router has methods to handle requests, such as GET
    1. There are two parameters, path, and the middleware function middleware
    2. The middleware function Middleware is an asynchronous method
      1. There are two arguments, the context CTX, and the next function
      2. As with KOA middleware, the next program needs to be called to execute the subsequent routing/middleware
  3. The KOA-router is used as middleware. Router. routes returns the middleware method

Implement the next function

The core of next’s implementation is to wrap functions in order, like an onion. An array is typically used to hold the methods to implement next

For example, if we have two methods f1 and f2, forming an array [f1, f2], our goal is to form () => f1(() => f2()). When we execute this function, the code we execute is f1(() => f2()). () => f2() is next for F1. The concrete implementation is

let middlewares = [f1, f2, f3...]
function compose(callback) {
  async function dispatch(i) {
    if (i === routers.length) return callback() // Boundary function
    const middleware = middlewares[i] // Retrieve the current function
    return middleware((a)= > dispatch(i+1)) / / out next
  }
  return dispatch(0)}/* compose results in a string of middlewares like this: () => F1 (() => f2(() => F3 (//...) )) * /
Copy the code

Another way to implement it is to use reduce, which will be a separate article in the future

Router. get and router.routes

Router. get = router.routes

Each call to router.get stores GET, path, and middleware in your instance’s _Router. When the request arrives, KOA calls router.routes, takes the middleware corresponding to get and path from the _router of the instance, and executes it. The next implementation simply passed an extra CTX. The specific process details are as follows:

  1. Create _router = [] on the instance to receive routing information
  2. Get (Path, middleware) and store get, path, and middleware to _router
  3. App.use (router.routes()) mounts the route to the KOA middleware and waits for the request to come. Router.routes () has been executed! The result of router.routes() being mounted to the middleware!
  4. The incoming request triggers the mounted routing function. Retrieve route information from _router and match it with path and method of the current request until all matched route information is obtained
  5. Compose the compose function in series so that it matches to the function. And perform the outermost function after series

In the code

class Router {
  constructor() {
    this._router = [];
  }
  compose(ctx, routers, koa_next) { // Change the name to next
    async function dispatch(i) {
      if (i === routers.length) return koa_next() // Boundary function. The routing middleware completes
      const router = routers[i]
      return router.middleware(ctx, () => dispatch(i + 1))}return dispatch(0)
  }
  get(path, middleware) {
    this._router.push({ method: 'GET', path, middleware })
  }
  routes() {
    return async (ctx, next) => { // This method is called first when the request arrives
      const method = ctx.method;
      const path = ctx.path;
      // Select a matching router to get middleware
      const routers = this._router.filter(route= > {
        // Ignore path regex matching for simplicity
        return route.path === path && route.method === method
      })

      // Concatenate the selected router (passed to next)
      return this.compose(ctx, routers, next)
    }
  }
}

module.exports = Router
Copy the code

At this point the code will run the example given above.

Secondary Route (prefix)

Router.prefix () adds a prefix to a route

// ...
const weixin = new Router();
const qq = new Router();

weixin.prefix('/weixin');
weixin.get('/home'.async (ctx, next) =>{/ *... * /});  // /weixin/home
weixin.get('/user'.async (ctx, next) =>{/ *... * /});  // /weixin/user

qq.prefix('/qq');
qq.get('/home'.async (ctx, next) =>{/ *... * /});  // /qq/home
qq.get('/user'.async (ctx, next) =>{/ *... * /});  // /qq/user

app.use(weixin.routes());
app.use(qq.routes());
// ...
Copy the code

In this way, the user page accessing weixin will visit /weixin/user, which is distinguished from QQ. Weixin and QQ this page has its own prefix, do not interfere with each other.

This is very simple, just need to record the prefix on the instance, when matching the route before the original path prefix can be achieved

class Router {
  constructor() {
    this._router = [];
    this._prefix = ' '; // Record the prefix
  }
  prefix(prefixPath) {
    this._prefix = prefixPath; // Add prefixPath to _prefix
  }
  compose(ctx, routers, koa_next) { // Change the name to next
    async function dispatch(i) {
      if (i === routers.length) return koa_next() // Boundary function. The routing middleware completes
      const router = routers[i]
      return router.middleware(ctx, () => dispatch(i + 1))}return dispatch(0)
  }
  get(path, middleware) {
    this._router.push({ method: 'GET', path, middleware })
  }
  routes() {
    return async (ctx, next) => { // This method is called first when the request arrives
      const method = ctx.method;
      const path = ctx.path;
      // Select a matching router to get middleware
      const routers = this._router.filter(route= > {
        // Ignore path regex matching for simplicity
        return (this._prefix + route.path) === path && route.method === method
      })

      // Concatenate the selected router (passed to next)
      return this.compose(ctx, routers, next)
    }
  }
}

module.exports = Router
Copy the code