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 }