中间件概念 #

一、中间件概述 #

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

八、总结 #

本章我们学习了:

  1. 中间件概念:什么是中间件、与Express对比
  2. Express兼容:@fastify/express使用
  3. 钩子系统:钩子类型、应用级、路由级
  4. 中间件模式:认证、日志、CORS、限流
  5. 执行流程:执行顺序、提前终止、错误传播
  6. 最佳实践:插件封装、条件性、复用、组合
  7. 迁移指南:中间件替换、迁移示例

接下来让我们学习请求钩子!

最后更新:2026-03-28