简体   繁体   中英

How to simulate 504 timeout error for http request inside a test in Go?

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?

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM