在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的资料请关注脚本之家其它相关文章!

相关文章

  • go中slice浅拷贝和深拷贝的实现

    go中slice浅拷贝和深拷贝的实现

    本文主要介绍了go中slice浅拷贝和深拷贝的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-09-09
  • Go语言必知的5个核心知识点之init、路径、输出、切片、Map

    Go语言必知的5个核心知识点之init、路径、输出、切片、Map

    数组和切片是 Go 语言中常见的数据结构,很多刚刚使用 Go 的开发者往往会混淆这两个概念,当然还有其他的一些知识点,这篇文章主要介绍了Go语言必知的5个核心知识点之init、路径、输出、切片、Map的相关资料,需要的朋友可以参考下
    2026-05-05
  • Go语言使用net/http构建一个RESTful API的示例代码

    Go语言使用net/http构建一个RESTful API的示例代码

    Go的标准库net/http提供了构建Web服务所需的强大功能,虽然众多第三方框架(如 Gin、Echo)已经封装了很多功能,但如果你真正想理解 Go的底层Web服务机制,那么使用net/http实现一个原生的RESTful API 是最好的入门方式,需要的朋友可以参考下本文
    2025-08-08
  • 关于go语言编码需要放到src 文件夹下的问题

    关于go语言编码需要放到src 文件夹下的问题

    这篇文章主要介绍了go语言编码需要放到src 文件夹下的相关知识,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • 使用golang进行http,get或postJson请求

    使用golang进行http,get或postJson请求

    这篇文章主要为大家详细介绍了如何使用golang进行http,get或postJson请求,文中的示例代码简洁易懂,具有一定的借鉴价值,感兴趣的小伙伴可以了解一下
    2023-12-12
  • Go语言定时任务cron的设计与使用

    Go语言定时任务cron的设计与使用

    这篇文章主要为大家详细介绍了Go语言中定时任务cron的设计与使用,文中的示例代码讲解详细,对我们深入掌握Go语言有一定的帮助,需要的可以参考下
    2023-11-11
  • Go语言之结构体与方法

    Go语言之结构体与方法

    这篇文章主要介绍了Go语言之结构体与方法,结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。下面我们就一起来学习什么是Go语言之结构体
    2021-10-10
  • Go实现后台任务调度系统的实例代码

    Go实现后台任务调度系统的实例代码

    平常我们在开发API的时候,前端传递过来的大批数据需要经过后端处理,如果后端处理的速度快,前端响应就快,反之则很慢,影响用户体验,为了解决这一问题,需要我们自己实现后台任务调度系统,本文将介绍如何用Go语言实现后台任务调度系统,需要的朋友可以参考下
    2023-06-06
  • golang 如何获取pem格式RSA公私钥长度

    golang 如何获取pem格式RSA公私钥长度

    这篇文章主要介绍了golang 如何获取pem格式RSA公私钥长度操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • 更高效的GoLevelDB:shardingdb实现分片和并发读写操作

    更高效的GoLevelDB:shardingdb实现分片和并发读写操作

    这篇文章主要介绍了更高效的GoLevelDB:shardingdb实现分片和并发读写操作的相关资料,需要的朋友可以参考下
    2023-09-09

最新评论