When exactly is an http.Request's context canceled on the server side, and how do you use it correctly?
Question 277MediumGo 1.22 to 1.25
For incoming server requests, r.Context() is canceled when any of these happens:
- the client's connection closes;
- the request is canceled (for example an HTTP/2 RST_STREAM);
- the
ServeHTTPmethod returns.
It descends from http.Server.BaseContext (which defaults to Background) and ConnContext, and it carries values such as http.ServerContextKey and http.LocalAddrContextKey.
func report(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
defer cancel()
rows, err := db.QueryContext(ctx, `SELECT ... heavy query ...`)
if err != nil {
if errors.Is(err, context.Canceled) {
return // client left; nobody is reading the response
}
http.Error(w, "timeout or db error", http.StatusGatewayTimeout)
return
}
defer rows.Close()
// stream rows...
}
Notes:
- To add values in middleware, use
r = r.WithContext(ctx), which makes a shallow copy of the request, and pass the newrdown tonext.ServeHTTP. http.TimeoutHandlerandServer.ReadTimeout/WriteTimeoutare separate mechanisms. OnlyTimeoutHandlercancels the context.- Over HTTP/1.1, the server notices a disconnect only through its background read, so detection can be delayed while the handler is still reading a large request body.
More on Context
- Q275What does context.WithoutCancel do and when would you use it?
- Q276What is wrong with this handler? What error will the goroutine see?
- Q278On the HTTP client side, how does context interact with http.Client.Timeout and response bodies?
- 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.