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. //