栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > Web开发 > JavaScript

koa-router源码学习小结

JavaScript 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

koa-router源码学习小结

koa 框架一直都保持着简洁性, 它只对 node 的 HTTP 模块进行了封装, 而在真正实际使用, 我们还需要更多地像路由这样的模块来构建我们的应用, 而 koa-router 是常用的 koa 的路由库. 这里通过解析 koa-router 的源码来达到深入学习的目的.

源码架构图

调用链路-routes()

HTTP请求调用流程

Usage

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

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

router.get('/', async (ctx, next) => {
 console.log('index');
 ctx.body = 'index';
});

app.use(router.routes()).use(router.allowedMethods());

app.listen(3000);

Router

function Router(opts) {
 if (!(this instanceof Router)) {
  return new Router(opts);
 }

 this.opts = opts || {};
 this.methods = this.opts.methods || [
  'HEAD',
  'OPTIONS',
  'GET',
  'PUT',
  'PATCH',
  'POST',
  'DELETe'
 ];

  // 存放router.param方法指定的参数的中间件
 this.params = {};
 // 存放layer实例
 this.stack = [];
};

Layer

function Layer(path, methods, middleware, opts) {
 this.opts = opts || {};
 this.name = this.opts.name || null;
 this.methods = [];
 // 存放path路径参数的一些属性,eg: /test/:str => { name: str, prefix: '/' ....}
 this.paramNames = [];
 // 存放该路由的中间件
 this.stack = Array.isArray(middleware) ? middleware : [middleware];

 methods.forEach(function(method) {
  var l = this.methods.push(method.toUpperCase());
  // 如果支持get请求,一并支持head请求
  if (this.methods[l-1] === 'GET') {
   this.methods.unshift('HEAD');
  }
 }, this);

 // ensure middleware is a function
 this.stack.forEach(function(fn) {
  var type = (typeof fn);
  if (type !== 'function') {
   throw new Error(
    methods.toString() + " `" + (this.opts.name || path) +"`: `middleware` "
    + "must be a function, not `" + type + "`"
   );
  }
 }, this);

 this.path = path;
 // 将路由转为正则表达式
 this.regexp = pathToRegExp(path, this.paramNames, this.opts);

 debug('defined route %s %s', this.methods, this.opts.prefix + this.path);
};

给Router实例挂载HTTP方法


var methods = require('methods');

methods.forEach(function (method) {
 Router.prototype[method] = function (name, path, middleware) {
  var middleware;

    // 如果指定了路由name属性
  if (typeof path === 'string' || path instanceof RegExp) {
   middleware = Array.prototype.slice.call(arguments, 2);
  } else {
   middleware = Array.prototype.slice.call(arguments, 1);
   path = name;
   name = null;
  }

    // 路由注册
  this.register(path, [method], middleware, {
   name: name
  });

  return this;
 };
});

Router.prototype.register


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

 var router = this;
 // layer实例数组,初始为空数组
 var stack = this.stack;

 // support array of paths
 if (Array.isArray(path)) {
   // 如果是多路径,递归注册路由
  path.forEach(function (p) {
   router.register.call(router, p, methods, middleware, opts);
  });

  return this;
 }

 // create route
 var 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
 Object.keys(this.params).forEach(function (param) {
   // 将router中this.params维护的参数中间件挂载到layer实例中
  route.param(param, this.params[param]);
 }, this);

  // 所有layer实例存放在router的stack属性中
 stack.push(route);

 return route;
};

Router.prototype.match


Router.prototype.match = function (path, method) {
  // layer实例组成的数组
 var layers = this.stack;
 var layer;
 var matched = {
  path: [],
  pathAndMethod: [],
  route: false
 };

 for (var len = layers.length, i = 0; i < len; i++) {
  layer = layers[i];

  debug('test %s %s', layer.path, layer.regexp);

    // 1.匹配路由
  if (layer.match(path)) {
   matched.path.push(layer);

      // 2.匹配http请求方法
   if (layer.methods.length === 0 || ~layer.methods.indexOf(method)) {
    matched.pathAndMethod.push(layer);
    // 3.指定了http请求方法,判定为路由匹配成功
    if (layer.methods.length) matched.route = true;
   }
  }
 }

 return matched;
};

Router.prototype.routes


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

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

    // 请求路由
  var path = router.opts.routerPath || ctx.routerPath || ctx.path;
  // 将注册路由和请求的路由进行匹配
  var matched = router.match(path, ctx.method);
  var layerChain, layer, i;

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

  ctx.router = router;

    // route属性是三次匹配的结果,表示最终是否匹配成功
  if (!matched.route) return next();

    // 同时满足路由匹配和http请求方法的layer数组
  var matchedLayers = matched.pathAndMethod
  // 匹配多个路由时认为最后一个是匹配有效的路由
  var mostSpecificLayer = matchedLayers[matchedLayers.length - 1]
  ctx._matchedRoute = mostSpecificLayer.path;
  if (mostSpecificLayer.name) {
   ctx._matchedRouteName = mostSpecificLayer.name;
  }

    // 将匹配的路由reduce为一个数组
  layerChain = matchedLayers.reduce(function(memo, layer) {
    // 执行注册路由中间件之前,对context中的一些参数进行设置
   memo.push(function(ctx, next) {
     // :path/XXX 捕获的路径
    ctx.captures = layer.captures(path, ctx.captures);
    // 捕获的路径上的参数, { key: value }
    ctx.params = layer.params(path, ctx.captures, ctx.params);
    // 路由名称
    ctx.routerName = layer.name;
    return next();
   });
   // 返回路由中间件的数组
   return memo.concat(layer.stack);
  }, []);

    // 处理为promise对象
  return compose(layerChain)(ctx, next);
 };

 dispatch.router = this;

 return dispatch;
};

Router.prototype.allowedMethod


Router.prototype.allowedMethods = function (options) {
 options = options || {};
 var implemented = this.methods;

 return function allowedMethods(ctx, next) {
   // 所有中间件执行完之后执行allowedMethod方法
  return next().then(function() {
   var allowed = {};

      // 没有响应状态码或者响应了404
   if (!ctx.status || ctx.status === 404) {
     // 在match方法中,匹配的路由的layer实例对象组成的数组
    ctx.matched.forEach(function (route) {
     route.methods.forEach(function (method) {
// 把匹配的路由的http方法保存起来,认为是允许的http请求方法
      allowed[method] = method;
     });
    });

    var allowedArr = Object.keys(allowed);

 // 如果该方法在router实例的methods中不存在
    if (!~implemented.indexOf(ctx.method)) {
      // 如果在初始化router时配置了throw属性为true
     if (options.throw) {
      var notImplementedThrowable;
      if (typeof options.notImplemented === 'function') {
 // 指定了报错函数
notImplementedThrowable = options.notImplemented(); // set whatever the user returns from their function
      } else {
 // 没有指定则抛出http异常
notImplementedThrowable = new HttpError.NotImplemented();
      }
      throw notImplementedThrowable;
     } else {
// 没有配置throw则响应501
      ctx.status = 501;
      // 设置响应头中的allow字段,返回允许的http方法
      ctx.set('Allow', allowedArr.join(', '));
     }
    } else if (allowedArr.length) {
     if (ctx.method === 'OPTIONS') {
// 如果是OPTIONS请求,则认为是请求成功,响应200,并根据OPTIONS请求约定返回允许的http方法
      ctx.status = 200;
      ctx.body = '';
      ctx.set('Allow', allowedArr.join(', '));
     } else if (!allowed[ctx.method]) {
// 如果请求方法在router实例的methods中存在,但是在匹配的路由中该http方法不存在
      if (options.throw) {
var notAllowedThrowable;
if (typeof options.methodNotAllowed === 'function') {
 notAllowedThrowable = options.methodNotAllowed(); // set whatever the user returns from their function
} else {
 notAllowedThrowable = new HttpError.MethodNotAllowed();
}
throw notAllowedThrowable;
      } else {
 // 响应405 http请求方法错误
ctx.status = 405;
ctx.set('Allow', allowedArr.join(', '));
      }
     }
    }
   }
  });
 };
};

Router.prototype.use


Router.prototype.use = function () {
 var router = this;
 var middleware = Array.prototype.slice.call(arguments);
 var path;

 // support array of paths
 // 如果第一个参数是一个数组,且数组中元素为字符串
 if (Array.isArray(middleware[0]) && typeof middleware[0][0] === 'string') {
   // 递归调用use方法
  middleware[0].forEach(function (p) {
   router.use.apply(router, [p].concat(middleware.slice(1)));
  });

  return this;
 }

 var hasPath = typeof middleware[0] === 'string';
 if (hasPath) {
  path = middleware.shift();
 }

 middleware.forEach(function (m) {
   // 如果这个中间件是由router.routes()方法返回的dispatch中间件,即这是一个嵌套的路由
  if (m.router) {
    // 遍历router.stack属性中所有的layer
   m.router.stack.forEach(function (nestedLayer) {
     // 被嵌套的路由需要以父路由path为前缀
    if (path) nestedLayer.setPrefix(path);
    // 如果父路由有指定前缀,被嵌套的路由需要把这个前缀再加上
    if (router.opts.prefix) nestedLayer.setPrefix(router.opts.prefix);
    router.stack.push(nestedLayer);
   });

   if (router.params) {
    Object.keys(router.params).forEach(function (key) {
     m.router.param(key, router.params[key]);
    });
   }
  } else {
   router.register(path || '(.*)', [], m, { end: false, ignoreCaptures: !hasPath });
  }
 });

 return this;
};

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持考高分网。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/79304.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号