[译] 初学者须要了解的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的文章。

相关文章
相关标签/搜索