Express源码

Express

路由机制

一个简单的express使用例子如下:

1
2
3
4
5
6
7
8
9
10
const app = express();
app.get("/home", (req, res) => {
res.end("home");
});
app.get("/users", (req, res) => {
res.end("users");
});
app.listen(3000, () => {
console.log("listening");
});

分析app应该是一个构造函数的实例,身上应该有get,listen方法.而且我们可以定义多个路由,应该单独抽离出来管理这些路由。因此需要两个构造函数( Application 负责新增路由,启动服务,Router 管理路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//Application.js
const http = require("http");
const Router = require("./router/index");

function Application() {
this.router = new Router();
}

Application.prototype.listen = function () {
const _this = this;

function done(req, res) {
res.end(`Connot ${req.method} ${req.url}`);
}

let server = http.createServer(function (req, res) {
_this.router.handle(req, res, done);
});
server.listen.apply(server, arguments);
};

Application.prototype.get = function (path, handler) {
this.router.get(path, handler);
};

//Router.js
const url = require("url");

function Router() {
this.stack = [];
}
Router.prototype.get = function (path, handler) {
this.stack.push({
path,
method: "get",
handler,
});
};
Router.prototype.handle = function (req, res, out) {
const { pathname } = url.parse(req.url);
const reqMethod = req.method.toLowerCase();
for (let i = 0; i < this.stack.length; i++) {
const { path, method, handler } = this.stack[i];
if (path === pathname && method === reqMethod) {
return handler(req, res);
}
}
out(req, res);
};
module.exports = Router;

而有时候我们还会这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
app.get('/',function(req,res,next){
console.log(1);
next('wrong');
},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');
}).get('/',function(err,req,res,next){
res.end('catch: '+err);
});
app.listen(3000);

显然之前的架构处理这样的书写就会很臃肿,因此我们还需要单独再抽象出两个类,Layer (负责管理路由) 和 Route(负责管理一个路由下的所有函数)类。同时原来的Router类也需要进行改进。

Router.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//Router.js
// 由于不停地在Router原型上加方法,可以定义一个对象,在这个对象身上加,最后让Router返回的实例原型指向这个对象 Object.setPrototypeOf(router, proto)
let Layer = require('./layer');
let Route = require('./route');
function Router(){
this.stack = [];
}
Router.prototype.route = function(path){
// layer 和 route之间的关系
let route = new Route();
let layer = new Layer(path,route.dispatch.bind(route)); // 将路径存储到layer中
layer.route = route;
this.stack.push(layer);
return route;
}
// 创建 Route 将handler传入到route中
Router.prototype.get = function(path,...handlers){
let route = this.route(path); // 将路径存入layer中
route.get(handlers);// 将handler存入到route中
}
// 每当浏览器发送请求 处理请求
Router.prototype.handle = function (req, res, out) {
let { pathname } = url.parse(req.url);
let idx = 0;
let next = () => {
if (idx >= this.stack.length) return out(); // 匹配不到调用not found
let layer = this.stack[idx++];
if (layer.match(pathname)) {
// 如果路径匹配到了 调用route的dispatch方法
layer.handle_request(req, res, next);
} else {
next(); // 匹配不到找下一层
}
};
next();
};

module.exports = Router;

Route.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//Route.js
let Layer = require("./layer");
function Route() {
this.stack = [];
}
Route.prototype.get = function (handlers) {
handlers.forEach((handler) => {
let layer = new Layer("/", handler);
layer.method = "get";
this.stack.push(layer);
});
};
Route.prototype.dispatch = function (req, res, out) {
let idx = 0;
let next = () => {
if (idx >= this.stack.length) return out();
let layer = this.stack[idx++];
// 如果方法匹配打牌了
if (layer.method === req.method.toLowerCase()) {
layer.handle_request(req, res, next); // 内部匹配完了在出来
} else {
next();
}
};
next();
};
module.exports = Route;

Layer.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Layer.js
function Layer(path,handler){
this.path = path;
this.handler = handler;
}
// 匹配路由
Layer.prototype.match = function(pathname){
return this.path == pathname
}
Layer.prototype.handle_request = function(req,res,next){
// 调用dispatch方法
this.handler(req,res,next);
}
module.exports = Layer;

中间件原理

其实已经在路由中用到过,只不过Express中中间件又分为好几种:

  • 应用级别的中间件 app.use()
  • 路由中间件
1
2
3
4
5
6
7
app.get("/home", (req, res, next) => {
console.log(123);
next()
});
app.get("/home", (req, res, next) => {
res.end("home");
});
  • 错误处理中间件
1
2
3
app.use((req,res)=>{
res.status(200).send("这个是404 没有路由匹配到!")
})
  • 内置中间件
1
app.use('/static',express.static("public"));
  • 不过原理都差不多
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// use方法
Application.prototype.use = function () {
if (!this._router) {
this._router = new Router();
}
this._router.use(...arguments); // 交给路由处理
};
//Router.js
Router.prototype.use = function (path, handler) {
if (typeof handler !== "function") {
handler = path;
path = "/";
}
let layer = new Layer(path, handler);
layer.route = undefined;
this.stack.push(layer); // 将当前层放到stack中
};

Router.prototype.handle = function (req, res, out) {
let { pathname } = url.parse(req.url);
let idx = 0;
letnext = () => {
if (idx >= this.stack.length) returnout(); // 匹配不到调用not found
let layer = this.stack[idx++];
// 如果匹配到
if (layer.match(pathname)) {
if (!layer.route) {
// 中间件
layer.handle_request(req, res, next);
} else {
// 路由
if (layer.route.methods[req.method.toLowerCase()]) {
layer.handle_request(req, res, next);
} else {
next();
}
}
} else {
next(); // 匹配不到找下一层
}
};
};

params解析

当我们编写路由的时候会有如下写法

1
2
3
4
5
const express = require('express')
const app = express()
app.get('/user/:name/:id',(req,res)=>{
res.end('OK')
})

而当我们在浏览器输入路由是这样的

1
localhost:3000/user/hxh/0522

首先会将app.get定义的路由用正则表达式进行替换,将:xxx部分替换成正则

1
2
3
4
5
6
7
8
const path = "/user/:name/:id";
const paramsName = [];
const regStr = path.replace(/:(\w+)/g, function (matchChar, group1) {
paramsName.push(group1);
return ('(\\w+)')
});
console.log(regStr);
console.log(regStr);///user/(\w+)/(\w+)

这样就得到了一个匹配url的正则字符串,再用这个字符串生成正则去匹配我们的路由,就能提取出params,最后把提取出来的值跟变量对应起来就好。

1
2
3
4
5
6
7
8
const reg = new RegExp(regStr);
const url = "localhost:3000/user/hxh/0522";
const res = url.match(reg);
const params = {}
for (let i = 0; i < paramsName.length; i++) {
params[paramsName[i]] = res[i + 1];
}
console.log(params);

Express源码
https://jing-jiu.github.io/jing-jiu/2022/08/10/Node/Express/
作者
Jing-Jiu
发布于
2022年8月10日
许可协议