-
Notifications
You must be signed in to change notification settings - Fork 0
/
options.go
263 lines (236 loc) · 10.4 KB
/
options.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
// Copyright 2022 Sylvain Müller. All rights reserved.
// Mount of this source code is governed by a Apache-2.0 license that can be found
// at https://github.com/tigerwill90/fox/blob/master/LICENSE.txt.
package fox
import (
"cmp"
)
type Option interface {
GlobalOption
RouteOption
}
type GlobalOption interface {
applyGlob(*Router)
}
type RouteOption interface {
applyRoute(*Route)
}
type globOptionFunc func(*Router)
func (o globOptionFunc) applyGlob(r *Router) {
o(r)
}
// nolint:unused
type pathOptionFunc func(*Route)
// nolint:unused
func (o pathOptionFunc) applyRoute(r *Route) {
o(r)
}
type optionFunc func(*Router, *Route)
func (o optionFunc) applyGlob(r *Router) {
o(r, nil)
}
func (o optionFunc) applyRoute(r *Route) {
o(nil, r)
}
// WithNoRouteHandler register an [HandlerFunc] which is called when no matching route is found.
// By default, the [DefaultNotFoundHandler] is used.
func WithNoRouteHandler(handler HandlerFunc) GlobalOption {
return globOptionFunc(func(r *Router) {
if handler != nil {
r.noRoute = handler
}
})
}
// WithNoMethodHandler register an [HandlerFunc] which is called when the request cannot be routed,
// but the same route exist for other methods. The "Allow" header it automatically set before calling the
// handler. By default, the [DefaultMethodNotAllowedHandler] is used. Note that this option automatically
// enable [WithNoMethod].
func WithNoMethodHandler(handler HandlerFunc) GlobalOption {
return globOptionFunc(func(r *Router) {
if handler != nil {
r.noMethod = handler
r.handleMethodNotAllowed = true
}
})
}
// WithOptionsHandler register an [HandlerFunc] which is called on automatic OPTIONS requests. By default, the router
// respond with a 200 OK status code. The "Allow" header it automatically set before calling the handler. Note that custom OPTIONS
// handler take priority over automatic replies. By default, [DefaultOptionsHandler] is used. Note that this option
// automatically enable [WithAutoOptions].
func WithOptionsHandler(handler HandlerFunc) GlobalOption {
return globOptionFunc(func(r *Router) {
if handler != nil {
r.autoOptions = handler
r.handleOptions = true
}
})
}
// WithMaxRouteParams set the maximum number of parameters allowed in a route. The default max is math.MaxUint16.
func WithMaxRouteParams(max uint16) GlobalOption {
return globOptionFunc(func(router *Router) {
router.maxParams = max
})
}
// WithMaxRouteParamKeyBytes set the maximum number of bytes allowed per parameter key in a route. The default max is
// math.MaxUint16.
func WithMaxRouteParamKeyBytes(max uint16) GlobalOption {
return globOptionFunc(func(router *Router) {
router.maxParamKeyBytes = max
})
}
// WithMiddleware attaches middleware to the router or to a specific route. The middlewares are executed
// in the order they are added. When applied globally, the middleware affects all handlers, including special handlers
// such as NotFound, MethodNotAllowed, AutoOption, and the internal redirect handler.
//
// This option can be applied on a per-route basis or globally:
// - If applied globally, the middleware will be applied to all routes and handlers by default.
// - If applied to a specific route, the middleware will only apply to that route and will be chained after any global middleware.
//
// Route-specific middleware must be explicitly reapplied when updating a route. If not, any middleware will be removed,
// and the route will fall back to using only global middleware (if any).
func WithMiddleware(m ...MiddlewareFunc) Option {
return optionFunc(func(router *Router, route *Route) {
if router != nil {
for i := range m {
router.mws = append(router.mws, middleware{m[i], AllHandlers, true})
}
}
if route != nil {
for i := range m {
route.mws = append(route.mws, middleware{m[i], RouteHandler, false})
}
}
})
}
// WithMiddlewareFor attaches middleware to the router for a specified scope. Middlewares provided will be chained
// in the order they were added. The scope parameter determines which types of handlers the middleware will be applied to.
// Possible scopes include [RouteHandler] (regular routes), [NoRouteHandler], [NoMethodHandler], [RedirectHandler],
// [OptionsHandler], and any combination of these. Use this option when you need fine-grained control over where the
// middleware is applied.
func WithMiddlewareFor(scope HandlerScope, m ...MiddlewareFunc) GlobalOption {
return globOptionFunc(func(r *Router) {
for i := range m {
r.mws = append(r.mws, middleware{m[i], scope, true})
}
})
}
// WithNoMethod enable to returns 405 Method Not Allowed instead of 404 Not Found
// when the route exist for another http verb. The "Allow" header it automatically set before calling the
// handler. Note that this option is automatically enabled when providing a custom handler with the
// option [WithNoMethodHandler].
func WithNoMethod(enable bool) GlobalOption {
return globOptionFunc(func(r *Router) {
r.handleMethodNotAllowed = enable
})
}
// WithAutoOptions enables automatic response to OPTIONS requests with, by default, a 200 OK status code.
// Use the [WithOptionsHandler] option to customize the response. When this option is enabled, the router automatically
// determines the "Allow" header value based on the methods registered for the given route. Note that custom OPTIONS
// handler take priority over automatic replies. This option is automatically enabled when providing a custom handler with
// the option [WithOptionsHandler].
func WithAutoOptions(enable bool) GlobalOption {
return globOptionFunc(func(r *Router) {
r.handleOptions = enable
})
}
// WithRedirectTrailingSlash enable automatic redirection fallback when the current request does not match but
// another handler is found with/without an additional trailing slash. E.g. /foo/bar/ request does not match
// but /foo/bar would match. The client is redirected with a http status code 301 for GET requests and 308 for
// all other methods.
//
// This option can be applied on a per-route basis or globally:
// - If applied globally, it affects all routes by default.
// - If applied to a specific route, it will override the global setting for that route.
// - The option must be explicitly reapplied when updating a route. If not, the route will fall back
// to the global configuration for trailing slash behavior.
//
// Note that this option is mutually exclusive with [WithIgnoreTrailingSlash], and if enabled will
// automatically deactivate [WithIgnoreTrailingSlash].
func WithRedirectTrailingSlash(enable bool) Option {
return optionFunc(func(router *Router, route *Route) {
if router != nil {
router.redirectTrailingSlash = enable
if enable {
router.ignoreTrailingSlash = false
}
}
if route != nil {
route.redirectTrailingSlash = enable
if enable {
route.ignoreTrailingSlash = false
}
}
})
}
// WithIgnoreTrailingSlash allows the router to match routes regardless of whether a trailing slash is present or not.
// E.g. /foo/bar/ and /foo/bar would both match the same handler. This option prevents the router from issuing
// a redirect and instead matches the request directly.
//
// This option can be applied on a per-route basis or globally:
// - If applied globally, it affects all routes by default.
// - If applied to a specific route, it will override the global setting for that route.
// - The option must be explicitly reapplied when updating a route. If not, the route will fall back
// to the global configuration for trailing slash behavior.
//
// Note that this option is mutually exclusive with [WithRedirectTrailingSlash], and if enabled will automatically
// deactivate [WithRedirectTrailingSlash].
func WithIgnoreTrailingSlash(enable bool) Option {
return optionFunc(func(router *Router, route *Route) {
if router != nil {
router.ignoreTrailingSlash = enable
if enable {
router.redirectTrailingSlash = false
}
}
if route != nil {
route.ignoreTrailingSlash = enable
if enable {
route.redirectTrailingSlash = false
}
}
})
}
// WithClientIPResolver sets the resolver for obtaining the "real" client IP address from HTTP requests.
// This resolver is used by the [Context.ClientIP] method. The resolver must be chosen and tuned for your network
// configuration to ensure it never returns an error -- i.e., never fails to find a candidate for the "real" IP.
// Consequently, getting an error result should be treated as an application error, perhaps even worthy of panicking.
// There is no sane default, so if no resolver is configured, [Context.ClientIP] returns [ErrNoClientIPResolver].
//
// This option can be applied on a per-route basis or globally:
// - If applied globally, it affects all routes by default.
// - If applied to a specific route, it will override the global setting for that route.
// - The option must be explicitly reapplied when updating a route. If not, the route will fall back
// to the global client IP resolver (if one is configured).
// - Setting the resolver to nil is equivalent to no resolver configured.
func WithClientIPResolver(resolver ClientIPResolver) Option {
return optionFunc(func(router *Router, route *Route) {
if router != nil && resolver != nil {
router.clientip = resolver
}
if route != nil {
// Apply no resolver if nil provided.
route.clientip = cmp.Or(resolver, ClientIPResolver(noClientIPResolver{}))
}
})
}
// WithAnnotations attach arbitrary metadata to routes. Annotations are key-value pairs that allow middleware, handler or
// any other components to modify behavior based on the attached metadata. Unlike context-based metadata, which is tied to
// the request lifetime, annotations are bound to the route's lifetime and remain static across all requests for that route.
// Annotations must be explicitly reapplied when updating a route.
func WithAnnotations(annotations ...Annotation) RouteOption {
return pathOptionFunc(func(route *Route) {
route.annots = append(route.annots, annotations...)
})
}
// DefaultOptions configure the router to use the [Recovery] middleware for the [RouteHandler] scope, the [Logger] middleware
// for [AllHandlers] scope and enable automatic OPTIONS response. Note that DefaultOptions push the [Recovery] and [Logger]
// middleware respectively to the first and second position of the middleware chains.
func DefaultOptions() GlobalOption {
return globOptionFunc(func(r *Router) {
r.mws = append([]middleware{
{Recovery(), RouteHandler, true},
{Logger(), AllHandlers, true},
}, r.mws...)
r.handleOptions = true
})
}