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里面存储的是各个配置项的信息。
这里看到express 构建了一个router对象,并且把相关的路由信息存储在stack里面。每一个layer里面装着regexp和相应的回调函数。(和我猜的差不多,哈哈哈)之后做的事,就是看源码,看如何存的了。既然我是通过app.get()方法配置的,直接找app.get()相应的源码就好了。
一步一步来
先是打开入口文件,这个很好找,是express.js.(源码1)
这里,通过mixin(app, proto, false)看出来,app的方法都是写在proto里面,也就是都写在application.js里面。直接在application.js里面搜索app.get,然而并没有搜到。。。之后我发现,原来express是这么做的。(源码2)
methods是各个方法名字组成的数组,get,post啥的。。找到了app.get()方法后,就看看它是如何生成上面的router对象的.很容易看到, 生成router对象应该就是在this.lazyrouter()这儿了。(源码3) 接下来很清晰,直接看Router函数就可以了。 router的构造函数在router文件夹下的index.js里面。(源码4)果然在这里看到了, 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)
这里可以看到,又出现了一个Route构造函数。route这个对象函数很重要,后面会详细说。 (这里先说答案,这个route里面会有一个stack数组,装着layer,里面的layer会放真正路由的回调函数)。再贴一次layer对象的数据结构
同时看Layer的构造函数,./router/layer.js(源码6) 这里看到layer里面regexp,path啥的属性都存起来了,但是回调函数还没有装起来.this.handle=fn. 按道理说hanldle里面应该就装着回调函数,但这里handle装的是route.dispatch.bind(route). 这个函数很重要,之后在有请求过来的时候,会先走到这个函数,通过这个函数再去调用我们配置路由的回调函数,之后会细说.再看看刚刚那个route对象,构造函数在./router/route.js里面(源码7)
可以看到这里面也有一个stack,这个stack里面同样装的是layer对象,不同的是这次的layer里面的handle是真实的路由回调函数了.在哪里装的??? 让我们回到(源码2)
route[method].apply(route, slice.call(arguments, 1));
这行代码,真正的执行代码在./router/route.js里面(源码8)
这里看到handles 就真的是路由回调函数,至于为什么是一个数组,因为可以为一个路由配置多个中间件,这个很好理解。之后的215行,会在new一个layer,这个layer的handle里面装的是真正的路由回调函数了。上面的过程就是通过app.get()等方法创建出layer的过程,我给他起名路由layer.先整理一下: 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.use()创建出来的layer,handle属性就是中间件函数,而不像上面那种路由的layer,handle装的是route.dispatch.bind(route),再在router属性的layer装真正的路由回调函数。同时这种layer,layer.router=undefined;整理第一步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(req,res,next);app.handle实现代码在application.js里面, 源码(2-2)
然后在通过router.handle(req,res,done);也就是在router文件夹下的index.js 核心代码了, 源码(2-3)
proto.handle代码太多,这里只贴出了核心代码。可以看到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方法。
总结
语文不太好 ,写的有点‘冗余’,希望感兴趣的同学认真看 ,能得到帮助