91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

koa-router源碼學習小結

發布時間:2020-08-21 19:03:47 來源:腳本之家 閱讀:139 作者:Gideon 欄目:web開發

koa 框架一直都保持著簡潔性, 它只對 node 的 HTTP 模塊進行了封裝, 而在真正實際使用, 我們還需要更多地像路由這樣的模塊來構建我們的應用, 而 koa-router 是常用的 koa 的路由庫. 這里通過解析 koa-router 的源碼來達到深入學習的目的.

源碼架構圖

koa-router源碼學習小結

調用鏈路-routes()

koa-router源碼學習小結

HTTP請求調用流程

koa-router源碼學習小結

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.<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實例數組,初始為空數組
 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

/**
 * 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實例組成的數組
 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;
  // 將注冊路由和請求的路由進行匹配
  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;
};

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

齐齐哈尔市| 临城县| 陇西县| 石阡县| 当雄县| 临澧县| 阜新市| 丰顺县| 大同市| 萍乡市| 玉溪市| 永和县| 定陶县| 耒阳市| 靖西县| 高台县| 邢台县| 乡宁县| 深泽县| 锡林郭勒盟| 鄄城县| 宜章县| 绵阳市| 固始县| 日照市| 凤台县| 建昌县| 台南县| 纳雍县| 邢台县| 布拖县| 文成县| 敦煌市| 瓦房店市| 留坝县| 鹤壁市| 抚顺县| 临高县| 海门市| 洪湖市| 九寨沟县|