diff --git a/_examples/routing/overview/main.go b/_examples/routing/overview/main.go index b9d5f106..c4be82eb 100644 --- a/_examples/routing/overview/main.go +++ b/_examples/routing/overview/main.go @@ -2,12 +2,16 @@ package main import ( "github.com/kataras/iris/v12" + "github.com/kataras/iris/v12/middleware/logger" ) func main() { app := iris.New() app.Logger().SetLevel("debug") + // Register a request logger middleware to the application. + app.Use(logger.New()) + // GET: http://localhost:8080 app.Get("/", info) @@ -112,6 +116,9 @@ func main() { ctx.Writef(name) }) + app.None("/secret", privateHandler) + app.Get("/public", execPrivateHandler) + // GET: http://localhost:8080/ // GET: http://localhost:8080/profile/anyusername // GET: http://localhost:8080/profile/anyusername/backups/any/number/of/paths/here @@ -130,6 +137,15 @@ func main() { app.Listen(":8080") } +func privateHandler(ctx iris.Context) { + ctx.WriteString(`This can only be executed programmatically through server's another route: +ctx.Exec(iris.MethodNone, "/secret")`) +} + +func execPrivateHandler(ctx iris.Context) { + ctx.Exec(iris.MethodNone, "/secret") +} + func info(ctx iris.Context) { method := ctx.Method() // the http method requested a server's resource. subdomain := ctx.Subdomain() // the subdomain, if any. diff --git a/context/handler.go b/context/handler.go index 63c3b35c..db6b7b68 100644 --- a/context/handler.go +++ b/context/handler.go @@ -3,10 +3,40 @@ package context import ( "path/filepath" "reflect" + "regexp" "runtime" "strings" + "sync" ) +var ( + handlerNames = make(map[*regexp.Regexp]string) + handlerNamesMu sync.RWMutex +) + +// PackageName is the Iris Go module package name. +var PackageName = strings.TrimSuffix(reflect.TypeOf(Handlers{}).PkgPath(), "/context") + +// 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() + handlerNames[regexp.MustCompile(original)] = replacement + handlerNamesMu.Unlock() +} + // 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; @@ -14,7 +44,7 @@ import ( // // 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(). +// 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. @@ -37,10 +67,21 @@ func valueOf(v interface{}) reflect.Value { } // HandlerName returns the handler's function name. -// See `context.HandlerName` to get function name of the current running handler in the chain. +// 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() - return runtime.FuncForPC(pc).Name() + name := runtime.FuncForPC(pc).Name() + handlerNamesMu.RLock() + for regex, newName := range handlerNames { + if regex.MatchString(name) { + name = newName + break + } + } + handlerNamesMu.RUnlock() + + return name } // HandlerFileLine returns the handler's file and line information. diff --git a/context/route.go b/context/route.go index 5cdb4ccb..1e017b1f 100644 --- a/context/route.go +++ b/context/route.go @@ -58,6 +58,9 @@ type RouteReadOnly interface { // MainHandlerName returns the first registered handler for the route. MainHandlerName() string + // MainHandlerIndex returns the first registered handler's index for the route. + MainHandlerIndex() int + // StaticSites if not empty, refers to the system (or virtual if embedded) directory // and sub directories that this "GET" route was registered to serve files and folders // that contain index.html (a site). The index handler may registered by other diff --git a/core/router/api_builder.go b/core/router/api_builder.go index 8ed33a29..fe5851f1 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -452,10 +452,13 @@ func (api *APIBuilder) CreateRoutes(methods []string, relativePath string, handl mainHandlers := context.Handlers(handlers) // before join the middleware + handlers + done handlers and apply the execution rules. - possibleMainHandlerName, mainHandlerIndex := context.MainHandlerName(mainHandlers) + mainHandlerName, mainHandlerIndex := context.MainHandlerName(mainHandlers) wd, _ := os.Getwd() mainHandlerFileName, mainHandlerFileNumber := context.HandlerFileLineRel(handlers[mainHandlerIndex], wd) + // re-calculate mainHandlerIndex in favor of the middlewares. + mainHandlerIndex = len(api.middleware) + len(api.beginGlobalHandlers) + mainHandlerIndex + // TODO: for UseGlobal/DoneGlobal that doesn't work. applyExecutionRules(api.handlerExecutionRules, &beginHandlers, &doneHandlers, &mainHandlers) @@ -474,18 +477,23 @@ func (api *APIBuilder) CreateRoutes(methods []string, relativePath string, handl routes := make([]*Route, len(methods)) for i, m := range methods { - route, err := NewRoute(m, subdomain, path, possibleMainHandlerName, routeHandlers, *api.macros) + route, err := NewRoute(m, subdomain, path, routeHandlers, *api.macros) if err != nil { // template path parser errors: api.errors.Addf("[%s:%d] %v -> %s:%s:%s", filename, line, err, m, subdomain, path) continue } - route.SourceFileName = mainHandlerFileName - route.SourceLineNumber = mainHandlerFileNumber - + // The caller tiself, if anonymous, it's the first line of `app.X("/path", here)` route.RegisterFileName = filename route.RegisterLineNumber = line + route.MainHandlerName = mainHandlerName + route.MainHandlerIndex = mainHandlerIndex + + // The main handler source, could be the same as the register's if anonymous. + route.SourceFileName = mainHandlerFileName + route.SourceLineNumber = mainHandlerFileNumber + // Add UseGlobal & DoneGlobal Handlers route.Use(api.beginGlobalHandlers...) route.Done(api.doneGlobalHandlers...) diff --git a/core/router/handler.go b/core/router/handler.go index f017968b..bedd1a16 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -174,7 +174,7 @@ func (h *routerHandler) Build(provider RoutesProvider) error { defer golog.SetTimeFormat(bckpTimeFormat) golog.SetTimeFormat("") - for _, method := range AllMethods { // TODO: MethodNone "NONE" with an (offline) status on r.Trace. + for _, method := range append(AllMethods, MethodNone) { methodRoutes := collect(method) for _, r := range methodRoutes { golog.Println(r.Trace()) diff --git a/core/router/route.go b/core/router/route.go index 84a553a5..105a4364 100644 --- a/core/router/route.go +++ b/core/router/route.go @@ -29,8 +29,9 @@ type Route struct { beginHandlers context.Handlers // Handlers are the main route's handlers, executed by order. // Cannot be empty. - Handlers context.Handlers `json:"-"` - MainHandlerName string `json:"mainHandlerName"` + Handlers context.Handlers `json:"-"` + MainHandlerName string `json:"mainHandlerName"` + MainHandlerIndex int `json:"mainHandlerIndex"` // temp storage, they're appended to the Handlers on build. // Execution happens after Begin and main Handler(s), can be empty. doneHandlers context.Handlers @@ -67,7 +68,7 @@ type Route struct { // handlers and the macro container which all routes should share. // It parses the path based on the "macros", // handlers are being changed to validate the macros at serve time, if needed. -func NewRoute(method, subdomain, unparsedPath, mainHandlerName string, +func NewRoute(method, subdomain, unparsedPath string, handlers context.Handlers, macros macro.Macros) (*Route, error) { tmpl, err := macro.Parse(unparsedPath, macros) if err != nil { @@ -87,15 +88,14 @@ func NewRoute(method, subdomain, unparsedPath, mainHandlerName string, formattedPath := formatPath(path) route := &Route{ - Name: defaultName, - Method: method, - methodBckp: method, - Subdomain: subdomain, - tmpl: tmpl, - Path: path, - Handlers: handlers, - MainHandlerName: mainHandlerName, - FormattedPath: formattedPath, + Name: defaultName, + Method: method, + methodBckp: method, + Subdomain: subdomain, + tmpl: tmpl, + Path: path, + Handlers: handlers, + FormattedPath: formattedPath, } return route, nil @@ -357,20 +357,17 @@ func ignoreHandlerTrace(name string) bool { return false } -func traceHandlerFile(name, line string, number int, seen map[string]struct{}) string { +func traceHandlerFile(name, line string, number int) string { file := fmt.Sprintf("(%s:%d)", line, number) - if _, printed := seen[file]; printed { - return "" - } - - seen[file] = struct{}{} - // trim the path for Iris' internal middlewares, e.g. // irs/mvc.GRPC.Apply.func1 - if internalName := "github.com/kataras/iris/v12"; strings.HasPrefix(name, internalName) { + if internalName := context.PackageName; strings.HasPrefix(name, internalName) { name = strings.Replace(name, internalName, "iris", 1) } + if internalName := "github.com/iris-contrib/middleware"; strings.HasPrefix(name, internalName) { + name = strings.Replace(name, internalName, "iris-contrib", 1) + } if ignoreHandlerTrace(name) { return "" @@ -408,6 +405,8 @@ func (r *Route) Trace() string { color = pio.Gray case http.MethodTrace: color = pio.Yellow + case MethodNone: + color = 196 // full red. } path := r.Tmpl().Src @@ -419,36 +418,60 @@ func (r *Route) Trace() string { s := fmt.Sprintf("%s: %s", pio.Rich(r.Method, color), path) // (@description) - if r.Description != "" { - s += fmt.Sprintf(" %s", pio.Rich(r.Description, pio.Cyan, pio.Underline)) + description := r.Description + if description == "" && r.Method == MethodNone { + description = "offline" + } + + if description != "" { + s += fmt.Sprintf(" %s", pio.Rich(description, pio.Cyan, pio.Underline)) } // (@route_rel_location) s += fmt.Sprintf(" (%s:%d)", r.RegisterFileName, r.RegisterLineNumber) - seen := make(map[string]struct{}) - - // if the main handler is not an anonymous function (so, differs from @route_rel_location) - // then * @handler_name (@handler_rel_location) - if r.SourceFileName != r.RegisterFileName || r.SourceLineNumber != r.RegisterLineNumber { - s += traceHandlerFile(r.MainHandlerName, r.SourceFileName, r.SourceLineNumber, seen) - } - wd, _ := os.Getwd() - for _, h := range r.Handlers { - name := context.HandlerName(h) - if name == r.MainHandlerName { - continue + for i, h := range r.Handlers { + // main handler info can be programmatically + // changed to be more specific, respect those options. + var ( + name string + file string + line int + ) + + // name = context.HandlerName(h) + // file, line = context.HandlerFileLineRel(h, wd) + // fmt.Printf("---handler index: %d, name: %s, line: %s:%d\n", i, name, file, line) + + if i == r.MainHandlerIndex { + name = r.MainHandlerName + file = r.SourceFileName + line = r.SourceLineNumber + // fmt.Printf("main handler index: %d, name: %s, line: %d\n", i, name, line) + } else { + name = context.HandlerName(h) + file, line = context.HandlerFileLineRel(h, wd) + + // If a middleware, e.g (macro) which changes the main handler index, + // skip it. + if file == r.SourceFileName && line == r.SourceLineNumber { + // fmt.Printf("[0] SKIP: handler index: %d, name: %s, line: %s:%d\n", i, name, file, line) + continue + } + // fmt.Printf("handler index: %d, name: %s, line: %d\n", i, name, line) } - file, line := context.HandlerFileLineRel(h, wd) + // If a handler is an anonymous function then it was already + // printed in the first line, skip it. if file == r.RegisterFileName && line == r.RegisterLineNumber { + // fmt.Printf("[1] SKIP: handler index: %d, name: %s, line: %s:%d\n", i, name, file, line) continue } // * @handler_name (@handler_rel_location) - s += traceHandlerFile(name, file, line, seen) + s += traceHandlerFile(name, file, line) } return s @@ -486,6 +509,10 @@ func (rd routeReadOnlyWrapper) MainHandlerName() string { return rd.Route.MainHandlerName } +func (rd routeReadOnlyWrapper) MainHandlerIndex() int { + return rd.Route.MainHandlerIndex +} + func (rd routeReadOnlyWrapper) StaticSites() []context.StaticSite { return rd.Route.StaticSites } diff --git a/middleware/logger/logger.go b/middleware/logger/logger.go index 51b4bef1..58b1c547 100644 --- a/middleware/logger/logger.go +++ b/middleware/logger/logger.go @@ -11,6 +11,10 @@ import ( "github.com/ryanuber/columnize" ) +func init() { + context.SetHandlerName("iris/middleware/logger.*", "Request Logger") +} + type requestLoggerMiddleware struct { config Config }