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

如何在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)
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥50 adb连接不到手机是怎么回事?
  • ¥15 vs2022无法联网
  • ¥15 TCP的客户端和服务器的互联
  • ¥15 VB.NET操作免驱摄像头
  • ¥15 笔记本上移动热点开关状态查询
  • ¥85 类鸟群Boids——仿真鸟群避障的相关问题
  • ¥15 CFEDEM自带算例错误,如何解决?
  • ¥15 有没有会使用flac3d软件的家人
  • ¥20 360摄像头无法解绑使用,请教解绑当前账号绑定问题,
  • ¥15 docker实践项目