在Golang中正确的修改HTTPRequest的Host的操作方法
作者:Kearth
背景
我们工作中经常需要通过HTTP请求Server的服务,比如脚本批量请求接口跑数据。在这个过程中,由于一些网关策略,部分Server会要求请求中Header里面附带Host参数。这时,我们可能会想到在Header里面直接赋值Host,比如这样:
req.Header.Add("Host", "www.example.com") req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
但请求的过程中会发现明明设置了,接收方却收不到这个Host。因此下面我会阐述为什么会这样,以及如何正确的修改HTTPRequest的Host。
为什么会这样
首先我们打印一下Header.Add
后的参数看看会怎样
map[Host:[www.example.com]]
再排除世界上有鬼的情况下,我们可以合理分析出: Header.Add
环节既然成功了,那么Host一定是在实际请求HTTP前被替换了
由于下一段代码就是client.Do,因此有理由怀疑这个操作在Do方法里面
// 发送请求并获取响应 resp, err := client.Do(req) if err != nil { fmt.Println("发送请求失败:", err) return }
追踪下去,可以看到net/http包的源码如下:
func (c *Client) Do(req *Request) (*Response, error) { return c.do(req) } func (c *Client) do(req *Request) (retres *Response, reterr error) { ... ... // 省略 host := "" if req.Host != "" && req.Host != req.URL.Host { // If the caller specified a custom Host header and the // redirect location is relative, preserve the Host header // through the redirect. See issue #22233. if u, _ := url.Parse(loc); u != nil && !u.IsAbs() { host = req.Host } } ireq := reqs[0] req = &Request{ Method: redirectMethod, Response: resp, URL: u, Header: make(Header), Host: host, Cancel: ireq.Cancel, ctx: ireq.ctx, } ... ... // 省略 }
这段指明:请求实际使用的Host默认为空。如果req.Host字段不为空,且不与URL的Host相同,会使用req.Host
// If the caller specified a custom Host header and the redirect location is relative, preserve the Host header through the redirect. // 如果调用者指定了自定义的 Host 标头并且重定向位置是相对路径的话,通过重定向保留该 Host 标头。
那么我们来看看req.Host字段是什么
// For server requests, Host specifies the host on which the // URL is sought. For HTTP/1 (per RFC 7230, section 5.4), this // is either the value of the "Host" header or the host name // given in the URL itself. For HTTP/2, it is the value of the // ":authority" pseudo-header field. // It may be of the form "host:port". For international domain // names, Host may be in Punycode or Unicode form. Use // golang.org/x/net/idna to convert it to either format if // needed. // To prevent DNS rebinding attacks, server Handlers should // validate that the Host header has a value for which the // Handler considers itself authoritative. The included // ServeMux supports patterns registered to particular host // names and thus protects its registered Handlers. // // For client requests, Host optionally overrides the Host // header to send. If empty, the Request.Write method uses // the value of URL.Host. Host may contain an international // domain name. Host string
这段注释主要解释了在 Go 语言中如何处理请求的 Host 标头。在服务器请求中,Host 指定要查找 URL 的主机,可能是 Host 标头的值或 URL 本身中给定的主机名。对于客户端请求,Host 可以选择性地覆盖要发送的 Host 标头,如果为空,则使用 URL.Host 的值。此外,还提到了国际化域名的处理和防止 DNS 重新绑定攻击的注意事项。
这基本跟我们上文的结论相互印证了,至此我们搞清楚了为什么Header里面的Host不生效:因为Do使用HTTP Request里面的Host字段,且不是Header里面的Host键对应值
怎么解决
显然,指明 req.Host 是一个较好的方案
req.Host = "www.example.com"
至此我们解决了这个问题
我们还能知道些什么
关于 issue #22233
if req.Host != "" && req.Host != req.URL.Host { // If the caller specified a custom Host header and the // redirect location is relative, preserve the Host header // through the redirect. See issue #22233. if u, _ := url.Parse(loc); u != nil && !u.IsAbs() { host = req.Host } }
我们注意到在这段代码中,提到了issue #22233,那么它到底是什么呢,我们一起来看看!
这个问题 #issue 22233 是2017年由 timonwong 提出的,当时版本是 go1.9.1 darwin/amd64
。问题内容是:客户端跟随重定向时不会保留 Host 标头 golang的一位维护者tombergan 响应了这个问题: 认为这绝对是个bug
但同时他也提出重定向时复制哪些header内容是没有一个较好的确切的指导的。
Parent: 645c661a (cmd/compile/internal/syntax: factor out list parsing) Author: Tom Bergan <tombergan@google.com> AuthorDate: 2017-10-13 15:56:37 -0700 Commit: Tom Bergan <tombergan@google.com> CommitDate: 2017-10-16 17:44:26 +0000 net/http: preserve Host header following a relative redirect If the client sends a request with a custom Host header and receives a relative redirect in response, the second request should use the same Host header as the first request. However, if the response is an abolute redirect, the Host header should not be preserved. See further discussion on the issue tracker. Fixes #22233 Change-Id: I8796e2fbc1c89b3445e651f739d5d0c82e727c14 Reviewed-on: https://go-review.googlesource.com/70792 Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com> Run-TryBot: Joe Tsai <thebrokentoaster@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org>
最终于2017-10-16这次提交中他修复了这个问题,从邮箱看这位大神应该是google的一位员工。代码改动如下:
至此我们了解了有关于 issue #22233
的全部
关于 Host 是什么
在前文中,Request的Host属性的注释中提到: Host指定了正在寻找的主机。
// For server requests, Host specifies the host on which the // URL is sought. For HTTP/1 (per RFC 7230, section 5.4), this // is either the value of the "Host" header or the host name // given in the URL itself. For HTTP/2, it is the value of the // ":authority" pseudo-header field.
从这里面提到的 RFC 7230
,section 5.4
可以看到
① Host提供目标URI的主机、端口信息,使服务器在单个IP地址上可以根据不同的主机名提供不同的服务和资源。(比如单机部署多个网站)
② HTTP/1.1必须发送Host字段。当代理服务接受到absolute-form形式的请求时,忽略Host字段,取请求中的主机信息。转发请求的时候需要基于接收的请求重新生成Host,而不是转发接受到的Host。(URL里面的主机信息优先级高于Host字段。转发请求的时候Host字段不透传)
③ Host本身可以任意修改,因此如果依赖Host字段进行代理转发、缓存密钥、身份验证等,需要先行校验Host值的合法性,避免Host头攻击
④ 对于缺少或者有多个Host字段的HTTP/1.1请求消息,服务器需要返回400 Bad Request 状态码 (Host字段有且只能有一个)
至此,我们弄明白了Host是什么
// example: www.example.org or www.example.org:8080 Host = uri-host [":" port];
以上就是在Golang中正确的修改HTTPRequest的Host的操作方法的详细内容,更多关于Golang修改HTTPRequest的Host的资料请关注脚本之家其它相关文章!