It’s incredibly easy to build web apps and APIs using Go’s net/http package. All you really need to do is to specify a route and its handler. But, how do you restrict the methods that routes can accept? In this short tutorial, I’ll show you how.
With the release of Go 1.22 there’s an easier way to restrict HTTP methods.
Find out in the new post.
Recently, I’ve been building a small encryption/decryption API with Go.
It’s been going well, giving me loads of ways to continue growing my Go skills, and to learn about encryption.
However, I’d been wondering (pretty much right from the get go) how I could restrict the allowed HTTP request methods for the API’s routes.
I’d had a bit of a look through net/http, after finding that HandleFunc()
only takes a route pattern and a handler function.
Unfortunately, I couldn’t find anything.
This seemed odd, as I’m used to being able to define the supported method(s) when defining the route and its handler, in PHP.
$app->get('/', RouteHandler::class);
$app->post('/album', RoutePostHandler::class);
$app->put('/album/:id', RoutePutHandler::class);
$app->route('/album/:id', RouteAnyHandler::class, ["get", "post"]);
Take the example code above, used in the Mezzio framework.
It defines four routes; (their handlers aren’t important).
The first three are only allowed to be requested with GET, POST, and PUT respectively.
The fourth, however, can be requested with either GET or POST.
The benefit of this approach is that you only need to look in one place to find a route’s allowed methods: the routing table definition.
Where do you do this with net/http?
I wasn’t, yet, sure.
Take a look at the small code example below, for example.
package main
import (
"io"
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
io.WriteString(writer, "Here is a response.\n")
})
log.Print("Starting server on :4000")
err := http.ListenAndServe(":4000", mux)
log.Fatal(err)
}
You can see that the app has a single route, /
, handled by a closure, which writes “Here is a response.” as the response’s body.
Nothing special about that.
Right?
Given that, requests can use any HTTP method, and the same response will be returned.
So how do you do restrict the allowed methods with net/http?
If, for example, the handler was processing a form, you’d restrict the allowed methods to just POST
.
What’s more, if users made requests with other methods, out of courtesy (and in compliance with the spec), you’d return an Allow header telling the user that the route only supports the POST
method.
Gladly, doing both is pretty trivial.
You’d refactor the closure, or your function if you weren’t using a closure, similar to following:
mux.HandleFunc("/",
func (writer http.ResponseWriter, request *http.Request) {
if request.Method != "POST" {
writer.Header().Set("Allow", "POST")
http.Error(
writer,
"That method is not allowed.",
http.StatusMethodNotAllowed,
)
return
}
io.WriteString(writer, "Here is a response.")
})
The if
statement checks if the request’s method is POST
.
If not, it does three things:
- Sets the
Allow
header to POST
.
- Sets the body of the response to
That method is not allowed.
.
- Sets the response’s status code to 405 Method Not Allowed.
The last two steps are done by the http.Error() utility method.
It’s a little time-saver that I came across last week.
It simplifies setting the response’s status code and body, avoiding the need to use http.ResponseWriter
directly.
Here’s an example of what you’d see if you made a GET request to the route with curl.
HTTP/1.1 405 Method Not Allowed
Allow: POST
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Fri, 01 Sep 2023 08:53:47 GMT
Content-Length: 28
That method is not allowed.
That’s how to restrict the allowed methods for a route with Go
Sure, doing so in Go isn’t what I’m used to when using PHP frameworks, such as Slim, Mezzio, or Laravel.
And, I’d love to be able to set the allowed methods when defining the route.
But, the approach is clear and succinct, and takes only a little bit of extra effort.
Do you use it?
If not, what’s your approach?
I’d love to learn from you too.
Share your approach and thoughts in the comments.
Join the discussion
comments powered by Disqus