摘要:源碼架構圖調用鏈路請求調用流程存放方法指定的參數的中間件存放實例存放路徑參數的一些屬性,存放該路由的中間件如果支持請求,一并支持請求將路由轉為正則表達式給實例掛載方法如果指定了路由屬性路由注冊實例數組,初始為空數
源碼架構圖 調用鏈路-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方法
/** * 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.Router.prototype.match} 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實例數組,初始為空數組 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; };
/** * Match given `path` and return corresponding routes. * * @param {String} path * @param {String} method * @returns {Object.Router.prototype.routes} returns layers that matched path and * path and method. * @private */ 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; };
/** * 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; // 將注冊路由和請求的路由進行匹配 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
/** * 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) { // 所有中間件執行完之后執行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
/** * 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 // 如果第一個參數是一個數組,且數組中元素為字符串 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; };
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/97520.html
摘要:本打算教一步步實現,因為要解釋的太多了,所以先簡化成版本,從實現部分功能到閱讀源碼,希望能讓你好理解一些。 本打算教一步步實現koa-router,因為要解釋的太多了,所以先簡化成mini版本,從實現部分功能到閱讀源碼,希望能讓你好理解一些。希望你之前有讀過koa源碼,沒有的話,給你鏈接 最核心需求-路由匹配 router最重要的就是路由匹配,我們就從最核心的入手 router.get...
摘要:問題描述在使用作為路由遇到了一個優先級問題如下代碼在訪問時路由會優先匹配到路由返回這個問題就很尷尬了項目空閑下來去翻看源碼終于找到了原因問題原因的源碼并不長和兩個文件加起來共一千多行代碼建議可以結合這篇文章閱讀其中造成這個問題的原因 問題描述 在使用Koa-router作為路由遇到了一個優先級問題.如下代碼 // routerPage.js file const router = re...
摘要:代碼結構執行流程上面兩張圖主要將的整體代碼結構和大概的執行流程畫了出來,畫的不夠具體。那下面主要講中的幾處的關鍵代碼解讀一下。全局的路由參數處理的中間件組成的對象。 代碼結構 showImg(https://segmentfault.com/img/remote/1460000007468236?w=1425&h=1772); 執行流程 showImg(https://segmentf...
摘要:第三篇,有關生態中比較重要的一個中間件第一篇源碼閱讀第二篇源碼閱讀與是什么首先,因為是一個管理中間件的平臺,而注冊一個中間件使用來執行。這里寫入的多個中間件都是針對該生效的。 第三篇,有關koa生態中比較重要的一個中間件:koa-router 第一篇:koa源碼閱讀-0 第二篇:koa源碼閱讀-1-koa與koa-compose koa-router是什么 首先,因為koa是一個管...
摘要:四路由注冊構造函數首先看了解一下構造函數限制必須采用關鍵字服務器支持的請求方法,后續方法會用到保存前置處理函數存儲在構造函數中初始化的和屬性最為重要,前者用來保存前置處理函數,后者用來保存實例化的對象。 一、前言 ??Koa為了保持自身的簡潔,并沒有捆綁中間件。但是在實際的開發中,我們需要和形形色色的中間件打交道,本文將要分析的是經常用到的路由中間件 -- koa-router。 ??...
閱讀 1048·2021-10-11 10:59
閱讀 3601·2021-09-26 09:55
閱讀 891·2019-08-30 15:55
閱讀 2650·2019-08-30 15:44
閱讀 434·2019-08-30 14:06
閱讀 680·2019-08-30 11:26
閱讀 3336·2019-08-30 10:49
閱讀 2466·2019-08-29 12:53