go: net/http: ResponseController to manipulate per-request timeouts (and other behaviors)
This proposal seeks to address #16100 (no way of manipulating timeouts in Handler), and is inspired by https://github.com/golang/go/issues/16100#issuecomment-396690586.
HTTP handler timeouts are specified on a per-Server basis: ReadTimeout
, WriteTimeout
. It would be very useful to permit these timeouts (and possibly other options) to be overridden on a per-handler basis. For example, a handler which serves a long-running streaming response may want to extend the WriteTimeout
after each write, and will likely want a different WriteTimeout
than a handler serving a short response.
A problem is that we have no good place at the moment to add functions that adjust these timeouts. We might add methods to the ResponseWriter
implementation and access them via type assertions (as is done with the existing Flush
and Hijack
methods), but this proliferation of undiscoverable magic methods scales poorly and does not interact well with middleware which wraps the ResponseWriter
type.
Proposal: Add a new concrete ResponseController
type which provides additional per-request controls. (A ResponseWriter
writes the response, a ResponseController
provides additional controls.) This type will supersede the existing Flusher
and Hijacker
APIs.
// A ResponseController is used by an HTTP handler to control the response.
//
// A ResponseController may not be used after the Handler.ServeHTTP method has returned.
type ResponseController struct{}
// NewResponseController creates a ResponseController for a request.
//
// The Request must be the original value passed to the Handler.ServeHTTP method.
// The ResponseWriter must be the original value passed to the Handler.ServeHTTP method,
// or have an Unwrap() method returning the original ResponseWriter.
func NewResponseController(rw ResponseWriter, req *Request) *ResponseController
// Flush flushes buffered data to the client.
func (rc *ResponseController) Flush() error
// Hijack lets the caller take over the connection.
// See the Hijacker interface for details.
func (rc *ResponseController) Hijack() (net.Conn, *bufio.ReadWriter, error)
We additionally add the ability to set the read and write deadline on a per-request basis, via ResponseController
. These functions take a deadline rather than a timeout, for consistency with net.Conn
.
// SetReadDeadline sets the deadline for reading the entire request, including the body.
// Reads from the request body after the deadline has been exceeded will return an error.
// A zero value means no deadline.
//
// The read deadline may not be extended after it has been exceeded.
func (rc *ResponseController) SetReadDeadline(deadline time.Time) error
// SetWriteDeadline sets the deadline for writing the response.
// Writes to the response body after the deadline has been exceeded will not block,
// but may succeed if the data has been buffered.
// A zero value means no deadline.
//
// The write deadline may not be extended after it has been exceeded.
func (rc *ResponseController) SetWriteDeadline(deadline time.Time) error
The Handler returned by http.TimeoutHandler
currently receives a ResponseWriter
which does not implement the Flush
or Hijack
methods. This will not change under this proposal: The *ResponseController
for a TimeoutHandler
will return a not-implemented error from Flush
, Hijack
, SetWriteDeadline
, and SetReadDeadline
.
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Reactions: 24
- Comments: 31 (17 by maintainers)
Commits related to this issue
- net/http: add ResponseController and per-handler timeouts The ResponseController type provides a discoverable interface to optional methods implemented by ResponseWriters. c := http.NewResponseCont... — committed to golang/go by neild 2 years ago
- doc/go1.20: add release notes for net/http and net/http/httputil For #41773 For #41773 For #50465 For #51914 For #53002 For #53896 For #53960 For #54136 For #54299 Change-Id: I729d5eafc1940d5706f980... — committed to golang/go by neild 2 years ago
- all: enable disabled HTTP/2 tests Update net/http to enable tests that pass with the latest update to the vendored x/net. Update a few tests: Windows apparently doesn't guarantee that time.Since(ti... — committed to golang/go by neild 2 years ago
- net/http/httptest: add support for http.ResponseController to ResponseRecorder #54136 (implemented in Go 1.20) added the "http".ResponseController type, which allows manipulating per-request timeout... — committed to dunglas/go by dunglas a year ago
- net/http/httptest: add support for http.ResponseController to ResponseRecorder CL #54136 (implemented in Go 1.20) added the "http".ResponseController type, which allows manipulating per-request timeo... — committed to dunglas/go by dunglas a year ago
- net/http/httptest: add support for ResponseController to ResponseRecorder CL #54136 (implemented in Go 1.20) added the "http".ResponseController type, which allows manipulating per-request timeouts. ... — committed to dunglas/go by dunglas a year ago
This will be in 1.20.
No change in consensus, so accepted. 🎉 This issue now tracks the work of implementing the proposal. — rsc for the proposal review group
Based on the discussion above, this proposal seems like a likely accept. — rsc for the proposal review group
This proposal is for a mechanism to permit a handler to adjust the read and write deadlines for a request after the handler has been called.
Sample usage would be something like:
Excited to see progress on #16100!
One thing that I don’t see specified is whether a call to SetReadDeadline or SetWriteDeadline overrides the ReadTimeout or WriteTimeout. I would suggest yes, so that servers could be configured with safe defaults, and then the timeouts only relaxed after checking auth, looking at the path, etc.
Also, ReadHeaderTimeout covers the entire period before ResponseController is available, correct?
Hello, also need this for streaming connections, a couple more questions to clarify:
Thanks for the proposal.
IIUC this will unblock removing timeoutHandler in kubernetes completely which has been a source of many hard to debug races in the past (https://github.com/kubernetes/kubernetes/issues/105884).
The
http2.Transport
has aWriteByteTimeout
setting which sets the maximum amount of time to wait on any given write. I could see extending that to the HTTP/2 server and the HTTP/1 transport and server. We’d want to think through how that interacts with HTTP/2 health checks.You could also use
ResponseController.SetReadDeadline
andResponseController.SetWriteDeadline
to limit the time for reading/writing a single chunk on a long-lived connection, resetting the deadline after every read/write.Changes to the proposal:
*Request
parameter to NewResponseController. We don’t need it, and I haven’t been able to think of anything that would require it in the future.ResponseWriter
passed toNewResponseController
implements aFlush
,Hijack
, etc. method, then theResponseController
will call them.Correct.
net/http
doesn’t really have any facilities for treating a connection differently based on a request received on it. (Aside from hijacking the conn entirely and removing it from thenet/http
package’s control.)