前面说了app.handle实际上是调用的router.handle,也知道了router的结构是在stack上添加了layer和router,所以router.handle需要做的就是从router.stack上找出对应的layer和router并执行回调函数:
// 真正处理路由的函数 proto.handle = function handle(req, res, done) { var self = this; var idx = 0; var stack = self.stack; // next方法来查找对应的layer和回调函数 next(); function next() { // 使用第三方库parseUrl获取path,如果没有path,直接返回 var path = parseUrl(req).pathname; if (path == null) { return done(); } var layer; var match; var route; while (match !== true && idx < stack.length) { layer = stack[idx++]; // 注意这里先执行 layer = stack[idx]; 再执行idx++; match = layer.match(path); // 调用layer.match来检测当前路径是否匹配 route = layer.route; // 没匹配上,跳出当次循环 if (match !== true) { continue; } // layer匹配上了,但是没有route,也跳出当次循环 if (!route) { continue; } // 匹配上了,看看route上有没有对应的method var method = req.method; var has_method = route._handles_method(method); // 如果没有对应的method,其实也是没匹配上,跳出当次循环 if (!has_method) { match = false; continue; } } // 循环完了还没有匹配的,就done了,其实就是404 if (match !== true) { return done(); } // 如果匹配上了,就执行对应的回调函数 return layer.handle_request(req, res, next); } };上面代码还用到了几个Layer和Route的实例方法:
layer.match(path): 检测当前layer的path是否匹配。
route._handles_method(method):检测当前route的method是否匹配。
layer.handle_request(req, res, next):使用layer的回调函数来处理请求。
这几个方法看起来并不复杂,我们后面一个一个来实现。
到这里其实还有个疑问。从他整个的匹配流程来看,他寻找的其实是router.stack.layer这一层,但是最终应该执行的回调却是在router.stack.layer.route.stack.layer.handle。这是怎么通过router.stack.layer找到最终的router.stack.layer.route.stack.layer.handle来执行的呢?
这要回到我们前面的router.route方法:
proto.route = function route(path) { var route = new Route(); var layer = new Layer(path, route.dispatch.bind(route)); layer.route = route; this.stack.push(layer); return route; }这里我们new Layer的时候给的回调其实是route.dispatch.bind(route),这个方法会再去route.stack上找到正确的layer来执行。所以router.handle真正的流程其实是:
找到path匹配的layer
拿出layer上的route,看看有没有匹配的method
layer和method都有匹配的,再调用route.dispatch去找出真正的回调函数来执行。
所以又多了一个需要实现的函数,route.dispatch。
layer.matchlayer.match是用来检测当前path是否匹配的函数,用到了一个第三方库path-to-regexp,这个库可以将path转为正则表达式,方便后面的匹配,这个库在中也出现过。
var pathRegexp = require("path-to-regexp"); module.exports = Layer; function Layer(path, fn) { this.path = path; this.handle = fn; this.method = ""; // 添加一个匹配正则 this.regexp = pathRegexp(path); // 快速匹配/ this.regexp.fast_slash = path === "http://www.likecs.com/"; }然后就可以添加match实例方法了:
Layer.prototype.match = function match(path) { var match; if (path != null) { if (this.regexp.fast_slash) { return true; } match = this.regexp.exec(path); } // 没匹配上,返回false if (!match) { return false; } // 不然返回true return true; }; layer.handle_requestlayer.handle_request是用来调用具体的回调函数的方法,其实就是拿出layer.handle来执行:
Layer.prototype.handle_request = function handle(req, res, next) { var fn = this.handle; fn(req, res, next); }; route._handles_methodroute._handles_method就是检测当前route是否包含需要的method,因为之前添加了一个methods对象,可以用它来进行快速查找:
Route.prototype._handles_method = function _handles_method(method) { var name = method.toLowerCase(); return Boolean(this.methods[name]); }; route.dispatchroute.dispatch其实是router.stack.layer的回调函数,作用是找到对应的router.stack.layer.route.stack.layer.handle并执行。
Route.prototype.dispatch = function dispatch(req, res, done) { var idx = 0; var stack = this.stack; // 注意这个stack是route.stack // 如果stack为空,直接done // 这里的done其实是router.stack.layer的next // 也就是执行下一个router.stack.layer if (stack.length === 0) { return done(); } var method = req.method.toLowerCase(); // 这个next方法其实是在router.stack.layer.route.stack上寻找method匹配的layer // 找到了就执行layer的回调函数 next(); function next() { var layer = stack[idx++]; if (!layer) { return done(); } if (layer.method && layer.method !== method) { return next(); } layer.handle_request(req, res, next); } };到这里其实Express整体的路由结构,注册和执行流程都完成了,贴下对应的官方源码:
Router类:https://github.com/expressjs/express/blob/master/lib/router/index.js
Layer类:https://github.com/expressjs/express/blob/master/lib/router/layer.js
Route类:https://github.com/expressjs/express/blob/master/lib/router/route.js
中间件