douqiao6563 2018-07-13 07:21
浏览 353
已采纳

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

I am trying to add a timeout option to a library in Go and have written the below test to mimic the behavior.

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)
}

But I always get below error, instead of http.StatusGatewayTimeout

=== RUN TestClientTimeout

--- FAIL: 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

panic: runtime error: invalid memory address or nil pointer dereference [recovered]

panic: runtime error: invalid memory address or nil pointer dereference

How do I fix this test, to return response with http.StatusGatewayTimeout(504) status code?

  • 写回答

1条回答 默认 最新

  • duanmeng9336 2018-07-13 07:48
    关注

    The reason you are getting the error context deadline exceeded is because the timeout on the context.Context of the client side of the request is shorter than the timeout in the server side handler. This means that the context.Context and therefore the client side http.DefaultClient gives up before any response is written.

    The panic: runtime error: invalid memory address... is because you are defer closing the body on the response, but the response is nil if an error is returned by the client.

    Here the response is nil, if the error is non-nil, change t.Error to 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()
    

    Getting to the real root of the problem, http.StatusGatewayTimeout is a server side timeout, which means that any timeouts created must be on the server side. The client http.DefaultClient will never create it's own server error response codes.

    To create server side timeouts you could wrap your handler function in an 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"))
    

    However, this will create a 503 - Service Unavailable error response code.

    The important thing to know about a 504 is that this is a "gateway" or "proxy" error response code. This means that it's very unlikely that this code comes out of the server that's actually handling the request. This code is more often seen from loadbalancers and proxies.

    504 GATEWAY TIMEOUT The server, while acting as a gateway or proxy, did not receive a timely response from an upstream server it needed to access in order to complete the request.

    You've already mocked the http.Server in the test method using httptest.NewServer(...) so you could just manually return the http.StatusGatewayTimeout response status in the handler function.

    handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusGatewayTimeout)
    })
    
    backend := httptest.NewServer(handlerFunc)
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥188 寻找能做王者评分提取的
  • ¥15 matlab用simulink求解一个二阶微分方程,要求截图
  • ¥30 matlab解优化问题代码
  • ¥15 写论文,需要数据支撑
  • ¥15 identifier of an instance of 类 was altered from xx to xx错误
  • ¥100 反编译微信小游戏求指导
  • ¥15 docker模式webrtc-streamer 无法播放公网rtsp
  • ¥15 学不会递归,理解不了汉诺塔参数变化
  • ¥15 基于图神经网络的COVID-19药物筛选研究
  • ¥30 软件自定义无线电该怎样使用