首页
/ Go net/http 新增 MethodQuery 常量:RFC 10008 QUERY 方法的支持与重定向行为解析

Go net/http 新增 MethodQuery 常量:RFC 10008 QUERY 方法的支持与重定向行为解析

2026-09-05 13:22:33作者:盛欣凯Ernestine

本文围绕 Go 标准库 net/http 包新增的 MethodQuery 常量展开:介绍 RFC 10008 定义的 HTTP QUERY 方法语义,说明 MethodQuery 在源码中的定义位置与 API 变更记录,并结合 client.goredirectBehavior 函数与 client_test.go 的测试用例,讲清 Go 客户端在遇到 QUERY 请求的重定向(301/302/303/307/308)时如何处理方法与请求体。读完后,你可以在 Go 代码中直接使用 http.MethodQuery 构造、路由 QUERY 请求,并准确预判 http.Client 的重定向行为。

一、变更背景:QUERY 方法进入 Go 标准库

官方变更说明记录在 doc/next/6-stdlib/99-minor/net/http/80058.md 中,核心内容只有一句话:

新增的 MethodQuery 常量表示 RFC 10008 中定义的 HTTP QUERY 方法。

对应的 API 变更条目保存在 api/next/80058.txt:

pkg net/http, const MethodQuery = "QUERY" #80058
pkg net/http, const MethodQuery ideal-string #80058

RFC 10008 为 HTTP 增加了一组面向"结构化查询"的方法,其中 QUERY 的核心定位是:客户端向服务器提交一个查询描述符(query descriptor)作为请求体,服务器按该描述符检索数据并返回。与 POST 不同,QUERY 在语义上被定义为安全的(safe)且幂等的(idempotent) 请求方法,这一点直接决定了 Go http.Client 后续的重定向策略(见第四节)。

二、MethodQuery 常量在 net/http 中的定义

所有 HTTP 方法常量集中定义在 src/net/http/method.go,新增的 MethodQuery 被追加在这一常量块的末尾:

// Common HTTP methods.
//
// Unless otherwise noted, these are defined in RFC 7231 section 4.3.
const (
	MethodGet     = "GET"
	MethodHead    = "HEAD"
	MethodPost    = "POST"
	MethodPut     = "PUT"
	MethodPatch   = "PATCH" // RFC 5789
	MethodDelete  = "DELETE"
	MethodConnect = "CONNECT"
	MethodOptions = "OPTIONS"
	MethodTrace   = "TRACE"
	MethodQuery   = "QUERY" // RFC 10008
)

有两个细节值得注意:

  1. 注释区分了出处MethodPatch 标注 RFC 5789,MethodQuery 标注 RFC 10008,其余方法默认出自 RFC 7231 第 4.3 节——MethodQuery 不属于经典七个方法之列,属于标准库对新规范方法的增量支持。
  2. 常量类型是理想的字符串(ideal string)。从 api/next/80058.txtideal-string 标记可以看到,MethodQuery 是未指定类型的字符串常量,因此既可以赋给 string 变量,也可以直接传给 http.NewRequest 等需要 string 的 API,并且参与编译期常量检查(例如误拼方法名会在 switch 比较场景中暴露)。

在业务代码中的典型用法:

// 构造一个 QUERY 请求
req, err := http.NewRequest(http.MethodQuery, "https://example.com/search", strings.NewReader(queryDescriptor))
if err != nil {
    log.Fatal(err)
}
resp, err := client.Do(req)

在服务器端,r.Method 会原样取到字符串 "QUERY",因此路由匹配时可以直接使用 http.MethodQuery 做比较,避免散落裸字符串:

http.HandleFunc("/search", func(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case http.MethodQuery:
        // 解析 r.Body 中的查询描述符,执行检索
    case http.MethodGet:
        // 简单查询走 query string
    }
})

此外,从源码结构看,client.go 中的 urlErrorOp 函数会把方法名转成错误信息中的操作名(大写转首字母小写),因此 QUERY 请求失败时,*url.ErrorOp 字段会呈现为 "Query",这为日志排查提供了与方法一一对应的线索。

三、QUERY 的方法语义:安全且幂等

理解这次变更的关键,是 QUERY 与 POST 的本质差异:

特性 POST(参照系) QUERY(RFC 10008)
是否携带请求体 是(查询描述符)
是否 safe 是(不改变服务器状态)
是否 idempotent 是(重复查询结果一致)

正因为"safe + idempotent",RFC 10008 第 2.5 节允许对 QUERY 的重定向沿用保留方法与请求体的处理方式。Go 客户端正是依据这一节实现了下面的逻辑。

四、客户端重定向行为:redirectBehavior 源码解析

Go http.Client 处理 3xx 重定向的核心函数是 client.go 中的 redirectBehavior(位于 checkRedirect 之后,约 L498 起):

// redirectBehavior describes what should happen when the
// client encounters a 3xx status code from the server.
func redirectBehavior(reqMethod string, resp *Response, ireq *Request) (redirectMethod string, shouldRedirect, includeBody bool) {
	switch resp.StatusCode {
	case 301, 302, 303:
		redirectMethod = reqMethod
		shouldRedirect = true
		includeBody = false

		// RFC 10008, Section 2.5: QUERY is safe and idempotent, so 301
		// and 302 preserve the method and re-send the body (as 307 and
		// 308 do); only 303 changes the method to GET.
		if reqMethod == "QUERY" && resp.StatusCode != 303 {
			includeBody = true
			if ireq.GetBody == nil && ireq.outgoingLength() != 0 {
				// We had a request body, and 301/302 require
				// re-sending it, but GetBody is not defined.
				shouldRedirect = false
			}
		} else if reqMethod != "GET" && reqMethod != "HEAD" {
			// RFC 2616 allowed automatic redirection only with GET and
			// HEAD requests. RFC 7231 lifts this restriction, but we still
			// restrict other methods to GET to maintain compatibility.
			// See Issue 18570.
			redirectMethod = "GET"
		}
	case 307, 308:
		redirectMethod = reqMethod
		shouldRedirect = true
		includeBody = true
		// ...
	}
	return redirectMethod, shouldRedirect, includeBody
}

逐条拆解这段分支逻辑:

  1. 默认路径(301/302/303):方法保留,请求体丢弃。若原方法既不是 GET 也不是 HEAD,则把方法降级为 GET——这是为兼容 RFC 2616 时代行为的保守策略(见 Issue 18570 注释)。
  2. QUERY 特殊分支:当 reqMethod == "QUERY" 且状态码不是 303 时,includeBody = true,即 301 和 302 会保留 QUERY 方法并原样重发请求体,行为与 307/308 对齐。注释明确引用了 RFC 10008 第 2.5 节作为依据。
  3. 303 的例外:303 See Other 会把方法改为 GET(走上面的 reqMethod != "GET" 分支),因为 303 的语义就是"用 GET 去 Location 取结果"。
  4. GetBody 缺失时的兜底:如果请求携带了请求体(outgoingLength() != 0)但没有设置 GetBody(即无法重放请求体),则 shouldRedirect = false,客户端放弃自动跳转,把当前 3xx 响应直接返回给调用方,而不是报错。这一兜底与 307/308 分支的处理完全一致,保证 QUERY 在"请求体不可重放"场景下的行为可预期。

汇总成行为对照表:

原方法 301 302 303 307 308
GET / HEAD 保留方法,无体 保留方法,无体 变 GET,无体 保留方法,带体 保留方法,带体
其他方法(含旧行为) 变 GET,无体 变 GET,无体 变 GET,无体 保留方法,带体 保留方法,带体
QUERY(本次变更) 保留 QUERY,带体 保留 QUERY,带体 变 GET,无体 保留 QUERY,带体 保留 QUERY,带体

对使用者而言,这意味着:只要你的 QUERY 请求通过 http.NewRequest 构造(该路径会默认填充 GetBody),遇到 301/302 时客户端会自动带着查询描述符跳转到新地址重新查询,无需手工处理;而如果你用 Request 字面量手工构造且未设置 GetBody,遇到需要重放请求体的重定向时,Client 会直接把 3xx 响应交还给你,由你决定是否手动跟随。

五、测试用例验证:TestQueryRedirects

上述行为在 client_test.goTestQueryRedirects(约 L404)中有完整覆盖。测试通过 testRedirectsByMethod(t, mode, "QUERY", ...) 复用既有的重定向测试框架,期望的请求序列(节选)清晰地演示了跳转链上方法与请求体的演变:

QUERY / "first"                                // 初始请求,带体 "first"
QUERY /?code=302&next=302 "c302"               // 302 -> 保留 QUERY 与体
QUERY /?code=302 "c302"                        // 继续 302 跳转
QUERY / "c302"
QUERY /?code=301&next=302,308 "c301"           // 301 -> 保留 QUERY 与体
QUERY /?code=302&next=308 "c301"
QUERY /?code=308 "c301"
QUERY / "c301"
QUERY /?code=303&next=301 "c303"               // 303 -> 方法改为 GET
GET /?code=301 ""
GET / ""
QUERY /?code=307&next=303,302 "c307"           // 307 -> 保留 QUERY 与体
QUERY /?code=303&next=302 "c307"
GET /?code=302 ""
GET / ""
QUERY /?code=404 "c404"                        // 404 终止跳转链

从断言序列可以确认三件事:301/302/307/308 全程保持 QUERY 方法和请求体;303 把方法切换为 GET 且体被丢弃;遇到非 3xx(如 404)时跳转链终止。测试以 http3SkippedMode 运行,即 HTTP/3 模式下该组用例会跳过,这是该测试框架对所有重定向测试的统一约定,与本次变更本身无关。

六、使用要点与适用前提

  • 适用版本:MethodQuery 属于当前开发线 api/next 中的新增常量(变更文档位于 doc/next 的 minor 变更目录),使用它需要构建包含该变更的工具链;在更早版本中若确有 QUERY 场景,只能使用字符串字面量 "QUERY"
  • 客户端:使用 http.Client.Do 时,QUERY 的 301/302 保留语义依赖请求体可重放(GetBody 非空);http.NewRequest 构造的请求会自动设置 GetBody,是推荐做法。
  • 服务器端:net/http 的服务器不会校验方法白名单,任何方法(包括 QUERY)都能被 ServeMux/自定义 Handler 接收,行为取决于你自己的路由代码;标准库层面本次变更只是补齐了方法常量与客户端重定向语义。
  • 互操作提示:QUERY 并非所有中间件、代理、HTTP 客户端都认识的方法,跨系统集成前建议确认对端(如反向代理、WAF)是否会放行非标准七法之外的方法。

七、小结

本次变更(doc/next/6-stdlib/99-minor/net/http/80058.md)以极小的 API 面——一个常量——把 RFC 10008 的 QUERY 方法纳入了 Go 标准库的方法体系,其真正的技术含量落在 client.goredirectBehavior:QUERY 因"安全且幂等"而获得与 307/308 相同的重定向待遇(301/302 保留方法与请求体),仅 303 例外转为 GET。配合 client_test.goTestQueryRedirects 的端到端断言,这一语义既有实现、也有回归保障,可以放心在生产代码中作为常量引用。

登录后查看全文
热门项目推荐
相关项目推荐