Express route 源碼解析

express route 源碼解析

使用express,對route部分比較好奇,花了兩天時間看了下源碼,今天分享出來,但願能夠給對express route實現一樣好奇的人帶來一些幫助.express

先寫幾行代碼

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和相應的回調函數看成一個對象存儲起來,最後存儲到一個數組中。app

2.當有請求過來,遍歷數組,拿出url和每個路由的regexp匹配,匹配成功了執行相應的回調函數。函數

3.next:當第2步完成後,若是調用next,執行了相應的回調函數後不停,繼續向下遍歷數組,執行相應的邏輯。若是沒有調用next, 就直接停了。post

第一步,express是怎麼存儲各個路由配置項,又是以什麼結構存儲的

console.log(app),找到了我想要的東西,app._router裏面存儲的是各個配置項的信息。this

這裏看到express 構建了一個router對象,而且把相關的路由信息存儲在stack裏面。每個layer裏面裝着regexp和相應的回調函數。(和我猜的差很少,哈哈哈)

以後作的事,就是看源碼,看如何存的了。既然我是經過app.get()方法配置的,直接找app.get()相應的源碼就行了。url

一步一步來

先是打開入口文件,這個很好找,是express.js.(源碼1)spa

這裏,經過mixin(app, proto, false)看出來,app的方法都是寫在proto裏面,也就是都寫在application.js裏面。直接在application.js裏面搜索app.get,然而並無搜到。。。以後我發現,原來express是這麼作的。(源碼2)3d

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方法。

總結

語文不太好 ,寫的有點‘冗餘’,但願感興趣的同窗認真看 ,能獲得幫助

相關文章
相關標籤/搜索