What role does context.Context play in an HTTP handler?
Quick Answer
Every incoming *http.Request carries a context.Context, accessible via r.Context(), that the server automatically cancels when the client disconnects or the request completes — this lets a handler propagate cancellation down into any slow downstream work (a database query, an outbound API call) so that work stops promptly instead of continuing pointlessly after nobody's waiting for the result anymore. Handlers commonly derive a timeout from the request context (context.WithTimeout(r.Context(), 2*time.Second)) to bound how long a single request is allowed to take, and pass that derived context into every downstream call that accepts one, so a slow dependency can't hang the whole request indefinitely.
Detailed Answer
Request cancellation is one of the most practically important uses of context.Context in real Go services.
The request's built-in context
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() // automatically cancelled if the client disconnects
result, err := slowDatabaseQuery(ctx)
if err != nil {
if errors.Is(err, context.Canceled) {
return // client already gone, don't bother writing a response
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(result)
}
If the client closes the connection (say, a user navigates away mid-request), r.Context()'s Done() channel closes automatically. Any downstream function that respects context cancellation, like a database driver checking ctx.Done(), can stop its work immediately instead of finishing a query nobody will ever read the result of.
Bounding a request with a timeout
func handler(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
result, err := slowDatabaseQuery(ctx)
if err != nil {
http.Error(w, "request timed out or failed", http.StatusGatewayTimeout)
return
}
json.NewEncoder(w).Encode(result)
}
This caps how long slowDatabaseQuery is allowed to run, regardless of how slow the underlying database happens to be at that moment, protecting the handler (and the server's limited resources) from a single slow dependency.
Why this cancellation is automatic
Go's HTTP server monitors the underlying connection and cancels the request's context the moment it detects the client is gone. Your handler never needs to poll for this explicitly. Passing that same context down through every layer of your call stack — database calls, outbound HTTP requests, anything accepting a context.Context — is what makes this cancellation take effect throughout the whole request, not just at the handler's top level.