Http 搭建服务 vs Express 搭建服务
Http 搭建服务
通过 http 我们可以快速搭建起一个服务器。
ini
const http = require("http");
const routes = [
{
url: "/",
handle(req, res) {
res.writeHead(200, {
"Content-type": "application/json",
});
res.end(
JSON.stringify({
code: 1000,
message: "成功",
})
);
},
},
{
url: "/list",
handle(req, res) {
res.writeHead(200, {
"Content-type": "application/json",
});
res.end(
JSON.stringify({
code: 1000,
message: "成功",
data: [
{ name: "张三", age: 12 },
{ name: "李四", age: 15 },
],
})
);
},
},
];
const server = http.createServer((req, res) => {
const url = req.url;
const targetRoute = routes.find((item) => item.url === url);
if (targetRoute) {
targetRoute.handle(req, res);
} else {
res.end("NOT FOUND " + req.method + " " + url);
}
});
server.listen(3000);
Express 搭建服务
有如下好处
-
- 内置路由管理。
-
- Express 支持多种中间件,如
body-parser
、morgan
、cors
等,可以轻松地处理请求和响应。扩展性和可维护性更好。
- Express 支持多种中间件,如
-
- 提供丰富的 HTTP 实用工具,如
res.json()
、res.send()
、res.redirect()
等,简化了响应处理。使得开发更加方便。
- 提供丰富的 HTTP 实用工具,如
javascript
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.json({
code: 1000,
message: "成功"
});
});
app.get('/list', (req, res) => {
res.json({
code: 1000,
message: "成功",
data: [
{ name: "张三", age: 12 },
{ name: "李四", age: 15 }
]
});
});
// 日志记录中间件
app.use((req, res, next) => {
console.log('Request received for path:', req.url);
next();
});
// 身份验证中间件
app.use((req, res, next) => {
if (req.headers.authorization === 'Bearer valid-token') {
next();
} else {
res.status(401).send('Unauthorized');
}
});
// 错误处理中间件
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send("Internal Server Error");
});;
app.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});
Express 模块总览
来梳理下 Express 各个模块,本章不会涉及代码实现细节,会先把 Express 初始化过、响应请求的相关流程梳理清楚。下章会针对具体的代码实现。来看下源码各个模块都有哪些功能。
css
express/
├── lib/
│ ├── application.js // app 方法挂载例如: app.init()、app.use()、app.handle()等
│ ├── express.js // 入口 app.init() 初始化
│ ├── request.js // 请求体属性拦截,增加 req.query、req.path、req.host、req.ip 等
│ ├── response.js // 响应体方法挂载, res.json()、res.status()、res.send()、res.header 等
│ ├── utils.js
│ └── view.js // 模板引擎相关
内置路由
首先来看下一张总的关系图,其中 Router、Layer、Route 都是路由系统中重要的概念。
【图1】
给出相关代码
javascript
var app = module.exports = express()
app.get('/', function middleware1(req, res, next) {
console.log('Middleware 1');
next();
}, function helloWorld(req, res){
res.send('Hello World');
});
app.get('/foo', function log(req, res) {
const params = req.params;
const host = req.host;
const query = req.query;
console.log(params, host, query);
res.end('foo')
})
/* istanbul ignore next */
if (!module.parent) {
app.listen(3000);
console.log('Express started on port 3000');
}
接下来目标就是把上面这个例子讲明白,就对 Express 中内置路由有大概的了解了。
Router 对象
Router 实例结构如下
router.stack 对应【图1】外层的 stack 部分。当我们每次调用 app.get() 时候就会往 router.stack 推送一个 Layer 实例对象。回调函数可以支持多个,对应的就是 middle1Fn、middle2Fn。
scss
app[method](path, middle1Fn, middle2Fn) // 这里的 method 常见有 get 请求、post 请求
总结
Router 对象就是维护所有路由的容器。具体路由信息会存放在 router.stack 中。
Layer 对象
Layer 实例结构如下
前面说过,router.stack 中存放的就是一个个 Layer 实例对象。layer 对象上有以下几个重要的属性。
- matchers 维护了匹配规则,判断 layer 对象是否匹配某个路径
- route 对象,维护了 layer 对象和一个 route 对象关联关系,正是同个这个关联关系,layer 对象可以找到 middle1Fn、middle2Fn回调中间件函数。
总结
- 外层 Layer 主要维护了 Route 关系。
- 通过 matchers 属性判断路由是否和 layer 是否匹配,如果匹配它会找到与他关联的 Route 实例对象。
- 通过 handle 调用 route dispatch 调度函数,来遍历 route stack 中的 layer 对象。
Route 结构
Route 实例结构如下
外层的 layer 会指向一个 Route 实例对象,route对象通过 stack 维护一个 Layer 列表。这里可能会有人疑惑了???怎么这里又出现了 Layer 对象。
其实 Layer 对象你可以理解为一个数据结构。
- 外层的 Layer 实例有这样几个关键属性 matchers、route 、handle 对应的是 route dispatch调度函数。
- 内层的 Layer 实例和外层的区别是,有 method、(name 指向中间件回调函数名)、没有 route 对象。 handle 对应的就是 middleware1 这样的回调函数。所以内存的 layer 就是实际处理路由回调函数的地方。
总结
-
你可以通过 router.stack 找到你匹配的外层 layer 对象。一次请求只会匹配一个路由,如果你定义了两个相同的路由,后面的会被忽略
-
然后通过 layer.route 找到 route 对象
-
route 对象通过 dispatch 调度函数遍历 stack 数组,取出 layer 对象,判断是否符合路由和方法进行执行
- dispatch 调度函数中核心就是实现了一个 next() 函数。该函数会遍历 route.stack 数组
-
内层 layer 对象就是对应 app.get('/', function middleware1(req, res, next) {}, function helloWorld() {}) middleware1 和 helloWorld 这两个中间件回调函数
-
调用内层 layer 对象中的 handle 函数相当于执行了 middleware1 中间件回调函数,middleware1 中执行 next() 函数继续遍历 route.stack 数组中的下一个 layer
中间件
Express是一个路由和中间件web框架,它自己的功能很少:Express应用程序本质上是一系列中间件函数调用。
中间件函数是可以访问请求对象(req)、响应对象(res)以及应用程序请求-响应周期中的 next 中间件函数。
Express应用程序可以使用以下类型的中间件:
- 应用程序中间件
- 路由器级别中间件
- 错误处理中间件
- 内置的中间件
- 第三方中间件
中间件基本使用
php
var express = require('express')
var app = express()
// 应用中间件
app.use(function (req, res, next) {
console.log('Time:', Date.now())
next()
})
// 内置中间件
// 设置静态文件目录
app.use(express.static('public'));
// 解析 URL 编码的请求体
app.use(express.urlencoded({ extended: true }));
// 路由中间键
router.get('/user/:id', function (req, res) {
res.send('User Info')
})
// 错误中间件
app.use(function (err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
});
中间件源码梳理
有了上面路由系统的认识后看中间件就简单多了
- 使用 app.use(middleFn) 后也会根据这个回调去创建一个外层的 layer
- 创建的 layer 有个重要的属性 slash 为 true 表示他可以匹配任意路径
- 当我们访问路由时候,还是会去调用Router.handle 中的 next() 函数去匹配对应外层的 layer 对象
- 中间件的 layer 对象会匹配任意路由,所以它会直接 app.use() 传入的回调函数
- 回调函数中执行了 next() 方法,该方法就是继续遍历 router.stack, 寻找下一个匹配的 layer 对象。如果是中间件的 layer则执行回调。如果是 app.[method] 注册的,则会遍历 route.stack 执行对应的逻辑
可以看到如果使用 app.use(middleFn1, middleFn2) 会在 router.stack 栈中加入两个 layer 对象。效果就像执行了两次 router.get('/', fn) 。只不过这个 layer 可以匹配任意路径和方法。
接口访问流程图。
总结
本文梳理了 Express 最核心的路由和中间件源码流程,剩下还有 request、response、模板引擎这些会下下一篇源码细节中再探讨 # Express 源码分析-代码讲解。