diff --git a/README.md b/README.md index 096c1e6d..797dc194 100644 --- a/README.md +++ b/README.md @@ -195,7 +195,7 @@ First of all, the most correct way to begin with a web framework is to learn the Iris has a great collection of handlers[[1]](middleware/)[[2]](https://github.com/iris-contrib/middleware) that you can use side by side with your web apps. However you are not limited to them - you are free to use any third-party middleware that is compatible with the [net/http](https://golang.org/pkg/net/http/) package, [_examples/convert-handlers](_examples/convert-handlers) will show you the way. -Iris, unlike others, is 100% compatible with the standards and that's why the majority of the big companies that adapt Go to their workflow, like a very famous US Television Network, trust Iris; it's always up-to-date and it will be aligned with the std `net/http` package which is modernized by the Go Author on each new release of the Go Programming Language forever. +Iris, unlike others, is 100% compatible with the standards and that's why the majority of the big companies that adapt Go to their workflow, like a very famous US Television Network, trust Iris; it's up-to-date and it will be always aligned with the std `net/http` package which is modernized by the Go Authors on each new release of the Go Programming Language. ### Articles diff --git a/_examples/routing/fallback-handlers/main.go b/_examples/routing/fallback-handlers/main.go index 66308ae4..72a07d14 100644 --- a/_examples/routing/fallback-handlers/main.go +++ b/_examples/routing/fallback-handlers/main.go @@ -1,14 +1,12 @@ package main -import ( - "github.com/kataras/iris" -) +import "github.com/kataras/iris" func main() { app := iris.New() // this works as expected now, - // will handle *all* expect DELETE requests, even if there is no routes + // will handle *all* expect DELETE requests, even if there is no routes. app.Get("/action/{p}", h) app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed)) @@ -20,8 +18,7 @@ func h(ctx iris.Context) { func fallbackHandler(ctx iris.Context) { if ctx.Method() == "DELETE" { - ctx.Next() - + ctx.NextOrNotFound() return } diff --git a/context/context.go b/context/context.go index d1cc75d6..b5001587 100644 --- a/context/context.go +++ b/context/context.go @@ -309,7 +309,21 @@ type Context interface { // // Note: Custom context should override this method in order to be able to pass its own context.Context implementation. Next() - // NextHandler returns(but it is NOT executes) the next handler from the handlers chain. + // NextOr checks if chain has a next handler, if so then it executes it + // otherwise it sets a new chain assigned to this Context based on the given handler(s) + // and executes its first handler. + // + // Returns true if next handler exists and executed, otherwise false. + // + // Note that if no next handler found and handlers are missing then + // it sends a Status Not Found (404) to the client and it stops the execution. + NextOr(handlers ...Handler) bool + // NextOrNotFound checks if chain has a next handler, if so then it executes it + // otherwise it sends a Status Not Found (404) to the client and stops the execution. + // + // Returns true if next handler exists and executed, otherwise false. + NextOrNotFound() bool + // NextHandler returns (it doesn't execute) the next handler from the handlers chain. // // Use .Skip() to skip this handler if needed to execute the next of this returning handler. NextHandler() Handler @@ -1262,7 +1276,39 @@ func (ctx *context) Next() { // or context.Next(ctx) Next(ctx) } -// NextHandler returns, but it doesn't executes, the next handler from the handlers chain. +// NextOr checks if chain has a next handler, if so then it executes it +// otherwise it sets a new chain assigned to this Context based on the given handler(s) +// and executes its first handler. +// +// Returns true if next handler exists and executed, otherwise false. +// +// Note that if no next handler found and handlers are missing then +// it sends a Status Not Found (404) to the client and it stops the execution. +func (ctx *context) NextOr(handlers ...Handler) bool { + if next := ctx.NextHandler(); next != nil { + next(ctx) + ctx.Skip() // skip this handler from the chain. + return true + } + + if len(handlers) == 0 { + ctx.NotFound() + ctx.StopExecution() + return false + } + + ctx.Do(handlers) + + return false +} + +// NextOrNotFound checks if chain has a next handler, if so then it executes it +// otherwise it sends a Status Not Found (404) to the client and stops the execution. +// +// Returns true if next handler exists and executed, otherwise false. +func (ctx *context) NextOrNotFound() bool { return ctx.NextOr() } + +// NextHandler returns (it doesn't execute) the next handler from the handlers chain. // // Use .Skip() to skip this handler if needed to execute the next of this returning handler. func (ctx *context) NextHandler() Handler { diff --git a/core/router/api_builder.go b/core/router/api_builder.go index ab3d3f2c..c5e0cc65 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -90,7 +90,7 @@ type APIBuilder struct { doneHandlers context.Handlers // global done handlers, order doesn't matter doneGlobalHandlers context.Handlers - // fallback stack, LIFO order + // fallback stack, LIFO order, initialized on first `Fallback`. fallbackStack *FallbackStack // the per-party relativePath string @@ -437,13 +437,13 @@ func (api *APIBuilder) DoneGlobal(handlers ...context.Handler) { // Fallback appends Handler(s) to the current fallback stack. // Handler(s) is(are) called from Fallback stack when no route found and before sending NotFound status. // Therefore Handler(s) in Fallback stack could send another thing than NotFound status, -// if `Context.Next()` method is not called. +// if `context.NextOrNotFound()` method is not called. // Done & DoneGlobal Handlers are not called. func (api *APIBuilder) Fallback(middleware ...context.Handler) { api.fallbackStack.Add(middleware) } -// FallBackStack returns Fallback stack, this is implementation of interface RoutesProvider +// GetFallBackStack returns Fallback stack, this is implementation of interface RoutesProvider // that is used in Router building by the RequestHandler. func (api *APIBuilder) GetFallBackStack() *FallbackStack { return api.fallbackStack diff --git a/core/router/fallback_stack.go b/core/router/fallback_stack.go index 1b8531f8..9ff89dd6 100644 --- a/core/router/fallback_stack.go +++ b/core/router/fallback_stack.go @@ -1,16 +1,12 @@ package router -import ( - "net/http" - - "github.com/kataras/iris/context" -) +import "github.com/kataras/iris/context" // FallbackStack is a stack (with LIFO calling order) for fallback handlers // A fallback handler(s) is(are) called from Fallback stack // when no route found and before sending NotFound status. // Therefore Handler(s) in Fallback stack could send another thing than NotFound status, -// if `Context.Next()` method is not called. +// if `context#NextOrNotFound()` method is not called. // Done & DoneGlobal Handlers are not called. type FallbackStack struct { parent *FallbackStack @@ -65,14 +61,5 @@ func (stk *FallbackStack) List() context.Handlers { return res } -// NewFallbackStack create a new Fallback stack with as first entry -// a handler which send NotFound status (the default) -func NewFallbackStack() *FallbackStack { - return &FallbackStack{ - handlers: context.Handlers{ - func(ctx context.Context) { - ctx.StatusCode(http.StatusNotFound) - }, - }, - } -} +// NewFallbackStack create a new empty Fallback stack. +func NewFallbackStack() *FallbackStack { return &FallbackStack{} } diff --git a/core/router/fallback_stack_test.go b/core/router/fallback_stack_test.go index 1499cd96..da229c89 100644 --- a/core/router/fallback_stack_test.go +++ b/core/router/fallback_stack_test.go @@ -94,8 +94,7 @@ func TestFallbackStackCall(t *testing.T) { // setup fallback handler app.Fallback(func(ctx context.Context) { if ctx.Method() != "GET" { - ctx.Next() - + ctx.NextOrNotFound() // it checks if we have next, otherwise fire 404 not found. return } diff --git a/core/router/handler.go b/core/router/handler.go index ce924493..18501b39 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -38,6 +38,11 @@ type routerHandler struct { trees []*tree hosts bool // true if at least one route contains a Subdomain. fallbackStack *FallbackStack + // on build: true if fallbackStack.Size() > 0, + // reduces the checks because fallbackStack is NEVER nil (api_builder.go always initializes it). + // If re-checked needed (serve-time fallback handler added) + // then a re-build/refresh of the application's router is necessary, as with every handler. + hasFallbackHandlers bool } var _ RequestHandler = &routerHandler{} @@ -93,6 +98,7 @@ func (h *routerHandler) Build(provider RoutesProvider) error { registeredRoutes := provider.GetRoutes() h.trees = h.trees[0:0] // reset, inneed when rebuilding. h.fallbackStack = provider.GetFallBackStack() + h.hasFallbackHandlers = h.fallbackStack.Size() > 0 // sort, subdomains goes first. sort.Slice(registeredRoutes, func(i, j int) bool { @@ -248,11 +254,12 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { } } - if h.fallbackStack == nil { - ctx.StatusCode(http.StatusNotFound) - } else { + if h.hasFallbackHandlers { ctx.Do(h.fallbackStack.List()) + return } + + ctx.StatusCode(http.StatusNotFound) } // RouteExists checks if a route exists