上一节实现了兼容老的路由写法,这一节来实现二级路由
二级路由实现核心:
- 进入中间件后,让对应的路由系统去进行匹配操作
- 中间件进去匹配需要删除 path,存起来出去时在加上
示意图:
代码实现如下:
router/index.js
const url = require("url");
const Route = require("./route");
const Layer = require("./layer");
const methods = require("methods");function Router() {// 创建路由系统let router = (req, res, next) => {// 二级路由// 让对应的路由系统去进行匹配操作router.handle(req, res, next);};// 兼容老的路由写法// 维护所有的路由router.stack = [];// router 链上得有 get 等方法// 让路由的实例可以通过链找到原来的方法router.__proto__ = proto;return router;
}let proto = {};proto.route = function (path) {// 产生 routelet route = new Route();// 产生 layer 让 layer 跟 route 进行关联let layer = new Layer(path, route.dispatch.bind(route));// 每个路由都具备一个 route 属性,稍后路径匹配到后会调用 route 中的每一层layer.route = route;// 把 layer 放到路由的栈中this.stack.push(layer);return route;
};methods.forEach((method) => {proto[method] = function (path, handlers) {// 1.用户调用 method 时,需要保存成一个 layer 当道栈中// 2.产生一个 Route 实例和当前的 layer 创造关系// 3.要将 route 的 dispatch 方法存到 layer 上let route = this.route(path);// 让 route 记录用户传入的 handler 并且标记这个 handler 是什么方法route[method](handlers);};
});proto.use = function (path, ...handlers) {// 默认第一个是路径,后面是一个个的方法,路径可以不传if (typeof path === "function") {handlers.unshift(path);path = "/";}// 如果是多个函数需要循环添加层for (let i = 0; i < handlers.length; i++) {let layer = new Layer(path, handlers[i]);// 中间件不需要 route 属性layer.route = undefined;this.stack.push(layer);}
};proto.handle = function (req, res, out) {console.log("请求到了");// 需要取出路由系统中 Router 存放的 layer 依次执行let { pathname } = url.parse(req.url);let idx = 0;let removed = "";let next = (err) => {// 遍历完后没有找到就直接走出路由系统if (idx >= this.stack.length) return out();let layer = this.stack[idx++];// 中间件出来需要加上 removed,继续往下匹配if (removed) {req.url = removed + pathname; // 匹配其他中间件removed = "";}if (err) {console.log("统一对中间件跟路由错误处理");// 找错误处理中间件if (!layer.route) {// 如果是中间件自己处理layer.handle_error(err, req, res, next);} else {// 路由则跳过,继续携带错误向下执行next(err);}} else {// 需要判断 layer 上的 path 和当前请求路由是否一致,一致就执行 dispatch 方法if (layer.match(pathname)) {// 中间件没有方法可以匹配,不能是错误处理中间件if (!layer.route) {if (layer.handler.length !== 4) {// 中间件进去匹配需要删除 path,存起来出去时用// 比如我们访问 /user/add,那么需要删除 /user,用 /add 去匹配用户里的路由是否有 /addif (layer.path !== "/") {removed = layer.path;req.url = pathname.slice(removed.length);}layer.handle_request(req, res, next);} else {// 错误中间件next();}} else {// 将遍历路由系统中下一层的方法传入// 加速匹配,如果用户注册过这个类型的方法在去执行if (layer.route.methods[req.method.toLowerCase()]) {layer.handle_request(req, res, next);} else {next();}}} else {next();}}};next();
};module.exports = Router;
route.js
:主要改造一下报错问题,需要判断 handlers 是否是数组,上一节是临时写了 ...handlers
const Layer = require("./layer");
const methods = require("methods");function Route() {this.stack = [];// 用来描述内部存过哪些方法this.methods = {};
}Route.prototype.dispatch = function (req, res, out) {// 稍后调用此方法时,回去栈中拿出对应的 handler 依次执行let idx = 0;console.log("this.stack----->", this.stack);let next = (err) => {// 如果内部迭代的时候出现错误,直接到外层的 stack 中err && out(err);// 遍历完后没有找到就直接走出路由系统if (idx >= this.stack.length) return out();let layer = this.stack[idx++];console.log("dispatch----->", layer.method);if (layer.method === req.method.toLowerCase()) {layer.handle_request(req, res, next);} else {next();}};next();
};
methods.forEach((method) => {Route.prototype[method] = function (handlers) {console.log("handlers----->", handlers);// 需要判断 handlers 是否是数组if (!Array.isArray(handlers)) {handlers = [handlers];}handlers.forEach((handler) => {// 这里的路径没有意义let layer = new Layer("/", handler);layer.method = method;// 做个映射表this.methods[method] = true;this.stack.push(layer);});};
});module.exports = Route;