-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathroute.go
More file actions
239 lines (212 loc) · 6.18 KB
/
route.go
File metadata and controls
239 lines (212 loc) · 6.18 KB
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
package fox
import (
"fmt"
"iter"
"slices"
"strings"
)
// Route represents an immutable HTTP route with associated handlers and settings.
type Route struct {
clientip ClientIPResolver
hbase HandlerFunc
hself HandlerFunc
hall HandlerFunc
annots map[any]any
name string
methods []string
mws []middleware
params []string
matchers []Matcher
methodFast string
pattern pattern
priority uint
handleSlash TrailingSlashOption
}
// Handle calls the handler with the provided [Context]. See also [Route.HandleMiddleware].
func (r *Route) Handle(c *Context) {
r.hbase(c)
}
// HandleMiddleware calls the handler with route-specific middleware applied, using the provided [Context].
// This method is not intended to be used as the handler for another route, as the middleware chain would
// be duplicated when registered. To reuse a route's handler, use [Route.Handle] directly or register
// a new route via [Router.AddRoute] or [Router.UpdateRoute].
func (r *Route) HandleMiddleware(c *Context, _ ...struct{}) {
// The variadic parameter is intentionally added to prevent this method from having the same signature
// as HandlerFunc, avoiding accidental misuse where a HandlerFunc is required.
r.hself(c)
}
// Methods returns an iterator over all HTTP methods this route responds to (if any), in lexicographical order.
func (r *Route) Methods() iter.Seq[string] {
return func(yield func(string) bool) {
for _, m := range r.methods {
if !yield(m) {
return
}
}
}
}
// Pattern returns the registered route pattern.
func (r *Route) Pattern() string {
return r.pattern.str
}
// Hostname returns the hostname part of the registered pattern if any.
func (r *Route) Hostname() string {
return r.pattern.str[:r.pattern.endHost]
}
// Path returns the path part of the registered pattern.
func (r *Route) Path() string {
return r.pattern.str[r.pattern.endHost:]
}
// Name returns the name of this [Route].
func (r *Route) Name() string {
return r.name
}
// Annotation returns the value associated with this [Route] for key, or nil if no value is associated with key.
// Successive calls to Annotation with the same key returns the same result.
func (r *Route) Annotation(key any) any {
return r.annots[key]
}
// TrailingSlashOption returns the configured [TrailingSlashOption] for this [Route].
func (r *Route) TrailingSlashOption() TrailingSlashOption {
return r.handleSlash
}
// ClientIPResolver returns the [ClientIPResolver] configured for this [Route], if any.
func (r *Route) ClientIPResolver() ClientIPResolver {
if _, ok := r.clientip.(noClientIPResolver); ok {
return nil
}
return r.clientip
}
// ParamsLen returns the number of parameters for this [Route].
func (r *Route) ParamsLen() int {
return len(r.params)
}
// Params returns an iterator over all parameters name for this [Route].
func (r *Route) Params() iter.Seq[string] {
return func(yield func(string) bool) {
for _, param := range r.params {
if !yield(param) {
return
}
}
}
}
// MatchersLen returns the number of matchers for this [Route].
func (r *Route) MatchersLen() int {
return len(r.matchers)
}
// Matchers returns an iterator over all matchers attached to this [Route].
func (r *Route) Matchers() iter.Seq[Matcher] {
return func(yield func(Matcher) bool) {
for _, m := range r.matchers {
if !yield(m) {
return
}
}
}
}
// MatchersPriority returns the matchers priority for this [Route].
func (r *Route) MatchersPriority() uint {
return r.priority
}
func (r *Route) String() string {
sb := new(strings.Builder)
routef(sb, r, 0, true)
return sb.String()
}
// match reports whether the request satisfies this route's method constraint (if any)
// and all attached matchers.
func (r *Route) match(method string, c *Context) bool {
// Fast path: routes with exactly one method and no matchers cache that
// method in methodFast.
if r.methodFast == method {
return true
}
return r.matchSlow(method, c)
}
// matchSlow handles the cases match's fast path does not cover: zero or many
// methods, and routes with matchers. It is kept out-of-line so match remains
// inlinable.
//
//go:noinline
func (r *Route) matchSlow(method string, c *Context) bool {
methods := r.methods
switch len(methods) {
case 0:
// No method constraint.
case 1:
// Avoid the slices.Contains overhead for the single-method case (which
// match's fast path leaves to us when matchers are present).
if methods[0] != method {
return false
}
default:
if !slices.Contains(methods, method) {
return false
}
}
sealed := onlyRequestContext{c}
for _, m := range r.matchers {
if !m.Match(sealed) {
return false
}
}
return true
}
// matchersEqual reports whether this [Route]'s matchers are equal to the provided matchers.
func (r *Route) matchersEqual(matchers []Matcher) bool {
if len(r.matchers) != len(matchers) {
return false
}
// Runs in O(n²) time, but the matched slice should be stack-allocated in most cases.
// A hash-based O(n) approach was considered, but for small arrays the cost of populating
// a map outweighs the quadratic comparison cost. Additionally, maps with more than 8 elements
// are heap-allocated, which adds to the cost.
matched := make([]bool, len(matchers))
outer:
for _, a := range r.matchers {
for i, b := range matchers {
if !matched[i] && a.Equal(b) {
matched[i] = true
continue outer
}
}
return false
}
return true
}
func routef(sb *strings.Builder, route *Route, pad int, showName bool) {
sb.WriteString(strings.Repeat(" ", pad))
sb.WriteString("method:")
if len(route.methods) > 0 {
first := route.methods[0]
sb.WriteString(first)
for _, method := range route.methods[1:] {
sb.WriteByte(',')
sb.WriteString(method)
}
} else {
sb.WriteString("*")
}
sb.WriteString(" pattern:")
sb.WriteString(route.pattern.str)
if route.name != "" && showName {
sb.WriteString(" name:")
sb.WriteString(route.name)
}
size := sb.Len()
for _, matcher := range route.matchers {
if m, ok := matcher.(fmt.Stringer); ok {
if sb.Len() > size {
sb.WriteByte(',')
}
if size == sb.Len() {
sb.WriteString(" matchers:{")
}
sb.WriteString(m.String())
}
}
if sb.Len() > size {
sb.WriteByte('}')
}
}