请求钩子 #
一、钩子概述 #
Fastify提供了完整的请求生命周期钩子系统,让你可以在请求处理的各个阶段执行自定义逻辑。
1.1 生命周期流程 #
text
Incoming Request
│
▼
┌─────────────────┐
│ onRequest │ ← 第一个钩子
└────────┬────────┘
│
▼
┌─────────────────┐
│ preParsing │ ← 解析请求体前
└────────┬────────┘
│
▼
┌─────────────────┐
│ parsing │ ← 内置解析
└────────┬────────┘
│
▼
┌─────────────────┐
│ preValidation │ ← Schema验证前
└────────┬────────┘
│
▼
┌─────────────────┐
│ validation │ ← Schema验证
└────────┬────────┘
│
▼
┌─────────────────┐
│ preHandler │ ← 路由处理前
└────────┬────────┘
│
▼
┌─────────────────┐
│ handler │ ← 路由处理函数
└────────┬────────┘
│
▼
┌─────────────────┐
│ preSerialization│ ← 序列化前
└────────┬────────┘
│
▼
┌─────────────────┐
│ serialization │ ← 响应序列化
└────────┬────────┘
│
▼
┌─────────────────┐
│ onSend │ ← 发送响应前
└────────┬────────┘
│
▼
┌─────────────────┐
│ onResponse │ ← 响应发送后
└────────┬────────┘
│
▼
Response Sent
1.2 钩子注册方式 #
javascript
fastify.addHook('onRequest', async (request, reply) => {
console.log('onRequest hook')
})
fastify.get('/route', {
onRequest: async (request, reply) => {
console.log('Route-level onRequest')
}
}, handler)
二、onRequest钩子 #
2.1 基本用法 #
onRequest是请求生命周期的第一个钩子。
javascript
fastify.addHook('onRequest', async (request, reply) => {
console.log('Request received:', request.url)
request.startTime = Date.now()
})
2.2 常见用途 #
请求追踪:
javascript
const { v4: uuidv4 } = require('uuid')
fastify.addHook('onRequest', async (request, reply) => {
request.id = request.headers['x-request-id'] || uuidv4()
reply.header('X-Request-Id', request.id)
})
认证检查:
javascript
fastify.addHook('onRequest', async (request, reply) => {
if (request.url.startsWith('/api')) {
const token = request.headers.authorization
if (!token) {
reply.code(401).send({ error: 'Unauthorized' })
return reply
}
}
})
请求日志:
javascript
fastify.addHook('onRequest', async (request, reply) => {
fastify.log.info({
type: 'request',
method: request.method,
url: request.url,
ip: request.ip,
userAgent: request.headers['user-agent']
})
})
2.3 提前终止请求 #
javascript
fastify.addHook('onRequest', async (request, reply) => {
if (request.headers['x-block'] === 'true') {
reply.code(403).send({ error: 'Blocked' })
return reply
}
})
三、preParsing钩子 #
3.1 基本用法 #
preParsing在请求体解析前执行,可以修改原始请求体。
javascript
fastify.addHook('preParsing', async (request, reply, payload) => {
console.log('Content-Type:', request.headers['content-type'])
return payload
})
3.2 解压缩请求体 #
javascript
const zlib = require('zlib')
fastify.addHook('preParsing', async (request, reply, payload) => {
const encoding = request.headers['content-encoding']
if (encoding === 'gzip') {
return payload.pipe(zlib.createGunzip())
}
if (encoding === 'deflate') {
return payload.pipe(zlib.createInflate())
}
return payload
})
3.3 请求体预处理 #
javascript
fastify.addHook('preParsing', async (request, reply, payload) => {
if (request.headers['content-type'] === 'application/x-www-form-urlencoded') {
return payload
}
return payload
})
四、preValidation钩子 #
4.1 基本用法 #
preValidation在Schema验证前执行。
javascript
fastify.addHook('preValidation', async (request, reply) => {
console.log('Before validation')
})
4.2 数据预处理 #
javascript
fastify.addHook('preValidation', async (request, reply) => {
if (request.body && typeof request.body === 'string') {
try {
request.body = JSON.parse(request.body)
} catch (e) {
reply.code(400).send({ error: 'Invalid JSON' })
return reply
}
}
})
4.3 添加默认值 #
javascript
fastify.addHook('preValidation', async (request, reply) => {
if (!request.body) {
request.body = {}
}
request.body.createdAt = new Date()
request.body.ip = request.ip
})
五、preHandler钩子 #
5.1 基本用法 #
preHandler是最常用的钩子,在路由处理函数前执行。
javascript
fastify.addHook('preHandler', async (request, reply) => {
console.log('Before handler')
})
5.2 认证处理 #
javascript
fastify.register(require('@fastify/jwt'), { secret: 'secret' })
fastify.addHook('preHandler', async (request, reply) => {
if (request.url.startsWith('/api')) {
try {
await request.jwtVerify()
} catch (err) {
reply.code(401).send({ error: 'Invalid token' })
}
}
})
5.3 权限检查 #
javascript
fastify.addHook('preHandler', async (request, reply) => {
if (request.user && request.user.role !== 'admin') {
if (request.url.startsWith('/admin')) {
reply.code(403).send({ error: 'Forbidden' })
}
}
})
5.4 数据加载 #
javascript
fastify.addHook('preHandler', async (request, reply) => {
if (request.params.id) {
request.resource = await fastify.db.collection('resources').findOne({
_id: request.params.id
})
if (!request.resource) {
reply.code(404).send({ error: 'Resource not found' })
}
}
})
六、preSerialization钩子 #
6.1 基本用法 #
preSerialization在响应序列化前执行,可以修改响应数据。
javascript
fastify.addHook('preSerialization', async (request, reply, payload) => {
console.log('Before serialization:', payload)
return payload
})
6.2 响应包装 #
javascript
fastify.addHook('preSerialization', async (request, reply, payload) => {
if (payload && typeof payload === 'object') {
return {
success: true,
data: payload,
timestamp: new Date().toISOString()
}
}
return payload
})
6.3 数据转换 #
javascript
fastify.addHook('preSerialization', async (request, reply, payload) => {
if (Array.isArray(payload)) {
return {
items: payload,
count: payload.length
}
}
return payload
})
6.4 敏感数据过滤 #
javascript
fastify.addHook('preSerialization', async (request, reply, payload) => {
if (payload && payload.password) {
const { password, ...safe } = payload
return safe
}
return payload
})
七、onSend钩子 #
7.1 基本用法 #
onSend在响应发送前执行,可以修改最终响应。
javascript
fastify.addHook('onSend', async (request, reply, payload) => {
console.log('Before send')
return payload
})
7.2 添加响应头 #
javascript
fastify.addHook('onSend', async (request, reply, payload) => {
reply.header('X-Response-Time', Date.now() - request.startTime)
return payload
})
7.3 响应体修改 #
javascript
fastify.addHook('onSend', async (request, reply, payload) => {
if (typeof payload === 'string') {
return payload.toUpperCase()
}
return payload
})
7.4 格式化JSON #
javascript
fastify.addHook('onSend', async (request, reply, payload) => {
if (request.query.pretty === 'true' && typeof payload === 'string') {
try {
const json = JSON.parse(payload)
return JSON.stringify(json, null, 2)
} catch (e) {
return payload
}
}
return payload
})
八、onResponse钩子 #
8.1 基本用法 #
onResponse在响应发送后执行,用于清理和日志。
javascript
fastify.addHook('onResponse', async (request, reply) => {
console.log('Response sent')
})
8.2 性能监控 #
javascript
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`
})
})
8.3 统计收集 #
javascript
const stats = {
requests: 0,
errors: 0,
totalDuration: 0
}
fastify.addHook('onResponse', async (request, reply) => {
stats.requests++
stats.totalDuration += Date.now() - request.startTime
if (reply.statusCode >= 400) {
stats.errors++
}
})
fastify.get('/stats', async (request, reply) => {
return stats
})
九、onError钩子 #
9.1 基本用法 #
onError在错误发生时执行。
javascript
fastify.addHook('onError', async (request, reply, error) => {
console.error('Error:', error.message)
})
9.2 错误日志 #
javascript
fastify.addHook('onError', async (request, reply, error) => {
fastify.log.error({
error: error.message,
stack: error.stack,
requestId: request.id,
method: request.method,
url: request.url
})
})
9.3 错误转换 #
javascript
fastify.addHook('onError', async (request, reply, error) => {
if (error.name === 'ValidationError') {
error.statusCode = 400
error.message = 'Validation failed'
}
if (error.name === 'UnauthorizedError') {
error.statusCode = 401
error.message = 'Authentication required'
}
})
十、钩子组合 #
10.1 多钩子协作 #
javascript
fastify.addHook('onRequest', async (request, reply) => {
request.startTime = Date.now()
request.id = generateRequestId()
})
fastify.addHook('preHandler', async (request, reply) => {
await authenticate(request)
})
fastify.addHook('onSend', async (request, reply, payload) => {
reply.header('X-Request-Id', request.id)
reply.header('X-Response-Time', Date.now() - request.startTime)
return payload
})
fastify.addHook('onResponse', async (request, reply) => {
logRequest(request, reply)
})
10.2 条件性钩子 #
javascript
fastify.addHook('preHandler', async (request, reply) => {
const routeConfig = request.routeConfig
if (routeConfig.auth) {
await authenticate(request, reply)
}
if (routeConfig.admin) {
await checkAdmin(request, reply)
}
})
fastify.get('/public', {
config: { auth: false, admin: false }
}, handler)
fastify.get('/private', {
config: { auth: true, admin: false }
}, handler)
fastify.get('/admin', {
config: { auth: true, admin: true }
}, handler)
十一、最佳实践 #
11.1 钩子命名 #
javascript
const hooks = {
logRequest: async (request, reply) => {
console.log('Request:', request.url)
},
authenticate: async (request, reply) => {
// 认证逻辑
},
checkPermission: async (request, reply) => {
// 权限检查
}
}
fastify.addHook('onRequest', hooks.logRequest)
fastify.addHook('preHandler', hooks.authenticate)
11.2 错误处理 #
javascript
fastify.addHook('preHandler', async (request, reply) => {
try {
await doSomething()
} catch (err) {
fastify.log.error(err)
throw err
}
})
11.3 避免阻塞 #
javascript
fastify.addHook('onResponse', async (request, reply) => {
setImmediate(() => {
logToExternalService(request, reply)
})
})
十二、总结 #
本章我们学习了:
- 生命周期流程:请求处理的完整流程
- onRequest:请求开始时的钩子
- preParsing:解析请求体前的钩子
- preValidation:Schema验证前的钩子
- preHandler:路由处理前的钩子
- preSerialization:序列化前的钩子
- onSend:发送响应前的钩子
- onResponse:响应发送后的钩子
- onError:错误处理钩子
- 钩子组合:多钩子协作、条件性钩子
接下来让我们学习错误处理!
最后更新:2026-03-28