Express 实现原理
在 Express 对象中存储一系列请求方法注册的中间件队列,使用 use()、get() 等方法向队列中添加中间件对象,中间件对象包括路径和方法两个属性,当请求匹配到中间件中的请求路径前缀时就会执行该中间件方法
Next 方法是一个有名的立即执行函数,当第一次匹配时会立即执行,并将该函数传递给用户中间件的方法为参数,若用户调用 next 方法时会调用我们定义好的 next 方法执行下一个中间件方法
const http = require("http");
/**
* 校验参数,并返回一个中间件对象
* @param {...any} args 路径或中间件回调函数的可变参数
*/
function checkout(...args) {
const middleware = {};
if (typeof args[0] === "string") {
middleware.path = args[0];
middleware.queue = args.slice(1);
} else {
middleware.path = "/";
middleware.queue = args.slice(0);
}
return middleware;
}
/**
* 为http中的请求响应对象扩展方法
* @param {Request} req 请求对象
* @param {Response} res 响应对象
*/
function extendsMethod(req, res) {
res.send = (data) => {
res.setHeader("Content-type", "application/json");
res.end(JSON.stringify(data))
}
}
/**
* 根据请求响应对象返回正真要触发的路由列表
* @param {Request} req 请求对象
* @param {Response} res 响应对象
* @param {Routers} routers Express中的路由对象
*/
function getRealRouters(req, res, routers) {
let curRouters = []; //存储可能要触发路由列表
let realRouters = []; //要真实触发的路由列表
curRouters.push(...routers.use); //use中所有中间件都可能会被执行
curRouters.push(...routers[req.method.toLowerCase()]); //根据请求方法获取相应可能执行的中间件
//由于中间件队列变量是小写的所以要要将请求方法转小写
curRouters.forEach(item => {
if (req.url.indexOf(item.path) === 0) { //请求路径与中间件路径前缀匹配时
//前缀匹配是为了注册前缀中间件可被执行到
realRouters.push(...item.queue); //会将中间件中的方法加入到要触发的中间件中
}
})
return realRouters;
}
class Express {
constructor() {
this.routers = {
use: [], //存放use方法注册的中间件
get: [], //存放get方法注册的中间件
}
}
use(...args) {
const middleware = checkout(...args);
this.routers.use.push(middleware); //添加到use队列
}
get(...args) {
const middleware = checkout(...args);
this.routers.get.push(middleware); //添加到get队列
}
listen(...args) {
const server = http.createServer((req, res) => {
extendsMethod(req, res); //扩展req和res对象方法
const realRouters = getRealRouters(req, res, this.routers); //获取要执行的中间件队列
(function next() {
const middleware = realRouters.shift();
if (middleware) { //取出一个中间件方法,若不为null
middleware(req, res, next); //则执行该方法,并将req和res参数传递給该中间件
//最重要的就是将next方法本身传递给该中间件,若中间件内部调用next方法就又会执行该立即执行函数
}
})();//立即执行
});
server.listen(...args);
}
}
module.exports = () => {
//工厂函数,外部调用该方法即可返回一个Express实例
return new Express()
}

Comments NOTHING