diff --git a/HISTORY.md b/HISTORY.md index 8377e52e..f3db04c7 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -18,6 +18,26 @@ 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`. +# Mo, 14 August 2017 | v8.2.6 + +Able to call done/end handlers inside a `Controller`, via optional `EndRequest(ctx context.Context)` function inside the controller struct. + +```go +// it's called after t.Get()/Post()/Put()/Delete()/Connect()/Head()/Patch()/Options()/Trace(). +func (t *testControllerEndRequestFunc) EndRequest(ctx context.Context) { + // 2. + // [your code goes here...] +} + +// will handle "GET" request HTTP method only. +func (t *testControllerEndRequestFunc) Get() { + // 1. + // [your code goes here...] +} +``` + +Look at the [v8.2.5 changelog](#su-13-august-2017--v825) to learn more about the new Iris Controllers feature. + # Su, 13 August 2017 | v8.2.5 Good news for devs that are used to write their web apps using the `MVC-style` app architecture. @@ -29,7 +49,8 @@ Our `Controller` supports many things among them are: - all HTTP Methods are supported, for example if want to serve `GET` then the controller should have a function named `Get()`, you can define more than one method function to serve in the same Controller struct - `persistence` data inside your Controller struct (share data between requests) via **`iris:"persistence"`** tag right to the field -- optional `Init` function to perform any initialization before the methods, useful to call middlewares or when many methods use the same collection of data +- optional `Init(ctx) or BeginRequest(ctx)` function to perform any initialization before the methods, useful to call middlewares or when many methods use the same collection of data +- optional `Done(ctx) or EndRequest(ctx)` function to perform any finalization after the methods executed - access to the request path parameters via the `Params` field - access to the template file that should be rendered via the `Tmpl` field - access to the template data that should be rendered inside the template file via `Data` field diff --git a/README.md b/README.md index b8db8f26..9b118b59 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Iris is a fast, simple and efficient micro web framework for Go. It provides a b ### 📑 Table of contents * [Installation](#-installation) -* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#su-13-august-2017--v825) +* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#mo-14-august-2017--v826) * [Learn](#-learn) * [HTTP Listening](_examples/#http-listening) * [Configuration](_examples/#configuration) diff --git a/_examples/README.md b/_examples/README.md index 2d21adfa..32ee5fb5 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -204,6 +204,7 @@ iris session manager lives on its own [package](https://github.com/kataras/iris/ - [Databases](sessions/database) * [File](sessions/database/file/main.go) * [BoltDB](sessions/database/boltdb/main.go) + * [LevelDB](sessions/database/leveldb/main.go) * [Redis](sessions/database/redis/main.go) > You're free to use your own favourite sessions package if you'd like so. diff --git a/_examples/routing/mvc/controllers/user.go b/_examples/routing/mvc/controllers/user.go index 0d44ba1b..6969b164 100644 --- a/_examples/routing/mvc/controllers/user.go +++ b/_examples/routing/mvc/controllers/user.go @@ -42,7 +42,7 @@ func (c *User) Get() { } /* Can use more than one, the factory will make sure -that the correct http methods are being registed for this +that the correct http methods are being registered for this controller, uncommend these if you want: func (c *User) Post() {} diff --git a/_examples/routing/mvc/controllers/user_go19.go b/_examples/routing/mvc/controllers/user_go19.go index ff710435..21504729 100644 --- a/_examples/routing/mvc/controllers/user_go19.go +++ b/_examples/routing/mvc/controllers/user_go19.go @@ -61,7 +61,7 @@ func (c *User) Get() { } /* Can use more than one, the factory will make sure -that the correct http methods are being registed for this +that the correct http methods are being registered for this controller, uncommend these if you want: func (c *User) Post() {} diff --git a/_examples/sessions/database/boltdb/main.go b/_examples/sessions/database/boltdb/main.go index 0b4524f5..7dd7ad30 100644 --- a/_examples/sessions/database/boltdb/main.go +++ b/_examples/sessions/database/boltdb/main.go @@ -86,7 +86,7 @@ func main() { app.Get("/update", func(ctx context.Context) { // updates expire date with a new date - sess.ShiftExpiraton(ctx) + sess.ShiftExpiration(ctx) }) app.Run(iris.Addr(":8080")) diff --git a/_examples/sessions/database/file/main.go b/_examples/sessions/database/file/main.go index 33cc180b..27a9bfca 100644 --- a/_examples/sessions/database/file/main.go +++ b/_examples/sessions/database/file/main.go @@ -82,7 +82,7 @@ func main() { app.Get("/update", func(ctx context.Context) { // updates expire date with a new date - sess.ShiftExpiraton(ctx) + sess.ShiftExpiration(ctx) }) app.Run(iris.Addr(":8080")) diff --git a/_examples/sessions/database/leveldb/main.go b/_examples/sessions/database/leveldb/main.go index 62c1c580..719eacb8 100644 --- a/_examples/sessions/database/leveldb/main.go +++ b/_examples/sessions/database/leveldb/main.go @@ -87,7 +87,7 @@ func main() { app.Get("/update", func(ctx context.Context) { // updates expire date with a new date - sess.ShiftExpiraton(ctx) + sess.ShiftExpiration(ctx) }) app.Run(iris.Addr(":8080")) diff --git a/_examples/sessions/database/redis/main.go b/_examples/sessions/database/redis/main.go index 8114e971..d49d5331 100644 --- a/_examples/sessions/database/redis/main.go +++ b/_examples/sessions/database/redis/main.go @@ -76,7 +76,7 @@ func main() { app.Get("/update", func(ctx context.Context) { // updates expire date with a new date - sess.ShiftExpiraton(ctx) + sess.ShiftExpiration(ctx) }) app.Run(iris.Addr(":8080")) diff --git a/_examples/sessions/securecookie/main.go b/_examples/sessions/securecookie/main.go index f068ec57..ae256938 100644 --- a/_examples/sessions/securecookie/main.go +++ b/_examples/sessions/securecookie/main.go @@ -64,7 +64,7 @@ func newApp() *iris.Application { app.Get("/update", func(ctx context.Context) { // updates expire date with a new date - mySessions.ShiftExpiraton(ctx) + mySessions.ShiftExpiration(ctx) }) app.Get("/destroy", func(ctx context.Context) { diff --git a/_examples/sessions/standalone/main.go b/_examples/sessions/standalone/main.go index 12d61e1f..20a63d0f 100644 --- a/_examples/sessions/standalone/main.go +++ b/_examples/sessions/standalone/main.go @@ -71,7 +71,7 @@ func main() { app.Get("/update", func(ctx context.Context) { // updates expire date - sess.ShiftExpiraton(ctx) + sess.ShiftExpiration(ctx) }) app.Get("/destroy", func(ctx context.Context) { diff --git a/_examples/tutorial/mvc-from-scratch/README.md b/_examples/tutorial/mvc-from-scratch/README.md index 6d0f506a..13c041b5 100644 --- a/_examples/tutorial/mvc-from-scratch/README.md +++ b/_examples/tutorial/mvc-from-scratch/README.md @@ -76,7 +76,7 @@ func (c *UserController) Get() { } /* Can use more than one, the factory will make sure -that the correct http methods are being registed for this +that the correct http methods are being registered for this controller, uncommend these if you want: func (c *User) Post() {} diff --git a/_examples/tutorial/mvc-from-scratch/controllers/user.go b/_examples/tutorial/mvc-from-scratch/controllers/user.go index 95b26454..88a65d55 100644 --- a/_examples/tutorial/mvc-from-scratch/controllers/user.go +++ b/_examples/tutorial/mvc-from-scratch/controllers/user.go @@ -37,7 +37,7 @@ func (c *User) Get() { } /* Can use more than one, the factory will make sure -that the correct http methods are being registed for this +that the correct http methods are being registered for this controller, uncommend these if you want: func (c *User) Post() {} diff --git a/core/router/controller.go b/core/router/controller.go index 5c387f12..4d89f78a 100644 --- a/core/router/controller.go +++ b/core/router/controller.go @@ -164,7 +164,8 @@ func registerController(p Party, path string, c interface{}) ([]*Route, error) { } } - customInitFuncIndex, _ := getCustomInitFuncIndex(typ) + customInitFuncIndex, _ := getCustomFuncIndex(typ, customInitFuncNames...) + customEndFuncIndex, _ := getCustomFuncIndex(typ, customEndFuncNames...) // check if has Any() or All() // if yes, then register all http methods and @@ -176,7 +177,7 @@ func registerController(p Party, path string, c interface{}) ([]*Route, error) { if has { routes := p.Any(path, controllerToHandler(elem, persistenceFields, - baseControllerFieldIndex, m.Index, customInitFuncIndex)) + baseControllerFieldIndex, m.Index, customInitFuncIndex, customEndFuncIndex)) return routes, nil } @@ -196,14 +197,14 @@ func registerController(p Party, path string, c interface{}) ([]*Route, error) { r := p.Handle(method, path, controllerToHandler(elem, persistenceFields, - baseControllerFieldIndex, httpMethodIndex, customInitFuncIndex)) + baseControllerFieldIndex, httpMethodIndex, customInitFuncIndex, customEndFuncIndex)) routes = append(routes, r) } return routes, nil } func controllerToHandler(elem reflect.Type, persistenceFields map[int]reflect.Value, - baseControllerFieldIndex, httpMethodIndex int, customInitFuncIndex int) context.Handler { + baseControllerFieldIndex, httpMethodIndex, customInitFuncIndex, customEndFuncIndex int) context.Handler { return func(ctx context.Context) { // create a new controller instance of that type(>ptr). c := reflect.New(elem) @@ -229,10 +230,10 @@ func controllerToHandler(elem reflect.Type, persistenceFields map[int]reflect.Va // init the new controller instance. b.init(ctx) - // calls the higher "Init(ctx context.Context)", + // call the higher "Init/BeginRequest(ctx context.Context)", // if exists. - if customInitFuncIndex > 0 { - callCustomInit(ctx, c, customInitFuncIndex) + if customInitFuncIndex >= 0 { + callCustomFuncHandler(ctx, c, customInitFuncIndex) } // if custom Init didn't stop the execution of the @@ -242,22 +243,39 @@ func controllerToHandler(elem reflect.Type, persistenceFields map[int]reflect.Va methodFunc.Interface().(func())() } + if !ctx.IsStopped() { + // call the higher "Done/EndRequest(ctx context.Context)", + // if exists. + if customEndFuncIndex >= 0 { + callCustomFuncHandler(ctx, c, customEndFuncIndex) + } + } + // finally, execute the controller. b.exec() } } -// Init can be used as a custom function -// to init the new instance of controller -// that is created on each new request. -// // Useful when more than one methods are using the same // request data. -const customInitFuncName = "Init" +var ( + // customInitFuncNames can be used as custom functions + // to init the new instance of controller + // that is created on each new request. + // One of these is valid, no both. + customInitFuncNames = []string{"Init", "BeginRequest"} + // customEndFuncNames can be used as custom functions + // to action when the method handler has been finished, + // this is the last step before server send the response to the client. + // One of these is valid, no both. + customEndFuncNames = []string{"Done", "EndRequest"} +) -func getCustomInitFuncIndex(typ reflect.Type) (initFuncIndex int, has bool) { - if m, has := typ.MethodByName(customInitFuncName); has { - return m.Index, has +func getCustomFuncIndex(typ reflect.Type, funcNames ...string) (initFuncIndex int, has bool) { + for _, customInitFuncName := range funcNames { + if m, has := typ.MethodByName(customInitFuncName); has { + return m.Index, has + } } return -1, false @@ -265,8 +283,8 @@ func getCustomInitFuncIndex(typ reflect.Type) (initFuncIndex int, has bool) { // the "cServeTime" is a new "c" instance // which is being used at serve time, inside the Handler. -// it calls the custom "Init", the check of this -// function made at build time, so it's a safe a call. -func callCustomInit(ctx context.Context, cServeTime reflect.Value, initFuncIndex int) { +// it calls the custom function (can be "Init", "BeginRequest", "End" and "EndRequest"), +// the check of this function made at build time, so it's a safe a call. +func callCustomFuncHandler(ctx context.Context, cServeTime reflect.Value, initFuncIndex int) { cServeTime.Method(initFuncIndex).Interface().(func(ctx context.Context))(ctx) } diff --git a/core/router/controller_test.go b/core/router/controller_test.go index 9f557e92..1b084a69 100644 --- a/core/router/controller_test.go +++ b/core/router/controller_test.go @@ -98,30 +98,38 @@ func TestControllerPersistenceFields(t *testing.T) { Body().Equal(data) } -type testControllerInitFunc struct { +type testControllerBeginAndEndRequestFunc struct { router.Controller Username string } +// called before of every method (Get() or Post()). +// // useful when more than one methods using the // same request values or context's function calls. -func (t *testControllerInitFunc) Init(ctx context.Context) { +func (t *testControllerBeginAndEndRequestFunc) BeginRequest(ctx context.Context) { t.Username = ctx.Params().Get("username") // or t.Params.Get("username") because the - // t.Ctx == ctx and is being initialized before this "Init" + // t.Ctx == ctx and is being initialized before this "BeginRequest" } -func (t *testControllerInitFunc) Get() { +// called after every method (Get() or Post()). +func (t *testControllerBeginAndEndRequestFunc) EndRequest(ctx context.Context) { + ctx.Writef("done") // append "done" to the response +} + +func (t *testControllerBeginAndEndRequestFunc) Get() { t.Ctx.Writef(t.Username) } -func (t *testControllerInitFunc) Post() { +func (t *testControllerBeginAndEndRequestFunc) Post() { t.Ctx.Writef(t.Username) } -func TestControllerInitFunc(t *testing.T) { + +func TestControllerBeginAndEndRequestFunc(t *testing.T) { app := iris.New() - app.Controller("/profile/{username}", new(testControllerInitFunc)) + app.Controller("/profile/{username}", new(testControllerBeginAndEndRequestFunc)) e := httptest.New(t, app) usernames := []string{ @@ -132,11 +140,13 @@ func TestControllerInitFunc(t *testing.T) { "bill", "whoisyourdaddy", } + doneResponse := "done" + for _, username := range usernames { e.GET("/profile/" + username).Expect().Status(httptest.StatusOK). - Body().Equal(username) + Body().Equal(username + doneResponse) e.POST("/profile/" + username).Expect().Status(httptest.StatusOK). - Body().Equal(username) + Body().Equal(username + doneResponse) } } diff --git a/doc.go b/doc.go index 6b3fe232..d5532561 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.2.5 +8.2.6 Installation @@ -695,9 +695,6 @@ you can define more than one method function to serve in the same Controller str Persistence data inside your Controller struct (share data between requests) via `iris:"persistence"` tag right to the field. -Optional `Init` function to perform any initialization before the methods, -useful to call middlewares or when many methods use the same collection of data. - Access to the request path parameters via the `Params` field. Access to the template file that should be rendered via the `Tmpl` field. @@ -712,6 +709,11 @@ Access to the low-level `context.Context` via the `Ctx` field. Flow as you used to, `Controllers` can be registered to any `Party`, including Subdomains, the Party's begin and done handlers work as expected. +Optional `Init(ctx) or BeginRequest(ctx)` function to perform any initialization before the methods, +useful to call middlewares or when many methods use the same collection of data. + +Optional `Done(ctx) or EndRequest(ctx)` function to perform any finalization after the methods executed. + Example Code: @@ -1448,7 +1450,7 @@ Example Code: app.Get("/update", func(ctx context.Context) { // updates expire date with a new date - sess.ShiftExpiraton(ctx) + sess.ShiftExpiration(ctx) }) app.Run(iris.Addr(":8080")) diff --git a/iris.go b/iris.go index 0d6cc4a8..a3233b95 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.2.5" + Version = "8.2.6" ) // HTTP status codes as registered with IANA. diff --git a/sessions/provider.go b/sessions/provider.go index ba22073d..bf539a8d 100644 --- a/sessions/provider.go +++ b/sessions/provider.go @@ -143,10 +143,10 @@ func (p *provider) Init(sid string, expires time.Duration) *Session { return newSession } -// UpdateExpiraton update expire date of a session. +// UpdateExpiration update expire date of a session. // if expires > 0 then it updates the destroy task. // if expires <=0 then it does nothing, to destroy a session call the `Destroy` func instead. -func (p *provider) UpdateExpiraton(sid string, expires time.Duration) bool { +func (p *provider) UpdateExpiration(sid string, expires time.Duration) bool { if expires <= 0 { return false } diff --git a/sessions/sessions.go b/sessions/sessions.go index 680e7c6e..15b1a6ec 100644 --- a/sessions/sessions.go +++ b/sessions/sessions.go @@ -119,19 +119,19 @@ func (s *Sessions) Start(ctx context.Context) *Session { return sess } -// ShiftExpiraton move the expire date of a session to a new date +// ShiftExpiration move the expire date of a session to a new date // by using session default timeout configuration. -func (s *Sessions) ShiftExpiraton(ctx context.Context) { - s.UpdateExpiraton(ctx, s.config.Expires) +func (s *Sessions) ShiftExpiration(ctx context.Context) { + s.UpdateExpiration(ctx, s.config.Expires) } -// UpdateExpiraton change expire date of a session to a new date +// UpdateExpiration change expire date of a session to a new date // by using timeout value passed by `expires` receiver. -func (s *Sessions) UpdateExpiraton(ctx context.Context, expires time.Duration) { +func (s *Sessions) UpdateExpiration(ctx context.Context, expires time.Duration) { cookieValue := s.decodeCookieValue(GetCookie(ctx, s.config.Cookie)) if cookieValue != "" { - if s.provider.UpdateExpiraton(cookieValue, expires) { + if s.provider.UpdateExpiration(cookieValue, expires) { s.updateCookie(ctx, cookieValue, expires) } }