Skip to content

Commit

Permalink
Choose best matching route available
Browse files Browse the repository at this point in the history
  • Loading branch information
cam72cam committed Apr 9, 2015
1 parent 2047b73 commit 0384255
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 24 deletions.
61 changes: 48 additions & 13 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,26 @@ func (r *router) AddRoute(method, pattern string, h ...Handler) Route {
}

func (r *router) Handle(res http.ResponseWriter, req *http.Request, context Context) {
bestMatch := NoMatch
var bestVals map[string]string
var bestRoute *route
for _, route := range r.getRoutes() {
ok, vals := route.Match(req.Method, req.URL.Path)
if ok {
params := Params(vals)
context.Map(params)
route.Handle(context, res)
return
match, vals := route.Match(req.Method, req.URL.Path)
if match.BetterThan(bestMatch) {
bestMatch = match
bestVals = vals
bestRoute = route
if match == ExactMatch {
break
}
}
}
if bestMatch != NoMatch {
params := Params(bestVals)
context.Map(params)
bestRoute.Handle(context, res)
return
}

// no routes exist, 404
c := &routeContext{context, 0, r.notFounds}
Expand Down Expand Up @@ -214,14 +225,38 @@ func newRoute(method string, pattern string, handlers []Handler) *route {
return &route
}

func (r route) MatchMethod(method string) bool {
return r.method == "*" || method == r.method || (method == "HEAD" && r.method == "GET")
type RouteMatch int

const (
NoMatch RouteMatch = iota
StarMatch
OverloadMatch
ExactMatch
)

//Higher number = better match
func (r RouteMatch) BetterThan(o RouteMatch) bool {
return r > o
}

func (r route) MatchMethod(method string) RouteMatch {
switch {
case method == r.method:
return ExactMatch
case method == "HEAD" && r.method == "GET":
return OverloadMatch
case r.method == "*":
return StarMatch
default:
return NoMatch
}
}

func (r route) Match(method string, path string) (bool, map[string]string) {
func (r route) Match(method string, path string) (RouteMatch, map[string]string) {
// add Any method matching support
if !r.MatchMethod(method) {
return false, nil
match := r.MatchMethod(method)
if match == NoMatch {
return match, nil
}

matches := r.regex.FindStringSubmatch(path)
Expand All @@ -232,9 +267,9 @@ func (r route) Match(method string, path string) (bool, map[string]string) {
params[name] = matches[i]
}
}
return true, params
return match, params
}
return false, nil
return NoMatch, nil
}

func (r *route) Validate() {
Expand Down
23 changes: 12 additions & 11 deletions router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,24 +236,25 @@ var routeTests = []struct {
path string

// out
ok bool
match RouteMatch
params map[string]string
}{
{"GET", "/foo/123/bat/321", true, map[string]string{"bar": "123", "baz": "321"}},
{"POST", "/foo/123/bat/321", false, map[string]string{}},
{"GET", "/foo/hello/bat/world", true, map[string]string{"bar": "hello", "baz": "world"}},
{"GET", "foo/hello/bat/world", false, map[string]string{}},
{"GET", "/foo/123/bat/321/", true, map[string]string{"bar": "123", "baz": "321"}},
{"GET", "/foo/123/bat/321//", false, map[string]string{}},
{"GET", "/foo/123//bat/321/", false, map[string]string{}},
{"GET", "/foo/123/bat/321", ExactMatch, map[string]string{"bar": "123", "baz": "321"}},
{"POST", "/foo/123/bat/321", NoMatch, map[string]string{}},
{"GET", "/foo/hello/bat/world", ExactMatch, map[string]string{"bar": "hello", "baz": "world"}},
{"GET", "foo/hello/bat/world", NoMatch, map[string]string{}},
{"GET", "/foo/123/bat/321/", ExactMatch, map[string]string{"bar": "123", "baz": "321"}},
{"GET", "/foo/123/bat/321//", NoMatch, map[string]string{}},
{"GET", "/foo/123//bat/321/", NoMatch, map[string]string{}},
{"HEAD", "/foo/123/bat/321/", OverloadMatch, map[string]string{"bar": "123", "baz": "321"}},
}

func Test_RouteMatching(t *testing.T) {
route := newRoute("GET", "/foo/:bar/bat/:baz", nil)
for _, tt := range routeTests {
ok, params := route.Match(tt.method, tt.path)
if ok != tt.ok || params["bar"] != tt.params["bar"] || params["baz"] != tt.params["baz"] {
t.Errorf("expected: (%v, %v) got: (%v, %v)", tt.ok, tt.params, ok, params)
match, params := route.Match(tt.method, tt.path)
if match != tt.match || params["bar"] != tt.params["bar"] || params["baz"] != tt.params["baz"] {
t.Errorf("expected: (%v, %v) got: (%v, %v)", tt.match, tt.params, match, params)
}
}
}
Expand Down

0 comments on commit 0384255

Please sign in to comment.