请求钩子 #

一、钩子概述 #

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)
  })
})

十二、总结 #

本章我们学习了:

  1. 生命周期流程:请求处理的完整流程
  2. onRequest:请求开始时的钩子
  3. preParsing:解析请求体前的钩子
  4. preValidation:Schema验证前的钩子
  5. preHandler:路由处理前的钩子
  6. preSerialization:序列化前的钩子
  7. onSend:发送响应前的钩子
  8. onResponse:响应发送后的钩子
  9. onError:错误处理钩子
  10. 钩子组合:多钩子协作、条件性钩子

接下来让我们学习错误处理!

最后更新:2026-03-28