#1539·hertz

URI.Update 误解了包含绝对 URL 的相对重定向 Location(例如 /login?redirect=https://host/),导致客户端无法执行重定向。

作者: lRoccoon创建于 2026年8月24日更新于 2026年8月25日

描述错误 protocol.URI.Update / UpdateBytes 通过在新 URI 字符串中搜索 // 来决定"绝对 URI":

go
// pkg/protocol/uri.go (v0.10.6)
func (u *URI) updateBytes(newURI, buf []byte) []byte {
	...
	n := bytes.Index(newURI, bytestr.StrSlashSlash)
	if n >= 0 {
		// absolute uri

一个 相对 重定向 Location,其 查询字符串中包含一个绝对 URL - 经典的 SSO 登录模式 Location: /login/redirect_to_sso?redirect=https://example.com/ - 在查询中包含 //,因此被错误地归类为绝对 URI。然后整个字符串从头开始重新解析,生成一个空主机(以及协议从 https 降级为 http)。

由于 client.DoRequestFollowRedirects 通过 getRedirectURLURI.UpdateBytes 解析每个跳转,因此 Client.DoRedirects 对于任何服务器的第二个跳转都会失败,该服务器以这种非常常见的重定向形状作出响应。根据拨号器,失败表现为 dial tcp :80: connection refused请求中缺少必需的 Host 头.

最小重现(无需网络)

go
u := protocol.AcquireURI()
u.Update("https://example.com/")
u.Update("/login/redirect_to_sso?redirect=https://example.com/")
fmt.Printf("%q host=%q scheme=%q\n", u.FullURI(), u.Host(), u.Scheme())
// got:  "http:///login/redirect_to_sso?redirect=https://example.com/" host="" scheme="http"
// want: "https://example.com/login/redirect_to_sso?redirect=https://example.com/" host="example.com"

控制案例(相同路径,查询中没有 //)正确解析:

go
u2 := protocol.AcquireURI()
u2.Update("https://example.com/")
u2.Update("/login/plain")
// "https://example.com/login/plain" host="example.com" ✅

端到端:使用任何服务器对 Location: /login?redirect=https://example.com/ 返回 302Client.DoRedirects

err = dial tcp :80: connect: connection refused
req.URI().String() = "http:///login/redirect_to_sso?redirect=https://example.com/"

预期行为

根据 RFC 3986 参考解析(以及 net/url.URL.ResolveReference),新的 URI 只有在它 一个协议(scheme://...)开始或是协议相对的 ( //) 才被视为绝对。一个前导 / 表示路径绝对相对引用; // 出现稍后(例如在查询中)不应影响分类。例如:

go
if bytes.HasPrefix(newURI, bytestr.StrSlashSlash) || schemePrefixed(newURI) {
	// absolute
}

(现有的 newURI[0] == '/' 分支已经正确处理了路径绝对的情况,一旦去除了误分类)。

环境

内容来源: cloudwego/hertz