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 route 原始碼解析](https://i.iter01.com/images/97dca64ae3927abaffd02ed21d57b6631e5a3ca492fe41982ddc3a1dc289f55b.png)
之後做的事,就是看原始碼,看如何存的了。既然我是通過app.get()方法配置的,直接找app.get()相應的原始碼就好了。
一步一步來
先是開啟入口檔案,這個很好找,是express.js.(原始碼1)
![Express route 原始碼解析](https://i.iter01.com/images/e1ac7c612a6cca64bc221886eade7e98cbdc225b867dfe43de8512c4843b30c5.png)
這裡,通過mixin(app, proto, false)看出來,app的方法都是寫在proto裡面,也就是都寫在application.js裡面。直接在application.js裡面搜尋app.get,然而並沒有搜到。。。之後我發現,原來express是這麼做的。(原始碼2)
![Express route 原始碼解析](https://i.iter01.com/images/15dfeea46093b960774e27c68e9a829b311af27b834c216d5a6c954f40e1c706.png)
![Express route 原始碼解析](https://i.iter01.com/images/04877b42c430c3ef821559ab0769eeeff0d23fa981ec5e6627fac2a7c001b305.png)
![Express route 原始碼解析](https://i.iter01.com/images/fa71e310d1ece58b629c67aa7a509784b9f24e3b01208dc830036f624a14498a.png)
果然在這裡看到了, 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)
![Express route 原始碼解析](https://i.iter01.com/images/4514392dd7a73e01f4854e9a7f0c63146e34d18460dbba64ac32872764391044.png)
再貼一次layer物件的資料結構
![Express route 原始碼解析](https://i.iter01.com/images/ea25fbe86446ab45067b59ab4fda769200e2dd12746caa0cf880ace248e17497.png)
![Express route 原始碼解析](https://i.iter01.com/images/c0d7de3e939e2b4db41792d3a52597f2bf3b39734533b7beb8843fe9200b7259.png)
再看看剛剛那個route物件,建構函式在./router/route.js裡面(原始碼7)
![Express route 原始碼解析](https://i.iter01.com/images/1bf47b8d495f8ba9dc78b1eff45c1fca7fe3172933fb1a5239a8ceeac896b4a5.png)
在哪裡裝的??? 讓我們回到(原始碼2)
route[method].apply(route, slice.call(arguments, 1));
這行程式碼,真正的執行程式碼在./router/route.js裡面(原始碼8)
![Express route 原始碼解析](https://i.iter01.com/images/b92240bf24a436547799de359cb23e5aa69b6f6b169bbd0c59ebb27df3b84505.png)
先整理一下: app._router,也就是最大的那個物件,裡面會有一個stack陣列,裝著通過app.get('/','callback')等建立出的layer,這種layer(有路由的,就是通過app.get()等方法建立出來的),會有一個handle,這個handle==route.dispatch.bind(route).同時會有一個route,裡面同樣會有一個stack,這裡面的layer的handle撞著真實的路由回撥函式。如下圖
![Express route 原始碼解析](https://i.iter01.com/images/d62f4160688db1650a8668ac5649c242680828fc93455af121f41d8ced45d5bc.png)
接下來再看通過app.use()建立的中介軟體.和上面差不過,直接看程式碼,再./router/index.js(原始碼9)
![Express route 原始碼解析](https://i.iter01.com/images/e00ba19f04e0490d4b64fd86de77dd54b84916cf17b3492ea00d2bdb46e9124a.png)
整理第一步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)
![Express route 原始碼解析](https://i.iter01.com/images/e1ac7c612a6cca64bc221886eade7e98cbdc225b867dfe43de8512c4843b30c5.png)
app.handle實現程式碼在application.js裡面, 原始碼(2-2)
![Express route 原始碼解析](https://i.iter01.com/images/ea9c16bddd33033ea6ff79e3d41d58dae467af491a459b6068fdbb1e8fd9cdc5.png)
然後在通過router.handle(req,res,done);也就是在router資料夾下的index.js 核心程式碼了, 原始碼(2-3)
![Express route 原始碼解析](https://i.iter01.com/images/1d52910f49dc164b1a75131d57509bea03ed7f5186869071a4558e72150f634d.png)
可以看到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)
![Express route 原始碼解析](https://i.iter01.com/images/c028830a182b876fe33d2eb7a1d7117dc634eaed1ec98911dc9cd195e95bb3f6.png)
這裡要小心一點了 ,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)
![Express route 原始碼解析](https://i.iter01.com/images/2f42aef4ec6fe2d29a08f8b48669f23c3b07a79e6ab9d4cb637a396d0e8cdf5f.png)
這裡可以看到,這個物件也就是帶路由得那種layer,裡面的lauer.route物件,如下圖
![Express route 原始碼解析](https://i.iter01.com/images/d62f4160688db1650a8668ac5649c242680828fc93455af121f41d8ced45d5bc.png)
這裡可以看到,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方法。
總結
語文不太好 ,寫的有點‘冗餘’,希望感興趣的同學認真看 ,能得到幫助