封裝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
|
大概思維圖如下:

首先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)原理的解讀~~~嘻嘻