diff --git a/_examples/routing/conditional-chain/main.go b/_examples/routing/conditional-chain/main.go
new file mode 100644
index 000000000..ec2765f84
--- /dev/null
+++ b/_examples/routing/conditional-chain/main.go
@@ -0,0 +1,49 @@
+package main
+
+import (
+ "github.com/kataras/iris"
+)
+
+func main() {
+ app := iris.New()
+ v1 := app.Party("/api/v1")
+
+ myFilter := func(ctx iris.Context) bool {
+ // don't do that on production, use session or/and database calls and etc.
+ ok, _ := ctx.URLParamBool("admin")
+ return ok
+ }
+
+ onlyWhenFilter1 := func(ctx iris.Context) {
+ ctx.Application().Logger().Infof("admin: %s", ctx.Params())
+ ctx.Next()
+ }
+
+ onlyWhenFilter2 := func(ctx iris.Context) {
+ // You can always use the per-request storage
+ // to perform actions like this ofc.
+ //
+ // this handler: ctx.Values().Set("is_admin", true)
+ // next handler: isAdmin := ctx.Values().GetBoolDefault("is_admin", false)
+ //
+ // but, let's simplify it:
+ ctx.HTML("
Hello Admin
")
+ ctx.Next()
+ }
+
+ // HERE:
+ // It can be registered anywhere, as a middleware.
+ // It will fire the `onlyWhenFilter1` and `onlyWhenFilter2` as middlewares (with ctx.Next())
+ // if myFilter pass otherwise it will just continue the handler chain with ctx.Next() by ignoring
+ // the `onlyWhenFilter1` and `onlyWhenFilter2`.
+ myMiddleware := iris.NewConditionalHandler(myFilter, onlyWhenFilter1, onlyWhenFilter2)
+
+ v1UsersRouter := v1.Party("/users", myMiddleware)
+ v1UsersRouter.Get("/", func(ctx iris.Context) {
+ ctx.HTML("requested: /api/v1/users")
+ })
+
+ // http://localhost:8080/api/v1/users
+ // http://localhost:8080/api/v1/users?admin=true
+ app.Run(iris.Addr(":8080"))
+}
diff --git a/context/context.go b/context/context.go
index f437e4475..0b37b8d8a 100644
--- a/context/context.go
+++ b/context/context.go
@@ -292,7 +292,6 @@ type Context interface {
// RequestPath returns the full request path,
// based on the 'escape'.
RequestPath(escape bool) string
-
// Host returns the host part of the current url.
Host() string
// Subdomain returns the subdomain of this request, if any.
@@ -300,6 +299,9 @@ type Context interface {
Subdomain() (subdomain string)
// IsWWW returns true if the current subdomain (if any) is www.
IsWWW() bool
+ // FullRqeuestURI returns the full URI,
+ // including the scheme, the host and the relative requested path/resource.
+ FullRequestURI() string
// RemoteAddr tries to parse and return the real client's request IP.
//
// Based on allowed headers names that can be modified from Configuration.RemoteAddrHeaders.
@@ -1465,11 +1467,11 @@ func (ctx *context) Host() string {
// GetHost returns the host part of the current URI.
func GetHost(r *http.Request) string {
- h := r.URL.Host
- if h == "" {
- h = r.Host
+ if host := r.Host; host != "" {
+ return host
}
- return h
+
+ return r.URL.Host
}
// Subdomain returns the subdomain of this request, if any.
@@ -1502,6 +1504,24 @@ func (ctx *context) IsWWW() bool {
return false
}
+// FullRqeuestURI returns the full URI,
+// including the scheme, the host and the relative requested path/resource.
+func (ctx *context) FullRequestURI() string {
+ scheme := ctx.request.URL.Scheme
+ if scheme == "" {
+ if ctx.request.TLS != nil {
+ scheme = "https:"
+ } else {
+ scheme = "http:"
+ }
+ }
+
+ host := ctx.Host()
+ path := ctx.Path()
+
+ return scheme + "//" + host + path
+}
+
const xForwardedForHeaderKey = "X-Forwarded-For"
// RemoteAddr tries to parse and return the real client's request IP.
diff --git a/context/handler.go b/context/handler.go
index 6d980513b..1dcf1a02d 100644
--- a/context/handler.go
+++ b/context/handler.go
@@ -34,3 +34,55 @@ func HandlerName(h Handler) string {
// return fmt.Sprintf("%s:%d", l, n)
return runtime.FuncForPC(pc).Name()
}
+
+// Filter is just a type of func(Handler) bool which reports whether an action must be performed
+// based on the incoming request.
+//
+// See `NewConditionalHandler` for more.
+type Filter func(Context) bool
+
+// NewConditionalHandler returns a single Handler which can be registered
+// as a middleware.
+// Filter is just a type of Handler which returns a boolean.
+// Handlers here should act like middleware, they should contain `ctx.Next` to proceed
+// to the next handler of the chain. Those "handlers" are registed to the per-request context.
+//
+//
+// It checks the "filter" and if passed then
+// it, correctly, executes the "handlers".
+//
+// If passed, this function makes sure that the Context's information
+// about its per-request handler chain based on the new "handlers" is always updated.
+//
+// If not passed, then simply the Next handler(if any) is executed and "handlers" are ignored.
+//
+// Example can be found at: _examples/routing/conditional-chain.
+func NewConditionalHandler(filter Filter, handlers ...Handler) Handler {
+ return func(ctx Context) {
+ if filter(ctx) {
+ // Note that we don't want just to fire the incoming handlers, we must make sure
+ // that it won't break any further handler chain
+ // information that may be required for the next handlers.
+ //
+ // The below code makes sure that this conditional handler does not break
+ // the ability that iris provides to its end-devs
+ // to check and modify the per-request handlers chain at runtime.
+ currIdx := ctx.HandlerIndex(-1)
+ currHandlers := ctx.Handlers()
+ if currIdx == len(currHandlers)-1 {
+ // if this is the last handler of the chain
+ // just add to the last the new handlers and call Next to fire those.
+ ctx.AddHandler(handlers...)
+ ctx.Next()
+ return
+ }
+ // otherwise insert the new handlers in the middle of the current executed chain and the next chain.
+ newHandlers := append(currHandlers[:currIdx], append(handlers, currHandlers[currIdx+1:]...)...)
+ ctx.SetHandlers(newHandlers)
+ ctx.Next()
+ return
+ }
+ // if not pass, then just execute the next.
+ ctx.Next()
+ }
+}
diff --git a/go19.go b/go19.go
index dd1961963..6d6c979e4 100644
--- a/go19.go
+++ b/go19.go
@@ -42,6 +42,12 @@ type (
// If Handler panics, the server (the caller of Handler) assumes that the effect of the panic was isolated to the active request.
// It recovers the panic, logs a stack trace to the server error log, and hangs up the connection.
Handler = context.Handler
+ // Filter is just a type of func(Handler) bool which reports whether an action must be performed
+ // based on the incoming request.
+ //
+ // See `NewConditionalHandler` for more.
+ // An alias for the `context/Filter`.
+ Filter = context.Filter
// A Map is a shortcut of the map[string]interface{}.
Map = context.Map
diff --git a/httptest/httptest.go b/httptest/httptest.go
index 50253c539..243f765b4 100644
--- a/httptest/httptest.go
+++ b/httptest/httptest.go
@@ -5,8 +5,9 @@ import (
"net/http"
"testing"
- "github.com/iris-contrib/httpexpect"
"github.com/kataras/iris"
+
+ "github.com/iris-contrib/httpexpect"
)
type (
diff --git a/iris.go b/iris.go
index d2e3e0df9..f899fcdfe 100644
--- a/iris.go
+++ b/iris.go
@@ -341,6 +341,24 @@ var (
//
// A shortcut for the `context#LimitRequestBodySize`.
LimitRequestBodySize = context.LimitRequestBodySize
+ // NewConditionalHandler returns a single Handler which can be registered
+ // as a middleware.
+ // Filter is just a type of Handler which returns a boolean.
+ // Handlers here should act like middleware, they should contain `ctx.Next` to proceed
+ // to the next handler of the chain. Those "handlers" are registed to the per-request context.
+ //
+ //
+ // It checks the "filter" and if passed then
+ // it, correctly, executes the "handlers".
+ //
+ // If passed, this function makes sure that the Context's information
+ // about its per-request handler chain based on the new "handlers" is always updated.
+ //
+ // If not passed, then simply the Next handler(if any) is executed and "handlers" are ignored.
+ // Example can be found at: _examples/routing/conditional-chain.
+ //
+ // A shortcut for the `context#NewConditionalHandler`.
+ NewConditionalHandler = context.NewConditionalHandler
// StaticEmbeddedHandler returns a Handler which can serve
// embedded into executable files.
//