koa-router源码解析

326 阅读1分钟

接着上一篇,看下koa是如何处理路由以及业务代码的,因为fnMiddleware(ctx).then 这种东西的存在,所以执行的时候需要把业务代码也都串入到middleware里头作为一个next,所以我们看下Koa-router是如何处理的。

概览

其中主要包括以下两项

  1. 注册: 当调用 router.get 的时候会通过调用 router.register , register 里通过传入的 path fn 实例化一个 Layer 对象, 然后push到router对象的route栈里头,来供请求时的调用。
  2. 请求:当请求的时候,走的逻辑是 creteContext -> fnMiddleware执行 -> 当遇到routes中间件的时候 -> 重新构造一个compose的函数,并传入next,以此传入之前的函数执行链里头 -> 执行完毕。

源码如下

1. register代码

旨在调用route.get的时候,把相关的属性包装成layer|route对象,存储到route的stack里头,供请求的时候进行查找。

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;
};

其中,layer的对象如下

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()} \`${this.opts.name || path}\`: \`middleware\` must be a function, not \`${type}\``
      );
  }

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

2. 请求时候的代码

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();
      });
      return memo.concat(layer.stack);
    }, []);

    return compose(layerChain)(ctx, next);
  };

  dispatch.router = this;

  return dispatch;
};

一言以蔽之,就是compose中嵌套compose,并且里头会push两个中间件,一个用来处理ctx.params,一个是业务的中间件。

步骤如下:
  • 把params挂载到context上
  • 然后执行后续的业务逻辑
  • 如果整体之后还有中间件,则会继续执行剩下的中间件

测试代码如下

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) => {
  	// 这里ctx上还没有params属性
    console.log(ctx);
    next();
  })
  .use(router.routes())
  .use(router.allowedMethods())

app.listen(3000);

这里如果你 curl http://localhost:3000/ssss 会返回 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);

 如果是这种情况,则会返回 last 。