forked from kataras/iris
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhandler.go
366 lines (316 loc) · 10.7 KB
/
handler.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
package context
import (
"os"
"path/filepath"
"reflect"
"regexp"
"runtime"
"strings"
"sync"
)
var (
// PackageName is the Iris Go module package name.
PackageName = strings.TrimSuffix(reflect.TypeOf(Handlers{}).PkgPath(), "/context")
// WorkingDir is the (initial) current directory.
WorkingDir, _ = os.Getwd()
)
var (
handlerNames = make(map[*NameExpr]string)
handlerNamesMu sync.RWMutex
ignoreMainHandlerNames = [...]string{
"iris.cache",
"iris.basicauth",
"iris.hCaptcha",
"iris.reCAPTCHA",
"iris.profiling",
"iris.recover",
"iris.accesslog",
"iris.grpc",
"iris.requestid",
"iris.rewrite",
"iris.cors",
"iris.jwt",
"iris.logger",
"iris.rate",
"iris.methodoverride",
}
)
// SetHandlerName sets a handler name that could be
// fetched through `HandlerName`. The "original" should be
// the Go's original regexp-featured (can be retrieved through a `HandlerName` call) function name.
// The "replacement" should be the custom, human-text of that function name.
//
// If the name starts with "iris" then it replaces that string with the
// full Iris module package name,
// e.g. iris/middleware/logger.(*requestLoggerMiddleware).ServeHTTP-fm to
// github.com/kataras/iris/v12/middleware/logger.(*requestLoggerMiddleware).ServeHTTP-fm
// for convenient between Iris versions.
func SetHandlerName(original string, replacement string) {
if strings.HasPrefix(original, "iris") {
original = PackageName + strings.TrimPrefix(original, "iris")
}
handlerNamesMu.Lock()
// If regexp syntax is wrong
// then its `MatchString` will compare through literal. Fixes an issue
// when a handler name is declared as it's and cause regex parsing expression error,
// e.g. `iris/cache/client.(*Handler).ServeHTTP-fm`
regex, _ := regexp.Compile(original)
handlerNames[&NameExpr{
literal: original,
regex: regex,
}] = replacement
handlerNamesMu.Unlock()
}
// NameExpr regex or literal comparison through `MatchString`.
type NameExpr struct {
regex *regexp.Regexp
literal string
}
// MatchString reports whether "s" is literal of "literal"
// or it matches the regex expression at "regex".
func (expr *NameExpr) MatchString(s string) bool {
if expr.literal == s { // if matches as string, as it's.
return true
}
if expr.regex != nil {
return expr.regex.MatchString(s)
}
return false
}
// A Handler responds to an HTTP request.
// It writes reply headers and data to the Context.ResponseWriter() and then return.
// Returning signals that the request is finished;
// it is not valid to use the Context after or concurrently with the completion of the Handler call.
//
// Depending on the HTTP client software, HTTP protocol version,
// and any intermediaries between the client and the iris server,
// it may not be possible to read from the Context.Request().Body after writing to the Context.ResponseWriter().
// Cautious handlers should read the Context.Request().Body first, and then reply.
//
// Except for reading the body, handlers should not modify the provided Context.
//
// If Handler panics, the server (the caller of Handler) assumes that the effect of the panic was isolated to the active request.
// It recovers the panic, logs a stack trace to the server error log, and hangs up the connection.
type Handler = func(*Context)
// Handlers is just a type of slice of []Handler.
//
// See `Handler` for more.
type Handlers = []Handler
func valueOf(v interface{}) reflect.Value {
if val, ok := v.(reflect.Value); ok {
return val
}
return reflect.ValueOf(v)
}
// HandlerName returns the handler's function name.
// See `Context.HandlerName` method to get function name of the current running handler in the chain.
// See `SetHandlerName` too.
func HandlerName(h interface{}) string {
pc := valueOf(h).Pointer()
name := runtime.FuncForPC(pc).Name()
handlerNamesMu.RLock()
for expr, newName := range handlerNames {
if expr.MatchString(name) {
name = newName
break
}
}
handlerNamesMu.RUnlock()
return trimHandlerName(name)
}
// HandlersNames returns a slice of "handlers" names
// separated by commas. Can be used for debugging
// or to determinate if end-developer
// called the same exactly Use/UseRouter/Done... API methods
// so framework can give a warning.
func HandlersNames(handlers ...interface{}) string {
if len(handlers) == 1 {
if hs, ok := handlers[0].(Handlers); ok {
asInterfaces := make([]interface{}, 0, len(hs))
for _, h := range hs {
asInterfaces = append(asInterfaces, h)
}
return HandlersNames(asInterfaces...)
}
}
names := make([]string, 0, len(handlers))
for _, h := range handlers {
names = append(names, HandlerName(h))
}
return strings.Join(names, ",")
}
// HandlerFileLine returns the handler's file and line information.
// See `context.HandlerFileLine` to get the file, line of the current running handler in the chain.
func HandlerFileLine(h interface{}) (file string, line int) {
pc := valueOf(h).Pointer()
return runtime.FuncForPC(pc).FileLine(pc)
}
// HandlerFileLineRel same as `HandlerFileLine` but it returns the path
// corresponding to its relative based on the package-level "WorkingDir" variable.
func HandlerFileLineRel(h interface{}) (file string, line int) {
file, line = HandlerFileLine(h)
if relFile, err := filepath.Rel(WorkingDir, file); err == nil {
if !strings.HasPrefix(relFile, "..") {
// Only if it's relative to this path, not parent.
file = "./" + relFile
}
}
return
}
// MainHandlerName tries to find the main handler that end-developer
// registered on the provided chain of handlers and returns its function name.
func MainHandlerName(handlers ...interface{}) (name string, index int) {
if len(handlers) == 0 {
return
}
if hs, ok := handlers[0].(Handlers); ok {
tmp := make([]interface{}, 0, len(hs))
for _, h := range hs {
tmp = append(tmp, h)
}
return MainHandlerName(tmp...)
}
for i := 0; i < len(handlers); i++ {
name = HandlerName(handlers[i])
if name == "" {
continue
}
index = i
if !ingoreMainHandlerName(name) {
break
}
}
return
}
func trimHandlerName(name string) string {
// trim the path for Iris' internal middlewares, e.g.
// irs/mvc.GRPC.Apply.func1
if internalName := PackageName; strings.HasPrefix(name, internalName) {
name = strings.Replace(name, internalName, "iris", 1)
}
if internalName := "github.com/iris-contrib"; strings.HasPrefix(name, internalName) {
name = strings.Replace(name, internalName, "iris-contrib", 1)
}
name = strings.TrimSuffix(name, "GRPC.Apply.func1")
return name
}
var ignoreHandlerNames = [...]string{
"iris/macro/handler.MakeHandler",
"iris/hero.makeHandler.func2",
"iris/core/router.ExecutionOptions.buildHandler",
"iris/core/router.(*APIBuilder).Favicon",
"iris/core/router.StripPrefix",
"iris/core/router.PrefixDir",
"iris/core/router.PrefixFS",
"iris/context.glob..func2.1",
}
// IgnoreHandlerName compares a static slice of Iris builtin
// internal methods that should be ignored from trace.
// Some internal methods are kept out of this list for actual debugging.
func IgnoreHandlerName(name string) bool {
for _, ignore := range ignoreHandlerNames {
if name == ignore {
return true
}
}
return false
}
// ingoreMainHandlerName reports whether a main handler of "name" should
// be ignored and continue to match the next.
// The ignored main handler names are literals and respects the `ignoreNameHandlers` too.
func ingoreMainHandlerName(name string) bool {
if IgnoreHandlerName(name) {
// If ignored at all, it can't be the main.
return true
}
for _, ignore := range ignoreMainHandlerNames {
if name == ignore {
return true
}
}
return false
}
// Filter is just a type of func(Context) bool which reports whether an action must be performed
// based on the incoming request.
//
// See `NewConditionalHandler` for more.
type Filter func(*Context) bool
// NewConditionalHandler returns a single Handler which can be registered
// as a middleware.
// Filter is just a type of Handler which returns a boolean.
// Handlers here should act like middleware, they should contain `ctx.Next` to proceed
// to the next handler of the chain. Those "handlers" are registered to the per-request context.
//
// It checks the "filter" and if passed then
// it, correctly, executes the "handlers".
//
// If passed, this function makes sure that the Context's information
// about its per-request handler chain based on the new "handlers" is always updated.
//
// If not passed, then simply the Next handler(if any) is executed and "handlers" are ignored.
//
// Example can be found at: _examples/routing/conditional-chain.
func NewConditionalHandler(filter Filter, handlers ...Handler) Handler {
return func(ctx *Context) {
if filter(ctx) {
// Note that we don't want just to fire the incoming handlers, we must make sure
// that it won't break any further handler chain
// information that may be required for the next handlers.
//
// The below code makes sure that this conditional handler does not break
// the ability that iris provides to its end-devs
// to check and modify the per-request handlers chain at runtime.
currIdx := ctx.HandlerIndex(-1)
currHandlers := ctx.Handlers()
if currIdx == len(currHandlers)-1 {
// if this is the last handler of the chain
// just add to the last the new handlers and call Next to fire those.
ctx.AddHandler(handlers...)
ctx.Next()
return
}
// otherwise insert the new handlers in the middle of the current executed chain and the next chain.
newHandlers := append(currHandlers[:currIdx+1], append(handlers, currHandlers[currIdx+1:]...)...)
ctx.SetHandlers(newHandlers)
ctx.Next()
return
}
// if not pass, then just execute the next.
ctx.Next()
}
}
// JoinHandlers returns a copy of "h1" and "h2" Handlers slice joined as one slice of Handlers.
func JoinHandlers(h1 Handlers, h2 Handlers) Handlers {
if len(h1) == 0 {
return h2
}
if len(h2) == 0 {
return h1
}
nowLen := len(h1)
totalLen := nowLen + len(h2)
// create a new slice of Handlers in order to merge the "h1" and "h2"
newHandlers := make(Handlers, totalLen)
// copy the already Handlers to the just created
copy(newHandlers, h1)
// start from there we finish, and store the new Handlers too
copy(newHandlers[nowLen:], h2)
return newHandlers
}
// UpsertHandlers like `JoinHandlers` but it does
// NOT copies the handlers entries and it does remove duplicates.
func UpsertHandlers(h1 Handlers, h2 Handlers) Handlers {
reg:
for _, handler := range h2 {
name := HandlerName(handler)
for i, registeredHandler := range h1 {
registeredName := HandlerName(registeredHandler)
if name == registeredName {
h1[i] = handler // replace this handler with the new one.
continue reg // break and continue to the next handler.
}
}
h1 = append(h1, handler) // or just insert it.
}
return h1
}