express框架的简单实现

express.png

上面这张图画了express的路由处理方式,这也是和koa最大的区别,栈上有一个个的路径以及处理方法,每个路径对应一个层,每个层里又会有处理方法或者路由,每个层里都有一个next用来传递,这相当于一个二维数组。

路由的实现

在使用express的时候,我们可以通过如下的方式来注册路由:

app.get("/",function(req,res){
    res.send("hello world"); 
}); 

实现路由中的"层":

首先要理解一个前面所说的层的概念,简单点,定义路由的每个get都是一层,layer.js模块主要是实现这个层,layer中有两个参数:path和处理函数

//layer.js
const pathToRegexp = require('path-to-regexp');
function Layer(path, handler) {
    //path:路径 handler:处理函数
    this.path = path;
    this.handler = handler;
    this.keys = [];
    this.regexp = pathToRegexp(this.path, this.keys);
}
//判断层和传入的路径是否匹配
Layer.prototype.match = function (path) {
    if (this.path == path) {
        return true;
    }
    if (!this.route) { 
        return path.startsWith(this.path + '/');
    }
    //如果这个Layer是一个路由的层
    if (this.route) {
        let matches = this.regexp.exec(path);
        if (matches) {
            this.params = {};
            for (let i = 1; i < matches.length; i++) {
                let name = this.keys[i - 1].name;
                let val = matches[i];
                this.params[name] = val;
            }
            return true;
        }
    }
    return false;
}
Layer.prototype.handle_request = function (req, res, next) {
    this.handler(req, res, next);
}
Layer.prototype.handle_error = function (err, req, res, next) {
    //错误处理函数
    if (this.handler.length != 4) {
        return next(err);
    }
    this.handler(err, req, res, next);
}
module.exports = Layer;

处理请求的路由

router中的index.js:根据文章开始画的图,router中应该有一个数组,也就是stack,当我们调用这里的route方法的时候会创建一个层(layer)并将path传进去,并调用route.dispatch交给route去处理,route主要用来创建一个route实例并挂到layer上并执行这个层,这里还定义了处理中间件和子路由的handle方法以及处理路径参数的process_params方法.

const Route = require('./route');
const Layer = require('./layer');
const url = require('url');
const methods = require('methods');
const init = require('./init');
const slice = Array.prototype.slice;
function Router() {
    function router(req, res, next) {
        router.handle(req, res, next);
    }
    Object.setPrototypeOf(router, proto);
    router.stack = [];
    router.paramCallbacks = {};//缓存路径参数和处理函数
    //加载内置中间件
    router.use(init);
    return router;
}
let proto = Object.create(null);
//创建一个Route实例并添加层
proto.route = function (path) {
    let route = new Route(path);
    let layer = new Layer(path, route.dispatch.bind(route));
    layer.route = route;
    this.stack.push(layer);

    return route;
}
proto.use = function (path, handler) {
    if (typeof handler != 'function') {
        handler = path;
        path = '/';
    }
    let layer = new Layer(path, handler);
    layer.route = undefined;//通过layer有没有route来判断是一个中间件函数还是一个路由
    this.stack.push(layer);
}
methods.forEach(function (method) {
    proto[method] = function (path) {
        let route = this.route(path);//向Router里添一层
        route[method].apply(route, slice.call(arguments, 1));
        return this;
    }
});
proto.param = function (name, handler) {
    if (!this.paramCallbacks[name]) {
        this.paramCallbacks[name] = [];
    }
    this.paramCallbacks[name].push(handler);
}
/**
 * 1.处理中间件
 * 2. 处理子路由容器 
 */
proto.handle = function (req, res, out) {
    let idx = 0, self = this, slashAdded = false, removed = '';
    let { pathname } = url.parse(req.url, true);
    function next(err) {
        if (removed.length > 0) {
            req.url = removed + req.url;
            removed = '';
        }
        if (idx >= self.stack.length) {
            return out(err);
        }
        let layer = self.stack[idx++];
        if (layer.match(pathname)) {
            if (!layer.route) { 
                removed = layer.path;
                req.url = req.url.slice(removed.length);
                if (err) {
                    layer.handle_error(err, req, res, next);
                } else {
                    layer.handle_request(req, res, next);
                }
            } else {
                if (layer.route && layer.route.handle_method(req.method)) {
                    req.params = layer.params;
                    self.process_params(layer, req, res, () => {
                        layer.handle_request(req, res, next);
                    });
                } else {
                    next(err);
                }
            }
        } else {
            next(err);
        }
    }
    next();
}
proto.process_params = function (layer, req, res, out) {
    //路径参数方法
    let keys = layer.keys;
    let self = this;
    //用来处理路径参数
    let paramIndex = 0 /**key索引**/, key/**key对象**/, name/**key的值**/, val, callbacks, callback;
    //调用一次param意味着处理一个路径参数
    function param() {
        if (paramIndex >= keys.length) {
            return out();
        }
        key = keys[paramIndex++];//先取出当前的key
        name = key.name;
        val = layer.params[name];
        callbacks = self.paramCallbacks[name];// 取出等待执行的回调函数数组
        if (!val || !callbacks) {//如果当前的key没有值,或者没有对应的回调就直接处理下一个key
            return param();
        }
        execCallback();
    }
    let callbackIndex = 0;
    function execCallback() {
        callback = callbacks[callbackIndex++];
        if (!callback) {
            return param();
        }
        callback(req, res, execCallback, val, name);
    }
    param();
}
module.exports = Router;

router中的route.js:一个路由类,每当get的时候创建一个路由对象,这里的dispatch是主要的业务处理方法

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.dispatch = function (req, res, out) {
    let idx = 0, self = this;
    function next(err) {
        if (err) {
            //错误处理,如果出错就跳过当前路由
            return out(err);
        }
        if (idx >= self.stack.length) {
            return out();
        }
        //取出一层
        let layer = self.stack[idx++];
        if (layer.method == req.method.toLowerCase()) {
            layer.handle_request(req, res, next);
        } else {
            next();
        }
    }
    next();
}
module.exports = Route;    

application.js

这个返回一个Applition类,这里先定义了一个lazyrouter方法,这个方法主要是用来懒加载,如果不调get的话就没有初始化的必要所以需要个懒加载;还有listen方法主要用来创建服务器并监听,这里面的done主要是处理没有路由规则和请求匹配。

const http = require('http');
const Router = require('./router');
const methods = require('methods');
function Application(){
    this.settings = {};//保存参数
    this.engins = {};//保存文件扩展名和渲染函数
}
Application.prototype.lazyrouter = function () {
    if (!this._router) {
        this._router = new Router();
    }
}
Application.prototype.param = function (name, handler) {
    this.lazyrouter();
    this._router.param.apply(this._router, arguments);
}
Application.prototype.set = function (key, val) {
    //设置,传一个参数表示获取
    if (arguments.length == 1) {
        return this.settings[key];
    }
    this.settings[key] = val;
}
//定义文件渲染方法
Application.prototype.engine = function (ext, render) {
    let extension = ext[0] == '.' ? ext : '.' + ext;
    this.engines[extension] = render;
}


methods.forEach(function (method) {
    Application.prototype[method] = function () {
        if (method == 'get' && arguments.length == 1) {
            return this.set(arguments[0]);
        }
        this.lazyrouter();
        //此处是为了支持多个处理函数
        this._router[method].apply(this._router, slice.call(arguments));
        return this;
    }
});
Application.prototype.route = function (path) {
    this.lazyrouter();
    //创建一个路由,然后创建一个layer ,layer.route = route.this.stack.push(layer)
    this._router.route(path);
}
//添加中间件,而中间件和普通的路由都是放在一个数组中的,放在this._router.stack
Application.prototype.use = function () {
    this.lazyrouter();
    this._router.use.apply(this._router, arguments);
}
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);
}
module.exports = Application;

这里介绍几个主要的模块,全部代码以及测试用例请见Github

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

推荐阅读更多精彩内容