-
Notifications
You must be signed in to change notification settings - Fork 125
/
router_setup.go
401 lines (337 loc) · 13.9 KB
/
router_setup.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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
package web
import (
"reflect"
"strings"
)
type httpMethod string
const (
httpMethodGet = httpMethod("GET")
httpMethodPost = httpMethod("POST")
httpMethodPut = httpMethod("PUT")
httpMethodDelete = httpMethod("DELETE")
httpMethodPatch = httpMethod("PATCH")
httpMethodHead = httpMethod("HEAD")
httpMethodOptions = httpMethod("OPTIONS")
)
var httpMethods = []httpMethod{httpMethodGet, httpMethodPost, httpMethodPut, httpMethodDelete, httpMethodPatch, httpMethodHead, httpMethodOptions}
// Router implements net/http's Handler interface and is what you attach middleware, routes/handlers, and subrouters to.
type Router struct {
// Hierarchy:
parent *Router // nil if root router.
children []*Router
maxChildrenDepth int
// For each request we'll create one of these objects
contextType reflect.Type
// Eg, "/" or "/admin". Any routes added to this router will be prefixed with this.
pathPrefix string
// Routeset contents:
middleware []*middlewareHandler
routes []*route
// The root pathnode is the same for a tree of Routers
root map[httpMethod]*pathNode
// This can can be set on any router. The target's ErrorHandler will be invoked if it exists
errorHandler reflect.Value
// This can only be set on the root handler, since by virtue of not finding a route, we don't have a target.
// (That being said, in the future we could investigate namespace matches)
notFoundHandler reflect.Value
// This can only be set on the root handler, since by virtue of not finding a route, we don't have a target.
optionsHandler reflect.Value
}
// NextMiddlewareFunc are functions passed into your middleware. To advance the middleware, call the function.
// You should usually pass the existing ResponseWriter and *Request into the next middlware, but you can
// chose to swap them if you want to modify values or capture things written to the ResponseWriter.
type NextMiddlewareFunc func(ResponseWriter, *Request)
// GenericMiddleware are middleware that doesn't have or need a context. General purpose middleware, such as
// static file serving, has this signature. If your middlware doesn't need a context, you can use this
// signature to get a small performance boost.
type GenericMiddleware func(ResponseWriter, *Request, NextMiddlewareFunc)
// GenericHandler are handlers that don't have or need a context. If your handler doesn't need a context,
// you can use this signature to get a small performance boost.
type GenericHandler func(ResponseWriter, *Request)
type route struct {
Router *Router
Method httpMethod
Path string
Handler *actionHandler
}
type middlewareHandler struct {
Generic bool
DynamicMiddleware reflect.Value
GenericMiddleware GenericMiddleware
}
type actionHandler struct {
Generic bool
DynamicHandler reflect.Value
GenericHandler GenericHandler
}
var emptyInterfaceType = reflect.TypeOf((*interface{})(nil)).Elem()
// New returns a new router with context type ctx. ctx should be a struct instance,
// whose purpose is to communicate type information. On each request, an instance of this
// context type will be automatically allocated and sent to handlers.
func New(ctx interface{}) *Router {
validateContext(ctx, nil)
r := &Router{}
r.contextType = reflect.TypeOf(ctx)
r.pathPrefix = "/"
r.maxChildrenDepth = 1
r.root = make(map[httpMethod]*pathNode)
for _, method := range httpMethods {
r.root[method] = newPathNode()
}
return r
}
// NewWithPrefix returns a new router (see New) but each route will have an implicit prefix.
// For instance, with pathPrefix = "/api/v2", all routes under this router will begin with "/api/v2".
func NewWithPrefix(ctx interface{}, pathPrefix string) *Router {
r := New(ctx)
r.pathPrefix = pathPrefix
return r
}
// Subrouter attaches a new subrouter to the specified router and returns it.
// You can use the same context or pass a new one. If you pass a new one, it must
// embed a pointer to the previous context in the first slot. You can also pass
// a pathPrefix that each route will have. If "" is passed, then no path prefix is applied.
func (r *Router) Subrouter(ctx interface{}, pathPrefix string) *Router {
validateContext(ctx, r.contextType)
// Create new router, link up hierarchy
newRouter := &Router{parent: r}
r.children = append(r.children, newRouter)
// Increment maxChildrenDepth if this is the first child of the router
if len(r.children) == 1 {
curParent := r
for curParent != nil {
curParent.maxChildrenDepth = curParent.depth()
curParent = curParent.parent
}
}
newRouter.contextType = reflect.TypeOf(ctx)
newRouter.pathPrefix = appendPath(r.pathPrefix, pathPrefix)
newRouter.root = r.root
return newRouter
}
// Middleware adds the specified middleware tot he router and returns the router.
func (r *Router) Middleware(fn interface{}) *Router {
vfn := reflect.ValueOf(fn)
validateMiddleware(vfn, r.contextType)
if vfn.Type().NumIn() == 3 {
r.middleware = append(r.middleware, &middlewareHandler{Generic: true, GenericMiddleware: fn.(func(ResponseWriter, *Request, NextMiddlewareFunc))})
} else {
r.middleware = append(r.middleware, &middlewareHandler{Generic: false, DynamicMiddleware: vfn})
}
return r
}
// Error sets the specified function as the error handler (when panics happen) and returns the router.
func (r *Router) Error(fn interface{}) *Router {
vfn := reflect.ValueOf(fn)
validateErrorHandler(vfn, r.contextType)
r.errorHandler = vfn
return r
}
// NotFound sets the specified function as the not-found handler (when no route matches) and returns the router.
// Note that only the root router can have a NotFound handler.
func (r *Router) NotFound(fn interface{}) *Router {
if r.parent != nil {
panic("You can only set a NotFoundHandler on the root router.")
}
vfn := reflect.ValueOf(fn)
validateNotFoundHandler(vfn, r.contextType)
r.notFoundHandler = vfn
return r
}
// OptionsHandler sets the specified function as the options handler and returns the router.
// Note that only the root router can have a OptionsHandler handler.
func (r *Router) OptionsHandler(fn interface{}) *Router {
if r.parent != nil {
panic("You can only set an OptionsHandler on the root router.")
}
vfn := reflect.ValueOf(fn)
validateOptionsHandler(vfn, r.contextType)
r.optionsHandler = vfn
return r
}
// Get will add a route to the router that matches on GET requests and the specified path.
func (r *Router) Get(path string, fn interface{}) *Router {
return r.addRoute(httpMethodGet, path, fn)
}
// Post will add a route to the router that matches on POST requests and the specified path.
func (r *Router) Post(path string, fn interface{}) *Router {
return r.addRoute(httpMethodPost, path, fn)
}
// Put will add a route to the router that matches on PUT requests and the specified path.
func (r *Router) Put(path string, fn interface{}) *Router {
return r.addRoute(httpMethodPut, path, fn)
}
// Delete will add a route to the router that matches on DELETE requests and the specified path.
func (r *Router) Delete(path string, fn interface{}) *Router {
return r.addRoute(httpMethodDelete, path, fn)
}
// Patch will add a route to the router that matches on PATCH requests and the specified path.
func (r *Router) Patch(path string, fn interface{}) *Router {
return r.addRoute(httpMethodPatch, path, fn)
}
// Head will add a route to the router that matches on HEAD requests and the specified path.
func (r *Router) Head(path string, fn interface{}) *Router {
return r.addRoute(httpMethodHead, path, fn)
}
// Options will add a route to the router that matches on OPTIONS requests and the specified path.
func (r *Router) Options(path string, fn interface{}) *Router {
return r.addRoute(httpMethodOptions, path, fn)
}
func (r *Router) addRoute(method httpMethod, path string, fn interface{}) *Router {
vfn := reflect.ValueOf(fn)
validateHandler(vfn, r.contextType)
fullPath := appendPath(r.pathPrefix, path)
route := &route{Method: method, Path: fullPath, Router: r}
if vfn.Type().NumIn() == 2 {
route.Handler = &actionHandler{Generic: true, GenericHandler: fn.(func(ResponseWriter, *Request))}
} else {
route.Handler = &actionHandler{Generic: false, DynamicHandler: vfn}
}
r.routes = append(r.routes, route)
r.root[method].add(fullPath, route)
return r
}
// Calculates the max child depth of the node. Leaves return 1. For Parent->Child, Parent is 2.
func (r *Router) depth() int {
max := 0
for _, child := range r.children {
childDepth := child.depth()
if childDepth > max {
max = childDepth
}
}
return max + 1
}
//
// Private methods:
//
// Panics unless validation is correct
func validateContext(ctx interface{}, parentCtxType reflect.Type) {
ctxType := reflect.TypeOf(ctx)
if ctxType.Kind() != reflect.Struct {
panic("web: Context needs to be a struct type")
}
if parentCtxType != nil && parentCtxType != ctxType {
if ctxType.NumField() == 0 {
panic("web: Context needs to have first field be a pointer to parent context")
}
fldType := ctxType.Field(0).Type
// Ensure fld is a pointer to parentCtxType
if fldType != reflect.PtrTo(parentCtxType) {
panic("web: Context needs to have first field be a pointer to parent context")
}
}
}
// Panics unless fn is a proper handler wrt ctxType
// eg, func(ctx *ctxType, writer, request)
func validateHandler(vfn reflect.Value, ctxType reflect.Type) {
var req *Request
var resp func() ResponseWriter
if !isValidHandler(vfn, ctxType, reflect.TypeOf(resp).Out(0), reflect.TypeOf(req)) {
panic(instructiveMessage(vfn, "a handler", "handler", "rw web.ResponseWriter, req *web.Request", ctxType))
}
}
func validateErrorHandler(vfn reflect.Value, ctxType reflect.Type) {
var req *Request
var resp func() ResponseWriter
if !isValidHandler(vfn, ctxType, reflect.TypeOf(resp).Out(0), reflect.TypeOf(req), emptyInterfaceType) {
panic(instructiveMessage(vfn, "an error handler", "error handler", "rw web.ResponseWriter, req *web.Request, err interface{}", ctxType))
}
}
func validateNotFoundHandler(vfn reflect.Value, ctxType reflect.Type) {
var req *Request
var resp func() ResponseWriter
if !isValidHandler(vfn, ctxType, reflect.TypeOf(resp).Out(0), reflect.TypeOf(req)) {
panic(instructiveMessage(vfn, "a 'not found' handler", "not found handler", "rw web.ResponseWriter, req *web.Request", ctxType))
}
}
func validateOptionsHandler(vfn reflect.Value, ctxType reflect.Type) {
var req *Request
var resp func() ResponseWriter
var methods []string
if !isValidHandler(vfn, ctxType, reflect.TypeOf(resp).Out(0), reflect.TypeOf(req), reflect.TypeOf(methods)) {
panic(instructiveMessage(vfn, "an 'options' handler", "options handler", "rw web.ResponseWriter, req *web.Request, methods []string", ctxType))
}
}
func validateMiddleware(vfn reflect.Value, ctxType reflect.Type) {
var req *Request
var resp func() ResponseWriter
var n NextMiddlewareFunc
if !isValidHandler(vfn, ctxType, reflect.TypeOf(resp).Out(0), reflect.TypeOf(req), reflect.TypeOf(n)) {
panic(instructiveMessage(vfn, "middleware", "middleware", "rw web.ResponseWriter, req *web.Request, next web.NextMiddlewareFunc", ctxType))
}
}
// Ensures vfn is a function, that optionally takes a *ctxType as the first argument, followed by the specified types. Handlers have no return value.
// Returns true if valid, false otherwise.
func isValidHandler(vfn reflect.Value, ctxType reflect.Type, types ...reflect.Type) bool {
fnType := vfn.Type()
if fnType.Kind() != reflect.Func {
return false
}
typesStartIdx := 0
typesLen := len(types)
numIn := fnType.NumIn()
numOut := fnType.NumOut()
if numOut != 0 {
return false
}
if numIn == typesLen {
// No context
} else if numIn == (typesLen + 1) {
// context, types
firstArgType := fnType.In(0)
if firstArgType != reflect.PtrTo(ctxType) && firstArgType != emptyInterfaceType {
return false
}
typesStartIdx = 1
} else {
return false
}
for _, typeArg := range types {
if fnType.In(typesStartIdx) != typeArg {
return false
}
typesStartIdx++
}
return true
}
// Since it's easy to pass the wrong method to a middleware/handler route, and since the user can't rely on static type checking since we use reflection,
// lets be super helpful about what they did and what they need to do.
// Arguments:
// - vfn is the failed method
// - addingType is for "You are adding {addingType} to a router...". Eg, "middleware" or "a handler" or "an error handler"
// - yourType is for "Your {yourType} function can have...". Eg, "middleware" or "handler" or "error handler"
// - args is like "rw web.ResponseWriter, req *web.Request, next web.NextMiddlewareFunc"
// - NOTE: args can be calculated if you pass in each type. BUT, it doesn't have example argument name, so it has less copy/paste value.
func instructiveMessage(vfn reflect.Value, addingType string, yourType string, args string, ctxType reflect.Type) string {
// Get context type without package.
ctxString := ctxType.String()
splitted := strings.Split(ctxString, ".")
if len(splitted) <= 1 {
ctxString = splitted[0]
} else {
ctxString = splitted[1]
}
str := "\n" + strings.Repeat("*", 120) + "\n"
str += "* You are adding " + addingType + " to a router with context type '" + ctxString + "'\n"
str += "*\n*\n"
str += "* Your " + yourType + " function can have one of these signatures:\n"
str += "*\n"
str += "* // If you don't need context:\n"
str += "* func YourFunctionName(" + args + ")\n"
str += "*\n"
str += "* // If you want your " + yourType + " to accept a context:\n"
str += "* func (c *" + ctxString + ") YourFunctionName(" + args + ") // or,\n"
str += "* func YourFunctionName(c *" + ctxString + ", " + args + ")\n"
str += "*\n"
str += "* Unfortunately, your function has this signature: " + vfn.Type().String() + "\n"
str += "*\n"
str += strings.Repeat("*", 120) + "\n"
return str
}
// Both rootPath/childPath are like "/" and "/users"
// Assumption is that both are well-formed paths.
// Returns a path without a trailing "/" unless the overall path is just "/"
func appendPath(rootPath, childPath string) string {
return strings.TrimRight(rootPath, "/") + "/" + strings.TrimLeft(childPath, "/")
}