express route 源码解析
使用express,对route部分比较好奇,花了两天时间看了下源码,今天分享出来,希望可以给对express route实现同样好奇的人带来一些帮助.
先写几行代码
app.use((req,res,next)=>{
console.log('use 中间件');
next();
})
app.get('/' , (req,res,next)=>{
res.end('main');
})
app.get('/book/*' , ()=>{
res.end('book')
})
复制代码
这里先配置几条路由,当有url匹配到路由就会执行相对应的回调函数。如果你在回调函数里面调用next(),它还会继续向下找符合的项,并执行相应的回调函数。这里看到express用起来真的很方便,但它内部是如何实现的呢?
最初的猜测:
1.一定是先把这些配置项存储起来,把每一个路由的regexp和相应的回调函数当作一个对象存储起来,最后存储到一个数组中。
2.当有请求过来,遍历数组,拿出url和每一个路由的regexp匹配,匹配成功了执行相应的回调函数。
3.next:当第2步完成后,如果调用next,执行了相应的回调函数后不停,继续向下遍历数组,执行相应的逻辑。如果没有调用next, 就直接停了。
第一步,express是怎么存储各个路由配置项,又是以什么结构存储的
console.log(app),找到了我想要的东西,app._router里面存储的是各个配置项的信息。
之后做的事,就是看源码,看如何存的了。既然我是通过app.get()方法配置的,直接找app.get()相应的源码就好了。
一步一步来
先是打开入口文件,这个很好找,是express.js.(源码1)
这里,通过mixin(app, proto, false)看出来,app的方法都是写在proto里面,也就是都写在application.js里面。直接在application.js里面搜索app.get,然而并没有搜到。。。之后我发现,原来express是这么做的。(源码2)
果然在这里看到了, router.stack里面应该装的就是各个layer了?.(后面会说到,layer就是装着每一个路由项,包括regexp,回调函数等)。不过现在还没有装呢,没执行this.stack.push()操作呢。还得往回看,回到(源码2),刚刚只是执行了this.lazyrouter();。之后执行了var route = this._router.route(path);这步开始装layer了。接下来挺重要的了。this._router.route(path),也就是./router/index.js下(源码5)
再贴一次layer对象的数据结构
再看看刚刚那个route对象,构造函数在./router/route.js里面(源码7)
在哪里装的??? 让我们回到(源码2)
route[method].apply(route, slice.call(arguments, 1));
这行代码,真正的执行代码在./router/route.js里面(源码8)
先整理一下: app._router,也就是最大的那个对象,里面会有一个stack数组,装着通过app.get('/','callback')等创建出的layer,这种layer(有路由的,就是通过app.get()等方法创建出来的),会有一个handle,这个handle==route.dispatch.bind(route).同时会有一个route,里面同样会有一个stack,这里面的layer的handle撞着真实的路由回调函数。如下图
接下来再看通过app.use()创建的中间件.和上面差不过,直接看代码,再./router/index.js(源码9)
整理第一步app._router的数据结构
app._router.stack = [layer,layer...]; 里面装着2种类型的layer。
第一种是通过app.get()等方式创建的layer.这种layer,handle==route.dispatch.bind(route)。layer.route = [layer..].同时route里面装着layer(layer.handle==真正的路由回调函数)。
第二种是通过app.use()创建的layer,这种layer,layer.handle就是真正的回调函数。同时layer.route=undefined;
第一步的源码过程看着会比较枯燥
第二步:如何通过路由找到相应回调函数,并执行以及next的实现
接下来看代码的过程就是从有请求来的时候,那部分代码看就可以了
一步一步来吧
还是看express.js里面的代码 , 源码(2-1)
app.handle实现代码在application.js里面, 源码(2-2)
然后在通过router.handle(req,res,done);也就是在router文件夹下的index.js 核心代码了, 源码(2-3)
可以看到stack也就是app._router.stack里面装的是各种layer,有通过app.get()等方式创建的带route的layer,也有通过app.use()创建的layer.route=undefined的layer.
之后在里面写了一个next方法,(据猜测就是回调函数里面的第三个参数next), 并执行了next();可以看到里面做的事情就是开始遍历layer数组,直到先找到的第一个match到的layer,之后会走到layer.handle_request(req, res, next)。这里面的next参数就是上面proto.handle里面定义的next函数
layer.handle_request()的源码在./router/layer.js里面.(源码2-4)
这里要小心一点了 ,fn = this.handle. 上面说过,layer有两种类型,一种是有路由的layer,layer.handle == route.dispatch.bind(route).另一种是app.use()那种,layer.handle就是回调函数。
先说第二种,如果先匹配到的layer是app.use()那种,此时执行fn(req,res,next),也就直接执行了回调函数。如果在回调函数里面调用了next()方法,也就是在走到上面proto.handle里面的next(),继续遍历stack数组的下一项,周而复始,直到遍历完。。(一起都看着那么的顺畅)
再来看第一种:执行fn(req,res,next);这时候走到的是route.dispatch.bind(route)这个函数里。(讲真的,这个函数真心重要)
上代码,route.dispatch的源码在./router/route.js (源码2-5)
这里可以看到,这个对象也就是带路由得那种layer,里面的lauer.route对象,如下图
这里可以看到,stack里面装的layer就是,layer.handle==真正的回调函数那种了。之后执行layer.handle_request(req, res, next),走的过程也就是第二种路由走的过程了。
但是会很奇怪,为什么在这里又定义了一个next()函数??
答案:这里面(也就是带路由的layer,layer.route.stack),stack同样是一个数组,这个数组里的layer,layer里面的handle== 真正路由回调函数,所以需要先把这个数组的layer遍历完,执行相应的layer.handle,然后再回到app._router.stack,继续遍历app._router.stack。(当然前提是都执行next,不执行next直接就停了啊)。
再贴段代码吧,layer.route.stack里面的两个layer分别装这两个回调函数。
app.get('/',(req,res,next)=>{
console.log(1);
next()
},(req,res,next)=>{
console.log(2);
next();
})
复制代码
所以这个next是为了遍历路由layer里面的stack,等都执行完,再去执行app._router.stack。
具体咋做的呢,继续看代码:dispatch函数的第三个函数done实际上就是proto.handle里面的next.在112行,随着在路由layer里面执行next后 , idx++; 等到数组满了,也就会走到done函数了,也就会继续遍历app._route.stack里面的layer了。
得个结论先
也就是说有两个next , 一个是最上面那个, proto.handle里面的next方法 ,控制着app._router.stack里面的layer, 一个是route.dispatch里面定义的next方法 ,控制着路由layer里面的route.stack里面的layer。也就是说通过app.get方法的回调函数里面的第三个参数next的定义是route.dispatch里面定义的next方法,app.use的回调函数的第三个参数next是proto.handle里面定义的next方法。
总结
语文不太好 ,写的有点‘冗余’,希望感兴趣的同学认真看 ,能得到帮助
本文深入剖析Express框架的路由机制,从源码角度解读路由配置、存储及执行流程,揭示next()函数的作用,帮助开发者理解Express路由背后的实现细节。
2294

被折叠的 条评论
为什么被折叠?



