封裝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
|
大概思維圖如下:
首先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)原理的解讀~~~嘻嘻