Example: HTTP Client Cancel a Request
A request can take longer than the caller is willing to wait.
In Go, the standard way to stop an outgoing HTTP request is to attach a context.Context to the request and cancel that context when the operation should stop.
This example is part of the Go HTTP client examples on gobase.net.
Go version: This article targets modern Go versions (Go 1.25+).
Quick Example
Use a request context with a short timeout:
The request is expected to fail with an error similar to:
The exact error text is less important than the behavior: the request carries a context, and the context deadline cancels the operation.
For reusable production code, prefer an explicit http.Client with appropriate timeout and transport settings rather than relying on http.DefaultClient.
Core Pattern
Create a context, attach it to the request, and cancel it when the operation is finished or should stop:
For an existing request, WithContext returns a shallow copy with the new context; it does not modify the original request:
For new requests, http.NewRequestWithContext is usually clearer.
For a timeout relative to the current time, use context.WithTimeout:
When the operation must end at a known absolute time, use context.WithDeadline:
What Cancellation Actually Does
For an outgoing client request, the request context controls the lifetime of the operation, including:
- obtaining a connection
- sending the request
- reading response headers
- reading the response body
Canceling the context tells net/http to abort the in-flight operation. If a body read is blocked waiting for more data when cancellation takes effect, that read returns an error.
Cancellation cannot discard bytes that have already been received and buffered. Those bytes may still be returned by a read before the cancellation error becomes visible.
This distinction matters when reading response bodies:
Do not assume that cancellation means every pending or future read immediately returns without data.
Cancellation Is an Error Condition
A canceled request normally returns an error.
Use errors.Is when you need to distinguish context cancellation from other failures:
http.Client returns errors wrapped in *url.Error. On modern Go versions, errors.Is can be used to inspect the underlying context cancellation error.
If you need to determine whether a failure was a timeout, inspect the *url.Error:
This is useful when a timeout may come from either a request context or http.Client.Timeout.
If your function adds context, wrap the error once:
The caller can still use errors.Is and errors.As on the returned error.
Do not convert cancellation into nil unless the surrounding API explicitly defines cancellation as a successful outcome.
Request Context vs Client.Timeout
Both mechanisms can terminate an HTTP request, but they serve different purposes.
Client.Timeout includes connection establishment, redirects, response headers, and response body reading. The timer remains active after Do returns and can interrupt later body reads.
For example:
Then add a shorter operation-specific timeout:
These are independent limits. Whichever expires first terminates the request.
A useful production model is:
Cancel While Reading the Response Body
A request may succeed and return a response, while reading its body later takes much longer than expected.
If the body read is still in progress when the context is canceled, the read is interrupted and returns an error.
This is one reason a request-scoped context is preferable to treating HTTP cancellation as merely a connection-establishment concern.
Cancel From Another Goroutine
Sometimes cancellation comes from an external event: a user action, shutdown signal, another subsystem, or a parent operation.
A channel is a simple way to represent such an event:
The important part is that the goroutine has a defined lifecycle. A cancellation goroutine that waits forever after the request completes leaks.
In larger systems, prefer propagating an existing parent context rather than creating an independent cancellation channel for every request.
Cancel a Request From a Parent Context
The most useful pattern is usually not to create cancellation from scratch.
Pass the caller's context through:
The caller controls the lifetime:
This makes cancellation composable.
A service handler can pass its request context to a downstream HTTP call. A worker can pass the job context. A shutdown context can propagate through all active operations.
Don't Use Transport.CancelRequest
Older Go code may contain:
Do not use this pattern in new code.
Transport.CancelRequest is deprecated. Request contexts are the modern cancellation mechanism, and they also work with HTTP/2.
Use:
or:
Then cancel the context.
Common Mistakes
Canceling immediately by accident
This cancels the request before it starts:
Call cancel() when the operation should stop, not immediately after creating the context.
Forgetting defer cancel
Even when a timeout will eventually expire, call the returned cancellation function:
Calling cancel releases resources associated with the derived context.
Using Client.Timeout as only a connection timeout
Client.Timeout is not merely a connection timeout: it covers the entire HTTP operation, including response body reading.
If you need a different deadline for one operation, use its request context.
Swallowing cancellation
Avoid:
Unless cancellation is explicitly part of your API's successful outcome, callers need to know that the operation did not complete.
Assuming cancellation discards already-received data
Cancellation does not erase bytes already buffered by the HTTP stack.
If a read returns both data and an error, process the data before handling the error.
Adding a timeout but continuing after it expires
A context timeout is useful only if the operation actually observes the context.
For HTTP requests, attach the context to the request:
Do not merely create a context and then use an unrelated request.
Production Example
A small helper can make cancellation and response handling explicit:
The caller owns the operation deadline:
The helper does not create its own unrelated context. That keeps cancellation under the caller's control.
Cancellation and Resource Cleanup
Cancellation does not replace normal cleanup.
If Do succeeds, the caller still owns the response body:
If cancellation interrupts a body read, the deferred Close still runs.
Calling Close more than once on an http.Response.Body will not panic, though a subsequent call may return an error. The better rule is to keep one clear cleanup path and make ownership obvious.
Do not rely on cancellation as a substitute for closing files, response bodies, pipes, or other resources.
Cancellation Is Not a Retry Policy
A canceled request tells you that the operation stopped. It does not tell you whether retrying is safe.
For example:
A retry requires a separate decision:
For side-effecting requests, cancellation can leave the caller uncertain about whether the server received and processed the request.
Do not automatically retry simply because the client received a cancellation error.
Rule of Thumb
Key Takeaways
- Attach a
context.Contextto every HTTP operation whose lifetime matters. - Use
WithTimeoutfor relative deadlines andWithDeadlinefor absolute deadlines. Client.Timeoutis a whole-operation safety limit, not merely a connection timeout.- Cancellation applies to response body reads as well as
Do. - Process data returned with an error before handling the error.
- Use
errors.Isforcontext.Canceledanderrors.Aswithurl.Error.Timeout()when classifying timeout failures. - Cancellation does not replace resource cleanup.
- Do not use
Transport.CancelRequestin new code. - Cancellation and retry policy are separate decisions.
For more production-oriented Go examples, see gobase.net.