How to use the http.ResponseController type
One of my favorite things about the recent Go 1.20 release is the new http.ResponseController
type, which brings with it three nice benefits:
- You can now override your server-wide read and write deadlines on a per request basis.
- The pattern for using the
http.Flusher
andhttp.Hijacker
interfaces is clearer and feels less hacky. No more type assertions necessary! - It makes it easier and safer to create and use custom
http.ResponseWriter
implementations.
The first two benefits are mentioned in the release notes, but the third one seems to have gone under the radar a bit... which is a shame, because it's very helpful!
Let's dive in a take a look.
Per-request deadlines
Go's http.Server
has ReadTimeout
and WriteTimeout
settings, which you can use to automatically close a HTTP connection if reading a request or writing response takes longer than a fixed amount of time. These settings are server-wide and apply to all requests, irrespective of the handler or URL.
With http.ResponseController
you can now use the SetReadDeadline()
and SetWriteDeadline()
methods to relax or tighten these settings on a per-request basis if you need too. For example:
This is particularly helpful in an application where you have a small number of handlers that need longer deadlines than all the others, for things like processing a file upload or carrying out a long-running operation.
A few other details to mention:
- If you set a short server-wide deadline, and that deadline is hit before you call
SetWriteDeadline()
orSetReadDeadline()
then they will have no effect. The server-wide deadline wins. - If your underlying
http.ResponseWriter
doesn't support setting per-request deadlines, then callingSetWriteDeadline()
orSetReadDeadline()
will return ahttp.ErrNotSupported
error. - You can effectively remove the server-wide deadline on a per-request basis by passing a zero-valued
time.Time
struct toSetWriteDeadline()
orSetReadDeadline()
. For example:
Flusher and Hijacker interfaces
The http.ResponseController
type also makes it slightly nicer to use the 'optional' http.Flusher
and http.Hijacker
interfaces. For example, before Go 1.20 you would use a code pattern like this this to flush response data to the client:
Now you can do this:
The pattern for hijacking a connection is similar:
Again, if your underlying http.ResponseWriter
doesn't support support flushing or hijacking, then calling Flush()
or Hijack()
on a http.ResponseController
will also return an http.ErrNotSupported
error.
Custom http.ResponseWriters
It's now also easier and safer to create and use custom http.ResponseWriter
implementations that still support flushing and hijacking.
It's probably easiest to explain how this works with an example, so let's look at the code for a custom http.ResponseWriter
implementation that records the HTTP status code of a response.
So here we've defined a custom statusResponseWriter
type, which embeds an existing http.ResponseWriter
and implements custom WriteHeader()
and Write()
methods to support the recording of the HTTP response status code.
But the important thing to notice here is the Unwrap()
method at the end, which returns the original embedded http.ResponseWriter
.
When you use the new http.ResponseController
type to to flush, hijack or set a deadline, it will call this Unwrap()
method to access the original http.ResponseWriter
. This is done recursively if necessary, so you can potentially layer multiple custom http.ResponseWriter
implementations on top of each other.
Let's look at a complete example, where we use this statusResponseWriter
in conjunction with some middleware to log response status codes, along with a handler that sends a 'normal' response and another that uses the new http.ResponseController
type to send a flushed response.
If you want, you can run this and try making requests to the /normal
and /flushed
endpoints:
You should see the response from the flushedHandler
in two parts, first the Write A...
part, then followed a second later by the Write B...
part.
And you should see that the statusResponseWriter
and logResponse
middleware have successfully written log messages, including the correct HTTP status code for each response.