第 5 章:HTTP 服务与中间件
原生 http 手写路由太繁琐,实战都用框架。这一章讲清 Express 的使用和它最核心的「中间件洋葱模型」,顺带对比 Koa。
5.1 从原生 http 到 Express
原生 http 处理路由要手动解析 URL,代码又臭又长:
javascript
const http = require('http');
http.createServer((req, res) => {
if (req.url === '/user' && req.method === 'GET') {
res.end('用户列表');
} else if (req.url === '/user' && req.method === 'POST') {
res.end('创建用户');
} else {
res.writeHead(404); res.end('Not Found');
}
}).listen(3000);Express 用「路由 + 中间件」把这件事变优雅:
javascript
import express from 'express';
const app = express();
app.use(express.json()); // 中间件:解析 JSON body
app.get('/user', (req, res) => { // 路由:GET /user
res.json({ list: [] });
});
app.post('/user', (req, res) => { // 路由:POST /user
res.status(201).json({ id: 1 });
});
app.listen(3000);5.2 什么是中间件
中间件是一个函数,签名 (req, res, next)。它在请求到达路由之前/之后执行,可以做日志、鉴权、解析 body、错误处理等通用逻辑:
javascript
// 日志中间件
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // 关键:调用 next() 才会继续往后走
});
// 鉴权中间件
app.use((req, res, next) => {
if (!req.headers.authorization) {
return res.status(401).json({ error: '未授权' });
}
next();
});核心规则:中间件里要么调用 next() 放行,要么 res.xxx() 直接返回响应。忘了 next() 请求就会「卡死」。
5.3 中间件洋葱模型(核心)
多个中间件的执行顺序,是理解 Express 的钥匙。请求像穿过一颗洋葱,先进后出:
请求 ──► ┌─────────────────────────────┐
│ 中间件1(进入,before) │
│ ┌─────────────────────┐ │
│ │ 中间件2(进入) │ │
│ │ ┌─────────────┐ │ │
│ │ │ 路由处理 │ │ │
│ │ └─────────────┘ │ │
│ │ 中间件2(退出,after)│ │
│ └─────────────────────┘ │
│ 中间件1(退出,after) │
响应 ◄── └─────────────────────────────┘用代码验证执行顺序:
javascript
app.use((req, res, next) => {
console.log('A 进入');
next();
console.log('A 退出');
});
app.use((req, res, next) => {
console.log('B 进入');
next();
console.log('B 退出');
});
app.get('/', (req, res) => {
console.log('路由处理');
res.end('ok');
});
// 输出顺序:A 进入 → B 进入 → 路由处理 → B 退出 → A 退出为什么是「洋葱」:next() 之前的代码是「进入」阶段,next() 之后的代码是「退出」阶段。这让中间件能在请求前后各做一半事——比如记录耗时、压缩响应。
5.4 Express vs Koa
两者都是主流框架,选型参考:
| 维度 | Express | Koa |
|---|---|---|
| 中间件 | 回调 (req,res,next) | async/await,更现代 |
| 体积 | 较大,功能全 | 极简核心,靠插件 |
| 生态 | 最庞大、资料最多 | 较新,由社区扩展 |
| 错误处理 | 手动 try/catch | async 天然传播到 try/catch |
| 上手 | 简单直接 | 需要理解洋葱模型 |
建议:新手/求稳用 Express(生态最全);追求现代 async 风格、想更灵活用 Koa。原理相通,会一个另一个很快上手。
5.5 一个完整的 REST API 示例
把路由、中间件、错误处理串起来:
javascript
import express from 'express';
const app = express();
app.use(express.json());
// 简单内存数据
const users = [];
// 日志中间件(进入/退出各做一半)
app.use((req, res, next) => {
const start = Date.now();
next();
console.log(`${req.method} ${req.url} 耗时 ${Date.now() - start}ms`);
});
// 路由
app.get('/users', (req, res) => res.json(users));
app.post('/users', (req, res) => {
users.push(req.body);
res.status(201).json(req.body);
});
// 兜底错误处理中间件(必须 4 个参数)
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: '服务器错误' });
});
app.listen(3000);5.6 本章小结
| 要点 | 说明 |
|---|---|
| 中间件 | (req, res, next),要么 next() 放行,要么直接响应 |
| 洋葱模型 | 请求先进后出,next() 前后各执行一半 |
| Express | 生态最全、上手简单,新手首选 |
| Koa | async 风格更现代,核心更极简 |
下一章预告
Web 服务会写了,现在进入 AI 时代的主角——用 Node 对接大模型 API,实现 SSE 流式输出,把上一章的 HTTP 和上一章的 stream 串起来。