Gorilla Mux Trailing Slashes

thub.com/gorilla/mux is a great library to extend the go functionality for routing. I have been using it for a while, and I am enjoying its simplicity and straightforwardness. If you have not used it before, I recommend checking it out.

However, there was one thing that bothered me: trailing slashes in the URL. Let me give you a concrete example of what I am talking about. Consider this example:

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

func main() {
	router := mux.NewRouter()
	router.HandleFunc("/items", postHandler).Methods(http.MethodPost)
	router.HandleFunc("/items", getHandler).Methods(http.MethodGet)
	log.Print("server starting")
	log.Fatal(http.ListenAndServe(":8888", router))
}

func postHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "method %s, url %s", r.Method, r.URL)
}

func getHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "method %s, url %s", r.Method, r.URL)
}

Very straight-forward. I am registering two routes: GET /items and POST /items. When you call POST /items, you will get the message:

method POST, url /items

However, if you call POST /items/ (notice the trailing slash), then you will get the surprising value:

404 page not found

Some suggested that you can use

Why is it not found, I wondered. What happens is that mux treats /items and /items/ as two separate URLs and requires two different handlers. Hence, in order to handle the trailing slash, you will need to register the same handler to handler the path /items and /items/. Very ugly solution if you ask me.

router := mux.NewRouter().StrictSlash(true)

Which allows mux to redirect the user who did /items/ to /items. This however does not work, because mux redirect POST to GET. This is definitely a bug if you ask me. This is not new to mux, but for some reason unknown to me, the mux team has not fixed it and the issue in github is closed as of today. So what is the solution?

It seems that the only solution I am able to get to is to remove the trailing slash before it gets to the router. This is by creating a middleware that reads the URL and removes the ending slash.

func removeTrailingSlash(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		r.URL.Path = strings.TrimSuffix(r.URL.Path, "/")
		next.ServeHTTP(w, r)
	})
}

And then adding the middleware to the http listener

// log.Fatal(http.ListenAndServe(":8888", router))
log.Fatal(http.ListenAndServe(":8888", removeTrailingSlash(router)))

Thanks to Nate who mentioned this solution in his blog.