diff --git a/example/main.go b/example/main.go index 9315af6..ccfae49 100644 --- a/example/main.go +++ b/example/main.go @@ -39,10 +39,10 @@ func main() { // can put them wherever you like. goji.Use(PlainText) - // If the last character of a pattern is an asterisk, the path is - // treated as a prefix, and can be used to implement sub-routes. - // Sub-routes can be used to set custom middleware on sub-applications. - // Goji's interfaces are completely composable. + // If the patterns ends with "/*", the path is treated as a prefix, and + // can be used to implement sub-routes. Sub-routes can be used to set + // custom middleware on sub-applications. Goji's interfaces are + // completely composable. admin := web.New() goji.Handle("/admin/*", admin) admin.Use(SuperSecure) diff --git a/web/mux.go b/web/mux.go index ee0d8b4..0004bae 100644 --- a/web/mux.go +++ b/web/mux.go @@ -35,11 +35,14 @@ and handler. Pattern must be one of the following types: - a path segment starting with with a colon will match any string placed at that position. e.g., "/:name" will match "/carl", binding "name" to "carl". - - a pattern ending with an asterisk will match any prefix of - that route. For instance, "/admin/*" will match "/admin/" and - "/admin/secret/lair". This is similar to Sinatra's wildcard, - but may only appear at the very end of the string and is - therefore significantly less powerful. + - a pattern ending with "/*" will match any route with that + prefix. For instance, the pattern "/u/:name/*" will match + "/u/carl/" and "/u/carl/projects/123", but not "/u/carl" + (because there is no trailing slash). In addition to any names + bound in the pattern, the special name "*" is bound to the + unmatched tail of the match, but including the leading "/". So + for the two matching examples above, "*" would be bound to "/" + and "/projects/123" respectively. - regexp.Regexp. The library assumes that it is a Perl-style regexp that is anchored on the left (i.e., the beginning of the string). If your regexp is not anchored on the left, a hopefully-identical diff --git a/web/pattern_test.go b/web/pattern_test.go index 3794270..7fb3c7a 100644 --- a/web/pattern_test.go +++ b/web/pattern_test.go @@ -127,37 +127,29 @@ var patternTests = []struct { }}, // String prefix tests - {parseStringPattern("/user/:user*"), + {parseStringPattern("/user/:user/*"), "/user/", []patternTest{ - pt("/user/bob", true, map[string]string{ + pt("/user/bob/", true, map[string]string{ "user": "bob", + "*": "/", }), pt("/user/bob/friends/123", true, map[string]string{ "user": "bob", - }), - pt("/user/", false, nil), - pt("/user//", false, nil), - }}, - {parseStringPattern("/user/:user/*"), - "/user/", []patternTest{ - pt("/user/bob/friends/123", true, map[string]string{ - "user": "bob", + "*": "/friends/123", }), pt("/user/bob", false, nil), pt("/user/", false, nil), pt("/user//", false, nil), }}, - {parseStringPattern("/user/:user/friends*"), + {parseStringPattern("/user/:user/friends/*"), "/user/", []patternTest{ - pt("/user/bob/friends", true, map[string]string{ + pt("/user/bob/friends/", true, map[string]string{ "user": "bob", + "*": "/", }), pt("/user/bob/friends/123", true, map[string]string{ "user": "bob", - }), - // This is a little unfortunate - pt("/user/bob/friends123", true, map[string]string{ - "user": "bob", + "*": "/123", }), pt("/user/bob/enemies", false, nil), }}, diff --git a/web/router_test.go b/web/router_test.go index b4c9730..b80862d 100644 --- a/web/router_test.go +++ b/web/router_test.go @@ -244,7 +244,7 @@ func TestPrefix(t *testing.T) { m := New() ch := make(chan string, 1) - m.Handle("/hello*", func(w http.ResponseWriter, r *http.Request) { + m.Handle("/hello/*", func(w http.ResponseWriter, r *http.Request) { ch <- r.URL.Path }) diff --git a/web/string_pattern.go b/web/string_pattern.go index 1dcb3c7..197c7c9 100644 --- a/web/string_pattern.go +++ b/web/string_pattern.go @@ -13,7 +13,7 @@ type stringPattern struct { pats []string breaks []byte literals []string - isPrefix bool + wildcard bool } func (s stringPattern) Prefix() string { @@ -28,8 +28,12 @@ func (s stringPattern) Run(r *http.Request, c *C) { func (s stringPattern) match(r *http.Request, c *C, dryrun bool) bool { path := r.URL.Path var matches map[string]string - if !dryrun && len(s.pats) > 0 { - matches = make(map[string]string, len(s.pats)) + if !dryrun { + if s.wildcard { + matches = make(map[string]string, len(s.pats)+1) + } else if len(s.pats) != 0 { + matches = make(map[string]string, len(s.pats)) + } } for i := 0; i < len(s.pats); i++ { sli := s.literals[i] @@ -56,14 +60,16 @@ func (s stringPattern) match(r *http.Request, c *C, dryrun bool) bool { path = path[m:] } // There's exactly one more literal than pat. - if s.isPrefix { - if !strings.HasPrefix(path, s.literals[len(s.pats)]) { + tail := s.literals[len(s.pats)] + if s.wildcard { + if !strings.HasPrefix(path, tail) { return false } - } else { - if path != s.literals[len(s.pats)] { - return false + if !dryrun { + matches["*"] = path[len(tail)-1:] } + } else if path != tail { + return false } if c == nil || dryrun { @@ -81,7 +87,7 @@ func (s stringPattern) match(r *http.Request, c *C, dryrun bool) bool { } func (s stringPattern) String() string { - return fmt.Sprintf("stringPattern(%q, %v)", s.raw, s.isPrefix) + return fmt.Sprintf("stringPattern(%q)", s.raw) } // "Break characters" are characters that can end patterns. They are not allowed @@ -93,11 +99,11 @@ const bc = "/.;," var patternRe = regexp.MustCompile(`[` + bc + `]:([^` + bc + `]+)`) func parseStringPattern(s string) stringPattern { - var isPrefix bool - // Routes that end in an asterisk ("*") are prefix routes - if len(s) > 0 && s[len(s)-1] == '*' { + raw := s + var wildcard bool + if strings.HasSuffix(s, "/*") { s = s[:len(s)-1] - isPrefix = true + wildcard = true } matches := patternRe.FindAllStringSubmatchIndex(s, -1) @@ -118,10 +124,10 @@ func parseStringPattern(s string) stringPattern { } literals[len(matches)] = s[n:] return stringPattern{ - raw: s, + raw: raw, pats: pats, breaks: breaks, literals: literals, - isPrefix: isPrefix, + wildcard: wildcard, } }