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.Hijackerinterfaces is clearer and feels less hacky. No more type assertions necessary!
- It makes it easier and safer to create and use custom
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.
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.
http.ResponseController you can now use the
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
SetReadDeadline()then they will have no effect. The server-wide deadline wins.
- If your underlying
http.ResponseWriterdoesn't support setting per-request deadlines, then calling
SetReadDeadline()will return a
- You can effectively remove the server-wide deadline on a per-request basis by passing a zero-valued
SetReadDeadline(). For example:
Flusher and Hijacker interfaces
http.ResponseController type also makes it slightly nicer to use the 'optional'
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
Hijack() on a
http.ResponseController will also return an
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
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
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
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
logResponse middleware have successfully written log messages, including the correct HTTP status code for each response.