從express原始碼中探析其路由機制

2022-01-11 05:46:57 字數 4333 閱讀 7981

在web開發中,乙個簡化的處理流程就是:客戶端發起請求,然後服務端進行處理,最後返回相關資料。不管對於哪種語言哪種框架,除去細節的處理,簡化後的模型都是一樣的。客戶端要發起請求,首先需要乙個標識,通常情況下是url,通過這個標識將請求傳送給服務端的某個具體處理程式,在這個過程中,請求可能會經歷一系列全域性處理,比如驗證、授權、url解析等,然後定位到某個處理程式進行業務處理,最後將生成的資料返回客戶端,客戶端將資料結合檢視模版呈現出合適的樣式。這個過程涉及到的模組比較多,本文只**前半部分,也就是從客戶端請求到伺服器端處理程式的過程,也可以叫做路由(其實就是如何定位到服務端處理程式的過程)。

為了作為對比,先簡單介紹一下asp.net webform和asp.net mvc是如何實現路由的。

asp.net webform比較特殊,由於是postback原理,定位處理程式的過程與mvc是不一樣的,對url的格式沒有嚴格的要求,url是直接對應後台檔案的,aspx中的伺服器表單預設是傳送到對應的aspx.cs檔案,它的定位是借助aspx頁面中的兩個隱藏域(__eventtarget和__eventargument)以及ipostbackeventhandler介面來實現的,通過這兩樣東西就可以直接定位到某個具體方法中,通常是某個控制項的某個事件處理程式。也就是說,在webform中,url僅能將請求定位到類中,要定位到真正的處理程式(方法)中,還需借助其他手段。

asp.net mvc與webform不同,url不再對應到後台檔案,那麼就必須通過某種手段來解析url,mvc中的後台處理程式稱為action,位於controller類中,為了使url能夠定位到action,mvc中的url有比較嚴格的格式要求,在url中需要包含controller和action,這樣後台就可以通過反射來動態生成controller例項然後呼叫對應的action。也就是說,在mvc中完全依靠url來實現後台處理程式的定位。

通過上面兩種方式的分析,我們發現url是不是指向檔案是無所謂的,但最終都是要根據其定位到某個具體的處理程式,也就是url到handler有個路由處理過程,只不過不同的框架有不同的處理方法。在express框架的使用過程中,隱隱約約感覺其路由過程如下圖所示:

到底是不是這樣呢?

我們知道,在使用express的時候,我們可以通過如下的方式來註冊路由:

(req,res));

methods.foreach(function

(method);

});

在這個方法中需要注意以下幾點:

要注意router與route的區別,router可以看作是乙個中介軟體容器,不僅可以存放路由中介軟體(route),還可以存放其他中介軟體,在lazyrouter方法中例項化router後會首先新增兩個中介軟體:query和init;而route僅僅是路由中介軟體,封裝了路由資訊。router和route都各自維護了乙個stack陣列,該陣列就是用來存放中介軟體和路由的。

router和route的stack是有差別的,這個差別主要體現在存放的layer(layer是用來封裝中介軟體的乙個資料結構)不太一樣,

由於router.stack中存放的中介軟體包括但不限於路由中介軟體,而只有路由中介軟體的執行才會依賴與請求method,因此router.stack裡的layer沒有method屬性,而是將其動態新增(layer的定義中沒有method欄位)到了route.stack的layer中;layer.route欄位也是動態新增的,可以通過該字段來判斷中介軟體是否是路由中介軟體。

//新增非路由中介軟體

proto.use = function

use(fn)

//add the middleware

debug('use %s %s', path, fn.name || '');

////例項化layer物件並進行初始化

var layer = new

layer(path, , fn);

//非路由中介軟體,該欄位賦值為undefined

layer.route =undefined;

this

.stack.push(layer);

}, this

);

return

this;};

//新增路由中介軟體

proto.route = function

(path), route.dispatch.bind(route));

//指向剛例項化的路由物件(非常重要),通過該字段將router和route關聯來起來

layer.route =route;

this

.stack.push(layer);

return

route;

};

對於路由中介軟體,路由容器中的stack(router.stack)裡面的layer通過route欄位指向了路由物件,那麼這樣一來,router.stack就和route.stack發生了關聯,關聯後的示意模型如下圖所示:

//新增非路由中介軟體

r.get(

function

(req,res,next)).get(

function

(req,res,next));

/* 還可以這麼寫,直接傳入多個function

r.get(function(req,res,next),function(req,res,next)

);*/

/*

或者這麼寫,直接傳入function陣列,可以是多維陣列
r.get([function(req,res,next),[function(req,res,next),function(req,res,next)]]

);*/

next();

在終端中輸入"cd expresstest"、"npm start"來啟動express,然後在瀏覽器中輸入"http://localhost:3000/test",我們發現在終端中輸出的內容與我們之前分析的完全一致,如下圖所示:

} var fns =flatten(slice.call(arguments, offset)); //從引數中取出處理函式列表

if (fns.length === 0)

//setup router

this

.lazyrouter(); //例項化router,並將其賦值給this._router

var router = this

._router;

fns.foreach(

function

//if (!fn || !fn.handle || !fn.set)

fn.mountpath =path;

fn.parent = this

;

// router.use(path, function

fn.handle(req, res,

function

(err) );

});// fn.emit('mount', this

); },

this

);

return

this;};

對於router還有一點需要說明一下,在其建構函式中有這麼一句**:router.__proto__ = proto;,通過router的__proto__屬性將其原型指向了proto物件,從而獲得了proto中定義的各個方法。

囉囉嗦嗦了這麼多,最後總結一下吧。

首先對於引言中的那個路由圖,基本上是對的,只不過express要面臨各種中介軟體的新增,所以將path與handler做了進一步的封裝(layer),然後將layer儲存在router.stack陣列中。

router可以看做是乙個存放了中介軟體的容器。對於裡面存放的路由中介軟體,router.stack中的layer有個route屬性指向了對應的路由物件,從而將router.stack與route.stack關聯起來,可以通過router遍歷到路由物件的各個處理程式。

從express原始碼中探析其路由機制

在web開發中,乙個簡化的處理流程就是 客戶端發起請求,然後服務端進行處理,最後返回相關資料。不管對於哪種語言哪種框架,除去細節的處理,簡化後的模型都是一樣的。客戶端要發起請求,首先需要乙個標識,通常情況下是url,通過這個標識將請求傳送給服務端的某個具體處理程式,在這個過程中,請求可能會經歷一系列...

express原始碼學習

express.js相當於過程式語言的main函式,是乙個入口,吐出express這個工廠函式。從 組織來看,我們會發現乙個有趣的現象。引入語句總是位於上方,主程式夾在中間,主程式用到的一些輔助函式放在後面。在node.js,我們會頻繁看到es5的一些新方法,這也node.js的特色之一。var c...

Node框架Express原始碼

主檔案 function url.parse req.url,true let requestmethod req.method.tolowercase let i 0 function next err let layer 取出來的路徑 可能是正則型別 if err else else else ...