--- /dev/null
+pkg net/http, method (*Request) PathValue(string) string #61410
+pkg net/http, method (*Request) SetPathValue(string, string) #61410
// It is unexported to prevent people from using Context wrong
// and mutating the contexts held by callers of the same request.
ctx context.Context
+
+ // The following fields are for requests matched by ServeMux.
+ pat *pattern // the pattern that matched
+ matches []string // values for the matching wildcards in pat
+ otherValues map[string]string // for calls to SetPathValue that don't match a wildcard
}
// Context returns the request's context. To change the context, use
return nil, nil, ErrMissingFile
}
+// PathValue returns the value for the named path wildcard in the ServeMux pattern
+// that matched the request.
+// It returns the empty string if the request was not matched against a pattern
+// or there is no such wildcard in the pattern.
+func (r *Request) PathValue(name string) string {
+ if i := r.patIndex(name); i >= 0 {
+ return r.matches[i]
+ }
+ return r.otherValues[name]
+}
+
+func (r *Request) SetPathValue(name, value string) {
+ if i := r.patIndex(name); i >= 0 {
+ r.matches[i] = value
+ } else {
+ if r.otherValues == nil {
+ r.otherValues = map[string]string{}
+ }
+ r.otherValues[name] = value
+ }
+}
+
+// patIndex returns the index of name in the list of named wildcards of the
+// request's pattern, or -1 if there is no such name.
+func (r *Request) patIndex(name string) int {
+ // The linear search seems expensive compared to a map, but just creating the map
+ // takes a lot of time, and most patterns will just have a couple of wildcards.
+ if r.pat == nil {
+ return -1
+ }
+ i := 0
+ for _, seg := range r.pat.segments {
+ if seg.wild && seg.s != "" {
+ if name == seg.s {
+ return i
+ }
+ i++
+ }
+ }
+ return -1
+}
+
func (r *Request) expectsContinue() bool {
return hasToken(r.Header.get("Expect"), "100-continue")
}
"math"
"mime/multipart"
. "net/http"
+ "net/http/httptest"
"net/url"
"os"
"reflect"
t.Error("errors.Is(ErrNotSupported, errors.ErrUnsupported) failed")
}
}
+
+func TestPathValueNoMatch(t *testing.T) {
+ // Check that PathValue and SetPathValue work on a Request that was never matched.
+ var r Request
+ if g, w := r.PathValue("x"), ""; g != w {
+ t.Errorf("got %q, want %q", g, w)
+ }
+ r.SetPathValue("x", "a")
+ if g, w := r.PathValue("x"), "a"; g != w {
+ t.Errorf("got %q, want %q", g, w)
+ }
+}
+
+func TestPathValue(t *testing.T) {
+ for _, test := range []struct {
+ pattern string
+ url string
+ want map[string]string
+ }{
+ {
+ "/{a}/is/{b}/{c...}",
+ "/now/is/the/time/for/all",
+ map[string]string{
+ "a": "now",
+ "b": "the",
+ "c": "time/for/all",
+ "d": "",
+ },
+ },
+ // TODO(jba): uncomment these tests when we implement path escaping (forthcoming).
+ // {
+ // "/names/{name}/{other...}",
+ // "/names/" + url.PathEscape("/john") + "/address",
+ // map[string]string{
+ // "name": "/john",
+ // "other": "address",
+ // },
+ // },
+ // {
+ // "/names/{name}/{other...}",
+ // "/names/" + url.PathEscape("john/doe") + "/address",
+ // map[string]string{
+ // "name": "john/doe",
+ // "other": "address",
+ // },
+ // },
+ } {
+ mux := NewServeMux()
+ mux.HandleFunc(test.pattern, func(w ResponseWriter, r *Request) {
+ for name, want := range test.want {
+ got := r.PathValue(name)
+ if got != want {
+ t.Errorf("%q, %q: got %q, want %q", test.pattern, name, got, want)
+ }
+ }
+ })
+ server := httptest.NewServer(mux)
+ defer server.Close()
+ _, err := Get(server.URL + test.url)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+}
+
+func TestSetPathValue(t *testing.T) {
+ mux := NewServeMux()
+ mux.HandleFunc("/a/{b}/c/{d...}", func(_ ResponseWriter, r *Request) {
+ kvs := map[string]string{
+ "b": "X",
+ "d": "Y",
+ "a": "Z",
+ }
+ for k, v := range kvs {
+ r.SetPathValue(k, v)
+ }
+ for k, w := range kvs {
+ if g := r.PathValue(k); g != w {
+ t.Errorf("got %q, want %q", g, w)
+ }
+ }
+ })
+ server := httptest.NewServer(mux)
+ defer server.Close()
+ _, err := Get(server.URL + "/a/b/c/d/e")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
// If there is no registered handler that applies to the request,
// Handler returns a “page not found” handler and an empty pattern.
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
- return mux.findHandler(r)
+ h, p, _, _ := mux.findHandler(r)
+ return h, p
}
// findHandler finds a handler for a request.
// If there is a matching handler, it returns it and the pattern that matched.
// Otherwise it returns a Redirect or NotFound handler with the path that would match
// after the redirect.
-func (mux *ServeMux) findHandler(r *Request) (h Handler, patStr string) {
+func (mux *ServeMux) findHandler(r *Request) (h Handler, patStr string, _ *pattern, matches []string) {
var n *routingNode
// TODO(jba): use escaped path. This is an independent change that is also part
// of proposal https://go.dev/issue/61410.
// but the path canonicalization does not.
_, _, u := mux.matchOrRedirect(r.URL.Host, r.Method, path, r.URL)
if u != nil {
- return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
+ return RedirectHandler(u.String(), StatusMovedPermanently), u.Path, nil, nil
}
// Redo the match, this time with r.Host instead of r.URL.Host.
// Pass a nil URL to skip the trailing-slash redirect logic.
- n, _, _ = mux.matchOrRedirect(r.Host, r.Method, path, nil)
+ n, matches, _ = mux.matchOrRedirect(r.Host, r.Method, path, nil)
} else {
// All other requests have any port stripped and path cleaned
// before passing to mux.handler.
// If the given path is /tree and its handler is not registered,
// redirect for /tree/.
var u *url.URL
- n, _, u = mux.matchOrRedirect(host, r.Method, path, r.URL)
+ n, matches, u = mux.matchOrRedirect(host, r.Method, path, r.URL)
if u != nil {
- return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
+ return RedirectHandler(u.String(), StatusMovedPermanently), u.Path, nil, nil
}
if path != r.URL.Path {
// Redirect to cleaned path.
patStr = n.pattern.String()
}
u := &url.URL{Path: path, RawQuery: r.URL.RawQuery}
- return RedirectHandler(u.String(), StatusMovedPermanently), patStr
+ return RedirectHandler(u.String(), StatusMovedPermanently), patStr, nil, nil
}
}
if n == nil {
// TODO(jba): support 405 (MethodNotAllowed) by checking for patterns with different methods.
- return NotFoundHandler(), ""
+ return NotFoundHandler(), "", nil, nil
}
- return n.handler, n.pattern.String()
+ return n.handler, n.pattern.String(), n.pattern, matches
}
// matchOrRedirect looks up a node in the tree that matches the host, method and path.
w.WriteHeader(StatusBadRequest)
return
}
- h, _ := mux.findHandler(r)
- // TODO(jba); save matches in Request.
+ h, _, pat, matches := mux.findHandler(r)
+ r.pat = pat
+ r.matches = matches
h.ServeHTTP(w, r)
}
r.Method = test.method
r.Host = "example.com"
r.URL = &url.URL{Path: test.path}
- gotH, _ := mux.findHandler(&r)
+ gotH, _, _, _ := mux.findHandler(&r)
got := fmt.Sprintf("%#v", gotH)
if got != test.wantHandler {
t.Errorf("%s %q: got %q, want %q", test.method, test.path, got, test.wantHandler)
if err != nil {
b.Fatal(err)
}
- if h, p := mux.findHandler(r); h != nil && p == "" {
+ if h, p, _, _ := mux.findHandler(r); h != nil && p == "" {
b.Error("impossible")
}
}