如何在Go中的测试中模拟HTTP请求的504超时错误?

时间:2018-07-13 07:21:09

标签: http go timeout

我正在尝试在Go中的库中添加一个timeout选项,并编写了以下测试来模拟这种行为。

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

        time.Sleep(100 * time.Millisecond)
        e := json.NewEncoder(w)
        err := e.Encode(&d)
        if err != nil {
            t.Error(err)
        }
        w.WriteHeader(http.StatusOK)
    }))

    url := backend.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))
    if err != nil {
        t.Error("Response error", err)
    }

    defer resp.Body.Close()

    t.Log(">>>>>>>Response is: ", resp)
}

但是我总是遇到错误,而不是http.StatusGatewayTimeout

  

===运行TestClientTimeout

     

---失败:TestClientTimeout(0.05s)

client_test.go:37: Timestamp before req 2018-07-13 09:10:14.936898 +0200 CEST m=+0.002048937
client_test.go:40: Response error Get http://127.0.0.1:49597: context deadline exceeded
     

紧急:运行时错误:无效的内存地址或nil指针取消引用[已恢复]

     

紧急:运行时错误:无效的内存地址或nil指针取消引用

如何修复此测试,以返回状态为http.StatusGatewayTimeout(504)的响应?

1 个答案:

答案 0 :(得分:3)

收到错误context deadline exceeded的原因是,请求客户端的context.Context上的超时时间短于服务器端处理程序中的超时时间。这意味着context.Context以及客户端http.DefaultClient都因此在写入任何响应之前就放弃了。

panic: runtime error: invalid memory address...是因为您推迟在响应上关闭主体,但是如果客户端返回错误,则响应为nil

此处响应为nil,如果错误为非nil,请将t.Error更改为t.Fatal

resp, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
    // this should be t.Fatal, or don't do the body close if there's an error
    t.Error("Response error", err)
}

defer resp.Body.Close()

要真正解决问题,http.StatusGatewayTimeout是服务器端超时,这意味着创建的任何超时都必须在服务器端。客户端http.DefaultClient将永远不会创建自己的服务器错误响应代码。

要创建服务器端超时,可以将处理程序函数包装在http.TimeoutHandler中:

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

    time.Sleep(100 * time.Millisecond)
    e := json.NewEncoder(w)
    err := e.Encode(&d)
    if err != nil {
        t.Error(err)
    }
    w.WriteHeader(http.StatusOK)
})

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

但是,这将创建一个503 - Service Unavailable错误响应代码。

要了解504,重要的一点是这是一个“网关”或“代理”错误响应代码。这意味着此代码不太可能从实际处理请求的服务器中发出。从负载均衡器和代理中经常可以看到此代码。

  

504网关超时   该服务器虽然充当网关或代理,但未收到其为完成请求而需要访问的上游服务器的及时响应。

您已经使用http.Server在测试方法中模拟了httptest.NewServer(...),因此您可以在处理函数中手动返回http.StatusGatewayTimeout响应状态。

handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusGatewayTimeout)
})

backend := httptest.NewServer(handlerFunc)