Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested ServeMux always returns "301 Moved Permanently"

Tags:

http

go

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.

like image 716
PaperBag Avatar asked Feb 21 '20 19:02

PaperBag


2 Answers

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.

enter image description here


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

like image 63
Matt Oestreich Avatar answered Nov 10 '22 05:11

Matt Oestreich


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.

like image 1
Benny Jobigan Avatar answered Nov 10 '22 05:11

Benny Jobigan