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.