Go

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 ServeHTTP method 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 new r down to next.ServeHTTP.
  • http.TimeoutHandler and Server.ReadTimeout/WriteTimeout are separate mechanisms. Only TimeoutHandler cancels 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

All 35 Context questions