express框架的簡單實(shí)現(xiàn)

express.png

上面這張圖畫了express的路由處理方式,這也是和koa最大的區(qū)別,棧上有一個個的路徑以及處理方法,每個路徑對應(yīng)一個層,每個層里又會有處理方法或者路由,每個層里都有一個next用來傳遞,這相當(dāng)于一個二維數(shù)組。

路由的實(shí)現(xiàn)

在使用express的時候,我們可以通過如下的方式來注冊路由:

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

實(shí)現(xiàn)路由中的"層":

首先要理解一個前面所說的層的概念,簡單點(diǎn),定義路由的每個get都是一層,layer.js模塊主要是實(shí)現(xiàn)這個層,layer中有兩個參數(shù):path和處理函數(shù)

//layer.js
const pathToRegexp = require('path-to-regexp');
function Layer(path, handler) {
    //path:路徑 handler:處理函數(shù)
    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) {
    //錯誤處理函數(shù)
    if (this.handler.length != 4) {
        return next(err);
    }
    this.handler(err, req, res, next);
}
module.exports = Layer;

處理請求的路由

router中的index.js:根據(jù)文章開始畫的圖,router中應(yīng)該有一個數(shù)組,也就是stack,當(dāng)我們調(diào)用這里的route方法的時候會創(chuàng)建一個層(layer)并將path傳進(jìn)去,并調(diào)用route.dispatch交給route去處理,route主要用來創(chuàng)建一個route實(shí)例并掛到layer上并執(zhí)行這個層,這里還定義了處理中間件和子路由的handle方法以及處理路徑參數(shù)的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 = {};//緩存路徑參數(shù)和處理函數(shù)
    //加載內(nèi)置中間件
    router.use(init);
    return router;
}
let proto = Object.create(null);
//創(chuàng)建一個Route實(shí)例并添加層
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來判斷是一個中間件函數(shù)還是一個路由
    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) {
    //路徑參數(shù)方法
    let keys = layer.keys;
    let self = this;
    //用來處理路徑參數(shù)
    let paramIndex = 0 /**key索引**/, key/**key對象**/, name/**key的值**/, val, callbacks, callback;
    //調(diào)用一次param意味著處理一個路徑參數(shù)
    function param() {
        if (paramIndex >= keys.length) {
            return out();
        }
        key = keys[paramIndex++];//先取出當(dāng)前的key
        name = key.name;
        val = layer.params[name];
        callbacks = self.paramCallbacks[name];// 取出等待執(zhí)行的回調(diào)函數(shù)數(shù)組
        if (!val || !callbacks) {//如果當(dāng)前的key沒有值,或者沒有對應(yīng)的回調(diào)就直接處理下一個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:一個路由類,每當(dāng)get的時候創(chuàng)建一個路由對象,這里的dispatch是主要的業(yè)務(wù)處理方法

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) {
            //錯誤處理,如果出錯就跳過當(dāng)前路由
            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方法,這個方法主要是用來懶加載,如果不調(diào)get的話就沒有初始化的必要所以需要個懶加載;還有l(wèi)isten方法主要用來創(chuàng)建服務(wù)器并監(jiān)聽,這里面的done主要是處理沒有路由規(guī)則和請求匹配。

const http = require('http');
const Router = require('./router');
const methods = require('methods');
function Application(){
    this.settings = {};//保存參數(shù)
    this.engins = {};//保存文件擴(kuò)展名和渲染函數(shù)
}
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) {
    //設(shè)置,傳一個參數(shù)表示獲取
    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();
        //此處是為了支持多個處理函數(shù)
        this._router[method].apply(this._router, slice.call(arguments));
        return this;
    }
});
Application.prototype.route = function (path) {
    this.lazyrouter();
    //創(chuàng)建一個路由,然后創(chuàng)建一個layer ,layer.route = route.this.stack.push(layer)
    this._router.route(path);
}
//添加中間件,而中間件和普通的路由都是放在一個數(shù)組中的,放在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() {
            //如果沒有任何路由規(guī)則匹配的話會走此函數(shù)
            res.end(`Cannot ${req.method} ${req.url}`);
        }
        self._router.handle(req, res, done);
    });
    server.listen(...arguments);
}
module.exports = Application;

這里介紹幾個主要的模塊,全部代碼以及測試用例請見Github

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容