廢話很少說了,在封裝Router以前咱們須要作些需求的準備:
·app從字面量變爲Application類
·豐富HTTP請求方法
·封裝Router
·路徑同樣的路由整合爲一組,引入Layer的概念
·增長路由控制,支持next方法,並增長錯誤捕獲功能
·執行Router.handle的時候傳入out參數
1.先來個測試用例來看看咱們要幹些什麼:
web
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
複製代碼
醬紫啊,那麼那麼咱們來實現代碼吧
首先新建下文件目錄了
expross
|
|-- lib
| |
| |-- router
| | |
| | |-- index.js
| | |
| | |-- layer.js
| | |
| | |-- route.js
| | |
| |-- expross.js
| |
| |-- application.js
|
|-- test
| |
| |-- router.js
|
大概思惟圖以下:
express
首先expross.js裏面
數組
const http=require("http");
const url=require("url");
const Application=require("./application");
function createApplication(){
return new Application();
};
module.exports=createApplication;
複製代碼
createApplication函數內部return了一個構造函數Application經過module.exports導出這個構造函數,在router.js裏面用express變量賦值require("../lib/express")來接收,而後用變量app=express(),至關於app是Application的實例了。
bash
application.js裏面代碼以下:
app
//實現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();
//這樣寫能夠支持多個處理函數
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.動態匹配方法,methods是一個數組裏面存放着一系列的web請求方法例如:app.get,app.post,appp.put等首先經過調用this. lazyrouter實例化一個Router對象,而後調用this._router.get方法實例化一個Route對象和new Layer對象,最後調用route[method]方法並傳入對應的處理程序完成path與handle的關聯。Router和Route都各自維護了一個stack數組,該數組就是用來存放每一層layer。
3.監聽一個端口爲3000的服務,傳入一個回調函數,裏面有一個done方法和執行Router原型對象上的handle方法並傳入3個參數請求(req)響應(res)done回調函數。
post
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=[];
}
//建立一個Route實例,向當前路由系統中添加一個層
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){
//建立路由實例,添加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.建立一個Router對象初始化Router.stack第一層是個空數組
2.建立一個Route實例,向當前路由系統添加一層,Router Layer 路徑 處理函數(route.dispath) 有一個特殊的route屬性,Route layer 路徑 處理函數(真正的業務代碼) 有一特殊的屬性method,把第一層的路由路徑(path)、對應方法(method)、函數(handle)放入到Router.stack中
3.methods動態匹配方法,return this是方便鏈式調用
4.Router原型上handle方法有3個參數請求(req)、響應(res)、out(上面的done方法),內部定義了索引idx=0,和保存了this,定義了個pathname變量解構請求的url地址,定義了next函數主要做用是判斷是否繼續下個路由層,next內部只匹配路徑Router.stack(判斷method是否匹配),若是匹配就執行Route.layer當前路由的第二層,不然就退出當前路由匹配下一個路由層
ui
router文件夾裏的route.js裏面代碼以下:
this
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(){//執行當前路由中的下一個函數
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方法主要是判斷是否執行當前路由中的下個函數,匹配的是方法名是否同樣。若是不匹配一樣是跳過當前路由找下一層路由來匹配
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和根據不一樣狀況傳過來的handle函數,原型上match方法是匹配當前層和傳入的路徑是否匹配,而原型上handle_request是執行傳過來的handle函數,也是爲了後期擴展作準備。 好了,我的理解寫完了,若有理解有誤的地方,熱烈歡迎指正。 敬請期待中間件(use)原理的解讀~~~嘻嘻