diff --git a/core/router/api_builder.go b/core/router/api_builder.go index af0b7a5b..a7ed4a6b 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -178,9 +178,8 @@ func overlapRoute(r *Route, next *Route) { ctx.Do(nextHandlers) } - // NOTE(@kataras): Any UseGlobal call will prepend to this, if they are - // in the same Party then it's expected, otherwise not. - r.beginHandlers = append(context.Handlers{decisionHandler}, r.beginHandlers...) + r.builtinBeginHandlers = append(context.Handlers{decisionHandler}, r.builtinBeginHandlers...) + r.overlappedLink = next } // APIBuilder the visible API for constructing the router @@ -411,7 +410,7 @@ func (api *APIBuilder) handle(errorCode int, method string, relativePath string, var err error for _, route = range routes { if route == nil { - break + continue } // global diff --git a/core/router/handler.go b/core/router/handler.go index 271fda76..bad9b29e 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -311,10 +311,9 @@ func (h *routerHandler) Build(provider RoutesProvider) error { return errgroup.Check(rp) } -func bindMultiParamTypesHandler(r *Route) { +func bindMultiParamTypesHandler(r *Route) { // like overlap feature but specifically for path parameters. r.BuildHandlers() - // println("here for top: " + top.Name + " and current route: " + r.Name) h := r.Handlers[1:] // remove the macro evaluator handler as we manually check below. f := macroHandler.MakeFilter(r.tmpl) if f == nil { @@ -347,7 +346,7 @@ func bindMultiParamTypesHandler(r *Route) { ctx.Next() } - r.topLink.beginHandlers = append(context.Handlers{decisionHandler}, r.topLink.beginHandlers...) + r.topLink.builtinBeginHandlers = append(context.Handlers{decisionHandler}, r.topLink.builtinBeginHandlers...) } func canHandleSubdomain(ctx *context.Context, subdomain string) bool { diff --git a/core/router/route.go b/core/router/route.go index bf71db0e..d5817425 100644 --- a/core/router/route.go +++ b/core/router/route.go @@ -29,7 +29,14 @@ type Route struct { tmpl macro.Template // Tmpl().Src: "/api/user/{id:uint64}" // temp storage, they're appended to the Handlers on build. // Execution happens before Handlers, can be empty. + // They run right after any builtinBeginHandlers. beginHandlers context.Handlers + // temp storage, these are always registered first as Handlers on Build. + // There are the handlers may be added by the framework and + // can NOT be modified by the end-developer (i.e overlapRoute & bindMultiParamTypesHandler), + // even if a function like UseGlobal is used. + builtinBeginHandlers context.Handlers + // Handlers are the main route's handlers, executed by order. // Cannot be empty. Handlers context.Handlers `json:"-"` @@ -52,7 +59,13 @@ type Route struct { RegisterFileName string `json:"registerFileName"` RegisterLineNumber int `json:"registerLineNumber"` + // see APIBuilder.handle, routerHandler.bindMultiParamTypesHandler and routerHandler.Build, + // it's the parent route of the last registered of the same path parameter. Specifically for path parameters. topLink *Route + // overlappedLink specifically for overlapRoute feature. + // keeps the second route of the same path pattern registered. + // It's used ONLY for logging. + overlappedLink *Route // Sitemap properties: https://www.sitemaps.org/protocol.html LastMod time.Time `json:"lastMod,omitempty"` @@ -189,15 +202,15 @@ func (r *Route) BuildHandlers() { r.OnBuild(r) } - if len(r.beginHandlers) > 0 { - r.Handlers = append(r.beginHandlers, r.Handlers...) - r.beginHandlers = r.beginHandlers[0:0] - } - - if len(r.doneHandlers) > 0 { - r.Handlers = append(r.Handlers, r.doneHandlers...) - r.doneHandlers = r.doneHandlers[0:0] - } // note: no mutex needed, this should be called in-sync when server is not running of course. + // prepend begin handlers. + r.Handlers = append(r.builtinBeginHandlers, append(r.beginHandlers, r.Handlers...)...) + // append done handlers. + r.Handlers = append(r.Handlers, r.doneHandlers...) + // reset the temp storage, so a second call of + // BuildHandlers will not re-add them (i.e RefreshRouter). + r.builtinBeginHandlers = r.builtinBeginHandlers[0:0] + r.beginHandlers = r.beginHandlers[0:0] + r.doneHandlers = r.doneHandlers[0:0] } // String returns the form of METHOD, SUBDOMAIN, TMPL PATH. @@ -494,6 +507,13 @@ func (r *Route) Trace(w io.Writer, stoppedIndex int) { } fmt.Fprintln(w) + + if r.overlappedLink != nil { + bckpDesc := r.overlappedLink.Description + r.overlappedLink.Description += " (overlapped)" + r.overlappedLink.Trace(w, -1) + r.overlappedLink.Description = bckpDesc + } } type routeReadOnlyWrapper struct { diff --git a/middleware/basicauth/basicauth.go b/middleware/basicauth/basicauth.go index d7548530..fc90eba3 100644 --- a/middleware/basicauth/basicauth.go +++ b/middleware/basicauth/basicauth.go @@ -9,7 +9,6 @@ import ( "sync" "time" - "github.com/kataras/iris/v12" "github.com/kataras/iris/v12/context" ) @@ -101,7 +100,7 @@ func (b *basicAuthMiddleware) findAuth(headerValue string) (*encodedUser, bool) func (b *basicAuthMiddleware) askForCredentials(ctx *context.Context) { ctx.Header("WWW-Authenticate", b.realmHeaderValue) - ctx.StatusCode(iris.StatusUnauthorized) + ctx.StatusCode(401) if b.askHandlerEnabled { b.config.OnAsk(ctx) } diff --git a/mvc/controller.go b/mvc/controller.go index a8b6edd4..55e29f47 100644 --- a/mvc/controller.go +++ b/mvc/controller.go @@ -415,13 +415,19 @@ func (c *ControllerActivator) handleMany(method, path, funcName string, override } func (c *ControllerActivator) saveRoutes(funcName string, routes []*router.Route, override bool) { + m, ok := c.Type.MethodByName(funcName) + if !ok { + return + } + + sourceFileName, sourceLineNumber := getSourceFileLine(c.Type, m) + relName := c.RelName() for _, r := range routes { r.Description = relName r.MainHandlerName = fmt.Sprintf("%s.%s", relName, funcName) - if m, ok := c.Type.MethodByName(funcName); ok { - r.SourceFileName, r.SourceLineNumber = context.HandlerFileLineRel(m.Func) - } + + r.SourceFileName, r.SourceLineNumber = sourceFileName, sourceLineNumber } // add this as a reserved method name in order to diff --git a/mvc/reflect.go b/mvc/reflect.go index 130f7ca4..0d336d6c 100644 --- a/mvc/reflect.go +++ b/mvc/reflect.go @@ -2,6 +2,8 @@ package mvc import ( "reflect" + + "github.com/kataras/iris/v12/context" ) var baseControllerTyp = reflect.TypeOf((*BaseController)(nil)).Elem() @@ -20,3 +22,37 @@ func indirectType(typ reflect.Type) reflect.Type { } return typ } + +func getSourceFileLine(ctrlType reflect.Type, m reflect.Method) (string, int) { // used for debug logs. + sourceFileName, sourceLineNumber := context.HandlerFileLineRel(m.Func) + if sourceFileName == "" { + elem := indirectType(ctrlType) + + for i, n := 0, elem.NumField(); i < n; i++ { + if f := elem.Field(i); f.Anonymous { + typ := indirectType(f.Type) + if typ.Kind() != reflect.Struct { + continue // field is not a struct. + } + + // why we do that? + // because if the element is not Ptr + // then it's probably used as: + // type ctrl { + // BaseCtrl + // } + // but BaseCtrl has not the method, *BaseCtrl does: + // (c *BaseCtrl) HandleHTTPError(...) + // so we are creating a new temporarly value ptr of that type + // and searching inside it for the method instead. + typ = reflect.New(typ).Type() + + if embeddedMethod, ok := typ.MethodByName(m.Name); ok { + sourceFileName, sourceLineNumber = context.HandlerFileLineRel(embeddedMethod.Func) + } + } + } + } + + return sourceFileName, sourceLineNumber +}