From e12513a5349a1187ef921a5ad70f1df3362886dd Mon Sep 17 00:00:00 2001 From: kataras Date: Tue, 22 Aug 2017 13:00:24 +0300 Subject: [PATCH] Update to 8.3.2 | Read HISTORY.md file Former-commit-id: e6ab761989d596cb004c39e65e04e8968d9461ab --- HISTORY.md | 40 ++++++++++++++++++++++++++++++++ README.md | 2 +- VERSION | 2 +- _benchmarks/README.md | 2 +- core/router/api_builder.go | 7 +++--- core/router/party.go | 1 + doc.go | 2 +- iris.go | 2 +- mvc/activator/activator.go | 15 ++++++++++-- mvc/activator/binder.go | 40 ++++++++++++++++++++++++++++++-- mvc/controller.go | 3 ++- mvc/controller_test.go | 47 ++++++++++++++++++++++++++++++++++++++ 12 files changed, 150 insertions(+), 13 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 85ef1212..8ff3bab7 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -18,6 +18,46 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene **How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`. + +# Tu, 22 August 2017 | v8.3.2 + + +### MVC + +When one or more values of handler type (`func(ctx context.Context)`) are passed +right to the controller initialization then they will be recognised and act as middleware(s) +that ran even before the controller activation, there is no reason to load +the whole controller if the main handler or its `BeginRequest` are not "allowed" to be executed. + +Example Code + +```go +func checkLogin(ctx context.Context) { + if !myCustomAuthMethodPassed { + // [set a status or redirect, you know what to do] + ctx.StatusCode(iris.StatusForbidden) + return + } + + // [continue to the next handler, at this example is our controller itself] + ctx.Next() +} + +// [...] +app.Controller(new(ProfileController), checkLogin) +// [...] +``` + +Usage of these kind of MVC features could be found at the [mvc/controller_test.go](https://github.com/kataras/iris/blob/master/mvc/controller_test.go#L174) source file. + +### Other minor enhancements + +- fix https://github.com/kataras/iris/issues/726[*](https://github.com/kataras/iris/commit/5e435fc54fe3dbf95308327c2180d1b444ef7e0d) +- fix redis sessiondb expiration[*](https://github.com/kataras/iris/commit/85cfc91544c981e87e09c5aa86bad4b85d0b96d3) +- update recursively when new version is available[*](https://github.com/kataras/iris/commit/cd3c223536c6a33653a7fcf1f0648123f2b968fd) +- some minor session enhancements[*](https://github.com/kataras/iris/commit/2830f3b50ee9c526ac792c3ce1ec1c08c24ea024) + + # Sa, 19 August 2017 | v8.3.1 First of all I want to thank you for the 100% green feedback you gratefully sent me you about diff --git a/README.md b/README.md index 96467262..9407d08f 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Iris may have reached version 8, but we're not stopping there. We have many feat ### 📑 Table of contents * [Installation](#-installation) -* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#sa-19-august-2017--v831) +* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#tu-22-august-2017--v832) * [Learn](#-learn) * [HTTP Listening](_examples/#http-listening) * [Configuration](_examples/#configuration) diff --git a/VERSION b/VERSION index 85a92f6d..03bba5cc 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.3.1:https://github.com/kataras/iris/blob/master/HISTORY.md#sa-19-august-2017--v831 \ No newline at end of file +8.3.2:https://github.com/kataras/iris/blob/master/HISTORY.md#tu-22-august-2017--v832 \ No newline at end of file diff --git a/_benchmarks/README.md b/_benchmarks/README.md index bb69c523..67598195 100644 --- a/_benchmarks/README.md +++ b/_benchmarks/README.md @@ -220,7 +220,7 @@ Iris Application written using **14 code of lines** ran for **8 seconds** servin ## Sessions -Spawn `5000000 requests` with 125 different "threads" targeting a static request path, sets and gets a session based on the name `"key"` and string value `"value"` and write tat session value to the response stream. +Spawn `5000000 requests` with 125 different "threads" targeting a static request path, sets and gets a session based on the name `"key"` and string value `"value"` and write that session value to the response stream. ### .NET Core (Kestrel) with Sessions diff --git a/core/router/api_builder.go b/core/router/api_builder.go index 2631df86..e0c4939f 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -492,15 +492,16 @@ func (api *APIBuilder) Any(relativePath string, handlers ...context.Handler) (ro // } // // Usage: app.Controller("/user/{id:int}", new(UserController), db, time.Now()) +// Note: Binded values of context.Handler type are being recognised as middlewares by the router. // // Read more at `/mvc#Controller`. func (api *APIBuilder) Controller(relativePath string, controller activator.BaseController, bindValues ...interface{}) (routes []*Route) { - registerFunc := func(method string, handler context.Handler) { + registerFunc := func(method string, handlers ...context.Handler) { if method == "ANY" || method == "ALL" { - routes = api.Any(relativePath, handler) + routes = api.Any(relativePath, handlers...) } else { - routes = append(routes, api.HandleMany(method, relativePath, handler)...) + routes = append(routes, api.HandleMany(method, relativePath, handlers...)...) } } diff --git a/core/router/party.go b/core/router/party.go index 283b6817..a1c9ec46 100644 --- a/core/router/party.go +++ b/core/router/party.go @@ -169,6 +169,7 @@ type Party interface { // } // // Usage: app.Controller("/user/{id:int}", new(UserController), db, time.Now()) + // Note: Binded values of context.Handler type are being recognised as middlewares by the router. // // Read more at `/mvc#Controller`. Controller(relativePath string, controller activator.BaseController, bindValues ...interface{}) []*Route diff --git a/doc.go b/doc.go index 3d960291..7f59e04d 100644 --- a/doc.go +++ b/doc.go @@ -35,7 +35,7 @@ Source code and other details for the project are available at GitHub: Current Version -8.3.1 +8.3.2 Installation diff --git a/iris.go b/iris.go index c2e341c3..78986ac0 100644 --- a/iris.go +++ b/iris.go @@ -32,7 +32,7 @@ import ( const ( // Version is the current version number of the Iris Web Framework. - Version = "8.3.1" + Version = "8.3.2" ) // HTTP status codes as registered with IANA. diff --git a/mvc/activator/activator.go b/mvc/activator/activator.go index e9088bf9..3f190c00 100644 --- a/mvc/activator/activator.go +++ b/mvc/activator/activator.go @@ -277,7 +277,7 @@ func buildMethodHandler(t TController, methodFuncIndex int) context.Handler { } // RegisterFunc used by the caller to register the result routes. -type RegisterFunc func(httpMethod string, handler context.Handler) +type RegisterFunc func(httpMethod string, handler ...context.Handler) // RegisterMethodHandlers receives a `TController`, description of the // user's controller, and calls the "registerFunc" for each of its @@ -292,8 +292,19 @@ func RegisterMethodHandlers(t TController, registerFunc RegisterFunc) { // http methods using the registerFunc, which is // responsible to convert these into routes // and add them to router via the APIBuilder. + + var handlers context.Handlers + + if t.binder != nil { + if m := t.binder.middleware; len(m) > 0 { + handlers = append(handlers, t.binder.middleware...) + } + } + for _, m := range t.Methods { - registerFunc(m.HTTPMethod, buildMethodHandler(t, m.Index)) + methodHandler := buildMethodHandler(t, m.Index) + registeredHandlers := append(handlers, methodHandler) + registerFunc(m.HTTPMethod, registeredHandlers...) } } diff --git a/mvc/activator/binder.go b/mvc/activator/binder.go index ad641d92..f10126bf 100644 --- a/mvc/activator/binder.go +++ b/mvc/activator/binder.go @@ -2,11 +2,17 @@ package activator import ( "reflect" + + "github.com/kataras/iris/context" ) type binder struct { values []interface{} fields []field + + // saves any middleware that may need to be passed to the router, + // statically, to gain performance. + middleware context.Handlers } // binder accepts a value of something @@ -26,19 +32,42 @@ func newBinder(elemType reflect.Type, values []interface{}) *binder { // if nothing valid found return nil, so the caller // can omit the binder. - if len(b.fields) == 0 { + if len(b.fields) == 0 && len(b.middleware) == 0 { return nil } return b } +func (b *binder) storeValueIfMiddleware(value reflect.Value) bool { + if value.CanInterface() { + if m, ok := value.Interface().(context.Handler); ok { + b.middleware = append(b.middleware, m) + return true + } + if m, ok := value.Interface().(func(context.Context)); ok { + b.middleware = append(b.middleware, m) + return true + } + } + return false +} + func (b *binder) lookup(elem reflect.Type) (fields []field) { for _, v := range b.values { value := reflect.ValueOf(v) + // handlers will be recognised as middleware, not struct fields. + // End-Developer has the option to call any handler inside + // the controller's `BeginRequest` and `EndRequest`, the + // state is respected from the method handler already. + if b.storeValueIfMiddleware(value) { + // stored as middleware, continue to the next field, we don't have + // to bind anything here. + continue + } + for i, n := 0, elem.NumField(); i < n; i++ { elemField := elem.Field(i) - if elemField.Type == value.Type() { // we area inside the correct type // println("[0] prepare bind filed for " + elemField.Name) @@ -111,6 +140,13 @@ func lookupStruct(elem reflect.Type, value reflect.Value) *field { } func (b *binder) handle(c reflect.Value) { + // we could make check for middlewares here but + // these could easly be used outside of the controller + // so we don't have to initialize a controller to call them + // so they don't belong actually here, we will register them to the + // router itself, before the controller's handler to gain performance, + // look `activator.go#RegisterMethodHandlers` for more. + elem := c.Elem() // controller should always be a pointer at this state for _, f := range b.fields { f.sendTo(elem) diff --git a/mvc/controller.go b/mvc/controller.go index 3d4329f6..1c18165a 100644 --- a/mvc/controller.go +++ b/mvc/controller.go @@ -58,6 +58,7 @@ import ( // } // // Usage: app.Controller("/user/{id:int}", new(UserController), db, time.Now()) +// Note: Binded values of context.Handler type are being recognised as middlewares by the router. // // Look `core/router/APIBuilder#Controller` method too. type Controller struct { @@ -140,7 +141,7 @@ func (c *Controller) RelPath() string { reqPath := c.Ctx.Path() if len(reqPath) == 0 { // it never come here - // but to protect ourselves jsut return an empty slash. + // but to protect ourselves just return an empty slash. return slashStr } // [1:]to ellimuate the prefixes like "//" diff --git a/mvc/controller_test.go b/mvc/controller_test.go index 45511ec2..17d9f815 100644 --- a/mvc/controller_test.go +++ b/mvc/controller_test.go @@ -171,6 +171,53 @@ func TestControllerBeginAndEndRequestFunc(t *testing.T) { } } +func TestControllerBeginAndEndRequestFuncBindMiddleware(t *testing.T) { + app := iris.New() + usernames := map[string]bool{ + "kataras": true, + "makis": false, + "efi": true, + "rg": false, + "bill": true, + "whoisyourdaddy": false, + } + middlewareCheck := func(ctx context.Context) { + for username, allow := range usernames { + if ctx.Params().Get("username") == username && allow { + ctx.Next() + return + } + } + + ctx.StatusCode(httptest.StatusForbidden) + ctx.Writef("forbidden") + } + + app.Controller("/profile/{username}", new(testControllerBeginAndEndRequestFunc), middlewareCheck) + + e := httptest.New(t, app) + + doneResponse := "done" + + for username, allow := range usernames { + getEx := e.GET("/profile/" + username).Expect() + if allow { + getEx.Status(httptest.StatusOK). + Body().Equal(username + doneResponse) + } else { + getEx.Status(httptest.StatusForbidden).Body().Equal("forbidden") + } + + postEx := e.POST("/profile/" + username).Expect() + if allow { + postEx.Status(httptest.StatusOK). + Body().Equal(username + doneResponse) + } else { + postEx.Status(httptest.StatusForbidden).Body().Equal("forbidden") + } + } +} + type Model struct { Username string }