Go?net?http超時(shí)應(yīng)用場(chǎng)景全面詳解
一、前言
在Go中編寫(xiě)HTTP服務(wù)器或客戶端時(shí),超時(shí)是最容易出錯(cuò)、最微妙的事情之一:有很多選擇,錯(cuò)誤在很長(zhǎng)一段時(shí)間內(nèi)都不會(huì)產(chǎn)生任何后果,直到網(wǎng)絡(luò)出現(xiàn)故障,進(jìn)程掛起。
二、超時(shí)時(shí)間
2.1 SetDeadline
首先,您需要了解Go為實(shí)現(xiàn)超時(shí)而公開(kāi)的網(wǎng)絡(luò)原語(yǔ):Deadlines。
由net.Conn使用Set[Read|Write]Deadline(time.time)方法公開(kāi),Deadlines是一個(gè)絕對(duì)時(shí)間,當(dāng)達(dá)到該時(shí)間時(shí),所有I/O操作都會(huì)失敗并出現(xiàn)超時(shí)錯(cuò)誤。
Deadlines不是超時(shí)。一旦設(shè)置,它們將永遠(yuǎn)有效(或直到下一次調(diào)用SetDeadline),無(wú)論在此期間是否以及如何使用連接。因此,要使用SetDeadline構(gòu)建超時(shí),您必須在每次讀/寫(xiě)操作之前調(diào)用它。
您可能不想自己調(diào)用SetDeadline,而是讓net/http使用其更高級(jí)別的超時(shí)為您調(diào)用它。但是,請(qǐng)記住所有超時(shí)都是根據(jù)Deadlines實(shí)現(xiàn)的,因此它們不會(huì)在每次發(fā)送或接收數(shù)據(jù)時(shí)重置。
2.2 Server Timeouts
因此你想在互聯(lián)網(wǎng)上公開(kāi)Go的帖子發(fā)現(xiàn)更多關(guān)于服務(wù)器超時(shí)的信息,特別是關(guān)于HTTP/2和Go 1.7錯(cuò)誤的信息:

對(duì)于暴露在Internet上的HTTP服務(wù)器來(lái)說(shuō),強(qiáng)制客戶端連接超時(shí)是至關(guān)重要的。否則,速度非常慢或正在消失的客戶端可能會(huì)泄漏文件描述符,并最終導(dǎo)致以下情況:
http: Accept error: accept tcp [::]:80: accept4: too many open files; retrying in 5ms
在http.Server中公開(kāi)了兩個(gè)超時(shí):ReadTimeout和WriteTimeout。您可以通過(guò)顯式使用服務(wù)器來(lái)設(shè)置它們:
srv := &http.Server{
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
log.Println(srv.ListenAndServe())ReadTimeout涵蓋了從接受連接到完全讀取請(qǐng)求正文的時(shí)間(如果您確實(shí)讀取了正文,否則到標(biāo)頭末尾)。它是通過(guò)在Accept之后立即調(diào)用SetReadDeadline在net/http中實(shí)現(xiàn)的。
WriteTimeout通常通過(guò)在readRequest結(jié)束時(shí)調(diào)用SetWriteDeadline來(lái)覆蓋從請(qǐng)求標(biāo)頭讀取結(jié)束到響應(yīng)寫(xiě)入結(jié)束的時(shí)間(也稱為ServeHTTP的生存期)。
但是,當(dāng)連接是HTTPS時(shí),在Accept之后會(huì)立即調(diào)用SetWriteDeadline,以便它也覆蓋作為TLS握手一部分寫(xiě)入的數(shù)據(jù)包。令人煩惱的是,這意味著(僅在這種情況下)WriteTimeout最終包括頭讀取和第一個(gè)字節(jié)等待。
當(dāng)你處理不受信任的客戶端和/或網(wǎng)絡(luò)時(shí),你應(yīng)該設(shè)置這兩個(gè)超時(shí),這樣客戶端就不會(huì)因?yàn)閷?xiě)或讀速度慢而中斷連接。
最后是http.TimeoutHandler。它不是Server參數(shù),而是一個(gè)限制ServeHTTP調(diào)用最長(zhǎng)持續(xù)時(shí)間的Handler包裝器。它的工作方式是緩沖響應(yīng),如果超過(guò)最后期限,則發(fā)送504網(wǎng)關(guān)超時(shí)。請(qǐng)注意,它在1.6中被提出,在1.6.2中。
三、http.ListenAndServe 做的是錯(cuò)誤的
順便說(shuō)一句,這意味著繞過(guò)http.Server的包級(jí)便利功能,如http.ListenAndServe、http.Listen AndServeTLS和http.Serve,不適合公共Internet服務(wù)器。
這些函數(shù)將Tmeouts保留為默認(rèn)的off值,無(wú)法啟用它們,因此如果使用它們,很快就會(huì)出現(xiàn)連接泄漏和文件描述符用完的情況。我至少犯過(guò)六次這個(gè)錯(cuò)誤。
相反,使用ReadTimeout和WriteTimeout創(chuàng)建一個(gè)http.Server實(shí)例,并使用其相應(yīng)的方法,就像上面幾段中的示例一樣。
3.1 streaming
非常令人惱火的是,沒(méi)有辦法從ServeHTTP訪問(wèn)底層的net.Conn,因此打算流式傳輸響應(yīng)的服務(wù)器被迫取消設(shè)置WriteTimeout(這也可能是它們默認(rèn)為0的原因)。這是因?yàn)槿绻麤](méi)有net.Conne訪問(wèn),就無(wú)法在每次寫(xiě)入之前調(diào)用SetWriteDeadline來(lái)實(shí)現(xiàn)適當(dāng)?shù)目臻e(而不是絕對(duì))超時(shí)。
此外,無(wú)法取消被阻止的ResponseWriter.Write,因?yàn)闆](méi)有記錄ResponseWriter.Close(您可以通過(guò)接口升級(jí)訪問(wèn))來(lái)取消阻止并發(fā)寫(xiě)入。因此,也沒(méi)有辦法用Timer手動(dòng)構(gòu)建超時(shí)。
可悲的是,這意味著流媒體服務(wù)器無(wú)法真正保護(hù)自己免受慢速閱讀客戶端的攻擊。
3.2 Client Timeouts
客戶端超時(shí)可能更簡(jiǎn)單,也可能更復(fù)雜,這取決于您使用的超時(shí),但對(duì)于防止資源泄漏或陷入困境同樣重要。
最容易使用的是http.Client的Timeout字段。它涵蓋了整個(gè)交換,從Dial(如果不重用連接)到讀取主體。
c := &http.Client{
Timeout: 15 * time.Second,
}
resp, err := c.Get("https://blog.filippo.io/")與上面的服務(wù)器端案例一樣,包級(jí)別的函數(shù)(如http.Get請(qǐng)求客戶端在沒(méi)有超時(shí)的情況下使用,因此在開(kāi)放式互聯(lián)網(wǎng)上使用是危險(xiǎn)的。
為了進(jìn)行更精細(xì)的控制,您可以設(shè)置許多其他更具體的超時(shí)
- net.Dialer.Timeout限制建立TCP連接所花費(fèi)的時(shí)間(如果需要新的連接)。
- http.Transport.TLS握手超時(shí)限制執(zhí)行TLS握手所花費(fèi)的時(shí)間
- http.Transport.ResponseHeaderTimeout限制讀取響應(yīng)標(biāo)頭所花費(fèi)的時(shí)間。
- http.Transport.ExpectContinueTimeout限制客戶端在發(fā)送包含Expect:100 continue的請(qǐng)求標(biāo)頭和接收發(fā)送正文的批準(zhǔn)之間等待的時(shí)間。
c := &http.Client{
Transport: &http.Transport{
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
}據(jù)我所知,沒(méi)有辦法具體限制發(fā)送請(qǐng)求所花費(fèi)的時(shí)間。讀取請(qǐng)求正文所花費(fèi)的時(shí)間可以通過(guò)time.Timer手動(dòng)控制,因?yàn)樗l(fā)生在Client方法返回之后(有關(guān)如何取消請(qǐng)求,請(qǐng)參閱下文)。
最后,1.7中新增了http.Transport.IdleConnTimeout。它不控制客戶端請(qǐng)求的阻塞階段,而是控制空閑連接在連接池中保持的時(shí)間。
請(qǐng)注意,客戶端默認(rèn)情況下會(huì)遵循重定向。http.Client.Timeout包括重定向后花費(fèi)的所有時(shí)間,而細(xì)粒度超時(shí)是針對(duì)每個(gè)請(qǐng)求的,因?yàn)閔ttp.Transport是一個(gè)沒(méi)有重定向概念的較低級(jí)別系統(tǒng)。
3.3 Cancel and Context
nethttp提供了兩種取消客戶端請(qǐng)求的方法:request.cancel和1.7中新增的Context。
Request.Cancel是一個(gè)可選通道,當(dāng)設(shè)置并關(guān)閉時(shí),會(huì)導(dǎo)致請(qǐng)求中止,就像達(dá)到Request.Timeout一樣。(它們實(shí)際上是通過(guò)相同的機(jī)制實(shí)現(xiàn)的,在寫(xiě)這篇文章時(shí),我在1.7中發(fā)現(xiàn)了一個(gè)錯(cuò)誤,所有取消都會(huì)作為超時(shí)錯(cuò)誤返回。)
我們可以使用Request.Cancel和time.Timer來(lái)構(gòu)建一個(gè)更精細(xì)的超時(shí),允許流式傳輸,每次我們成功從Body讀取一些數(shù)據(jù)時(shí)都會(huì)將截止日期向后推:
package main
import (
"io"
"io/ioutil"
"log"
"net/http"
"time"
)
func main() {
c := make(chan struct{})
timer := time.AfterFunc(5*time.Second, func() {
close(c)
})
// Serve 256 bytes every second.
req, err := http.NewRequest("GET", "http://httpbin.org/range/2048?duration=8&chunk_size=256", nil)
if err != nil {
log.Fatal(err)
}
req.Cancel = c
log.Println("Sending request...")
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
log.Println("Reading body...")
for {
timer.Reset(2 * time.Second)
// Try instead: timer.Reset(50 * time.Millisecond)
_, err = io.CopyN(ioutil.Discard, resp.Body, 256)
if err == io.EOF {
break
} else if err != nil {
log.Fatal(err)
}
}
}在上面的例子中,我們?cè)谡?qǐng)求的Do階段設(shè)置了5秒的超時(shí),但隨后讀取正文,每次超時(shí)2秒??梢杂肋h(yuǎn)這樣流媒體,而不會(huì)有陷入困境的風(fēng)險(xiǎn)。如果在超過(guò)2秒的時(shí)間內(nèi)沒(méi)有收到正文數(shù)據(jù),那么io.CopyN將返回net/http:requestcancelled。
在1.7中,上下文包升級(jí)為標(biāo)準(zhǔn)庫(kù)。關(guān)于上下文有很多需要學(xué)習(xí)的地方,但就目的而言,您應(yīng)該知道它們會(huì)取代和棄用Request.Cancel。
要使用Contexts取消請(qǐng)求,我們只需獲取一個(gè)新的Context及其帶有Context.WithCancel的cancel()函數(shù),并使用request.WithContext創(chuàng)建一個(gè)綁定到它的request。當(dāng)我們想取消請(qǐng)求時(shí),我們通過(guò)調(diào)用cancel來(lái)取消Context:
ctx, cancel := context.WithCancel(context.TODO())
timer := time.AfterFunc(5*time.Second, func() {
cancel()
})
req, err := http.NewRequest("GET", "http://httpbin.org/range/2048?duration=8&chunk_size=256", nil)
if err != nil {
log.Fatal(err)
}
req = req.WithContext(ctx)以上就是Go net http超時(shí)應(yīng)用場(chǎng)景全面詳解的詳細(xì)內(nèi)容,更多關(guān)于Go net http超時(shí)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
go語(yǔ)言實(shí)現(xiàn)通過(guò)FTP庫(kù)自動(dòng)上傳web日志
這篇文章主要介紹了go語(yǔ)言實(shí)現(xiàn)通過(guò)FTP庫(kù)自動(dòng)上傳web日志,非常簡(jiǎn)單實(shí)用,需要的小伙伴快來(lái)參考下吧。2015-03-03
Golang標(biāo)準(zhǔn)庫(kù)time包日常用法小結(jié)
本文主要介紹了Golang標(biāo)準(zhǔn)庫(kù)time包日常用法小結(jié),可以通過(guò)它們來(lái)獲取當(dāng)前時(shí)間、創(chuàng)建指定時(shí)間、解析時(shí)間字符串、控制時(shí)間間隔等操作,感興趣的可以了解一下2023-11-11
go語(yǔ)言計(jì)算兩個(gè)時(shí)間的時(shí)間差方法
這篇文章主要介紹了go語(yǔ)言計(jì)算兩個(gè)時(shí)間的時(shí)間差方法,涉及Python操作時(shí)間的技巧,需要的朋友可以參考下2015-03-03
如何解析golang中Context在HTTP服務(wù)中的角色
這篇文章主要介紹了如何解析golang中Context在HTTP服務(wù)中的角色問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
基于Go語(yǔ)言實(shí)現(xiàn)應(yīng)用IP防火墻
在公司里面經(jīng)常會(huì)聽(tīng)到某應(yīng)用有安全漏洞問(wèn)題,沒(méi)有做安全加固,IP防火墻就是一個(gè)典型的安全加固解決方案,下面我們就來(lái)學(xué)習(xí)一下如何使用go語(yǔ)言實(shí)現(xiàn)IP防火墻吧2023-11-11

