[譯] 初學者須要瞭解的Go語言中的HTTP timeout

原文連接 itnext.io/http-reques…golang

​ 對於提升分佈式系統的可用性,請求超時是很是重要的一個部分,當系統某個部分出現故障的時超時機制能夠下降故障對整個分佈式系統的影響,就以下面這條twitter中提到的。json

問題

在go語言中應該如何合理的模擬一個504 http.StatusGatewayTimeout響應呢?bash

以前在開發一個OAuth token受權功能的時候,我曾試着用httptest去模擬服務端超時並返回504 http.StatusGatewayTimeout響應,然而我實現的效果倒是客戶端因爲沒有在設定的時間內獲得響應而超時退出,而不是服務端返回了504的status code。如同大多數,當時我像下面這樣使用標準庫的HTTP包去建立一個client對象並指定timeout屬性:負載均衡

client := http.Client{Timeout: 5 * time.Second}
複製代碼

須要發起http請求時,建立上面這樣一個http client對象看起來是一個很是簡單和直接的方式。然而不少關於請求超時的細節被忽視了,包括客戶端超時、服務端超時和負載均衡器的超時。分佈式

客戶端超時

在客戶端,http請求超時有多種不一樣的定義方式,取決於你關注整個請求-響應週期的那個部分。具體說來,一個完整的請求-響應週期由Dialer(三次握手), TLS握手, 請求頭及請求體的生成和發送,響應頭及響應體的接收。除了定義一個完整的請求-響應週期的超時時間以外,go語言還支持定義這個週期的某個組成部分的超時時間,有以下三個經常使用的方式:ide

  • http.Client
  • context
  • http.Transport

http.Client

經過http.Client能夠定義從三次握手(Dialer)到接收到響應體的一個完整的請求-響應週期的超時時間。http.Client結構有一個可選的類型爲time.DurationTimeout字段ui

client := http.Client{Timeout: 5 * time.Second}
複製代碼

Context

go語言的context包提供了WithTimeout, WithDeadline, WithCancel三個實用的方法分別去實現具備超時時間的,具備過時時間的和能夠手動取消的http請求。使用context包的WithTimeout方法,配合上http.Request對象的WithContext方法,咱們能夠控制從請求發送到到手響應之間超時時間(不包括TCP三次握手和TLS握手的耗時):url

ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
req, err := http.NewRequest("GET", url, nil)
if err != nil {
    t.Error("Request error", err)
}

resp, err := http.DefaultClient.Do(req.WithContext(ctx))
複製代碼

http.Transport

經過使用自定義的http.Transport並指定DialContext屬性來建立http.Client對象,能夠控制Dialer的超時時間(即三次握手的超時時間):spa

transport := &http.Transport{
    DialContext: (&net.Dialer{   
        Timeout: timeout,
    }).DialContext,
}
client := http.Client{Transport: transport}
複製代碼

解決方案

基於上面的問題分析和可選方案,我嘗試經過context.WithTimeout來控制 http.Request的超時時間。然而獲得了以下的error:code

client_test.go:40: Response error Get http://127.0.0.1:49597: context deadline exceeded
複製代碼

這並無解決個人問題,由於我想實現服務端返回504 http.StatusGatewayTimout的響應。

服務端超時

上述在客戶端使用context.WithTimeout()的方案,當設定的時間內沒有完成請求-響應時,客戶端發起http請求的方法終止而且返回了一個error,而不是我想要的服務端返回了504的http status code。

經過下面的方式可讓httptest server每次都返回超時的狀態碼:

httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request){
    w.WriteHeader(http.StatusGatewayTimeout)
}))
複製代碼

然而若是想讓服務端在處理客戶端請求超時時返回504 status code,咱們能夠在服務端程序裏用http.TimeoutHandler去裝飾一下本來的handler來實現:

func TestClientTimeout(t *testing.T) {
    handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        d := map[string]interface{}{
            "id":    "12",
            "scope": "test-scope",
        }

        time.Sleep(100 * time.Millisecond) //<- Any value > 20ms
        b, err:= json.Marshal(d)
        if err != nil {
            t.Error(err)
        }
        io.WriteString(w, string(b))
        w.WriteHeader(http.StatusOK)
    })

    backend := httptest.NewServer(http.TimeoutHandler(handlerFunc, 20*time.Millisecond, "server timeout"))

    url := backend.URL
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        t.Error("Request error", err)
        return
    }

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        t.Error("Response error", err)
        return
    }

    defer resp.Body.Close()
}
複製代碼

對於剛接觸go語言的gopher來講,理解這些上層的http timeout的工做原理很是有用!若是你想了解更多go語言中關於http timeout的細節,一直要讀一下這篇來自Cloudflare的文章。

相關文章
相關標籤/搜索