When using a nested http.ServeMux
to define my server's endpoints, I encountered this problem: the handler would always respond with a "301 Moved Permanently" to any request, even when the URL path should match.
Example:
package main
import "net/http"
func main() {
api := http.NewServeMux()
api.HandleFunc("ping", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong\n"))
})
root := http.NewServeMux()
root.Handle("/api/", http.StripPrefix("/api/", api))
http.ListenAndServe(":8080", root)
}
When trying to access /api/ping
, the server redirects to /ping
(which returns 404, of course).
The same thing happens with any route under /api/
- /api/foo
redirects to /foo
.
I am using Go 1.13 and curl 7.58.
Edit: to make testing easier in Firefox, I disabled caching.. otherwise I would have to manually clear cache to get accurate results. I did not seem to have this same issue when using Chrome, though.
Keep in mind I am new to Go but this issue was driving me crazy... I was experiencing the same exact behavior that you are (obviously)..
It would seem Go/http is picky about how patterns are formatted..
I messed with this for about an hour and was finally able to get a working example using the following code:
// Working Code
package main
import "net/http"
func main() {
root := http.NewServeMux()
api := http.NewServeMux()
api.HandleFunc("/ping", myHandlerFunc)
root.Handle("/api/", http.StripPrefix("/api", api))
http.ListenAndServe(":8080", root)
}
func myHandlerFunc(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong\n"))
}
I tried as many different configurations as you can imagine (as far as /
forward slashes are concerned) and the above code was the only way I could get it to work..
Specifically referring to:
// Leading forward slash on /ping
api.HandleFunc("/ping", myHandlerFunc)
// The first /api/ is surrounded in forward slashes,
// the second /api only contains a leading forward slash
root.Handle("/api/", http.StripPrefix("/api", api))
Changing the code to this causes 404's...
// DOES NOT WORK!!
package main
import "net/http"
func main() {
root := http.NewServeMux()
api := http.NewServeMux()
api.HandleFunc("/ping", myHandlerFunc)
root.Handle("/api", http.StripPrefix("/api", api))
http.ListenAndServe(":8080", root)
}
func myHandlerFunc(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong\n"))
}
I hope this helps in some way! Cheers
I was just dealing with this the other day. I think ServeMux
is expecting rooted trees (starting with /
) or it interprets the path as a host name.
Patterns name fixed, rooted paths, like "/favicon.ico", or rooted subtrees, like "/images/".... Patterns may optionally begin with a host name, restricting matches to URLs on that host only.
I speculate that ping
is being interpreted as a host name and it's causing weirdness in the way the servemux is operating.
In the other solutions that people have written, they're changing around the positions of /
so that the ping
route ends up as /ping
.
Personally, I didn't like that I had to write /api/
in one spot and /api
in another spot. In my particular case I decided to using something like:
root.Handle(createAPIEndpoints("/api/"))
...
func createAPIEndpoints(base string) (string, *http.ServeMux) {
mux := http.NewServeMux()
mux.HandleFunc(base+"ping", func(...){...})
mux.HandleFunc(base+"another", func(...){...})
// another buried servemux
mux.Handle(createMoreEndpoints(base+"more/"))
return base, mux
}
However, if you want to wrap handlers with handlers (like to use StripPrefix
or other sorts of middleware, this doesn't work as nicely due to returning 2 values.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With