This repository has been archived by the owner on Jul 29, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 12
/
router.go
231 lines (204 loc) · 7.04 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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
package muxy
import (
"net/http"
)
// Matcher registers patterns as routes and matches requests.
type Matcher interface {
// Route returns a Route for the given pattern.
Route(pattern string) (*Route, error)
// Match matches registered routes against the incoming request and
// stores URL variables in the request context.
Match(r *http.Request) (http.Handler, *http.Request)
// Build returns a URL string for the given route and variables.
Build(r *Route, vars ...string) (string, error)
}
// -----------------------------------------------------------------------------
// Variable is a type used to set and retrieve route variables from the request
// context.
type Variable string
// Var returns the route variable with the given name from the request context.
//
// The returned value may be empty if the variable wasn't set.
func Var(r *http.Request, name string) string {
v, _ := r.Context().Value(Variable(name)).(string)
return v
}
// -----------------------------------------------------------------------------
// New creates a new Router for the given matcher.
func New(m Matcher) *Router {
r := &Router{
matcher: m,
Routes: map[*Route]string{},
NamedRoutes: map[string]*Route{},
}
r.Router = r
return r
}
// Router matches the URL of incoming requests against
// registered routes and calls the appropriate handler.
type Router struct {
// matcher holds the Matcher implementation used by this router.
matcher Matcher
// Router holds the main router referenced by subrouters.
Router *Router
// Pattern holds the pattern prefix used to create new routes.
Pattern string
// Noun holds the name prefix used to create new routes.
Noun string
// Middleware holds the middleware to apply in new routes.
Middleware []func(http.Handler) http.Handler
// Routes maps all routes to their correspondent patterns.
Routes map[*Route]string
// NamedRoutes maps route names to their correspondent routes.
NamedRoutes map[string]*Route
}
// Use appends the given middleware to this router.
func (r *Router) Use(middleware ...func(http.Handler) http.Handler) *Router {
r.Middleware = append(r.Middleware, middleware...)
return r
}
// Group creates a group for the given pattern prefix. All routes registered in
// the resulting router will prepend the prefix to its pattern. For example:
//
// // Create a new router.
// r := muxy.New(matcher)
// // Create a group for the routes that share pattern prefix "/admin".
// g := r.Group("/admin")
// // Register a route in the admin group, and add handlers for two HTTP
// // methods. These handlers will be served for the path "/admin/products".
// g.Route("/products").Get(listProducts).Post(updateProducts)
func (r *Router) Group(pattern string) *Router {
return &Router{
Router: r.Router,
Pattern: r.Pattern + pattern,
Noun: r.Noun,
Middleware: r.Middleware,
}
}
// Name sets the name prefix used for new routes. All routes registered in
// the resulting router will prepend the prefix to its name.
func (r *Router) Name(name string) *Router {
r.Noun = r.Noun + name
return r
}
// Mount imports all routes from the given router into this one.
//
// Combined with Group() and Name(), it is possible to submount a router
// defined in a different package using pattern and name prefixes.
// For example:
//
// // Create a new router.
// r := muxy.New(matcher)
// // Create a group for the routes starting with the pattern "/admin",
// // set the name prefix as "admin:" and register all routes from the
// // external router.
// g := r.Group("/admin").Name("admin:").Mount(admin.Router)
func (r *Router) Mount(src *Router) *Router {
for k, _ := range src.Router.Routes {
route := r.Route(k.Pattern).Name(k.Noun)
for method, handler := range k.Handlers {
route.Handle(handler, method)
}
}
return r
}
// Route creates a new Route for the given pattern.
func (r *Router) Route(pattern string) *Route {
route, err := r.Router.matcher.Route(r.Pattern + pattern)
if err != nil {
panic(err)
}
route.Router = r
route.Pattern = r.Pattern + pattern
route.Noun = r.Noun
r.Router.Routes[route] = r.Pattern + pattern
return route
}
// URL returns a URL string for the given route name and variables.
func (r *Router) URL(name string, vars ...string) string {
if route, ok := r.Router.NamedRoutes[name]; ok {
u, err := r.Router.matcher.Build(route, vars...)
if err != nil {
panic(err)
}
return u
}
return ""
}
// ServeHTTP dispatches to the handler whose pattern matches the request.
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if h, hreq := r.Router.matcher.Match(req); h != nil {
h.ServeHTTP(w, hreq)
return
}
http.NotFound(w, req)
}
// -----------------------------------------------------------------------------
// Route stores a URL pattern to be matched and the handler to be served
// in case of a match, optionally mapping HTTP methods to different handlers.
type Route struct {
// Router holds the router that registered this route.
Router *Router
// Pattern holds the route pattern.
Pattern string
// Noun holds the route name.
Noun string
// Handlers maps request methods to the handlers that will handle them.
Handlers map[string]http.Handler
}
// Name defines the route name used for URL building.
func (r *Route) Name(name string) *Route {
r.Noun = r.Noun + name
if _, ok := r.Router.Router.NamedRoutes[r.Noun]; ok {
panic("muxy: duplicated name: " + r.Noun)
}
r.Router.Router.NamedRoutes[r.Noun] = r
return r
}
// Handle sets the given handler to be served for the optional request methods.
func (r *Route) Handle(h http.Handler, methods ...string) *Route {
for i := len(r.Router.Middleware) - 1; i >= 0; i-- {
h = r.Router.Middleware[i](h)
}
if r.Handlers == nil {
r.Handlers = make(map[string]http.Handler, len(methods))
}
if methods == nil {
r.Handlers[""] = h
} else {
for _, m := range methods {
r.Handlers[m] = h
}
}
return r
}
// Below are convenience methods that map HTTP verbs to http.Handler, equivalent
// to call r.Handle(h, "METHOD-NAME").
// Delete sets the given handler to be served for the request method DELETE.
func (r *Route) Delete(h http.Handler) *Route {
return r.Handle(h, "DELETE")
}
// Get sets the given handler to be served for the request method GET.
func (r *Route) Get(h http.Handler) *Route {
return r.Handle(h, "GET")
}
// Head sets the given handler to be served for the request method HEAD.
func (r *Route) Head(h http.Handler) *Route {
return r.Handle(h, "HEAD")
}
// Options sets the given handler to be served for the request method OPTIONS.
func (r *Route) Options(h http.Handler) *Route {
return r.Handle(h, "OPTIONS")
}
// Patch sets the given handler to be served for the request method PATCH.
func (r *Route) Patch(h http.Handler) *Route {
return r.Handle(h, "PATCH")
}
// Post sets the given handler to be served for the request method POST.
func (r *Route) Post(h http.Handler) *Route {
return r.Handle(h, "POST")
}
// Put sets the given handler to be served for the request method PUT.
func (r *Route) Put(h http.Handler) *Route {
return r.Handle(h, "PUT")
}