Update to 8.2.6 | More on Iris Controllers: optional EndRequest. Read HISTORY.md

Former-commit-id: 5255ca5c4a898e2f3e7388f3af0457599e65f87f
This commit is contained in:
kataras 2017-08-14 16:21:51 +03:00
parent 8cd07719a6
commit 35620f6ecb
19 changed files with 105 additions and 53 deletions

View File

@ -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`. **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 # 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. 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 - 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 - `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 request path parameters via the `Params` field
- access to the template file that should be rendered via the `Tmpl` 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 - access to the template data that should be rendered inside the template file via `Data` field

View File

@ -26,7 +26,7 @@ Iris is a fast, simple and efficient micro web framework for Go. It provides a b
### 📑 Table of contents ### 📑 Table of contents
* [Installation](#-installation) * [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) * [Learn](#-learn)
* [HTTP Listening](_examples/#http-listening) * [HTTP Listening](_examples/#http-listening)
* [Configuration](_examples/#configuration) * [Configuration](_examples/#configuration)

View File

@ -204,6 +204,7 @@ iris session manager lives on its own [package](https://github.com/kataras/iris/
- [Databases](sessions/database) - [Databases](sessions/database)
* [File](sessions/database/file/main.go) * [File](sessions/database/file/main.go)
* [BoltDB](sessions/database/boltdb/main.go) * [BoltDB](sessions/database/boltdb/main.go)
* [LevelDB](sessions/database/leveldb/main.go)
* [Redis](sessions/database/redis/main.go) * [Redis](sessions/database/redis/main.go)
> You're free to use your own favourite sessions package if you'd like so. > You're free to use your own favourite sessions package if you'd like so.

View File

@ -42,7 +42,7 @@ func (c *User) Get() {
} }
/* Can use more than one, the factory will make sure /* 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: controller, uncommend these if you want:
func (c *User) Post() {} func (c *User) Post() {}

View File

@ -61,7 +61,7 @@ func (c *User) Get() {
} }
/* Can use more than one, the factory will make sure /* 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: controller, uncommend these if you want:
func (c *User) Post() {} func (c *User) Post() {}

View File

@ -86,7 +86,7 @@ func main() {
app.Get("/update", func(ctx context.Context) { app.Get("/update", func(ctx context.Context) {
// updates expire date with a new date // updates expire date with a new date
sess.ShiftExpiraton(ctx) sess.ShiftExpiration(ctx)
}) })
app.Run(iris.Addr(":8080")) app.Run(iris.Addr(":8080"))

View File

@ -82,7 +82,7 @@ func main() {
app.Get("/update", func(ctx context.Context) { app.Get("/update", func(ctx context.Context) {
// updates expire date with a new date // updates expire date with a new date
sess.ShiftExpiraton(ctx) sess.ShiftExpiration(ctx)
}) })
app.Run(iris.Addr(":8080")) app.Run(iris.Addr(":8080"))

View File

@ -87,7 +87,7 @@ func main() {
app.Get("/update", func(ctx context.Context) { app.Get("/update", func(ctx context.Context) {
// updates expire date with a new date // updates expire date with a new date
sess.ShiftExpiraton(ctx) sess.ShiftExpiration(ctx)
}) })
app.Run(iris.Addr(":8080")) app.Run(iris.Addr(":8080"))

View File

@ -76,7 +76,7 @@ func main() {
app.Get("/update", func(ctx context.Context) { app.Get("/update", func(ctx context.Context) {
// updates expire date with a new date // updates expire date with a new date
sess.ShiftExpiraton(ctx) sess.ShiftExpiration(ctx)
}) })
app.Run(iris.Addr(":8080")) app.Run(iris.Addr(":8080"))

View File

@ -64,7 +64,7 @@ func newApp() *iris.Application {
app.Get("/update", func(ctx context.Context) { app.Get("/update", func(ctx context.Context) {
// updates expire date with a new date // updates expire date with a new date
mySessions.ShiftExpiraton(ctx) mySessions.ShiftExpiration(ctx)
}) })
app.Get("/destroy", func(ctx context.Context) { app.Get("/destroy", func(ctx context.Context) {

View File

@ -71,7 +71,7 @@ func main() {
app.Get("/update", func(ctx context.Context) { app.Get("/update", func(ctx context.Context) {
// updates expire date // updates expire date
sess.ShiftExpiraton(ctx) sess.ShiftExpiration(ctx)
}) })
app.Get("/destroy", func(ctx context.Context) { app.Get("/destroy", func(ctx context.Context) {

View File

@ -76,7 +76,7 @@ func (c *UserController) Get() {
} }
/* Can use more than one, the factory will make sure /* 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: controller, uncommend these if you want:
func (c *User) Post() {} func (c *User) Post() {}

View File

@ -37,7 +37,7 @@ func (c *User) Get() {
} }
/* Can use more than one, the factory will make sure /* 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: controller, uncommend these if you want:
func (c *User) Post() {} func (c *User) Post() {}

View File

@ -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() // check if has Any() or All()
// if yes, then register all http methods and // if yes, then register all http methods and
@ -176,7 +177,7 @@ func registerController(p Party, path string, c interface{}) ([]*Route, error) {
if has { if has {
routes := p.Any(path, routes := p.Any(path,
controllerToHandler(elem, persistenceFields, controllerToHandler(elem, persistenceFields,
baseControllerFieldIndex, m.Index, customInitFuncIndex)) baseControllerFieldIndex, m.Index, customInitFuncIndex, customEndFuncIndex))
return routes, nil return routes, nil
} }
@ -196,14 +197,14 @@ func registerController(p Party, path string, c interface{}) ([]*Route, error) {
r := p.Handle(method, path, r := p.Handle(method, path,
controllerToHandler(elem, persistenceFields, controllerToHandler(elem, persistenceFields,
baseControllerFieldIndex, httpMethodIndex, customInitFuncIndex)) baseControllerFieldIndex, httpMethodIndex, customInitFuncIndex, customEndFuncIndex))
routes = append(routes, r) routes = append(routes, r)
} }
return routes, nil return routes, nil
} }
func controllerToHandler(elem reflect.Type, persistenceFields map[int]reflect.Value, 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) { return func(ctx context.Context) {
// create a new controller instance of that type(>ptr). // create a new controller instance of that type(>ptr).
c := reflect.New(elem) c := reflect.New(elem)
@ -229,10 +230,10 @@ func controllerToHandler(elem reflect.Type, persistenceFields map[int]reflect.Va
// init the new controller instance. // init the new controller instance.
b.init(ctx) b.init(ctx)
// calls the higher "Init(ctx context.Context)", // call the higher "Init/BeginRequest(ctx context.Context)",
// if exists. // if exists.
if customInitFuncIndex > 0 { if customInitFuncIndex >= 0 {
callCustomInit(ctx, c, customInitFuncIndex) callCustomFuncHandler(ctx, c, customInitFuncIndex)
} }
// if custom Init didn't stop the execution of the // if custom Init didn't stop the execution of the
@ -242,31 +243,48 @@ func controllerToHandler(elem reflect.Type, persistenceFields map[int]reflect.Va
methodFunc.Interface().(func())() 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. // finally, execute the controller.
b.exec() 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 // Useful when more than one methods are using the same
// request data. // 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) { func getCustomFuncIndex(typ reflect.Type, funcNames ...string) (initFuncIndex int, has bool) {
for _, customInitFuncName := range funcNames {
if m, has := typ.MethodByName(customInitFuncName); has { if m, has := typ.MethodByName(customInitFuncName); has {
return m.Index, has return m.Index, has
} }
}
return -1, false return -1, false
} }
// the "cServeTime" is a new "c" instance // the "cServeTime" is a new "c" instance
// which is being used at serve time, inside the Handler. // which is being used at serve time, inside the Handler.
// it calls the custom "Init", the check of this // it calls the custom function (can be "Init", "BeginRequest", "End" and "EndRequest"),
// function made at build time, so it's a safe a call. // 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) { func callCustomFuncHandler(ctx context.Context, cServeTime reflect.Value, initFuncIndex int) {
cServeTime.Method(initFuncIndex).Interface().(func(ctx context.Context))(ctx) cServeTime.Method(initFuncIndex).Interface().(func(ctx context.Context))(ctx)
} }

View File

@ -98,30 +98,38 @@ func TestControllerPersistenceFields(t *testing.T) {
Body().Equal(data) Body().Equal(data)
} }
type testControllerInitFunc struct { type testControllerBeginAndEndRequestFunc struct {
router.Controller router.Controller
Username string Username string
} }
// called before of every method (Get() or Post()).
//
// useful when more than one methods using the // useful when more than one methods using the
// same request values or context's function calls. // 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") t.Username = ctx.Params().Get("username")
// or t.Params.Get("username") because the // 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) t.Ctx.Writef(t.Username)
} }
func (t *testControllerInitFunc) Post() { func (t *testControllerBeginAndEndRequestFunc) Post() {
t.Ctx.Writef(t.Username) t.Ctx.Writef(t.Username)
} }
func TestControllerInitFunc(t *testing.T) {
func TestControllerBeginAndEndRequestFunc(t *testing.T) {
app := iris.New() app := iris.New()
app.Controller("/profile/{username}", new(testControllerInitFunc)) app.Controller("/profile/{username}", new(testControllerBeginAndEndRequestFunc))
e := httptest.New(t, app) e := httptest.New(t, app)
usernames := []string{ usernames := []string{
@ -132,11 +140,13 @@ func TestControllerInitFunc(t *testing.T) {
"bill", "bill",
"whoisyourdaddy", "whoisyourdaddy",
} }
doneResponse := "done"
for _, username := range usernames { for _, username := range usernames {
e.GET("/profile/" + username).Expect().Status(httptest.StatusOK). e.GET("/profile/" + username).Expect().Status(httptest.StatusOK).
Body().Equal(username) Body().Equal(username + doneResponse)
e.POST("/profile/" + username).Expect().Status(httptest.StatusOK). e.POST("/profile/" + username).Expect().Status(httptest.StatusOK).
Body().Equal(username) Body().Equal(username + doneResponse)
} }
} }

12
doc.go
View File

@ -35,7 +35,7 @@ Source code and other details for the project are available at GitHub:
Current Version Current Version
8.2.5 8.2.6
Installation 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) Persistence data inside your Controller struct (share data between requests)
via `iris:"persistence"` tag right to the field. 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 request path parameters via the `Params` field.
Access to the template file that should be rendered via the `Tmpl` 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`, Flow as you used to, `Controllers` can be registered to any `Party`,
including Subdomains, the Party's begin and done handlers work as expected. 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: Example Code:
@ -1448,7 +1450,7 @@ Example Code:
app.Get("/update", func(ctx context.Context) { app.Get("/update", func(ctx context.Context) {
// updates expire date with a new date // updates expire date with a new date
sess.ShiftExpiraton(ctx) sess.ShiftExpiration(ctx)
}) })
app.Run(iris.Addr(":8080")) app.Run(iris.Addr(":8080"))

View File

@ -32,7 +32,7 @@ import (
const ( const (
// Version is the current version number of the Iris Web Framework. // 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. // HTTP status codes as registered with IANA.

View File

@ -143,10 +143,10 @@ func (p *provider) Init(sid string, expires time.Duration) *Session {
return newSession 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 updates the destroy task.
// if expires <=0 then it does nothing, to destroy a session call the `Destroy` func instead. // 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 { if expires <= 0 {
return false return false
} }

View File

@ -119,19 +119,19 @@ func (s *Sessions) Start(ctx context.Context) *Session {
return sess 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. // by using session default timeout configuration.
func (s *Sessions) ShiftExpiraton(ctx context.Context) { func (s *Sessions) ShiftExpiration(ctx context.Context) {
s.UpdateExpiraton(ctx, s.config.Expires) 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. // 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)) cookieValue := s.decodeCookieValue(GetCookie(ctx, s.config.Cookie))
if cookieValue != "" { if cookieValue != "" {
if s.provider.UpdateExpiraton(cookieValue, expires) { if s.provider.UpdateExpiration(cookieValue, expires) {
s.updateCookie(ctx, cookieValue, expires) s.updateCookie(ctx, cookieValue, expires)
} }
} }