DEBUG.WIKI
OnlineSIM:在线接收短信,虚拟号码租用
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →
RackNerd;$21.99/年 · 1GB KVM

Hono 改写 POST 为 GET 后报 Request with GET/HEAD method cannot have body

methodOverride 复制有正文的 Request,再把方法改为 GET/HEAD,可能在路由执行前失败。核对正式4.13.13实现,优先直接只读请求并保留方法授权边界。

POST 请求带着正文,通过 X-Method-Override: GET 进入 Hono 的 methodOverride 后返回 500,底层信息是 Request with GET/HEAD method cannot have body.。如果无正文的同类请求能进入 GET 路由,检查方法改写时是否把 POST 正文一起带到了新 Request。

Hono 4.13.13 的正式 methodOverride 实现中,header 分支从原 Request 构造新请求,只修改 method 和相关头;query 分支也会传递原 body。目标成为 GET 或 HEAD 时,非空 body 与 Fetch Request 的约束冲突,异常发生在重新分派给路由之前。

路由尚未执行,Request 构造已经失败

例如应用原本配置了这个入口:

app.use('/posts', methodOverride({ app, header: 'X-Method-Override' }));
app.get('/posts', (c) => c.text('ok'));

app 是已有 Hono 实例,methodOverride 来自 hono/method-override。此处收到带正文的 POST 并覆盖为 GET,会走到上述条件;不能靠修改 GET handler 中的 JSON 或表单读取来修复,因为该 handler 可能根本没有运行。

改写到 DELETE 等允许正文的方法,和改写到 GET 的限制不同;没有正文的 POST→GET 也不是同一触发输入。诊断时应分别检查入口方法、目标方法、是否有正文,以及 override 来自 header、query 还是表单,不要把所有 500 都归为这个问题。

只读操作优先直接发送 GET

可以控制客户端时,让真正的查询直接使用 GET,并按现有接口契约提供查询参数或请求头,不要先构造有正文的 POST 再改写:

const response = await fetch('/posts', {
  method: 'GET',
});

这是浏览器中已有同源接口的结构示例,认证和查询参数仍由应用按原规则提供。不要为了去掉正文,把密码、令牌或大块业务数据自动搬到 URL 中;也不要把创建、删除等有副作用的操作改成 GET 来绕过错误。

旧客户端必须通过 POST 覆盖写方法时,应在该入口明确允许的目标方法,并对 GET/HEAD 这类不兼容的带正文输入返回应用约定的客户端错误,而不是继续送进 Request 构造后变成 500。鉴权、CSRF 和方法授权规则仍须按最终业务操作执行,不能只检查外层 POST。

如果需要自行实现透明的 GET/HEAD 改写,必须明确丢弃正文和描述正文的 Content-Type、Content-Length,并从 URL 等信息创建真正无正文的新请求;只改 method,或者继续把旧 Request 整体复制过去,都不能保证去掉 body。这会影响输入语义,应在应用维护的适配层审定,不能把一个局部补丁冒充通用中间件升级。

表单路径还可能涉及方法字段移除后的正文重建。它与 URL 编码分支的旧长度头问题不是同一个判断条件;修好了 GET/HEAD 正文冲突,并不自动证明其他方法的表单长度和头也正确。

验收要覆盖原来有正文的覆盖请求、无正文对照、正常写方法和直接 GET。确认预期路由真正执行、错误返回符合接口约定,而不是仅把所有构造异常转成空的成功响应。4.13.13 的相关正式实现仍有这个边界,不能假定升级到该版本就已提供透明修复。