Go?net?http超時(shí)應(yīng)用場(chǎng)景全面詳解
一、前言
在Go中編寫HTTP服務(wù)器或客戶端時(shí),超時(shí)是最容易出錯(cuò)、最微妙的事情之一:有很多選擇,錯(cuò)誤在很長一段時(shí)間內(nèi)都不會(huì)產(chǎn)生任何后果,直到網(wǎng)絡(luò)出現(xiàn)故障,進(jìn)程掛起。
二、超時(shí)時(shí)間
2.1 SetDeadline
首先,您需要了解Go為實(shí)現(xiàn)超時(shí)而公開的網(wǎng)絡(luò)原語:Deadlines。
由net.Conn使用Set[Read|Write]Deadline(time.time)方法公開,Deadlines是一個(gè)絕對(duì)時(shí)間,當(dāng)達(dá)到該時(shí)間時(shí),所有I/O操作都會(huì)失敗并出現(xiàn)超時(shí)錯(cuò)誤。
Deadlines不是超時(shí)。一旦設(shè)置,它們將永遠(yuǎn)有效(或直到下一次調(diào)用SetDeadline),無論在此期間是否以及如何使用連接。因此,要使用SetDeadline構(gòu)建超時(shí),您必須在每次讀/寫操作之前調(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)上公開Go的帖子發(fā)現(xiàn)更多關(guān)于服務(wù)器超時(shí)的信息,特別是關(guān)于HTTP/2和Go 1.7錯(cuò)誤的信息:
對(duì)于暴露在Internet上的HTTP服務(wù)器來說,強(qiáng)制客戶端連接超時(shí)是至關(guān)重要的。否則,速度非常慢或正在消失的客戶端可能會(huì)泄漏文件描述符,并最終導(dǎo)致以下情況:
http: Accept error: accept tcp [::]:80: accept4: too many open files; retrying in 5ms
在http.Server中公開了兩個(gè)超時(shí):ReadTimeout和WriteTimeout。您可以通過顯式使用服務(wù)器來設(shè)置它們:
srv := &http.Server{ ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, } log.Println(srv.ListenAndServe())
ReadTimeout涵蓋了從接受連接到完全讀取請(qǐng)求正文的時(shí)間(如果您確實(shí)讀取了正文,否則到標(biāo)頭末尾)。它是通過在Accept之后立即調(diào)用SetReadDeadline在net/http中實(shí)現(xiàn)的。
WriteTimeout通常通過在readRequest結(jié)束時(shí)調(diào)用SetWriteDeadline來覆蓋從請(qǐng)求標(biāo)頭讀取結(jié)束到響應(yīng)寫入結(jié)束的時(shí)間(也稱為ServeHTTP的生存期)。
但是,當(dāng)連接是HTTPS時(shí),在Accept之后會(huì)立即調(diào)用SetWriteDeadline,以便它也覆蓋作為TLS握手一部分寫入的數(shù)據(jù)包。令人煩惱的是,這意味著(僅在這種情況下)WriteTimeout最終包括頭讀取和第一個(gè)字節(jié)等待。
當(dāng)你處理不受信任的客戶端和/或網(wǎng)絡(luò)時(shí),你應(yīng)該設(shè)置這兩個(gè)超時(shí),這樣客戶端就不會(huì)因?yàn)閷懟蜃x速度慢而中斷連接。
最后是http.TimeoutHandler。它不是Server參數(shù),而是一個(gè)限制ServeHTTP調(diào)用最長持續(xù)時(shí)間的Handler包裝器。它的工作方式是緩沖響應(yīng),如果超過最后期限,則發(fā)送504網(wǎng)關(guān)超時(shí)。請(qǐng)注意,它在1.6中被提出,在1.6.2中。
三、http.ListenAndServe 做的是錯(cuò)誤的
順便說一句,這意味著繞過http.Server的包級(jí)便利功能,如http.ListenAndServe、http.Listen AndServeTLS和http.Serve,不適合公共Internet服務(wù)器。
這些函數(shù)將Tmeouts保留為默認(rèn)的off值,無法啟用它們,因此如果使用它們,很快就會(huì)出現(xiàn)連接泄漏和文件描述符用完的情況。我至少犯過六次這個(gè)錯(cuò)誤。
相反,使用ReadTimeout和WriteTimeout創(chuàng)建一個(gè)http.Server實(shí)例,并使用其相應(yīng)的方法,就像上面幾段中的示例一樣。
3.1 streaming
非常令人惱火的是,沒有辦法從ServeHTTP訪問底層的net.Conn,因此打算流式傳輸響應(yīng)的服務(wù)器被迫取消設(shè)置WriteTimeout(這也可能是它們默認(rèn)為0的原因)。這是因?yàn)槿绻麤]有net.Conne訪問,就無法在每次寫入之前調(diào)用SetWriteDeadline來實(shí)現(xiàn)適當(dāng)?shù)目臻e(而不是絕對(duì))超時(shí)。
此外,無法取消被阻止的ResponseWriter.Write,因?yàn)闆]有記錄ResponseWriter.Close(您可以通過接口升級(jí)訪問)來取消阻止并發(fā)寫入。因此,也沒有辦法用Timer手動(dòng)構(gòu)建超時(shí)。
可悲的是,這意味著流媒體服務(wù)器無法真正保護(hù)自己免受慢速閱讀客戶端的攻擊。
3.2 Client Timeouts
客戶端超時(shí)可能更簡單,也可能更復(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)求客戶端在沒有超時(shí)的情況下使用,因此在開放式互聯(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ù)我所知,沒有辦法具體限制發(fā)送請(qǐng)求所花費(fèi)的時(shí)間。讀取請(qǐng)求正文所花費(fèi)的時(shí)間可以通過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è)沒有重定向概念的較低級(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í)際上是通過相同的機(jī)制實(shí)現(xiàn)的,在寫這篇文章時(shí),我在1.7中發(fā)現(xiàn)了一個(gè)錯(cuò)誤,所有取消都會(huì)作為超時(shí)錯(cuò)誤返回。)
我們可以使用Request.Cancel和time.Timer來構(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秒。可以永遠(yuǎn)這樣流媒體,而不會(huì)有陷入困境的風(fēng)險(xiǎn)。如果在超過2秒的時(shí)間內(nèi)沒有收到正文數(shù)據(jù),那么io.CopyN將返回net/http:requestcancelled。
在1.7中,上下文包升級(jí)為標(biāo)準(zhǔn)庫。關(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í),我們通過調(diào)用cancel來取消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語言實(shí)現(xiàn)通過FTP庫自動(dòng)上傳web日志
這篇文章主要介紹了go語言實(shí)現(xiàn)通過FTP庫自動(dòng)上傳web日志,非常簡單實(shí)用,需要的小伙伴快來參考下吧。2015-03-03Golang標(biāo)準(zhǔn)庫time包日常用法小結(jié)
本文主要介紹了Golang標(biāo)準(zhǔn)庫time包日常用法小結(jié),可以通過它們來獲取當(dāng)前時(shí)間、創(chuàng)建指定時(shí)間、解析時(shí)間字符串、控制時(shí)間間隔等操作,感興趣的可以了解一下2023-11-11go語言計(jì)算兩個(gè)時(shí)間的時(shí)間差方法
這篇文章主要介紹了go語言計(jì)算兩個(gè)時(shí)間的時(shí)間差方法,涉及Python操作時(shí)間的技巧,需要的朋友可以參考下2015-03-03如何解析golang中Context在HTTP服務(wù)中的角色
這篇文章主要介紹了如何解析golang中Context在HTTP服務(wù)中的角色問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03基于Go語言實(shí)現(xiàn)應(yīng)用IP防火墻
在公司里面經(jīng)常會(huì)聽到某應(yīng)用有安全漏洞問題,沒有做安全加固,IP防火墻就是一個(gè)典型的安全加固解決方案,下面我們就來學(xué)習(xí)一下如何使用go語言實(shí)現(xiàn)IP防火墻吧2023-11-11