Skip to content

Commit

Permalink
New 'Application.UseRouter(...Handler)'. Read HISTORY.md
Browse files Browse the repository at this point in the history
  • Loading branch information
kataras committed Aug 9, 2020
1 parent 24de7bf commit da029d6
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 35 deletions.
2 changes: 2 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
49 changes: 42 additions & 7 deletions core/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
86 changes: 58 additions & 28 deletions core/router/router_handlers_order_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,60 +7,54 @@
package router_test

import (
"fmt"
"testing"

"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/context"

"github.com/kataras/iris/v12/httptest"
)

// test registering of below handlers
// 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) {
Expand All @@ -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)

Expand All @@ -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)

Expand All @@ -91,19 +89,22 @@ 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")
}

func TestMiddlewareByUseDoneAndUseGlobalDef(t *testing.T) {
app := iris.New()
app.UseRouter(firstUseRouterHandler, secondUseRouterHandler)

app.Use(firstUseHandler, secondUseHandler)
app.Done(firstDoneHandler, secondDoneHandler)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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")
}

0 comments on commit da029d6

Please sign in to comment.