Skip to content

Commit 136d54f

Browse files
committed
Merge pull request gorilla#51 from ttencate/master
Make PathPrefix more polite towards StrictSlash setting, and add better tests for the latter
2 parents 270c425 + b864f07 commit 136d54f

File tree

4 files changed

+151
-40
lines changed

4 files changed

+151
-40
lines changed

mux.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,20 @@ func (r *Router) GetRoute(name string) *Route {
109109
return r.getNamedRoutes()[name]
110110
}
111111

112-
// StrictSlash defines the slash behavior for new routes.
112+
// StrictSlash defines the trailing slash behavior for new routes. The initial
113+
// value is false.
113114
//
114115
// When true, if the route path is "/path/", accessing "/path" will redirect
115-
// to the former and vice versa.
116+
// to the former and vice versa. In other words, your application will always
117+
// see the path as specified in the route.
116118
//
117-
// Special case: when a route sets a path prefix, strict slash is
118-
// automatically set to false for that route because the redirect behavior
119-
// can't be determined for prefixes.
119+
// When false, if the route path is "/path", accessing "/path/" will not match
120+
// this route and vice versa.
121+
//
122+
// Special case: when a route sets a path prefix using the PathPrefix() method,
123+
// strict slash is ignored for that route because the redirect behavior can't
124+
// be determined from a prefix alone. However, any subrouters created from that
125+
// route inherit the original StrictSlash setting.
120126
func (r *Router) StrictSlash(value bool) *Router {
121127
r.strictSlash = value
122128
return r

mux_test.go

Lines changed: 119 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@ import (
1313
)
1414

1515
type routeTest struct {
16-
title string // title of the test
17-
route *Route // the route being tested
18-
request *http.Request // a request to test the route
19-
vars map[string]string // the expected vars of the match
20-
host string // the expected host of the match
21-
path string // the expected path of the match
22-
shouldMatch bool // whether the request is expected to match the route at all
16+
title string // title of the test
17+
route *Route // the route being tested
18+
request *http.Request // a request to test the route
19+
vars map[string]string // the expected vars of the match
20+
host string // the expected host of the match
21+
path string // the expected path of the match
22+
shouldMatch bool // whether the request is expected to match the route at all
23+
shouldRedirect bool // whether the request should result in a redirect
2324
}
2425

2526
func TestHost(t *testing.T) {
@@ -151,6 +152,33 @@ func TestPath(t *testing.T) {
151152
path: "/111/222/333",
152153
shouldMatch: true,
153154
},
155+
{
156+
title: "Path route, match with trailing slash in request and path",
157+
route: new(Route).Path("/111/"),
158+
request: newRequest("GET", "http://localhost/111/"),
159+
vars: map[string]string{},
160+
host: "",
161+
path: "/111/",
162+
shouldMatch: true,
163+
},
164+
{
165+
title: "Path route, do not match with trailing slash in path",
166+
route: new(Route).Path("/111/"),
167+
request: newRequest("GET", "http://localhost/111"),
168+
vars: map[string]string{},
169+
host: "",
170+
path: "/111",
171+
shouldMatch: false,
172+
},
173+
{
174+
title: "Path route, do not match with trailing slash in request",
175+
route: new(Route).Path("/111"),
176+
request: newRequest("GET", "http://localhost/111/"),
177+
vars: map[string]string{},
178+
host: "",
179+
path: "/111/",
180+
shouldMatch: false,
181+
},
154182
{
155183
title: "Path route, wrong path in request in request URL",
156184
route: new(Route).Path("/111/222/333"),
@@ -214,6 +242,15 @@ func TestPathPrefix(t *testing.T) {
214242
path: "/111",
215243
shouldMatch: true,
216244
},
245+
{
246+
title: "PathPrefix route, match substring",
247+
route: new(Route).PathPrefix("/1"),
248+
request: newRequest("GET", "http://localhost/111/222/333"),
249+
vars: map[string]string{},
250+
host: "",
251+
path: "/1",
252+
shouldMatch: true,
253+
},
217254
{
218255
title: "PathPrefix route, URL prefix in request does not match",
219256
route: new(Route).PathPrefix("/111"),
@@ -579,26 +616,74 @@ func TestNamedRoutes(t *testing.T) {
579616
}
580617

581618
func TestStrictSlash(t *testing.T) {
582-
var r *Router
583-
var req *http.Request
584-
var route *Route
585-
var match *RouteMatch
586-
var matched bool
587-
588-
// StrictSlash should be ignored for path prefix.
589-
// So we register a route ending in slash but it doesn't attempt to add
590-
// the slash for a path not ending in slash.
591-
r = NewRouter()
619+
r := NewRouter()
592620
r.StrictSlash(true)
593-
route = r.NewRoute().PathPrefix("/static/")
594-
req, _ = http.NewRequest("GET", "http://localhost/static/logo.png", nil)
595-
match = new(RouteMatch)
596-
matched = r.Match(req, match)
597-
if !matched {
598-
t.Errorf("Should match request %q -- %v", req.URL.Path, getRouteTemplate(route))
621+
622+
tests := []routeTest{
623+
{
624+
title: "Redirect path without slash",
625+
route: r.NewRoute().Path("/111/"),
626+
request: newRequest("GET", "http://localhost/111"),
627+
vars: map[string]string{},
628+
host: "",
629+
path: "/111/",
630+
shouldMatch: true,
631+
shouldRedirect: true,
632+
},
633+
{
634+
title: "Do not redirect path with slash",
635+
route: r.NewRoute().Path("/111/"),
636+
request: newRequest("GET", "http://localhost/111/"),
637+
vars: map[string]string{},
638+
host: "",
639+
path: "/111/",
640+
shouldMatch: true,
641+
shouldRedirect: false,
642+
},
643+
{
644+
title: "Redirect path with slash",
645+
route: r.NewRoute().Path("/111"),
646+
request: newRequest("GET", "http://localhost/111/"),
647+
vars: map[string]string{},
648+
host: "",
649+
path: "/111",
650+
shouldMatch: true,
651+
shouldRedirect: true,
652+
},
653+
{
654+
title: "Do not redirect path without slash",
655+
route: r.NewRoute().Path("/111"),
656+
request: newRequest("GET", "http://localhost/111"),
657+
vars: map[string]string{},
658+
host: "",
659+
path: "/111",
660+
shouldMatch: true,
661+
shouldRedirect: false,
662+
},
663+
{
664+
title: "Propagate StrictSlash to subrouters",
665+
route: r.NewRoute().PathPrefix("/static/").Subrouter().Path("/images/"),
666+
request: newRequest("GET", "http://localhost/static/images"),
667+
vars: map[string]string{},
668+
host: "",
669+
path: "/static/images/",
670+
shouldMatch: true,
671+
shouldRedirect: true,
672+
},
673+
{
674+
title: "Ignore StrictSlash for path prefix",
675+
route: r.NewRoute().PathPrefix("/static/"),
676+
request: newRequest("GET", "http://localhost/static/logo.png"),
677+
vars: map[string]string{},
678+
host: "",
679+
path: "/static/",
680+
shouldMatch: true,
681+
shouldRedirect: false,
682+
},
599683
}
600-
if match.Handler != nil {
601-
t.Errorf("Should not redirect")
684+
685+
for _, test := range tests {
686+
testRoute(t, test)
602687
}
603688
}
604689

@@ -627,6 +712,7 @@ func testRoute(t *testing.T, test routeTest) {
627712
host := test.host
628713
path := test.path
629714
url := test.host + test.path
715+
shouldRedirect := test.shouldRedirect
630716

631717
var match RouteMatch
632718
ok := route.Match(request, &match)
@@ -664,6 +750,14 @@ func testRoute(t *testing.T, test routeTest) {
664750
return
665751
}
666752
}
753+
if shouldRedirect && match.Handler == nil {
754+
t.Errorf("(%v) Did not redirect", test.title)
755+
return
756+
}
757+
if !shouldRedirect && match.Handler != nil {
758+
t.Errorf("(%v) Unexpected redirect", test.title)
759+
return
760+
}
667761
}
668762
}
669763

regexp.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,13 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*rout
9898
}
9999
// Done!
100100
return &routeRegexp{
101-
template: template,
102-
matchHost: matchHost,
103-
regexp: reg,
104-
reverse: reverse.String(),
105-
varsN: varsN,
106-
varsR: varsR,
101+
template: template,
102+
matchHost: matchHost,
103+
strictSlash: strictSlash,
104+
regexp: reg,
105+
reverse: reverse.String(),
106+
varsN: varsN,
107+
varsR: varsR,
107108
}, nil
108109
}
109110

@@ -114,6 +115,8 @@ type routeRegexp struct {
114115
template string
115116
// True for host match, false for path match.
116117
matchHost bool
118+
// The strictSlash value defined on the route, but disabled if PathPrefix was used.
119+
strictSlash bool
117120
// Expanded regexp.
118121
regexp *regexp.Regexp
119122
// Reverse template.
@@ -216,7 +219,7 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
216219
m.Vars[v] = pathVars[k+1]
217220
}
218221
// Check if we should redirect.
219-
if r.strictSlash {
222+
if v.path.strictSlash {
220223
p1 := strings.HasSuffix(req.URL.Path, "/")
221224
p2 := strings.HasSuffix(v.path.template, "/")
222225
if p1 != p2 {

route.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,8 @@ func (r *Route) Methods(methods ...string) *Route {
259259
// Path -----------------------------------------------------------------------
260260

261261
// Path adds a matcher for the URL path.
262-
// It accepts a template with zero or more URL variables enclosed by {}.
262+
// It accepts a template with zero or more URL variables enclosed by {}. The
263+
// template must start with a "/".
263264
// Variables can define an optional regexp pattern to me matched:
264265
//
265266
// - {name} matches anything until the next slash.
@@ -283,9 +284,16 @@ func (r *Route) Path(tpl string) *Route {
283284

284285
// PathPrefix -----------------------------------------------------------------
285286

286-
// PathPrefix adds a matcher for the URL path prefix.
287+
// PathPrefix adds a matcher for the URL path prefix. This matches if the given
288+
// template is a prefix of the full URL path. See Route.Path() for details on
289+
// the tpl argument.
290+
//
291+
// Note that it does not treat slashes specially ("/foobar/" will be matched by
292+
// the prefix "/foo") so you may want to use a trailing slash here.
293+
//
294+
// Also note that the setting of Router.StrictSlash() has no effect on routes
295+
// with a PathPrefix matcher.
287296
func (r *Route) PathPrefix(tpl string) *Route {
288-
r.strictSlash = false
289297
r.err = r.addRegexpMatcher(tpl, false, true)
290298
return r
291299
}

0 commit comments

Comments
 (0)