在Golang中正确的修改HTTPRequest的Host的操作方法

 更新时间:2023年12月31日 08:33:09   作者:Kearth  
我们工作中经常需要通过HTTP请求Server的服务,比如脚本批量请求接口跑数据,由于一些网关策略,部分Server会要求请求中Header里面附带Host参数,所以本文给大家介绍了如何在Golang中正确的修改HTTPRequest的Host,需要的朋友可以参考下

背景

我们工作中经常需要通过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的资料请关注脚本之家其它相关文章!

相关文章

  • Golang实现IP地址转整数的方法详解

    Golang实现IP地址转整数的方法详解

    在 Go 语言中,将 IP 地址转换为整数涉及到解析 IP 地址并处理其字节表示,本文给大家介绍了Golang实现IP地址转整数的方法,文中有详细的代码示例供大家参考,需要的朋友可以参考下
    2024-02-02
  • Go语言格式化动词使用详解

    Go语言格式化动词使用详解

    这篇文章主要介绍了Go语言格式化动词使用详解的相关资料,需要的朋友可以参考下
    2023-08-08
  • Golang实践笔录之读取yaml配置文件

    Golang实践笔录之读取yaml配置文件

    YAML是YAML Ain't a Markup Language的缩写,YAML不是一种标记语言,相比JSON格式的方便,这篇文章主要给大家介绍了关于Golang实践笔录之读取yaml配置文件的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-01-01
  • 一些关于Go程序错误处理的相关建议

    一些关于Go程序错误处理的相关建议

    错误处理在每个语言中都是一项重要内容,众所周知,通常写程序时遇到的分为异常与错误两种,Golang中也不例外,这篇文章主要给大家介绍了一些关于Go程序错误处理的相关建议,需要的朋友可以参考下
    2021-09-09
  • golang基础之字符串与int、int64类型互相转换

    golang基础之字符串与int、int64类型互相转换

    这篇文章主要给大家介绍了关于golang基础之字符串与int、int64类型互相转换的相关资料,在Go语言中string转int是一项常见的操作,需要的朋友可以参考下
    2023-07-07
  • golang 如何自动下载所有依赖包

    golang 如何自动下载所有依赖包

    这篇文章主要介绍了golang 自动下载所有依赖包的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04
  • 探索Golang实现Redis持久化AOF实例

    探索Golang实现Redis持久化AOF实例

    这篇文章主要为大家介绍了Golang实现Redis持久化AOF实例探索,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-01-01
  • 浅谈GoLang几种读文件方式的比较

    浅谈GoLang几种读文件方式的比较

    这篇文章主要介绍了浅谈GoLang几种读文件方式的比较,一般来说常用的有三种。使用Read加上buffer,使用bufio库和ioutil 库,非常具有实用价值,需要的朋友可以参考下
    2019-01-01
  • 深入理解Golang之http server的实现

    深入理解Golang之http server的实现

    这篇文章主要介绍了深入理解Golang之http server的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-11-11
  • golang 切片的三种使用方式及区别的说明

    golang 切片的三种使用方式及区别的说明

    这篇文章主要介绍了golang 切片的三种使用方式及区别的说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04

最新评论