在@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
相关产品推荐
相关产品推荐

