Customizing responses
By default, every response that your handlers send has the HTTP status code 200 OK
(which indicates to the user that their request was received and processed successfully), plus three automatic system-generated headers: a Date
header, and the Content-Length
and Content-Type
of the response body. For example:
In this chapter we’ll dive in to how to customize the response headers your handlers send, and also look at a couple of other ways that you can send plain text responses to users.
HTTP status codes
First of all, let’s update our snippetCreatePost
handler so that it sends a 201 Created
status code rather than 200 OK
. To do this, you can use the w.WriteHeader()
method in your handlers like so:
(Yes, this is a bit silly because the handler isn’t actually creating anything yet! But it nicely illustrates the pattern to set a custom status code.)
Although this change looks straightforward, there are a couple of nuances I should explain:
It’s only possible to call
w.WriteHeader()
once per response, and after the status code has been written it can’t be changed. If you try to callw.WriteHeader()
a second time Go will log a warning message.If you don’t call
w.WriteHeader()
explicitly, then the first call tow.Write()
will automatically send a200
status code to the user. So, if you want to send a non-200 status code, you must callw.WriteHeader()
before any call tow.Write()
.
Restart the server, then use curl to make a POST
request to http://localhost:4000/snippet/create
again. You should see that the HTTP response now has a 201 Created
status code similar to this:
Status code constants
The net/http
package provides constants for HTTP status codes, which we can use instead of writing the status code number ourselves. Using these constants is good practice because it helps prevent mistakes due to typos, and it can also help make your code clearer and self-documenting — especially when dealing with less-commonly-used status codes.
Let’s update our snippetCreatePost
handler to use the constant http.StatusCreated
instead of the integer 201
, like so:
Customizing headers
You can also customize the HTTP headers sent to a user by changing the response header map. Probably the most common thing you’ll want to do is include an additional header in the map, which you can do using the w.Header().Add()
method.
To demonstrate this, let’s add a Server: Go
header to the response that our home
handler sends. If you’re following along, go ahead and update the handler code like so:
Let’s try this out by using curl to make another request to http://localhost:4000/
. This time you should see that the response now includes a new Server: Go
header, like so:
Writing response bodies
So far in this book we’ve been using w.Write()
to send specific HTTP response bodies to a user. And while this is the simplest and most fundamental way to send a response, in practice it’s far more common to pass your http.ResponseWriter
value to another function that writes the response for you.
In fact, there are a lot of functions that you can use to write a response!
The key thing to understand is this… because the http.ResponseWriter
value in your handlers has a Write()
method, it satisfies the io.Writer
interface.
If you’re new to Go, then the concept of interfaces can be a bit confusing and I don’t want to get too hung up on it right now. But at a practical level, it means that any functions where you see an io.Writer
parameter, you can pass in your http.ResponseWriter
value and whatever is being written will subsequently be sent as the body of the HTTP response.
That means you can use standard library functions like io.WriteString()
and the fmt.Fprint*()
family (all of which accept an io.Writer
parameter) to write plain-text response bodies too.
Let’s leverage this, and update the code in our snippetView
handler to use the fmt.Fprintf()
function. This will allow us to interpolate the wildcard id
value in our response body message and write the response in a single line, like so:
Additional information
Content sniffing
In order to automatically set the Content-Type
header, Go content sniffs the response body with the http.DetectContentType()
function. If this function can’t guess the content type, Go will fall back to setting the header Content-Type: application/octet-stream
instead.
The http.DetectContentType()
function generally works quite well, but a common gotcha for web developers is that it can’t distinguish JSON from plain text. So, by default, JSON responses will be sent with a Content-Type: text/plain; charset=utf-8
header. You can prevent this from happening by setting the correct header manually in your handler like so:
Manipulating the header map
In this chapter we used w.Header().Add()
to add a new header to the response header map. But there are also Set()
, Del()
, Get()
and Values()
methods that you can use to manipulate and read from the header map too.
Header canonicalization
When you’re using the Set()
, Add()
, Del()
, Get()
and Values()
methods on the header map, the header name will always be canonicalized using the textproto.CanonicalMIMEHeaderKey()
function. This converts the first letter and any letter following a hyphen to upper case, and the rest of the letters to lowercase. This has the practical implication that when calling these methods the header name is case-insensitive.
If you need to avoid this canonicalization behavior, you can edit the underlying header map directly. It has the type map[string][]string
behind the scenes. For example: