Go

On the HTTP client side, how does context interact with http.Client.Timeout and response bodies?

Question 278MediumGo 1.22 to 1.25

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

All 35 Context questions