On the HTTP client side, how does context interact with http.Client.Timeout and response bodies?
Attach the context with http.NewRequestWithContext (or req.WithContext). Cancellation then applies to the whole exchange: dialing, TLS, writing the request, waiting for headers, and reading the body. If the context is canceled while you are still reading resp.Body, the next Read fails.
http.Client.Timeout is a separate, per-client limit covering the same span. The earlier of the two wins. Use Client.Timeout as a safety net and a per-request context for the actual budget.
func getJSON(ctx context.Context, c *http.Client, url string, v any) error {
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel() // must run AFTER the body is fully read, which defer ensures
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return err
}
resp, err := c.Do(req)
if err != nil {
return err // wraps context.DeadlineExceeded as *url.Error
}
defer resp.Body.Close()
return json.NewDecoder(resp.Body).Decode(v)
}
Classic bug: a helper that creates the timeout context, calls Do, and returns resp with defer cancel() inside. The body is canceled before the caller can read it, and the caller gets "context canceled" errors from Read. Either read the body inside the helper, or return the cancel function (or a wrapper that cancels on Body.Close).
More on Context
- Q276What is wrong with this handler? What error will the goroutine see?
- Q277When exactly is an http.Request's context canceled on the server side, and how do you use it correctly?
- Q279How does context cancellation work with database/sql, including transactions and rows?
- Q280Write a function that runs a blocking call with a context and returns early on cancellation. What goroutine leak must you avoid?
- Q281How should a long-running worker loop respect cancellation? Discuss select fairness and CPU-bound loops.
- Q282How does errgroup.WithContext use context, and what is a common bug with it?