Fastify GET 正常,HEAD 却把 Response 送进 Buffer.byteLength
Fastify 5.12.7 自动 HEAD 在 Response 解包前计算长度,可能报 Buffer.byteLength 类型错误。声明独立 HEAD 并保留状态、头字段与鉴权,分别验收 GET 正文和 HEAD。
同一个 Fastify 路由 GET 返回 200,HEAD 却返回 500,堆栈提示 Buffer.byteLength 收到了 Response 对象。若 GET handler 返回的是 Web Response,应检查自动生成的 HEAD 在哪个阶段剥离正文。
Fastify 5.12.7 的自动 HEAD onSend 处理器认识 Node stream 和 Web ReadableStream,但 Response 本身既不是其中一种,也不是字符串或 Buffer。它在 reply 正常解包 Response 之前收到原始对象,最后调用 Buffer.byteLength,因类型不符报错。
临时使用明确的 HEAD 路由
如果接口需要 HEAD,可停用该 GET 的自动 HEAD,并在同一位置声明独立处理。下面只示意固定文本资源,GET 与 HEAD 使用同样的公开元数据:
app.get('/message', { exposeHeadRoute: false }, async () => {
return new Response('hello world', {
status: 200,
headers: { 'content-type': 'text/plain; charset=utf-8' },
});
});
app.head('/message', async (request, reply) => {
return reply
.code(200)
.header('content-type', 'text/plain; charset=utf-8')
.header('content-length', '11')
.send();
});
片段放在已有 app 的启动注册阶段,运行环境需要提供 Response。11 是这段固定 ASCII 文本的字节数,不能拿来写入动态资源。原路由如果需要认证、缓存协商、ETag 或其他钩子,HEAD 也要经过对应控制并反映真实资源状态。
动态场景可以共享取得资源元数据的业务函数,但不要让 HEAD 为了算长度完整执行一次有副作用的 GET 业务。没有可靠长度时,不要根据 JavaScript 字符串 length 伪造 Content-Length;它不是 UTF-8 字节数,也不代表流的最终长度。
不能以空 HEAD 正文证明 GET 完整
HEAD 本就不应返回实体正文。这里只核对状态和可适用的响应头是否与相应 GET 一致,不是让 HEAD 返回 'hello world' 来绕过 Buffer 的类型检查。
这也不同于异步发送压缩结果被提前结束:那条路径需要 return 或 await reply;本题即使 GET 正确返回 Response,自动 HEAD 仍可能在解包之前失败。只补一个 return 并不能改变 payload 类型。
正式 5.12.7 仍保留上述 HEAD 判断,不能把支持 ReadableStream 的修正推广为已覆盖 Response。后续采用框架修复时,应检查确实包含 Response 解包,再移除临时的显式路由;回退时保留原路由配置与两种请求的验收样本。
验收使用真实 GET 和 HEAD,比较状态、Content-Type、适用的缓存头及长度,确认 HEAD 没有正文、GET 仍能完整读取。只用应用健康检查的 HEAD 返回 200,不能证明其他返回类型也已覆盖。


