中间件概念 #
一、中间件概述 #
1.1 什么是中间件 #
中间件是在请求和响应之间执行的函数,可以:
- 执行任何代码
- 修改请求和响应对象
- 结束请求-响应循环
- 调用下一个中间件
1.2 Fastify与Express中间件对比 #
| 特性 | Express | Fastify |
|---|---|---|
| 中间件模式 | 线性链式调用 | 钩子系统 |
| 性能 | 较低 | 更高 |
| 异步支持 | 需要包装 | 原生支持 |
| 错误处理 | 需要next(err) | 自动捕获 |
| 作用域 | 全局 | 可封装 |
1.3 Fastify的中间件方案 #
Fastify使用钩子系统替代传统中间件:
javascript
// Express风格
app.use((req, res, next) => {
console.log('Middleware')
next()
})
// Fastify风格
fastify.addHook('onRequest', async (request, reply) => {
console.log('Hook')
})
二、Express中间件兼容 #
2.1 使用@fastify/express #
javascript
const fastify = require('fastify')()
fastify.register(require('@fastify/express'))
fastify.use((req, res, next) => {
console.log('Express middleware')
next()
})
fastify.get('/', async (request, reply) => {
return { message: 'Hello' }
})
fastify.listen({ port: 3000 })
2.2 使用Express中间件 #
javascript
const fastify = require('fastify')()
const helmet = require('helmet')
const morgan = require('morgan')
fastify.register(require('@fastify/express'))
fastify.use(morgan('combined'))
fastify.use(helmet())
fastify.listen({ port: 3000 })
2.3 中间件限制 #
使用Express中间件有一些限制:
- 性能不如原生钩子
- 无法使用Fastify的Schema验证
- 无法使用Fastify的装饰器
推荐:尽可能使用Fastify原生插件替代Express中间件。
三、Fastify钩子系统 #
3.1 钩子类型 #
text
请求生命周期钩子
─────────────────────────────────────
onRequest - 请求开始
preParsing - 解析前
preValidation - 验证前
preHandler - 处理前
preSerialization - 序列化前
onSend - 发送前
onResponse - 响应后
onError - 错误时
3.2 应用级钩子 #
javascript
fastify.addHook('onRequest', async (request, reply) => {
console.log('Application-level hook')
})
fastify.addHook('preHandler', async (request, reply) => {
request.customData = 'data'
})
3.3 路由级钩子 #
javascript
fastify.get('/protected', {
preHandler: async (request, reply) => {
if (!request.headers.authorization) {
reply.code(401).send({ error: 'Unauthorized' })
}
}
}, async (request, reply) => {
return { message: 'Protected route' }
})
3.4 钩子数组 #
javascript
fastify.get('/multiple-hooks', {
preHandler: [
async (request, reply) => {
console.log('First hook')
},
async (request, reply) => {
console.log('Second hook')
},
async (request, reply) => {
console.log('Third hook')
}
]
}, async (request, reply) => {
return { message: 'Multiple hooks' }
})
四、中间件模式实现 #
4.1 认证中间件 #
javascript
const fp = require('fastify-plugin')
async function authMiddleware(fastify, opts) {
fastify.decorate('authenticate', async function (request, reply) {
const token = request.headers.authorization
if (!token) {
reply.code(401).send({ error: 'No token provided' })
return
}
try {
const decoded = await verifyToken(token)
request.user = decoded
} catch (err) {
reply.code(401).send({ error: 'Invalid token' })
}
})
}
module.exports = fp(authMiddleware, {
name: 'auth-middleware'
})
使用:
javascript
fastify.get('/profile', {
preHandler: fastify.authenticate
}, async (request, reply) => {
return { user: request.user }
})
4.2 日志中间件 #
javascript
fastify.addHook('onRequest', async (request, reply) => {
request.startTime = Date.now()
})
fastify.addHook('onResponse', async (request, reply) => {
const duration = Date.now() - request.startTime
fastify.log.info({
method: request.method,
url: request.url,
statusCode: reply.statusCode,
duration: `${duration}ms`
})
})
4.3 CORS中间件 #
javascript
fastify.addHook('onRequest', async (request, reply) => {
reply.header('Access-Control-Allow-Origin', '*')
reply.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
reply.header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
if (request.method === 'OPTIONS') {
reply.code(204).send()
return reply
}
})
4.4 限流中间件 #
javascript
const rateLimit = new Map()
fastify.addHook('onRequest', async (request, reply) => {
const ip = request.ip
const limit = 100
const windowMs = 60000
const now = Date.now()
const windowStart = now - windowMs
if (!rateLimit.has(ip)) {
rateLimit.set(ip, [])
}
const requests = rateLimit.get(ip).filter(time => time > windowStart)
if (requests.length >= limit) {
reply.code(429).send({ error: 'Too many requests' })
return reply
}
requests.push(now)
rateLimit.set(ip, requests)
})
五、中间件执行流程 #
5.1 执行顺序 #
javascript
fastify.addHook('onRequest', async () => console.log('1. onRequest'))
fastify.addHook('preParsing', async () => console.log('2. preParsing'))
fastify.addHook('preValidation', async () => console.log('3. preValidation'))
fastify.addHook('preHandler', async () => console.log('4. preHandler'))
fastify.get('/test', {
preHandler: async () => console.log('4.5. Route preHandler')
}, async () => {
console.log('5. Handler')
return { ok: true }
})
fastify.addHook('preSerialization', async () => console.log('6. preSerialization'))
fastify.addHook('onSend', async () => console.log('7. onSend'))
fastify.addHook('onResponse', async () => console.log('8. onResponse'))
输出:
text
1. onRequest
2. preParsing
3. preValidation
4. preHandler
4.5. Route preHandler
5. Handler
6. preSerialization
7. onSend
8. onResponse
5.2 提前终止 #
javascript
fastify.addHook('onRequest', async (request, reply) => {
if (request.headers['x-block'] === 'true') {
reply.code(403).send({ error: 'Blocked' })
return reply
}
})
fastify.get('/test', async (request, reply) => {
return { message: 'This will not be reached if blocked' }
})
5.3 错误传播 #
javascript
fastify.addHook('preHandler', async (request, reply) => {
throw new Error('Something went wrong')
})
fastify.setErrorHandler((error, request, reply) => {
reply.code(500).send({ error: error.message })
})
六、中间件最佳实践 #
6.1 使用插件封装 #
javascript
const fp = require('fastify-plugin')
async function middlewarePlugin(fastify, opts) {
fastify.addHook('onRequest', async (request, reply) => {
console.log('Request:', request.url)
})
}
module.exports = fp(middlewarePlugin, {
name: 'middleware'
})
6.2 条件性中间件 #
javascript
fastify.addHook('preHandler', async (request, reply) => {
if (request.routeConfig.auth) {
await fastify.authenticate(request, reply)
}
})
fastify.get('/public', {
config: { auth: false }
}, async (request, reply) => {
return { public: true }
})
fastify.get('/private', {
config: { auth: true }
}, async (request, reply) => {
return { private: true }
})
6.3 中间件复用 #
javascript
const authHook = async (request, reply) => {
if (!request.headers.authorization) {
reply.code(401).send({ error: 'Unauthorized' })
}
}
fastify.get('/profile', { preHandler: authHook }, handler)
fastify.get('/settings', { preHandler: authHook }, handler)
fastify.get('/orders', { preHandler: authHook }, handler)
6.4 中间件组合 #
javascript
const compose = (...hooks) => {
return async (request, reply) => {
for (const hook of hooks) {
await hook(request, reply)
}
}
}
const validateUser = async (request, reply) => {
if (!request.user) {
reply.code(401).send({ error: 'Unauthorized' })
}
}
const validateAdmin = async (request, reply) => {
if (request.user.role !== 'admin') {
reply.code(403).send({ error: 'Forbidden' })
}
}
fastify.get('/admin', {
preHandler: compose(validateUser, validateAdmin)
}, async (request, reply) => {
return { message: 'Admin area' }
})
七、Express迁移指南 #
7.1 常见中间件替换 #
| Express中间件 | Fastify替代 |
|---|---|
| helmet | @fastify/helmet |
| cors | @fastify/cors |
| body-parser | 内置 |
| morgan | 内置Pino日志 |
| express-session | @fastify/session |
| cookie-parser | @fastify/cookie |
| multer | @fastify/multipart |
| compression | @fastify/compress |
7.2 迁移示例 #
Express:
javascript
const express = require('express')
const helmet = require('helmet')
const cors = require('cors')
const morgan = require('morgan')
const app = express()
app.use(helmet())
app.use(cors())
app.use(morgan('combined'))
app.use(express.json())
app.get('/users', (req, res) => {
res.json({ users: [] })
})
app.listen(3000)
Fastify:
javascript
const fastify = require('fastify')({
logger: true
})
fastify.register(require('@fastify/helmet'))
fastify.register(require('@fastify/cors'))
fastify.get('/users', async (request, reply) => {
return { users: [] }
})
fastify.listen({ port: 3000 })
八、总结 #
本章我们学习了:
- 中间件概念:什么是中间件、与Express对比
- Express兼容:@fastify/express使用
- 钩子系统:钩子类型、应用级、路由级
- 中间件模式:认证、日志、CORS、限流
- 执行流程:执行顺序、提前终止、错误传播
- 最佳实践:插件封装、条件性、复用、组合
- 迁移指南:中间件替换、迁移示例
接下来让我们学习请求钩子!
最后更新:2026-03-28