Browse Source

Replace Sub() with trailing * in patterns

This change replaces a bit of API surface area (the Sub() method on Muxes) with
a slightly more expressive pattern syntax. I'm mostly doing this because it
seems cleaner: the "*" gets to take on a meaning very similar to what it means
in Sinatra (without growing regexp-like middle-of-a-path globbing, which sounds
terrifying and not particularly useful), and we get to nuke a useless function
from the API.
Carl Jackson 12 years ago
parent
commit
35af451420
7 changed files with 60 additions and 49 deletions
  1. +0
    -6
      default.go
  2. +3
    -1
      example/main.go
  3. +13
    -4
      web/mux.go
  4. +8
    -1
      web/pattern.go
  5. +19
    -6
      web/pattern_test.go
  6. +15
    -29
      web/router.go
  7. +2
    -2
      web/router_test.go

+ 0
- 6
default.go View File

@ -101,12 +101,6 @@ func Trace(pattern interface{}, handler interface{}) {
DefaultMux.Trace(pattern, handler)
}
// Add a sub-route to the default Mux. See the documentation for web.Mux.Sub
// for more information.
func Sub(pattern string, handler interface{}) {
DefaultMux.Sub(pattern, handler)
}
// Set the NotFound handler for the default Mux. See the documentation for
// web.Mux.NotFound for more information.
func NotFound(handler interface{}) {


+ 3
- 1
example/main.go View File

@ -38,10 +38,12 @@ func main() {
// can put them wherever you like.
goji.Use("PlainText", 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.
admin := web.New()
goji.Sub("/admin", admin)
goji.Handle("/admin/*", admin)
admin.Use("SuperSecure", SuperSecure)
// Set up admin routes. Note that sub-routes do *not* mutate the path in


+ 13
- 4
web/mux.go View File

@ -30,11 +30,20 @@ following types:
- func(c *web.C, http.Handler) http.Handler
All of the route-adding functions on Mux take two untyped parameters: pattern
and handler. Pattern must be one of the following types:
- string (interpreted as a Sinatra pattern)
- string. It will be interpreted as a Sinatra-like pattern. In
particular, the following syntax is recognized:
- 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.
- 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 left-anchored
regexp will be created and used instead.
is anchored on the left (i.e., the beginning of the string). If your
regexp is not anchored on the left, a hopefully-identical
left-anchored regexp will be created and used instead.
- web.Pattern
Handler must be one of the following types:
- http.Handler


+ 8
- 1
web/pattern.go View File

@ -200,7 +200,14 @@ func (s stringPattern) String() string {
var patternRe = regexp.MustCompile(`/:([^/]+)`)
func parseStringPattern(s string, isPrefix bool) stringPattern {
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] == '*' {
s = s[:len(s)-1]
isPrefix = true
}
matches := patternRe.FindAllStringSubmatchIndex(s, -1)
pats := make([]string, len(matches))
literals := make([]string, len(matches)+1)


+ 19
- 6
web/pattern_test.go View File

@ -81,14 +81,14 @@ var patternTests = []struct {
}},
// String pattern tests
{parseStringPattern("/hello", false),
{parseStringPattern("/hello"),
"/hello", []patternTest{
pt("/hello", true, nil),
pt("/hell", false, nil),
pt("/hello/", false, nil),
pt("/hello/world", false, nil),
}},
{parseStringPattern("/hello/:name", false),
{parseStringPattern("/hello/:name"),
"/hello/", []patternTest{
pt("/hello/world", true, map[string]string{
"name": "world",
@ -97,7 +97,7 @@ var patternTests = []struct {
pt("/hello/", false, nil),
pt("/hello/my/love", false, nil),
}},
{parseStringPattern("/a/:a/b/:b", false),
{parseStringPattern("/a/:a/b/:b"),
"/a/", []patternTest{
pt("/a/1/b/2", true, map[string]string{
"a": "1",
@ -108,8 +108,8 @@ var patternTests = []struct {
pt("/a/1/b/2/3", false, nil),
}},
// String sub-pattern tests
{parseStringPattern("/user/:user", true),
// String prefix tests
{parseStringPattern("/user/:user*"),
"/user/", []patternTest{
pt("/user/bob", true, map[string]string{
"user": "bob",
@ -120,7 +120,16 @@ var patternTests = []struct {
pt("/user/", false, nil),
pt("/user//", false, nil),
}},
{parseStringPattern("/user/:user/friends", true),
{parseStringPattern("/user/:user/*"),
"/user/", []patternTest{
pt("/user/bob/friends/123", true, map[string]string{
"user": "bob",
}),
pt("/user/bob", false, nil),
pt("/user/", false, nil),
pt("/user//", false, nil),
}},
{parseStringPattern("/user/:user/friends*"),
"/user/", []patternTest{
pt("/user/bob/friends", true, map[string]string{
"user": "bob",
@ -128,6 +137,10 @@ var patternTests = []struct {
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",
}),
pt("/user/bob/enemies", false, nil),
}},
}


+ 15
- 29
web/router.go View File

@ -83,14 +83,14 @@ type Pattern interface {
Match(r *http.Request, c *C, dryrun bool) bool
}
func parsePattern(p interface{}, isPrefix bool) Pattern {
func parsePattern(p interface{}) Pattern {
switch p.(type) {
case Pattern:
return p.(Pattern)
case *regexp.Regexp:
return parseRegexpPattern(p.(*regexp.Regexp))
case string:
return parseStringPattern(p.(string), isPrefix)
return parseStringPattern(p.(string))
default:
log.Fatalf("Unknown pattern type %v. Expected a web.Pattern, "+
"regexp.Regexp, or a string.", p)
@ -179,7 +179,7 @@ func (rt *router) route(c C, w http.ResponseWriter, r *http.Request) {
}
func (rt *router) handleUntyped(p interface{}, m method, h interface{}) {
pat := parsePattern(p, false)
pat := parsePattern(p)
rt.handle(pat, m, parseHandler(h))
}
@ -201,9 +201,18 @@ func (rt *router) handle(p Pattern, m method, h Handler) {
// functions here "m" instead of the standard "rt", since they will eventually
// be shown on the documentation for the Mux that they are included in.
// Dispatch to the given handler when the pattern matches, regardless of HTTP
// method. See the documentation for type Mux for a description of what types
// are accepted for pattern and handler.
/*
Dispatch to the given handler when the pattern matches, regardless of HTTP
method. See the documentation for type Mux for a description of what types are
accepted for pattern and handler.
This method is commonly used to implement sub-routing: an admin application, for
instance, can expose a single handler that is attached to the main Mux by
calling Handle("/admin*", adminHandler) or similar. Note that this function
doesn't strip this prefix from the path before forwarding it on (e.g., the
handler will see the full path, including the "/admin" part), but this
functionality can easily be performed by an extra middleware layer.
*/
func (m *router) Handle(pattern interface{}, handler interface{}) {
m.handleUntyped(pattern, mALL, handler)
}
@ -276,29 +285,6 @@ func (m *router) Trace(pattern interface{}, handler interface{}) {
m.handleUntyped(pattern, mTRACE, handler)
}
/*
Dispatch to the given handler when the given (Sinatra-like) pattern matches a
prefix of the path. This function explicitly takes a string parameter since you
can implement this behavior with a regular expression using the standard
Handle() function.
This function is probably most helpful when implementing sub-routing: an admin
application, for instance, can expose a single handler, and can be hooked up all
at once by attaching a sub-route at "/admin".
Notably, this function does not strip the matched prefix from downstream
handlers, so in the above example the handler would recieve requests with path
"/admin/foo/bar", for example, instead of just "/foo/bar". Luckily, this is a
problem easily surmountable by middleware.
See the documentation for type Mux for a description of what types are accepted
for handler.
*/
func (m *router) Sub(pattern string, handler interface{}) {
pat := parsePattern(pattern, true)
m.handle(pat, mALL, parseHandler(handler))
}
// Set the fallback (i.e., 404) handler for this mux. See the documentation for
// type Mux for a description of what types are accepted for handler.
//


+ 2
- 2
web/router_test.go View File

@ -155,12 +155,12 @@ func TestNotFound(t *testing.T) {
}
}
func TestSub(t *testing.T) {
func TestPrefix(t *testing.T) {
t.Parallel()
rt := makeRouter()
ch := make(chan string, 1)
rt.Sub("/hello", func(w http.ResponseWriter, r *http.Request) {
rt.Handle("/hello*", func(w http.ResponseWriter, r *http.Request) {
ch <- r.URL.Path
})


Loading…
Cancel
Save