在Golang中正確的修改HTTPRequest的Host的操作方法
背景
我們工作中經(jīng)常需要通過HTTP請(qǐng)求Server的服務(wù),比如腳本批量請(qǐng)求接口跑數(shù)據(jù)。在這個(gè)過程中,由于一些網(wǎng)關(guān)策略,部分Server會(huì)要求請(qǐng)求中Header里面附帶Host參數(shù)。這時(shí),我們可能會(huì)想到在Header里面直接賦值Host,比如這樣:
req.Header.Add("Host", "www.example.com") req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
但請(qǐng)求的過程中會(huì)發(fā)現(xiàn)明明設(shè)置了,接收方卻收不到這個(gè)Host。因此下面我會(huì)闡述為什么會(huì)這樣,以及如何正確的修改HTTPRequest的Host。
為什么會(huì)這樣
首先我們打印一下Header.Add
后的參數(shù)看看會(huì)怎樣
map[Host:[www.example.com]]
再排除世界上有鬼的情況下,我們可以合理分析出: Header.Add
環(huán)節(jié)既然成功了,那么Host一定是在實(shí)際請(qǐng)求HTTP前被替換了
由于下一段代碼就是client.Do,因此有理由懷疑這個(gè)操作在Do方法里面
// 發(fā)送請(qǐng)求并獲取響應(yīng) resp, err := client.Do(req) if err != nil { fmt.Println("發(fā)送請(qǐng)求失敗:", 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, } ... ... // 省略 }
這段指明:請(qǐng)求實(shí)際使用的Host默認(rèn)為空。如果req.Host字段不為空,且不與URL的Host相同,會(huì)使用req.Host
// If the caller specified a custom Host header and the redirect location is relative, preserve the Host header through the redirect. // 如果調(diào)用者指定了自定義的 Host 標(biāo)頭并且重定向位置是相對(duì)路徑的話,通過重定向保留該 Host 標(biāo)頭。
那么我們來看看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 語言中如何處理請(qǐng)求的 Host 標(biāo)頭。在服務(wù)器請(qǐng)求中,Host 指定要查找 URL 的主機(jī),可能是 Host 標(biāo)頭的值或 URL 本身中給定的主機(jī)名。對(duì)于客戶端請(qǐng)求,Host 可以選擇性地覆蓋要發(fā)送的 Host 標(biāo)頭,如果為空,則使用 URL.Host 的值。此外,還提到了國際化域名的處理和防止 DNS 重新綁定攻擊的注意事項(xiàng)。
這基本跟我們上文的結(jié)論相互印證了,至此我們搞清楚了為什么Header里面的Host不生效:因?yàn)镈o使用HTTP Request里面的Host字段,且不是Header里面的Host鍵對(duì)應(yīng)值
怎么解決
顯然,指明 req.Host 是一個(gè)較好的方案
req.Host = "www.example.com"
至此我們解決了這個(gè)問題
我們還能知道些什么
關(guān)于 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,那么它到底是什么呢,我們一起來看看!
這個(gè)問題 #issue 22233 是2017年由 timonwong 提出的,當(dāng)時(shí)版本是 go1.9.1 darwin/amd64
。問題內(nèi)容是:客戶端跟隨重定向時(shí)不會(huì)保留 Host 標(biāo)頭 golang的一位維護(hù)者tombergan 響應(yīng)了這個(gè)問題: 認(rèn)為這絕對(duì)是個(gè)bug
但同時(shí)他也提出重定向時(shí)復(fù)制哪些header內(nèi)容是沒有一個(gè)較好的確切的指導(dǎo)的。
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這次提交中他修復(fù)了這個(gè)問題,從郵箱看這位大神應(yīng)該是google的一位員工。代碼改動(dòng)如下:
至此我們了解了有關(guān)于 issue #22233
的全部
關(guān)于 Host 是什么
在前文中,Request的Host屬性的注釋中提到: Host指定了正在尋找的主機(jī)。
// 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提供目標(biāo)URI的主機(jī)、端口信息,使服務(wù)器在單個(gè)IP地址上可以根據(jù)不同的主機(jī)名提供不同的服務(wù)和資源。(比如單機(jī)部署多個(gè)網(wǎng)站)
② HTTP/1.1必須發(fā)送Host字段。當(dāng)代理服務(wù)接受到absolute-form形式的請(qǐng)求時(shí),忽略Host字段,取請(qǐng)求中的主機(jī)信息。轉(zhuǎn)發(fā)請(qǐng)求的時(shí)候需要基于接收的請(qǐng)求重新生成Host,而不是轉(zhuǎn)發(fā)接受到的Host。(URL里面的主機(jī)信息優(yōu)先級(jí)高于Host字段。轉(zhuǎn)發(fā)請(qǐng)求的時(shí)候Host字段不透傳)
③ Host本身可以任意修改,因此如果依賴Host字段進(jìn)行代理轉(zhuǎn)發(fā)、緩存密鑰、身份驗(yàn)證等,需要先行校驗(yàn)Host值的合法性,避免Host頭攻擊
④ 對(duì)于缺少或者有多個(gè)Host字段的HTTP/1.1請(qǐng)求消息,服務(wù)器需要返回400 Bad Request 狀態(tài)碼 (Host字段有且只能有一個(gè))
至此,我們弄明白了Host是什么
// example: www.example.org or www.example.org:8080 Host = uri-host [":" port];
以上就是在Golang中正確的修改HTTPRequest的Host的操作方法的詳細(xì)內(nèi)容,更多關(guān)于Golang修改HTTPRequest的Host的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang語言實(shí)現(xiàn)gRPC的具體使用
本文主要介紹了Golang語言實(shí)現(xiàn)gRPC的具體使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08Golang利用casbin實(shí)現(xiàn)權(quán)限驗(yàn)證詳解
Casbin是一個(gè)強(qiáng)大的、高效的開源訪問控制框架,其權(quán)限管理機(jī)制支持多種訪問控制模型,Casbin只負(fù)責(zé)訪問控制。本文將利用casbin實(shí)現(xiàn)權(quán)限驗(yàn)證功能,需要的可以參考一下2023-02-02Golang使用ReverseProxy實(shí)現(xiàn)反向代理的方法
本文介紹了如何使用Golang的ReverseProxy實(shí)現(xiàn)反向代理,包括源碼結(jié)構(gòu)解析和官方單機(jī)示例NewSingleHostReverseProxy,同時(shí)指出,若要實(shí)現(xiàn)負(fù)載均衡,需要自行開發(fā),還提供了一個(gè)簡單的HTTP服務(wù)用于測(cè)試,感興趣的朋友跟隨小編一起看看吧2024-09-09golang validator參數(shù)校驗(yàn)的實(shí)現(xiàn)
這篇文章主要介紹了golang validator參數(shù)校驗(yàn)的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10