express 源碼閱讀之封裝Router

封裝Router

廢話不多說了祟剔,在封裝Router之前我們需要做些需求的準(zhǔn)備:
·app從字面量變?yōu)锳pplication類
·豐富HTTP請求方法
·封裝Router
·路徑一樣的路由整合為一組士八,引入Layer的概念
·增加路由控制说敏,支持next方法蚌讼,并增加錯誤捕獲功能
·執(zhí)行Router.handle的時候傳入out參數(shù)
1.先來個測試用例來看看我們要干些什么:

app.get('/',function(req,res,next){
    console.log(1);
    next();
},function(req,res,next){
    console.log(11);
    next();
}).get('/',function(req,res,next){
    console.log(2);
    next();
}).get('/',function(req,res,next){
    console.log(3);
    res.end('ok');
});
app.listen(3000);
控制臺打印出來的結(jié)果是:1,11,2,3

醬紫啊,那么那么我們來實現(xiàn)代碼吧
首先新建下文件目錄了
expross
|
|-- lib
| |
| |-- router
| | |
| | |-- index.js
| | |
| | |-- layer.js
| | |
| | |-- route.js
| | |
| |-- expross.js
| |
| |-- application.js
|
|-- test
| |
| |-- router.js
|
大概思維圖如下:


router.png

首先expross.js里面

const http=require("http");
const url=require("url");
const Application=require("./application");
function createApplication(){
    return new Application();
};
module.exports=createApplication;

createApplication函數(shù)內(nèi)部return了一個構(gòu)造函數(shù)Application通過module.exports導(dǎo)出這個構(gòu)造函數(shù)端礼,在router.js里面用express變量賦值require("../lib/express")來接收括饶,然后用變量app=express(),相當(dāng)于app是Application的實例了恕洲。

application.js里面代碼如下:

//實現(xiàn)Router和應(yīng)用的分離
const http=require("http");
const Router=require("./router");
const methods=require("methods");
const slice=Array.prototype.slice;
Application.prototype.lazyrouter=function(){
    if(!this._router){
        this._router=new Router();
    }
}
methods.forEach(function(method){
    Application.prototype[method]=function(path){
        this.lazyrouter();
        //這樣寫可以支持多個處理函數(shù)
        this._router[method].apply(this._router,slice.call(arguments));
        return this;//支持app.get().get().post().listen()連寫
    }
})
Application.prototype.listen=function(){
    let self=this;
    let server=http.createServer(function(req,res){
        function done(){
            res.end(`Cannot ${req.method} ${req.url}`)
        };
        self._router.handle(req,res,done);
    });
    server.listen(...arguments);
}

1.lazyrouter方法只會在首次調(diào)用時實例化Router對象塔橡,然后將其賦值給app._router字段

2.動態(tài)匹配方法,methods是一個數(shù)組里面存放著一系列的web請求方法例如:app.get霜第,app.post葛家,appp.put等首先通過調(diào)用this. lazyrouter實例化一個Router對象,然后調(diào)用this._router.get方法實例化一個Route對象和new Layer對象泌类,最后調(diào)用route[method]方法并傳入對應(yīng)的處理程序完成path與handle的關(guān)聯(lián)癞谒。Router和Route都各自維護(hù)了一個stack數(shù)組,該數(shù)組就是用來存放每一層layer。
3.監(jiān)聽一個端口為3000的服務(wù)弹砚,傳入一個回調(diào)函數(shù)双仍,里面有一個done方法和執(zhí)行Router原型對象上的handle方法并傳入3個參數(shù)請求(req)響應(yīng)(res)done回調(diào)函數(shù)。

router文件夾里的index.js里面代碼如下:

const Route=require("./route");
const url=require("url");
const Layer=require("./layer");
const methods=require("methods");
const slice=Array.prototype.slice;
function Router(){
    this.stack=[];
}
//創(chuàng)建一個Route實例桌吃,向當(dāng)前路由系統(tǒng)中添加一個層
Router.prototype.route=function(path){
    let route=new Route(path);
    layer=new Layer(path,route.dispath.bind(route));
    layer.route=route;
    this.stack.push(layer);
    return route;
}
methods.forEach(function(method){
    Router.prototype[method]=function(path){
        //創(chuàng)建路由實例朱沃,添加Router Layer
        let route=this.route(path);
        //調(diào)用路由方法 添加route Layer
        route[method].apply(route,slice.call(arguments,1));
    }
    return this;
})
Router.prototype.handle=function(req,res,out){
    let idx=0,self=this;
    let {pathname}=url.parse(req.url,true);
    function next(){//下個路由層
        if(idx>=self.stack.length){
            return out();
        }
        let layer=self.stack[idx++];
        //值匹配路徑router.stack
        if(layer.match(pathname)&&layer.route&&layer.route.handle_method(req.method.toLowerCase())){
            layer.handle_request(req,res,next);
        }else{
            next();
        }
    }
}

1.創(chuàng)建一個Router對象初始化Router.stack第一層是個空數(shù)組
2.創(chuàng)建一個Route實例,向當(dāng)前路由系統(tǒng)添加一層茅诱,Router Layer 路徑 處理函數(shù)(route.dispath) 有一個特殊的route屬性逗物,Route layer 路徑 處理函數(shù)(真正的業(yè)務(wù)代碼) 有一特殊的屬性method,把第一層的路由路徑(path)让簿、對應(yīng)方法(method)敬察、函數(shù)(handle)放入到Router.stack中
3.methods動態(tài)匹配方法,return this是方便鏈?zhǔn)秸{(diào)用
4.Router原型上handle方法有3個參數(shù)請求(req)尔当、響應(yīng)(res)莲祸、out(上面的done方法),內(nèi)部定義了索引idx=0,和保存了this椭迎,定義了個pathname變量解構(gòu)請求的url地址锐帜,定義了next函數(shù)主要作用是判斷是否繼續(xù)下個路由層,next內(nèi)部只匹配路徑Router.stack(判斷method是否匹配)畜号,如果匹配就執(zhí)行Route.layer當(dāng)前路由的第二層缴阎,否則就退出當(dāng)前路由匹配下一個路由層

router文件夾里的route.js里面代碼如下:

const Layer=require("./layer");
const methods=require("methods");
const slice=Array.prototype.slice;
function Route(path){
    this.path=path;
    this.stack=[];
    this.methods={};
}
Route.prototype.handle_method=function(method){
    method=method.toLowerCase();
    return this.methods[method];
}
methods.forEach(function(method){
    Route.prototype[method]=function(){
        let handlers=slice.call(arguments);
        this.methods[method]=true;
        for(let i=0;i<handlers.length;i++){
            let layer=new Layer("/",handlers[i]);
            layer.method=method;
            this.stack.push(layer);
        }
        return this;//方便鏈?zhǔn)秸{(diào)用
    }
})
Route.prototype.dispath=function(req,res,out){
    let idx=0,self=this;
    function next(){//執(zhí)行當(dāng)前路由中的下一個函數(shù)
        if(idx>=this.stack.length){
           return out();//route.dispath里的out剛好是Router的next
        }
        let layer=this.stack[idx++];
        if(layer.method==req.method.toLowerCase()){//匹配方法名是否一樣
            layer.handler_request(req,res,next);//為了以后擴(kuò)展
        }else{
            next();
        }
    }
    next();
}
module.exports=Route;

1.這里的Route.stack存的是當(dāng)前路由的第二次
2.Route原型上的dispath方法主要是判斷是否執(zhí)行當(dāng)前路由中的下個函數(shù),匹配的是方法名是否一樣简软。如果不匹配同樣是跳過當(dāng)前路由找下一層路由來匹配

router文件夾里的layer.js里面代碼如下:

function Layer(path,handler){
    this.path=path;
    this.handler=handler;
}
//判斷這一層和傳入的路徑是否匹配
Layer.prototype.match=function(path){
    return this.path=path;
}
Layer.prototype.handle_request=function(req,res,next){
    this.handler(req,res,next);
}

layer里主要保存了path和根據(jù)不同情況傳過來的handle函數(shù)蛮拔,原型上match方法是匹配當(dāng)前層和傳入的路徑是否匹配,而原型上handle_request是執(zhí)行傳過來的handle函數(shù)痹升,也是為了后期擴(kuò)展做準(zhǔn)備建炫。
好了,個人理解寫完了疼蛾,如有理解有誤的地方肛跌,熱烈歡迎指正。
敬請期待中間件(use)原理的解讀~~~嘻嘻

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末察郁,一起剝皮案震驚了整個濱河市衍慎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌皮钠,老刑警劉巖稳捆,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異麦轰,居然都是意外死亡眷柔,警方通過查閱死者的電腦和手機(jī)期虾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來驯嘱,“玉大人,你說我怎么就攤上這事喳坠【掀溃” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵壕鹉,是天一觀的道長剃幌。 經(jīng)常有香客問我,道長晾浴,這世上最難降的妖魔是什么负乡? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮脊凰,結(jié)果婚禮上抖棘,老公的妹妹穿的比我還像新娘。我一直安慰自己狸涌,他們只是感情好切省,可當(dāng)我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著帕胆,像睡著了一般朝捆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上懒豹,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天芙盘,我揣著相機(jī)與錄音,去河邊找鬼脸秽。 笑死儒老,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的豹储。 我是一名探鬼主播贷盲,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼剥扣!你這毒婦竟也來了巩剖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤钠怯,失蹤者是張志新(化名)和其女友劉穎佳魔,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晦炊,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡鞠鲜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年宁脊,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贤姆。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡榆苞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出霞捡,到底是詐尸還是另有隱情坐漏,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布碧信,位于F島的核電站赊琳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏砰碴。R本人自食惡果不足惜躏筏,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望呈枉。 院中可真熱鬧趁尼,春花似錦、人聲如沸碴卧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽住册。三九已至婶博,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間荧飞,已是汗流浹背凡人。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留叹阔,地道東北人挠轴。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像耳幢,于是被迫代替她去往敵國和親岸晦。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容