How is context used in net/http on both server and client sides? What happens when a client disconnects mid-request?
Question 449MediumGo 1.22 to 1.25
Server: each request carries r.Context(), which is canceled when the client closes the connection, when an HTTP/2 stream is reset, or when ServeHTTP returns. Pass it to every downstream call (DB, RPC) so abandoned work stops. The handler goroutine itself is not killed — you must check ctx.Done() or rely on context-aware libraries.
Client: use http.NewRequestWithContext; cancellation aborts dialing, waiting for headers and reading the body.
func search(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
rows, err := db.QueryContext(ctx, "SELECT id FROM items WHERE name LIKE $1", r.URL.Query().Get("q"))
if err != nil {
switch {
case errors.Is(err, context.Canceled):
return // client went away; nobody to respond to
case errors.Is(err, context.DeadlineExceeded):
http.Error(w, "timeout", http.StatusGatewayTimeout)
default:
http.Error(w, "db error", http.StatusInternalServerError)
}
return
}
defer rows.Close()
// ...
}
For work that must outlive the request (audit log write), use context.WithoutCancel(r.Context()) (Go 1.21) to keep values but drop cancellation. Store request-scoped values with an unexported key type to avoid collisions: type ctxKey struct{}.
More on Standard Library, HTTP & Systems Design in Go
- Q447How does http.Client connection pooling work, and what are common mistakes with http.Client?
- Q448Why must you close resp.Body, and why is closing alone sometimes not enough?
- Q450What does this handler send to the client, and why?
- Q451Explain encoding/json struct tag behavior: omitempty, omitzero, "-", and ",string". What does this print?
- Q452What does this print? How do you preserve large integer precision when decoding unknown JSON?
- Q453You implemented MarshalJSON but it isn't called. Why? Explain receiver rules and the recursion trap.