国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

koa-router源碼學(xué)習(xí)小結(jié)

2019-11-19 13:00:18
字體:
供稿:網(wǎng)友

koa 框架一直都保持著簡潔性, 它只對 node 的 HTTP 模塊進(jìn)行了封裝, 而在真正實(shí)際使用, 我們還需要更多地像路由這樣的模塊來構(gòu)建我們的應(yīng)用, 而 koa-router 是常用的 koa 的路由庫. 這里通過解析 koa-router 的源碼來達(dá)到深入學(xué)習(xí)的目的.

源碼架構(gòu)圖

調(diào)用鏈路-routes()

HTTP請求調(diào)用流程

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方法指定的參數(shù)的中間件 this.params = {}; // 存放layer實(shí)例 this.stack = [];};

Layer

function Layer(path, methods, middleware, opts) { this.opts = opts || {}; this.name = this.opts.name || null; this.methods = []; // 存放path路徑參數(shù)的一些屬性,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; // 將路由轉(zhuǎn)為正則表達(dá)式 this.regexp = pathToRegExp(path, this.paramNames, this.opts); debug('defined route %s %s', this.methods, this.opts.prefix + this.path);};

給Router實(shí)例掛載HTTP方法

/** * Create `router.verb()` methods, where *verb* is one of the HTTP verbs such * as `router.get()` or `router.post()`. * * Match URL patterns to callback functions or controller actions using `router.verb()`, * where **verb** is one of the HTTP verbs such as `router.get()` or `router.post()`. * * Additionaly, `router.all()` can be used to match against all methods. * * ```javascript * router *  .get('/', (ctx, next) => { *   ctx.body = 'Hello World!'; *  }) *  .post('/users', (ctx, next) => { *   // ... *  }) *  .put('/users/:id', (ctx, next) => { *   // ... *  }) *  .del('/users/:id', (ctx, next) => { *   // ... *  }) *  .all('/users/:id', (ctx, next) => { *   // ... *  }); * ``` * * When a route is matched, its path is available at `ctx._matchedRoute` and if named, * the name is available at `ctx._matchedRouteName` * * Route paths will be translated to regular expressions using * [path-to-regexp](https://github.com/pillarjs/path-to-regexp). * * Query strings will not be considered when matching requests. * * #### Named routes * * Routes can optionally have names. This allows generation of URLs and easy * renaming of URLs during development. * * ```javascript * router.get('user', '/users/:id', (ctx, next) => { * // ... * }); * * router.url('user', 3); * // => "/users/3" * ``` * * #### Multiple middleware * * Multiple middleware may be given: * * ```javascript * router.get( *  '/users/:id', *  (ctx, next) => { *   return User.findOne(ctx.params.id).then(function(user) { *    ctx.user = user; *    next(); *   }); *  }, *  ctx => { *   console.log(ctx.user); *   // => { id: 17, name: "Alex" } *  } * ); * ``` * * ### Nested routers * * Nesting routers is supported: * * ```javascript * var forums = new Router(); * var posts = new Router(); * * posts.get('/', (ctx, next) => {...}); * posts.get('/:pid', (ctx, next) => {...}); * forums.use('/forums/:fid/posts', posts.routes(), posts.allowedMethods()); * * // responds to "/forums/123/posts" and "/forums/123/posts/123" * app.use(forums.routes()); * ``` * * #### Router prefixes * * Route paths can be prefixed at the router level: * * ```javascript * var router = new Router({ *  prefix: '/users' * }); * * router.get('/', ...); // responds to "/users" * router.get('/:id', ...); // responds to "/users/:id" * ``` * * #### URL parameters * * Named route parameters are captured and added to `ctx.params`. * * ```javascript * router.get('/:category/:title', (ctx, next) => { *  console.log(ctx.params); *  // => { category: 'programming', title: 'how-to-node' } * }); * ``` * * The [path-to-regexp](https://github.com/pillarjs/path-to-regexp) module is * used to convert paths to regular expressions. * * @name get|put|post|patch|delete|del * @memberof module:koa-router.prototype * @param {String} path * @param {Function=} middleware route middleware(s) * @param {Function} callback route callback * @returns {Router} */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

/** * Create and register a route. * * @param {String} path Path string. * @param {Array.<String>} methods Array of HTTP verbs. * @param {Function} middleware Multiple middleware also accepted. * @returns {Layer} * @private */Router.prototype.register = function (path, methods, middleware, opts) { opts = opts || {}; var router = this; // layer實(shí)例數(shù)組,初始為空數(shù)組 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 });  // 設(shè)置前置路由 if (this.opts.prefix) {  route.setPrefix(this.opts.prefix); } // add parameter middleware Object.keys(this.params).forEach(function (param) {   // 將router中this.params維護(hù)的參數(shù)中間件掛載到layer實(shí)例中  route.param(param, this.params[param]); }, this);  // 所有l(wèi)ayer實(shí)例存放在router的stack屬性中 stack.push(route); return route;};

Router.prototype.match

/** * Match given `path` and return corresponding routes. * * @param {String} path * @param {String} method * @returns {Object.<path, pathAndMethod>} returns layers that matched path and * path and method. * @private */Router.prototype.match = function (path, method) {  // layer實(shí)例組成的數(shù)組 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

/** * Returns router middleware which dispatches a route matching the request. * * @returns {Function} */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;  // 將注冊路由和請求的路由進(jìn)行匹配  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屬性是三次匹配的結(jié)果,表示最終是否匹配成功  if (!matched.route) return next();    // 同時滿足路由匹配和http請求方法的layer數(shù)組  var matchedLayers = matched.pathAndMethod  // 匹配多個路由時認(rèn)為最后一個是匹配有效的路由  var mostSpecificLayer = matchedLayers[matchedLayers.length - 1]  ctx._matchedRoute = mostSpecificLayer.path;  if (mostSpecificLayer.name) {   ctx._matchedRouteName = mostSpecificLayer.name;  }    // 將匹配的路由reduce為一個數(shù)組  layerChain = matchedLayers.reduce(function(memo, layer) {    // 執(zhí)行注冊路由中間件之前,對context中的一些參數(shù)進(jìn)行設(shè)置   memo.push(function(ctx, next) {     // :path/XXX 捕獲的路徑    ctx.captures = layer.captures(path, ctx.captures);    // 捕獲的路徑上的參數(shù), { key: value }    ctx.params = layer.params(path, ctx.captures, ctx.params);    // 路由名稱    ctx.routerName = layer.name;    return next();   });   // 返回路由中間件的數(shù)組   return memo.concat(layer.stack);  }, []);    // 處理為promise對象  return compose(layerChain)(ctx, next); }; dispatch.router = this; return dispatch;};

Router.prototype.allowedMethod

/** * Returns separate middleware for responding to `OPTIONS` requests with * an `Allow` header containing the allowed methods, as well as responding * with `405 Method Not Allowed` and `501 Not Implemented` as appropriate. * * @example * * ```javascript * var Koa = require('koa'); * var Router = require('koa-router'); * * var app = new Koa(); * var router = new Router(); * * app.use(router.routes()); * app.use(router.allowedMethods()); * ``` * * **Example with [Boom](https://github.com/hapijs/boom)** * * ```javascript * var Koa = require('koa'); * var Router = require('koa-router'); * var Boom = require('boom'); * * var app = new Koa(); * var router = new Router(); * * app.use(router.routes()); * app.use(router.allowedMethods({ *  throw: true, *  notImplemented: () => new Boom.notImplemented(), *  methodNotAllowed: () => new Boom.methodNotAllowed() * })); * ``` * * @param {Object=} options * @param {Boolean=} options.throw throw error instead of setting status and header * @param {Function=} options.notImplemented throw the returned value in place of the default NotImplemented error * @param {Function=} options.methodNotAllowed throw the returned value in place of the default MethodNotAllowed error * @returns {Function} */Router.prototype.allowedMethods = function (options) { options = options || {}; var implemented = this.methods; return function allowedMethods(ctx, next) {   // 所有中間件執(zhí)行完之后執(zhí)行allowedMethod方法  return next().then(function() {   var allowed = {};      // 沒有響應(yīng)狀態(tài)碼或者響應(yīng)了404   if (!ctx.status || ctx.status === 404) {     // 在match方法中,匹配的路由的layer實(shí)例對象組成的數(shù)組    ctx.matched.forEach(function (route) {     route.methods.forEach(function (method) {       // 把匹配的路由的http方法保存起來,認(rèn)為是允許的http請求方法      allowed[method] = method;     });    });    var allowedArr = Object.keys(allowed);        // 如果該方法在router實(shí)例的methods中不存在    if (!~implemented.indexOf(ctx.method)) {      // 如果在初始化router時配置了throw屬性為true     if (options.throw) {      var notImplementedThrowable;      if (typeof options.notImplemented === 'function') {        // 指定了報錯函數(shù)       notImplementedThrowable = options.notImplemented(); // set whatever the user returns from their function      } else {        // 沒有指定則拋出http異常       notImplementedThrowable = new HttpError.NotImplemented();      }      throw notImplementedThrowable;     } else {       // 沒有配置throw則響應(yīng)501      ctx.status = 501;      // 設(shè)置響應(yīng)頭中的allow字段,返回允許的http方法      ctx.set('Allow', allowedArr.join(', '));     }    } else if (allowedArr.length) {     if (ctx.method === 'OPTIONS') {       // 如果是OPTIONS請求,則認(rèn)為是請求成功,響應(yīng)200,并根據(jù)OPTIONS請求約定返回允許的http方法      ctx.status = 200;      ctx.body = '';      ctx.set('Allow', allowedArr.join(', '));     } else if (!allowed[ctx.method]) {       // 如果請求方法在router實(shí)例的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 {        // 響應(yīng)405 http請求方法錯誤       ctx.status = 405;       ctx.set('Allow', allowedArr.join(', '));      }     }    }   }  }); };};

Router.prototype.use

/** * Use given middleware. * * Middleware run in the order they are defined by `.use()`. They are invoked * sequentially, requests start at the first middleware and work their way * "down" the middleware stack. * * @example * * ```javascript * // session middleware will run before authorize * router *  .use(session()) *  .use(authorize()); * * // use middleware only with given path * router.use('/users', userAuth()); * * // or with an array of paths * router.use(['/users', '/admin'], userAuth()); * * app.use(router.routes()); * ``` * * @param {String=} path * @param {Function} middleware * @param {Function=} ... * @returns {Router} */Router.prototype.use = function () { var router = this; var middleware = Array.prototype.slice.call(arguments); var path; // support array of paths // 如果第一個參數(shù)是一個數(shù)組,且數(shù)組中元素為字符串 if (Array.isArray(middleware[0]) && typeof middleware[0][0] === 'string') {   // 遞歸調(diào)用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;};

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持武林網(wǎng)。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 时尚| 剑河县| 柳林县| 宁安市| 神池县| 奇台县| 天气| 龙川县| 民县| 伊宁市| 英超| 铜鼓县| 南召县| 双流县| 白银市| 长海县| 保德县| 凤庆县| 桂东县| 灯塔市| 遵义县| 兴宁市| 海宁市| 清水河县| 芷江| 合山市| 紫阳县| 左云县| 邹平县| 城步| 福建省| 溧阳市| 嘉祥县| 大理市| 北海市| 西丰县| 大田县| 蓬安县| 宕昌县| 闵行区| 开封县|