express 源碼閱讀之封裝Router

封裝Router

廢話不多說了,在封裝Router之前我們需要做些需求的準備:
·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);
控制臺打印出來的結果是: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ù)內部return了一個構造函數(shù)Application通過module.exports導出這個構造函數(shù),在router.js里面用express變量賦值require("../lib/express")來接收,然后用變量app=express(),相當于app是Application的實例了。

application.js里面代碼如下:

//實現(xiàn)Router和應用的分離
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方法只會在首次調用時實例化Router對象,然后將其賦值給app._router字段

2.動態(tài)匹配方法,methods是一個數(shù)組里面存放著一系列的web請求方法例如:app.get,app.post,appp.put等首先通過調用this. lazyrouter實例化一個Router對象,然后調用this._router.get方法實例化一個Route對象和new Layer對象,最后調用route[method]方法并傳入對應的處理程序完成path與handle的關聯(lián)。Router和Route都各自維護了一個stack數(shù)組,該數(shù)組就是用來存放每一層layer。
3.監(jiān)聽一個端口為3000的服務,傳入一個回調函數(shù),里面有一個done方法和執(zhí)行Router原型對象上的handle方法并傳入3個參數(shù)請求(req)響應(res)done回調函數(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實例,向當前路由系統(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);
        //調用路由方法 添加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實例,向當前路由系統(tǒng)添加一層,Router Layer 路徑 處理函數(shù)(route.dispath) 有一個特殊的route屬性,Route layer 路徑 處理函數(shù)(真正的業(yè)務代碼) 有一特殊的屬性method,把第一層的路由路徑(path)、對應方法(method)、函數(shù)(handle)放入到Router.stack中
3.methods動態(tài)匹配方法,return this是方便鏈式調用
4.Router原型上handle方法有3個參數(shù)請求(req)、響應(res)、out(上面的done方法),內部定義了索引idx=0,和保存了this,定義了個pathname變量解構請求的url地址,定義了next函數(shù)主要作用是判斷是否繼續(xù)下個路由層,next內部只匹配路徑Router.stack(判斷method是否匹配),如果匹配就執(zhí)行Route.layer當前路由的第二層,否則就退出當前路由匹配下一個路由層

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;//方便鏈式調用
    }
})
Route.prototype.dispath=function(req,res,out){
    let idx=0,self=this;
    function next(){//執(zhí)行當前路由中的下一個函數(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);//為了以后擴展
        }else{
            next();
        }
    }
    next();
}
module.exports=Route;

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

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方法是匹配當前層和傳入的路徑是否匹配,而原型上handle_request是執(zhí)行傳過來的handle函數(shù),也是為了后期擴展做準備。
好了,個人理解寫完了,如有理解有誤的地方,熱烈歡迎指正。
敬請期待中間件(use)原理的解讀~~~嘻嘻

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容