You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在@fastify/http-proxy的async preHandler中触发FastifyError问题排查

问题:Fastify代理preHandler异步操作导致"Reply was already sent"错误

我原本用以下代码实现代理功能,运行正常:

const fastify = require('fastify')({ logger: true })
const fastifyHttpProxy = require('@fastify/http-proxy')

fastify.register(fastifyHttpProxy, {
  upstream: ...,
  prefix: '/',
  http2: false,
  rewritePrefix: ...,
  preValidation: (request, reply, done) => {
    // 一些验证逻辑
    done()
  },
  preHandler: (request, reply, next) => {
    // 一些处理逻辑
    request.raw.url = `${request.raw.url}/some-path`
    next()
  }
})

fastify.listen({ port: 3000 }, (err, address) => {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
  fastify.log.info(`Server listening on ${address}`)
})

但给preHandler添加异步操作后,出现了如下错误:

err":{"type":"FastifyError","message":"Reply was already sent, did you forget to \"return reply\" in \"//app1/index.html\" (GET)?","stack":"FastifyError: Reply was already sent, did you forget to \"return reply\" in \"//app1/index.html\" (GET)?\n    at Reply.send 

哪怕只是加个简单的await Promise.resolve()就能复现问题:

const fastify = require('fastify')({ logger: true })
const fastifyHttpProxy = require('@fastify/http-proxy')

fastify.register(fastifyHttpProxy, {
  upstream: ...,
  prefix: '/',
  http2: false,
  rewritePrefix: ...,
  preValidation: (request, reply, done) => {
    // 一些验证逻辑
    done()
  },
  preHandler: async (request, reply, next) => {
    // 一些处理逻辑,比如修改路径
    await Promise.resolve(1)
    request.raw.url = `${request.raw.url}/some-path`
    next()
  }
})

fastify.listen({ port: 3000 }, (err, address) => {
  if (err) {
    fastify.log.error(err)
    process.exit(1)
  }
  fastify.log.info(`Server listening on ${address}`)
})

错误原因

Fastify的钩子函数支持两种异步处理方式:

  • 使用async/await(返回Promise)
  • 使用回调函数(调用next()或done())

当你把preHandler声明为async函数时,Fastify会自动等待这个Promise完成,默认认为钩子已经执行完毕。但你同时又调用了next(),相当于重复通知Fastify“钩子执行完毕”,导致内部逻辑冲突,触发了“响应已发送”的错误。

解决方法

方法一:去掉next(),用async/await自然处理异步

既然用了async函数,就不需要再调用next(),异步操作完成后函数自动resolve,Fastify会继续执行后续流程:

preHandler: async (request, reply) => {
  // 异步认证逻辑示例
  await someAuthCheck(request.headers.authorization)
  request.raw.url = `${request.raw.url}/some-path`
}

方法二:不使用async,用回调处理异步

如果习惯用回调式写法,就不要加async,而是在异步操作完成后调用next(),同时记得捕获错误并传给next()让Fastify处理:

preHandler: (request, reply, next) => {
  Promise.resolve(1)
    .then(() => {
      request.raw.url = `${request.raw.url}/some-path`
      next()
    })
    .catch(err => next(err))
}

如果是认证场景,验证不通过时可以直接用reply.code(401).send('Unauthorized')终止请求,不需要调用next()。

内容的提问来源于stack exchange,提问作者Richard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 20:52:12