I left off with a look at how KOA handles routing and business code, because fnMiddleware(CTX).then exists, so you need to string business code into the middleware as a next. So let’s take a look at how koa-router is handled.

An overview of

It mainly includes the following two items

  1. Register: when calledrouter.getWill be through the callrouter.register , registerThrough the incomingpath fnInstantiate aLayerObject, which is then pushed into the Route stack of the Router object for invocation on request.
  2. Request: when requested, the logic goes to creteContext -> fnMiddleware to execute -> Routes middleware to execute -> re-compose a compose function and pass it to next, passing it to the previous function execution chain -> complete.

The source code is as follows

1. The register code

To invoke the route. The get, wrap related attributes as a layer | route objects, stored in the route of the stack, to look up for the request.

Router.prototype.register = function (path, methods, middleware, opts) {
  opts = opts || {};

  const router = this;
  const stack = this.stack;

  // support array of paths
  if (Array.isArray(path)) {
    for (let i = 0; i < path.length; i++) {
      const curPath = path[i];
      router.register.call(router, curPath, methods, middleware, opts);
    }

    return this;
  }

  // create route
  const route = new Layer(path, methods, middleware, {
    end: opts.end === false ? opts.end : true.name: opts.name,
    sensitive: opts.sensitive || this.opts.sensitive || false.strict: opts.strict || this.opts.strict || false.prefix: opts.prefix || this.opts.prefix || "".ignoreCaptures: opts.ignoreCaptures
  });

  if (this.opts.prefix) {
    route.setPrefix(this.opts.prefix);
  }

  // add parameter middleware
  for (let i = 0; i < Object.keys(this.params).length; i++) {
    const param = Object.keys(this.params)[i];
    route.param(param, this.params[param]);
  }

  stack.push(route);

  debug('defined route %s %s', route.methods, route.path);

  return route;
};
Copy the code

Where, the object of Layer is as follows

function Layer(path, methods, middleware, opts) {
  this.opts = opts || {};
  this.name = this.opts.name || null;
  this.methods = [];
  this.paramNames = [];
  this.stack = Array.isArray(middleware) ? middleware : [middleware];

  for(let i = 0; i < methods.length; i++) {
    const l = this.methods.push(methods[i].toUpperCase());
    if (this.methods[l-1= = ='GET') this.methods.unshift('HEAD');
  }

  // ensure middleware is a function
  for (let i = 0; i < this.stack.length; i++) {
    const fn = this.stack[i];
    const type = (typeof fn);
    if(type ! = ='function')
      throw new Error(
        `${methods.toString()}\ `The ${this.opts.name || path}\`: \`middleware\` must be a function, not \`${type}\ ` `
      );
  }

  this.path = path;
  this.regexp = pathToRegexp(path, this.paramNames, this.opts);
};
Copy the code

2. Request time code

Router.prototype.routes = Router.prototype.middleware = function () {
  const router = this;

  let dispatch = function dispatch(ctx, next) {
    debug('%s %s', ctx.method, ctx.path);

    const path = router.opts.routerPath || ctx.routerPath || ctx.path;
    const matched = router.match(path, ctx.method);
    let layerChain;

    if (ctx.matched) {
      ctx.matched.push.apply(ctx.matched, matched.path);
    } else {
      ctx.matched = matched.path;
    }

    ctx.router = router;

    if(! matched.route)return next();

    const matchedLayers = matched.pathAndMethod
    const mostSpecificLayer = matchedLayers[matchedLayers.length - 1]
    ctx._matchedRoute = mostSpecificLayer.path;
    if (mostSpecificLayer.name) {
      ctx._matchedRouteName = mostSpecificLayer.name;
    }

    layerChain = matchedLayers.reduce(function(memo, layer) {
      memo.push(function(ctx, next) {
        ctx.captures = layer.captures(path, ctx.captures);
        ctx.params = ctx.request.params = layer.params(path, ctx.captures, ctx.params);
        ctx.routerPath = layer.path;
        ctx.routerName = layer.name;
        ctx._matchedRoute = layer.path;
        if (layer.name) {
          ctx._matchedRouteName = layer.name;
        }
        return next();
      });
      returnmemo.concat(layer.stack); } []);return compose(layerChain)(ctx, next);
  };

  dispatch.router = this;

  return dispatch;
};
Copy the code

In short, compose is composed nested within compose and pushes two middleware components, one for ctx.params and one for the business.

The steps are as follows:
  • Mount params to the context
  • The subsequent business logic is then executed
  • If there is middleware behind the whole, the remaining middleware continues to be executed

The test code is as follows

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

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

router.get('/'.(ctx, next) = > {
  ctx.body = '/';
});
router.get('/ssss'.(ctx, next) = > {
  ctx.body = 'sssss';
});

app
  .use((ctx, next) = > {
  	// There is no params property on CTX yet
    console.log(ctx);
    next();
  })
  .use(router.routes())
  .use(router.allowedMethods())

app.listen(3000);
Copy the code

If you curl http://localhost:3000/ssss will return here SSSS;

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

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

router.get('/'.(ctx, next) = > {
  ctx.body = '/';
});
router.get('/ssss'.(ctx, next) = > {
  ctx.body = 'sssss';
  next();
});

app
  .use((ctx, next) = > {
    console.log(ctx);
    next();
  })
  .use(router.routes())
  .use(router.allowedMethods())
  .use((ctx, next) = > {
    ctx.body = 'last'
  })

app.listen(3000);
Copy the code

If this is the case, last is returned.