-
Notifications
You must be signed in to change notification settings - Fork 1
/
router_group.go
278 lines (227 loc) · 9.61 KB
/
router_group.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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
package kumi
import (
"net/http"
"github.com/justinas/alice"
)
// HTTPMethods is a list of HTTP methods kumi supports.
var HTTPMethods = []string{GET, HEAD, POST, PUT, PATCH, OPTIONS, DELETE}
// HTTP method constants.
const (
GET = "GET"
HEAD = "HEAD"
POST = "POST"
PUT = "PUT"
PATCH = "PATCH"
DELETE = "DELETE"
OPTIONS = "OPTIONS"
)
// RouteChecker checks to see if the router has a matching route
// for a given method and path.
type RouteChecker interface {
HasRoute(method string, path string) bool
}
// Router defines an interface that allows for interchangeable routers.
type Router interface {
RouteChecker
Handle(method string, pattern string, handler http.Handler)
ServeHTTP(http.ResponseWriter, *http.Request)
NotFoundHandler(http.Handler)
// MethodNotAllowedHandler registers handlers for MethodNotAllowed
// responses. The router is responsible for setting the Allow response
// header here.
MethodNotAllowedHandler(http.Handler)
}
// RouterGroup wraps the Router interface to provide route grouping by
// a base pattern path and shared middleware.
type RouterGroup interface {
RouteChecker
// Group generates a new RouterGroup from the current RouterGroup that
// shares middleware. Any middleware appended to the
// group will be appended to it's parent's middleware.
Group(middleware ...func(http.Handler) http.Handler) RouterGroup
// GroupPath generates a new RouterGroup from the current RouterGroup that
// shares a base path and middleware. Any middleware appended to the
// group will be appended to it's parent's middleware.
GroupPath(pattern string, middleware ...func(http.Handler) http.Handler) RouterGroup
// Use applies middleware to all handlers defined after the Use call in
// this RouterGroup or it's descendants.
Use(middleware ...func(http.Handler) http.Handler)
// Defines a handler and optional middleware for a GET request at pattern.
Get(pattern string, handler http.HandlerFunc)
// Defines a handler and optional middleware for a POST request at pattern.
Post(pattern string, handler http.HandlerFunc)
// Defines a handler and optional middleware for a PUT request at pattern.
Put(pattern string, handler http.HandlerFunc)
// Defines a handler and optional middleware for a PATCH request at pattern.
Patch(pattern string, handler http.HandlerFunc)
// Defines a handler and optional middleware for a HEAD request at pattern.
// Kumi defines this automatically for all GET routes. If you want
// to define your own Head handler, define it before defining
// the Get handler for the same pattern.
Head(pattern string, handler http.HandlerFunc)
// Defines a handler and optional middleware for a OPTIONS request at pattern.
Options(pattern string, handler http.HandlerFunc)
// Defines a handler and optional middleware for a DELETE request at pattern.
Delete(pattern string, handler http.HandlerFunc)
// Defines a handler and optional middleware for all
// HTTP method requests at pattern.
All(pattern string, handler http.HandlerFunc)
// NotFoundHandler registers a handler to run when no matching route is found.
NotFoundHandler(http.HandlerFunc)
// MethodNotAllowedHandler registers a handler to run when a route is valid,
// but not for the requested HTTP method.
MethodNotAllowedHandler(http.HandlerFunc)
// AutoOptionsMethod enables functionality so that all routes are
// automatically created with an OPTIONS route.
AutoOptionsMethod()
// ServeHTTP implements the http.Handler interface.
ServeHTTP(http.ResponseWriter, *http.Request)
}
// routerGroup implements RouterGroup.
type routerGroup struct {
pattern string
router Router
middleware alice.Chain
autoOptionsMethod bool
}
var _ RouterGroup = &routerGroup{}
// Group creates a sub-group of the router based on a route prefix. Any middleware
// added to the group will be appended to the parent's middleware.
func (g *routerGroup) Group(middleware ...func(http.Handler) http.Handler) RouterGroup {
c := make([]alice.Constructor, len(middleware))
for i := range middleware {
c[i] = alice.Constructor(middleware[i])
}
return &routerGroup{
router: g.router,
middleware: g.middleware.Append(c...),
autoOptionsMethod: g.autoOptionsMethod,
}
}
// GroupPath creates a sub-group of the router based on a route prefix. Any middleware
// added to the group will be appended to the parent's middleware.
func (g *routerGroup) GroupPath(pattern string, middleware ...func(http.Handler) http.Handler) RouterGroup {
c := make([]alice.Constructor, len(middleware))
for i := range middleware {
c[i] = alice.Constructor(middleware[i])
}
return &routerGroup{
pattern: pattern,
router: g.router,
middleware: g.middleware.Append(c...),
autoOptionsMethod: g.autoOptionsMethod,
}
}
// Use adds middleware to any routes used in this RouterGroup.
func (g *routerGroup) Use(middleware ...func(http.Handler) http.Handler) {
c := make([]alice.Constructor, len(middleware))
for i := range middleware {
c[i] = alice.Constructor(middleware[i])
}
g.middleware = g.middleware.Append(c...)
}
// Get defines an HTTP GET endpoint with one or more handlers.
// It will also register a HEAD endpoint. Kumi will automatically
// use a bodyless response writer.
func (g *routerGroup) Get(pattern string, handler http.HandlerFunc) {
g.handle(GET, pattern, handler)
}
// Post defines an HTTP POST endpoint with one or more handlers.
func (g *routerGroup) Post(pattern string, handler http.HandlerFunc) {
g.handle(POST, pattern, handler)
}
// Put defines an HTTP PUT endpoint with one or more handlers.
func (g *routerGroup) Put(pattern string, handler http.HandlerFunc) {
g.handle(PUT, pattern, handler)
}
// Patch defines an HTTP PATCH endpoint with one or more handlers.
func (g *routerGroup) Patch(pattern string, handler http.HandlerFunc) {
g.handle(PATCH, pattern, handler)
}
// Head defines an HTTP HEAD endpoint with one or more handlers.
// Kumi defines this automatically for all GET routes. If you want
// to define your own Head handler, define it before defining
// the Get handler for the same pattern.
func (g *routerGroup) Head(pattern string, handler http.HandlerFunc) {
g.handle(HEAD, pattern, handler)
}
// Options defines an HTTP OPTIONS endpoint with one or more handlers.
// If you are using CORS, Kumi defines this automatically for all routes.
// If you want to define your own Options handler, define it before defining
// other methods against the same pattern.
func (g *routerGroup) Options(pattern string, handler http.HandlerFunc) {
g.handle(OPTIONS, pattern, handler)
}
// Delete defines an HTTP DELETE endpoint with one or more handlers.
func (g *routerGroup) Delete(pattern string, handler http.HandlerFunc) {
g.handle(DELETE, pattern, handler)
}
// All is a convenience function that adds a handler to
// GET/HEAD/POST/PUT/PATCH/DELETE methods.
// Note HEAD/OPTIONS are set in the handle method automatically.
func (g *routerGroup) All(pattern string, handler http.HandlerFunc) {
for _, method := range HTTPMethods {
if g.autoOptionsMethod && method == OPTIONS {
continue
}
g.handle(method, pattern, handler)
}
}
// NotFoundHandler runs when no route is found.
// inhermitMiddleware determines if the global and group middleware chain
// should run on a not found request. You can optionally set to false and
// include a custom middleware chain in the handlers parameters.
func (g *routerGroup) NotFoundHandler(handler http.HandlerFunc) {
g.router.NotFoundHandler(g.middleware.ThenFunc(handler))
}
// MethodNotAllowedHandler runs when a route exists at the current
// path -- but not for the request method used.
// inhermitMiddleware determines if the global and group middleware chain
// should run on a method not allowed request. You can optionally set to
// false and include a custom middleware chain in the handlers parameters.
func (g *routerGroup) MethodNotAllowedHandler(handler http.HandlerFunc) {
g.router.MethodNotAllowedHandler(g.middleware.ThenFunc(handler))
}
// AutoOptionsMethod enables functionality so that all routes are
// automatically created with an OPTIONS route.
func (g *routerGroup) AutoOptionsMethod() {
g.autoOptionsMethod = true
}
// HasRoute checks to see if the router has a matching route
// for that method and path.
func (g *routerGroup) HasRoute(method string, path string) bool {
return g.router.HasRoute(method, path)
}
// ServeHTTP ...
func (g *routerGroup) ServeHTTP(w http.ResponseWriter, r *http.Request) {
g.router.ServeHTTP(w, r)
}
// handle consolidates all of the middleware into a route that satisfies the
// router.Handle interface
func (g *routerGroup) handle(method, pattern string, handler http.HandlerFunc) {
if handler == nil {
panic("cannot send a nil http.HandlerFunc")
}
h := g.middleware.ThenFunc(handler)
pattern = g.pattern + pattern
g.router.Handle(method, pattern, h)
// Add HEAD to all GET routes if no route is already defined.
if method == GET && !g.router.HasRoute(HEAD, pattern) {
g.router.Handle(HEAD, pattern, h)
}
// Add OPTIONS to all CORS routes if no route is already defined.
if g.autoOptionsMethod && method != OPTIONS && !g.router.HasRoute(OPTIONS, pattern) {
g.router.Handle(OPTIONS, pattern, h)
}
}
// MiddlewareFunc wraps an http.HandlerFunc so it implements func(http.Handler) http.Handler.
// Do not use this if you are wrapping ResponseWriter or using r.WithContext -
// both values need to be passed to fn.ServeHTTP in order to be accessible downstream.
func MiddlewareFunc(fn http.HandlerFunc) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fn(w, r)
next.ServeHTTP(w, r)
})
}
}