From da029d6f37228c7c0fed685187a2bd4ef35b5b3e Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sun, 9 Aug 2020 22:28:44 +0300 Subject: [PATCH] New 'Application.UseRouter(...Handler)'. Read HISTORY.md --- HISTORY.md | 2 + core/router/router.go | 49 +++++++++++-- core/router/router_handlers_order_test.go | 86 +++++++++++++++-------- 3 files changed, 102 insertions(+), 35 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 4f9b67d76..6fb588728 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -359,6 +359,8 @@ Response: Other Improvements: +- `Application.UseRouter(...Handler)` - to register handlers before the main router, useful on handlers that should control whether the router itself should ran or not. Independently of the incoming request's method and path values. These handlers will be executed ALWAYS against ALL incoming requests. Example of use-case: CORS. + - `*versioning.Group` type is a full `Party` now. - `Party.UseOnce` - either inserts a middleware, or on the basis of the middleware already existing, replace that existing middleware instead. diff --git a/core/router/router.go b/core/router/router.go index ac02caca2..015f40bda 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -18,7 +18,8 @@ import ( // User can refresh the router with `RefreshRouter` whenever a route's field is changed by him. type Router struct { mu sync.Mutex // for Downgrade, WrapRouter & BuildRouter, - // not indeed but we don't to risk its usage by third-parties. + + preHandlers context.Handlers // run before requestHandler, as middleware, same way context's handlers run, see `UseRouter`. requestHandler RequestHandler // build-accessible, can be changed to define a custom router or proxy, used on RefreshRouter too. mainHandler http.HandlerFunc // init-accessible wrapperFunc WrapperFunc @@ -86,6 +87,25 @@ func (router *Router) FindClosestPaths(subdomain, searchPath string, n int) []st return list } +// UseRouter registers one or more handlers that are fired +// before the main router's request handler. +// +// Use this method to register handlers, that can ran +// independently of the incoming request's method and path values, +// that they will be executed ALWAYS against ALL incoming requests. +// Example of use-case: CORS. +// +// Note that because these are executed before the router itself +// the Context should not have access to the `GetCurrentRoute` +// as it is not decided yet which route is responsible to handle the incoming request. +// It's one level higher than the `WrapRouter`. +// The context SHOULD call its `Next` method in order to proceed to +// the next handler in the chain or the main request handler one. +// ExecutionRules are NOT applied here. +func (router *Router) UseRouter(handlers ...context.Handler) { + router.preHandlers = append(router.preHandlers, handlers...) +} + // BuildRouter builds the router based on // the context factory (explicit pool in this case), // the request handler which manages how the main handler will multiplexes the routes @@ -130,12 +150,27 @@ func (router *Router) BuildRouter(cPool *context.Pool, requestHandler RequestHan } // the important - router.mainHandler = func(w http.ResponseWriter, r *http.Request) { - ctx := cPool.Acquire(w, r) - // Note: we can't get all r.Context().Value key-value pairs - // and save them to ctx.values. - router.requestHandler.HandleRequest(ctx) - cPool.Release(ctx) + if len(router.preHandlers) > 0 { + handlers := append(router.preHandlers, func(ctx *context.Context) { + // set the handler index back to 0 so the route's handlers can be executed as exepcted. + ctx.HandlerIndex(0) + // execute the main request handler, this will fire the found route's handlers + // or if error the error code's associated handler. + router.requestHandler.HandleRequest(ctx) + }) + + router.mainHandler = func(w http.ResponseWriter, r *http.Request) { + ctx := cPool.Acquire(w, r) + // execute the final handlers chain. + ctx.Do(handlers) + cPool.Release(ctx) + } + } else { + router.mainHandler = func(w http.ResponseWriter, r *http.Request) { + ctx := cPool.Acquire(w, r) + router.requestHandler.HandleRequest(ctx) + cPool.Release(ctx) + } } if router.wrapperFunc != nil { // if wrapper used then attach that as the router service diff --git a/core/router/router_handlers_order_test.go b/core/router/router_handlers_order_test.go index 02c52b4ee..aff3f0ed5 100644 --- a/core/router/router_handlers_order_test.go +++ b/core/router/router_handlers_order_test.go @@ -7,11 +7,10 @@ package router_test import ( + "fmt" "testing" "github.com/kataras/iris/v12" - "github.com/kataras/iris/v12/context" - "github.com/kataras/iris/v12/httptest" ) @@ -19,48 +18,43 @@ import ( // with a different order but the route's final // response should be the same at all cases. var ( - mainResponse = "main" - mainHandler = func(ctx *context.Context) { - ctx.WriteString(mainResponse) - ctx.Next() + writeHandler = func(s string) iris.Handler { + return func(ctx iris.Context) { + ctx.WriteString(s) + ctx.Next() + } } + mainResponse = "main" + mainHandler = writeHandler(mainResponse) + firstUseResponse = "use1" - firstUseHandler = func(ctx *context.Context) { - ctx.WriteString(firstUseResponse) - ctx.Next() - } + firstUseHandler = writeHandler(firstUseResponse) secondUseResponse = "use2" - secondUseHandler = func(ctx *context.Context) { - ctx.WriteString(secondUseResponse) - ctx.Next() - } + secondUseHandler = writeHandler(secondUseResponse) + + firstUseRouterResponse = "userouter1" + firstUseRouterHandler = writeHandler(firstUseRouterResponse) + + secondUseRouterResponse = "userouter2" + secondUseRouterHandler = writeHandler(secondUseRouterResponse) firstUseGlobalResponse = "useglobal1" - firstUseGlobalHandler = func(ctx *context.Context) { - ctx.WriteString(firstUseGlobalResponse) - ctx.Next() - } + firstUseGlobalHandler = writeHandler(firstUseGlobalResponse) secondUseGlobalResponse = "useglobal2" - secondUseGlobalHandler = func(ctx *context.Context) { - ctx.WriteString(secondUseGlobalResponse) - ctx.Next() - } + secondUseGlobalHandler = writeHandler(secondUseGlobalResponse) firstDoneResponse = "done1" - firstDoneHandler = func(ctx *context.Context) { - ctx.WriteString(firstDoneResponse) - ctx.Next() - } + firstDoneHandler = writeHandler(firstDoneResponse) secondDoneResponse = "done2" - secondDoneHandler = func(ctx *context.Context) { + secondDoneHandler = func(ctx iris.Context) { ctx.WriteString(secondDoneResponse) } - finalResponse = firstUseGlobalResponse + secondUseGlobalResponse + + finalResponse = firstUseRouterResponse + secondUseRouterResponse + firstUseGlobalResponse + secondUseGlobalResponse + firstUseResponse + secondUseResponse + mainResponse + firstDoneResponse + secondDoneResponse testResponse = func(t *testing.T, app *iris.Application, path string) { @@ -73,6 +67,9 @@ var ( func TestMiddlewareByRouteDef(t *testing.T) { app := iris.New() + app.UseRouter(firstUseRouterHandler) + app.UseRouter(secondUseRouterHandler) + app.Get("/mypath", firstUseGlobalHandler, secondUseGlobalHandler, firstUseHandler, secondUseHandler, mainHandler, firstDoneHandler, secondDoneHandler) @@ -81,6 +78,7 @@ func TestMiddlewareByRouteDef(t *testing.T) { func TestMiddlewareByUseAndDoneDef(t *testing.T) { app := iris.New() + app.UseRouter(firstUseRouterHandler, secondUseRouterHandler) app.Use(firstUseGlobalHandler, secondUseGlobalHandler, firstUseHandler, secondUseHandler) app.Done(firstDoneHandler, secondDoneHandler) @@ -91,12 +89,14 @@ func TestMiddlewareByUseAndDoneDef(t *testing.T) { func TestMiddlewareByUseUseGlobalAndDoneDef(t *testing.T) { app := iris.New() + app.Use(firstUseHandler, secondUseHandler) // if failed then UseGlobal didnt' registered these handlers even before the // existing middleware. app.UseGlobal(firstUseGlobalHandler, secondUseGlobalHandler) app.Done(firstDoneHandler, secondDoneHandler) + app.UseRouter(firstUseRouterHandler, secondUseRouterHandler) app.Get("/mypath", mainHandler) testResponse(t, app, "/mypath") @@ -104,6 +104,7 @@ func TestMiddlewareByUseUseGlobalAndDoneDef(t *testing.T) { func TestMiddlewareByUseDoneAndUseGlobalDef(t *testing.T) { app := iris.New() + app.UseRouter(firstUseRouterHandler, secondUseRouterHandler) app.Use(firstUseHandler, secondUseHandler) app.Done(firstDoneHandler, secondDoneHandler) @@ -123,6 +124,8 @@ func TestMiddlewareByUseDoneAndUseGlobalDef(t *testing.T) { func TestMiddlewareByUseGlobalUseAndDoneGlobalDef(t *testing.T) { app := iris.New() + app.UseRouter(firstUseRouterHandler) + app.UseRouter(secondUseRouterHandler) app.UseGlobal(firstUseGlobalHandler) app.UseGlobal(secondUseGlobalHandler) @@ -137,6 +140,7 @@ func TestMiddlewareByUseGlobalUseAndDoneGlobalDef(t *testing.T) { func TestMiddlewareByDoneUseAndUseGlobalDef(t *testing.T) { app := iris.New() + app.UseRouter(firstUseRouterHandler, secondUseRouterHandler) app.Done(firstDoneHandler, secondDoneHandler) app.Use(firstUseHandler, secondUseHandler) @@ -148,3 +152,29 @@ func TestMiddlewareByDoneUseAndUseGlobalDef(t *testing.T) { testResponse(t, app, "/mypath") } + +func TestUseRouterStopExecution(t *testing.T) { + app := iris.New() + app.UseRouter(func(ctx iris.Context) { + ctx.WriteString("stop") + // no ctx.Next, so the router has not even the chance to work. + }) + app.Get("/", writeHandler("index")) + + e := httptest.New(t, app) + e.GET("/").Expect().Status(iris.StatusOK).Body().Equal("stop") + + app = iris.New() + app.OnErrorCode(iris.StatusForbidden, func(ctx iris.Context) { + ctx.Writef("err: %v", ctx.GetErr()) + }) + app.UseRouter(func(ctx iris.Context) { + ctx.StopWithPlainError(iris.StatusForbidden, fmt.Errorf("custom error")) + // stopped but not data written yet, the error code handler + // should be responsible of it (use StopWithError to write and close). + }) + app.Get("/", writeHandler("index")) + + e = httptest.New(t, app) + e.GET("/").Expect().Status(iris.StatusForbidden).Body().Equal("err: custom error") +}