-
Notifications
You must be signed in to change notification settings - Fork 18k
net/http: path values are not passed to other *http.ServeMux #69077
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
This comes from the way you've shallow cloned internal http.Request state, resulting in the SetPathValue being set to path matches which will be overridden by ServeMux. Closing as not a bug. |
Change https://go.dev/cl/608655 mentions this issue: |
@seankhliao but no other way to do this! http.Request.Clone() do the same. Using new Request is too much. |
It is not possible to simply create a router that would use path values from an upstream router that uses wildcards. |
I think this can help you package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"testing"
)
func Test_main(t *testing.T) {
rt1 := http.NewServeMux()
rt2 := http.NewServeMux()
rt1.HandleFunc("GET /{myid}/{tail...}", func(w http.ResponseWriter, r *http.Request) {
t.Logf("r1.GET /{myid}/{tail...}: myid=%q tail=%q", r.PathValue("myid"), r.PathValue("tail"))
p := stripToLastSlash(r.URL.Path, 2)
rp := stripToLastSlash(r.URL.RawPath, 2)
r2 := new(http.Request)
*r2 = *r
r2.URL = new(url.URL)
*r2.URL = *r.URL
r2.URL.Path = p
r2.URL.RawPath = rp
tail := r.PathValue("tail")
if tail == "" {
t.Error("r1: tail not found")
}
r2.SetPathValue("tail", tail)
rt2.ServeHTTP(w, r2)
})
rt2.HandleFunc("GET /{tail...}", func(w http.ResponseWriter, r *http.Request) {
t.Logf("r2.GET /: tail=%q", r.PathValue("tail"))
if r.PathValue("tail") == "" {
t.Error("r2: tail not found")
}
fmt.Fprintf(w, "%s", r.PathValue("tail"))
})
ts := httptest.NewServer(rt1)
defer ts.Close()
if _, body := testRequest(t, ts, "GET", "/123/456/789", nil); body != "456/789" {
t.Error("tail was not provided")
}
}
func stripToLastSlash(s string, cnt int) string {
pos := 0
for i, r := range s {
if r == '/' {
pos = i
cnt--
if cnt <= 0 {
break
}
}
}
return s[pos:]
}
func testRequest(t *testing.T, ts *httptest.Server, method, path string, body io.Reader) (*http.Response, string) {
req, err := http.NewRequest(method, ts.URL+path, body)
if err != nil {
t.Fatal(err)
return nil, ""
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
return nil, ""
}
respBody, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
return nil, ""
}
defer resp.Body.Close()
return resp, string(respBody)
}
|
@w3nl1ng Ok, that's fine for 'tail'. But what about 'myid'? What if we need to pass 'myid' without wildcard in rt2.HandleFunc? func TestPathValueProviding(t *testing.T) {
rt1 := http.NewServeMux()
rt2 := http.NewServeMux()
rt1.HandleFunc("GET /{myid}/{tail...}", func(w http.ResponseWriter, r *http.Request) {
myid := r.PathValue("myid")
if myid == "" {
t.Error("r1: myid not found")
}
r2 := r.Clone(r.Context())
r2.SetPathValue("myid", myid)
r2.URL.Path = "/" + r.PathValue("tail")
r2.URL.RawPath = ""
rt2.ServeHTTP(w, r2)
})
rt2.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
if r.PathValue("myid") == "" {
t.Error("r2: myid not found")
}
t.Log(r.URL.Path)
})
ts := httptest.NewServer(rt1)
defer ts.Close()
res, err := http.Get(ts.URL + "/123/456/789")
if err != nil {
t.Fatal(err)
}
res.Body.Close()
if res.StatusCode != 200 {
t.Error("status code", res.StatusCode)
}
} |
@covrom If you want get 'myid' in rt2, you should register it at url. // Lastly, match the pattern (there can be at most one) that has a multi
// wildcard in this position to the rest of the path.
if c := n.multiChild; c != nil {
// Don't record a match for a nameless wildcard (which arises from a
// trailing slash in the pattern).
if c.pattern.lastSegment().s != "" {
matches = append(matches, pathUnescape(path[1:])) // remove initial slash
}
return c, matches
}
return nil, nil |
@covrom This means if you call HandleFunc like this, it won't match any pattern for you. The value pairs you previously set yourself will also be overwritten rt2.HandleFunc("GET /", xxx) And if you call it like this, it will try to set the value of 'a', 'b' and 'c' for you. rt2.HandleFunc("GET /{a}{b}{c...}", xxx) let's back to this line, what is important is that your r2 are not passed to rt2 unchanged, ServeHTTP will dispatch r2 according to rt2's internal information rt2.ServeHTTP(w, r2) |
Go version
go version go1.23.0 linux/amd64
Output of
go env
in your module/workspace:What did you do?
I create a test with a main router and a subrouter and try to provide the path value from the main router into subrouter handler:
What did you see happen?
tail
path value is not provided to subrouter:What did you expect to see?
r2.GET /
must returns body "456/789"The text was updated successfully, but these errors were encountered: