阅读 62

源码系列 —— koa-route

这是我参与更文挑战的第 28 天,活动详情查看:更文挑战

koa-routekoa 的一个路由中间件。

在不使用该中间件的情况下,我们实现路由的方式如下所示:

const Koa = require('koa');
const app = new Koa();

// switch-case 实现
const route = ctx => {
    switch (ctx.path) {
        case '/name':
            ctx.body = 'LvLin';
            return;
        case '/age':
            ctx.body = '25';
            return;
        default:
            return;
    }
};

app.use(route);
app.listen(3000);
复制代码

通过koa-route,我们实现路由代码如下:

const Koa = require('koa');
const route = require('koa-route');
const app = new Koa();

const name = ctx => {
    ctx.body = 'LvLin';
}

const age = ctx => {
    ctx.body = '25';
}

app.use(route.get('/name', name));
app.use(route.get('/age', age));
app.listen(3000);
复制代码

复杂一些的例子如下:

const _ = require('koa-route');
const Koa = require('koa');
const app = new Koa();

const db = {
  tobi: { name: 'tobi', species: 'ferret' },
  loki: { name: 'loki', species: 'ferret' },
  jane: { name: 'jane', species: 'ferret' }
};

const pets = {
  list: (ctx) => {
    const names = Object.keys(db);
    ctx.body = 'pets: ' + names.join(', ');
  },

  show: (ctx, name) => {
    const pet = db[name];
    if (!pet) return ctx.throw('cannot find that pet', 404);
    ctx.body = pet.name + ' is a ' + pet.species;
  }
};

app.use(_.get('/pets', pets.list));
app.use(_.get('/pets/:name', pets.show));

app.listen(3000);
console.log('listening on port 3000');
复制代码

源码分析

看一下外部依赖:

const pathToRegexp = require('path-to-regexp');
const debug = require('debug')('koa-route');
const methods = require('methods');
复制代码

methods 源码很简单,以数组形式导出了 Node 支持的所有 HTTP 方法。

path-to-regexp 用于将路由 path 转换成正则表达式,通过该正则能够匹配符合的请求path

koa-route 源码分析如下:

// 对 methods 进行遍历,初始化
methods.forEach(function(method){
  module.exports[method] = create(method);
});

module.exports.del = module.exports.delete;
module.exports.all = create();

function create(method) {
  if (method) method = method.toUpperCase();

  return function(path, fn, opts){
    // 返回路径的匹配正则
    const re = pathToRegexp(path, opts);
    debug('%s %s -> %s', method || 'ALL', path, re);

    const createRoute = function(routeFunc){
      return function (ctx, next){
        // 判断请求的 method 是否匹配
        if (!matches(ctx, method)) return next();

        // 判断 path 是否匹配
        const m = re.exec(ctx.path);
        if (m) {
          // 获取 path 携带的参数,并进行 url 解码
          const args = m.slice(1).map(decode);
          ctx.routePath = path;
          debug('%s %s matches %s %j', ctx.method, path, ctx.path, args);
          // ctx 作为路由中间件第一个参数
          args.unshift(ctx);
          // next 方法作为路由中间件最后一个参数
          args.push(next);
          return Promise.resolve(routeFunc.apply(ctx, args));
        }

        // 路径不匹配,进入下一个
        return next();
      }
    };

    if (fn) {
      return createRoute(fn);
    } else {
      return createRoute;
    }
  }
}


// url 解码
function decode(val) {
  if (val) return decodeURIComponent(val);
}


// 检查请求 method 是否一致
function matches(ctx, method) {
  if (!method) return true;
  if (ctx.method === method) return true;
  if (method === 'GET' && ctx.method === 'HEAD') return true;
  return false;
}

复制代码

相关资料

methods

koa-route

文章分类
前端
文章标签