123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- 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(Context{}).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
- }
|