-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmux.go
239 lines (222 loc) · 7.27 KB
/
mux.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
// Copyright 2021 - 2023 PurpleSec Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package routex
import (
"context"
"net/http"
"regexp"
"sort"
"sync"
"time"
)
const (
// ErrInvalidPath is returned from the 'Add*' functions when the path is empty.
ErrInvalidPath = errStr("supplied path is invalid")
// ErrInvalidRegexp is returned from the 'AddExp*' functions when the Regexp
// expression is nil.
ErrInvalidRegexp = errStr("cannot use a nil Regexp")
// ErrInvalidHandler is returned from the 'Add*' functions when the Handler is
// nil.
ErrInvalidHandler = errStr("cannot use a nil Handler")
// ErrInvalidMethod is an error returned when the HTTP method names provided
// are empty.
ErrInvalidMethod = errStr("supplied methods contains an empty method name")
)
// Mux is a http Handler that can be used to handle connections based on Regex
// expression paths. Matching groups passed in the request URL values may be parsed
// out and passed to the resulting request.
//
// This Handler supports a base context that can be used to signal closure to all
// running Handlers.
type Mux struct {
lock sync.RWMutex
Error, Error404 ErrorHandler
Error405, Error500 ErrorHandler
ctx context.Context
log logger
Default Handler
wares *wares
routes router
Timeout time.Duration
}
// Route is an interface that allows for modification of an added HTTP route after
// being created.
//
// One example function is adding route-specific middleware.
type Route interface {
Middleware(m ...Middleware) Route
}
// Handler is a fork of the http.Handler interface. This interface supplies a base
// Context to be used and augments the supplied Request to have options for getting
// formatted JSON body content or getting the URL match groups.
type Handler interface {
Handle(context.Context, http.ResponseWriter, *Request)
}
// ErrorHandler is an interface that allows for handling any error returns to be
// reported to the client instead of using the default methods.
//
// The 'HandleError' method will be called with an error status code, error message
// and the standard 'Handler' options (except the Context).
type ErrorHandler interface {
HandleError(int, string, http.ResponseWriter, *Request)
}
// New returns a new Mux instance.
func New() *Mux {
return new(Mux)
}
// SetLog will set the internal logger for the Mux instance. This can be used to
// debug any errors during runtime.
func (m *Mux) SetLog(l logger) {
m.log = l
}
// NewContext creates a new Mux and applies the supplied Context as the Mux base Context.
func NewContext(x context.Context) *Mux {
return &Mux{ctx: x}
}
// Must adds the Handler to the supplied regex expression path. Path values must
// be unique and don't have to contain regex expressions.
//
// Regex match groups can be used to grab data out of the call and will be placed
// in the 'Values' Request map.
//
// This function panics if a duplicate path exists or the regex expression is invalid.
//
// This function will add a handler that will be considered the 'default' handler
// for the path and will be called unless a method-based Handler is also specified
// and that HTTP method is used.
func (m *Mux) Must(path string, h Handler, methods ...string) Route {
v, err := m.Add(path, h, methods...)
if err != nil {
panic(err.Error())
}
return v
}
// Add adds the Handler to the supplied regex expression path. Path values must be
// unique and don't have to contain regex expressions.
//
// Regex match groups can be used to grab data out of the call and will be placed
// in the 'Values' Request map.
//
// This function returns an error if a duplicate path exists or the regex expression
// is invalid.
//
// This function will add a handler that will be considered the 'default' handler
// for the path and will be called unless a method-based Handler is also specified
// and that HTTP method is used.
func (m *Mux) Add(path string, h Handler, methods ...string) (Route, error) {
if len(path) == 0 {
return nil, ErrInvalidPath
}
if h == nil {
return nil, ErrInvalidHandler
}
x, err := regexp.Compile(path)
if err != nil {
return nil, &errValue{s: `path "` + path + `" compile`, e: err}
}
return m.add(path, methods, x, h)
}
// MustExp adds the Handler to the supplied regex expression. Path values must be
// unique and don't have to contain regex expressions.
//
// Regex match groups can be used to grab data out of the call and will be placed
// in the 'Values' Request map.
//
// This function panics if a duplicate path exists or the regex expression is invalid.
//
// This function will add a handler that will be considered the 'default' handler
// for the path and will be called unless a method-based Handler is also specified
// and that HTTP method is used.
func (m *Mux) MustExp(exp *regexp.Regexp, h Handler, methods ...string) Route {
v, err := m.AddExp(exp, h, methods...)
if err != nil {
panic(err.Error())
}
return v
}
// AddExp adds the Handler to the supplied regex expression. Path values must be
// unique and don't have to contain regex expressions.
//
// Regex match groups can be used to grab data out of the call and will be placed
// in the 'Values' Request map.
//
// This function returns an error if a duplicate path exists or the regex expression
// is invalid.
//
// This function will add a handler that will be considered the 'default' handler
// for the path and will be called unless a method-based Handler is also specified
// and that HTTP method is used.
func (m *Mux) AddExp(exp *regexp.Regexp, h Handler, methods ...string) (Route, error) {
if exp == nil {
return nil, ErrInvalidRegexp
}
v := exp.String()
if len(v) == 0 {
return nil, ErrInvalidPath
}
if h == nil {
return nil, ErrInvalidHandler
}
return m.add(v, methods, exp, h)
}
func (m *Mux) add(path string, methods []string, x *regexp.Regexp, h Handler) (*handler, error) {
for _, n := range methods {
if len(n) == 0 {
return nil, ErrInvalidMethod
}
}
if m.lock.Lock(); len(m.routes) > 0 {
for i := range m.routes {
if m.routes[i].matcher.String() != path {
continue
}
if len(methods) > 0 {
if m.routes[i].method == nil {
m.routes[i].method = make(map[string]*handler, len(methods))
}
v := &handler{h: h}
for _, n := range methods {
m.routes[i].method[n] = v
}
m.lock.Unlock()
return v, nil
}
if m.routes[i].base != nil {
m.lock.Unlock()
return nil, errStr(`matcher path "` + path + `" already exists`)
}
v := &handler{h: h}
m.routes[i].base = v
m.lock.Unlock()
return v, nil
}
}
var (
v = &handler{h: h}
e = &entry{matcher: x}
)
if len(methods) > 0 {
e.method = make(map[string]*handler, len(methods))
for _, n := range methods {
e.method[n] = v
}
} else {
e.base = v
}
m.routes = append(m.routes, e)
sort.Sort(m.routes)
m.lock.Unlock()
return v, nil
}