仿Express源码实现(-)

310 阅读6分钟

Express是什么

Express 是一个基于 Node.js 封装的上层服务框架,它提供了更简洁的 API 更实用的新功能。它通过中间件和路由让程序的组织管理变的更加容易;它提供了丰富的 HTTP 工具;它让动态视图的渲染变的更加容易;本文主要根据源码,实现核心的中间件,路由功能

中间件

中间件就是处理HTTP请求的函数,用来完成各种特定的任务 比如检查用户是否登录、检测用户是否有权限访问等,它的特点是:

  • 一个中间件处理完请求和响应可以把相应数据再传递给下一个中间件
  • 回调函数的next参数,表示接受其他中间件的调用,函数体中的next(),表示将请求数据传递给下一个中间件
  • 可以根据路径来区分进行返回执行不同的中间件
    中间件

中间件用法

const express = require('./express');
const app = express();
//使用use来定义一个中间件 next也是一个函数,调用它则意味着当前的中间件执行完毕,可以继续向下执行别的中间件了
app.use(function (req, res, next) {
    res.setHeader('Content-Type', 'text/html;charset=utf8');
    console.log('没有路径的中间件');
    //调用next的时候如果传一个任意参数就表示此函数发生了错误,
    //然后express就会跳过后面所有的中间件和路由
    //交给错误处理中间件来处理
    next('我错了');
});
app.use('/water', function (req, res, next) {
    console.log('过滤杂质');
    next();
});
//错误处理中间件有四个参数
app.use('/hello', function (err, req, res, next) {
    res.end('hello ' + err);
});
app.use('/water', function (err, req, res, next) {
    //res.end('water1 ' + err);
    next(err);
});
app.use('/water', function (err, req, res, next) {
    res.end('water2 ' + err);
});
app.listen(8080, function () {
    console.log('server started at 8080');
});

路由

相比中间件,Routing 显然更好。与中间价类似,路由对请求处理函数进行了拆分。不同的是,路由根据请求的 URL 和 HTTP 方法来决定处理方式的。

  • 路由的用法
const express = require('express');
const app = express();
app.route('/user').get(function (req, res) {
    res.end('get');
}).post(function (req, res) {
    res.end('postget');
}).put(function (req, res) {
    res.end('put');
}).delete(function (req, res) {
    res.end('delete');
})
app.listen(3000);

路由和中间件

  • 通过用法可以看出,路由和中间件在使用上,都是通过注册地址,进行相关的操作
  • 源码中,路由和中间件在具体实现上,都是有相同的操作,都有如下关系
  • express实例 定义express实例,并导出,在这里主要是创建了一个application实例
const http = require('http');
const url = require('url');
const Router = require('./router');
const Application = require('./application');
function createApplicaton() {
    return new Application();
}
createApplicaton.Router = Router;
module.exports = createApplicaton;
  • 路由和中间件的方法都定义在~application~方法中
//实现Router 和应用的分离
const Router = require('./router');
const http = require('http');
const methods = require('methods');//['get','post']
const slice = Array.prototype.slice;
// methods = http.METHODS
function Application() {
    this.settings = {};//用来保存参数
    this.engines = {};//用来保存文件扩展名和渲染函数的函数
}
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}`);
        }
        //如果路由系统无法处理,也就是没有一条路由规则跟请求匹配,是会把请求交给done
        self._router.handle(req, res, done);
    });
    server.listen(...arguments);
}
module.exports = Application;
  • 路由和中间件的地址每一层通过注册layer 层,在layer层中放入route对象具体实现如下
  • layer实现
const pathToRegexp = require('path-to-regexp');
function Layer(path, handler) {
    this.path = path;
    this.handler = handler;
    this.keys = [];
    // this.path =/user/:uid   this.keys = [{name:'uid'}];
    this.regexp = pathToRegexp(this.path, this.keys);
}
//判断这一层和传入的路径是否匹配
Layer.prototype.match = function (path) {
    if (this.path == path) {
        return true;
    }
    if (!this.route) {//这一层是一个中间件层  /user/2
        // this.path = /user  
        return path.startsWith(this.path + '/');
    }
    //如果这个Layer是一个路由的 Layer
    if (this.route) {
        let matches = this.regexp.exec(path); //   /user/1
   
        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;
  • route实现
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();//route.dispath里的out刚好是Router的next
        }
        let layer = self.stack[idx++];
        if (layer.method == req.method.toLowerCase()) {
            layer.handle_request(req, res, next);
        } else {
            next();
        }
    }
    next();
}
module.exports = Route;
  • 通过router 对象导致实现如下
const Route = require('./route');
const Layer = require('./layer');
const url = require('url');
const methods = require('methods');
const init = require('../middle/init');
const slice = Array.prototype.slice;
//let r = Router()
//let r = new Router();
function Router() {
    function router(req, res, next) {
        router.handle(req, res, next);
    }
    Object.setPrototypeOf(router, proto);
    router.stack = [];
    //声明一个对象,用来缓存路径参数名它对应的回调函数数组
    router.paramCallbacks = {};
    //在router一加载就会加载内置 中间件
    // query
    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] = [];
    }
    // {uid:[handle1,hander2]}
    this.paramCallbacks[name].push(handler);
}
/**
 * 1.处理中间件
 * 2. 处理子路由容器 
 */
proto.handle = function (req, res, out) {
    //slashAdded是否添加过/ removed指的是被移除的字符串
    let idx = 0, self = this, slashAdded = false, removed = '';
    // /user/2
    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++];
        //在此匹配路径 params   正则+url= req.params
        if (layer.match(pathname)) {// layer.params
            if (!layer.route) { //这一层是中间件层//  /user/2
                removed = layer.path;//  /user
                req.url = req.url.slice(removed.length);// /2
                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)) {
                    //把layer的parmas属性拷贝给req.params
                    req.params = layer.params;
                    self.process_params(layer, req, res, () => {
                        layer.handle_request(req, res, next);
                    });
                } else {
                    next(err);
                }
            }
        } else {
            next(err);
        }
    }
    next();
}
//用来处理param参数,处理完成后会走out函数
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;// uid
        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();//如果此key已经没有回调等待执行,则意味本key处理完毕,该执行一下key
        }
        callback(req, res, execCallback, val, name);
    }
    param();
}
module.exports = Router;
  • 实现结构如下
/**
 * Router
 *   stack
 *      layer
 *         path route
 *                 method handler
 * Layer
 * Router Layer 路径 处理函数(route.dispatch) 有一个特殊的route属性
 * Route  layer  路径 处理函数(真正的业务代码)  有一特殊的属性method
 */

由于本人文笔有限,能力有限,对于具体的细节未实现之处,待后续修改补充