-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathoptions.go
254 lines (228 loc) · 10.1 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
// 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
PathOption
}
type GlobalOption interface {
applyGlob(*Router)
}
type PathOption interface {
applyPath(*Route)
}
type globOptionFunc func(*Router)
func (o globOptionFunc) applyGlob(r *Router) {
o(r)
}
// nolint:unused
type pathOptionFunc func(*Route)
// nolint:unused
func (o pathOptionFunc) applyPath(r *Route) {
o(r)
}
type optionFunc func(*Router, *Route)
func (o optionFunc) applyGlob(r *Router) {
o(r, nil)
}
func (o optionFunc) applyPath(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
}
})
}
// 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
}
}
})
}
// WithClientIPStrategy sets the strategy for obtaining the "real" client IP address from HTTP requests.
// This strategy is used by the Context.ClientIP method. The strategy 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 strategy is configured, Context.ClientIP returns ErrNoClientIPStrategy.
//
// 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 strategy (if one is configured).
// - Setting the strategy to nil is equivalent to no strategy configured.
func WithClientIPStrategy(strategy ClientIPStrategy) Option {
return optionFunc(func(router *Router, route *Route) {
if router != nil && strategy != nil {
router.ipStrategy = strategy
}
if route != nil {
// Apply no strategy if nil provided.
route.ipStrategy = cmp.Or(strategy, ClientIPStrategy(noClientIPStrategy{}))
}
})
}
// 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) PathOption {
return pathOptionFunc(func(route *Route) {
route.annots = append(route.annots, annotations...)
})
}
// WithAnnotation attaches a single key-value annotation to a route. See also [WithAnnotations] and [Annotation] for more details.
func WithAnnotation(key string, value any) PathOption {
return pathOptionFunc(func(route *Route) {
route.annots = append(route.annots, Annotation{key, value})
})
}
// 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
})
}