express 源码阅读之封装Router

封装Router

废话不多说了,在封装Router之前我们需要做些需求的准备:
·app从字面量变为Application类
·丰富HTTP请求方法
·封装Router
·路径一样的路由整合为一组,引入Layer的概念
·增加路由控制,支持next方法,并增加错误捕获功能
·执行Router.handle的时候传入out参数
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

酱紫啊,那么那么我们来实现代码吧
首先新建下文件目录了
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函数内部return了一个构造函数Application通过module.exports导出这个构造函数,在router.js里面用express变量赋值require("../lib/express")来接收,然后用变量app=express(),相当于app是Application的实例了。

application.js里面代码如下:

//实现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回调函数。

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当前路由的第二层,否则就退出当前路由匹配下一个路由层

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(){//执行当前路由中的下一个函数
        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)原理的解读~~~嘻嘻

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容