-
Notifications
You must be signed in to change notification settings - Fork 6
/
router.go
117 lines (95 loc) · 2.75 KB
/
router.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package fireball
import (
"net/http"
"strings"
"github.com/zpatrick/go-cache"
)
// Router is an interface that matches an *http.Request to a RouteMatch.
// If no matches are found, a nil RouteMatch should be returned.
type Router interface {
Match(*http.Request) (*RouteMatch, error)
}
// RouterFunc is a function which implements the Router interface
type RouterFunc func(*http.Request) (*RouteMatch, error)
func (rf RouterFunc) Match(r *http.Request) (*RouteMatch, error) {
return rf(r)
}
// BasicRouter attempts to match requests based on its Routes.
// This router supports variables in the URL by using ":variable" notation in URL sections.
// For example, the following are all valid Paths:
// "/home"
// "/movies/:id"
// "/users/:userID/purchases/:purchaseID"
// Matched Path Variables can be retrieved in Handlers by the Context:
// func Handler(c *Context) (Response, error) {
// id := c.PathVariables["id"]
// ...
// }
type BasicRouter struct {
Routes []*Route
cache *cache.Cache
}
// NewBasicRouter returns a new BasicRouter with the specified Routes
func NewBasicRouter(routes []*Route) *BasicRouter {
return &BasicRouter{
Routes: routes,
cache: cache.New(),
}
}
// Match attempts to match the *http.Request to a Route.
// Successful matches are cached for improved performance.
func (r *BasicRouter) Match(req *http.Request) (*RouteMatch, error) {
key := r.cacheKey(req)
if rm, ok := r.cache.GetOK(key); ok {
return rm.(*RouteMatch), nil
}
for _, route := range r.Routes {
if rm := r.matchRoute(route, req); rm != nil {
r.cache.Set(key, rm)
return rm, nil
}
}
return nil, nil
}
func (r *BasicRouter) matchRoute(route *Route, req *http.Request) *RouteMatch {
handler := route.Handlers[req.Method]
if handler == nil {
return nil
}
pathVariables, ok := r.matchPathVariables(route, req.URL.Path)
if !ok {
return nil
}
routeMatch := &RouteMatch{
Handler: handler,
PathVariables: pathVariables,
}
return routeMatch
}
func (r *BasicRouter) matchPathVariables(route *Route, url string) (map[string]string, bool) {
if url != "/" {
url = strings.TrimSuffix(url, "/")
}
if route.Path != "/" {
route.Path = strings.TrimSuffix(route.Path, "/")
}
routeSections := strings.Split(route.Path, "/")
urlSections := strings.Split(url, "/")
if len(routeSections) != len(urlSections) {
return nil, false
}
variables := map[string]string{}
for i, routeSection := range routeSections {
urlSection := urlSections[i]
if strings.HasPrefix(routeSection, ":") {
key := routeSection[1:]
variables[key] = urlSection
} else if routeSection != urlSection {
return nil, false
}
}
return variables, true
}
func (r *BasicRouter) cacheKey(req *http.Request) string {
return req.Method + req.URL.String()
}