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`.
# 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

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
* [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)

View File

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

View File

@ -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() {}

View File

@ -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() {}

View File

@ -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"))

View File

@ -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"))

View File

@ -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"))

View File

@ -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"))

View File

@ -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) {

View File

@ -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) {

View File

@ -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() {}

View File

@ -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() {}

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()
// 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)
}

View File

@ -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)
}
}

12
doc.go
View File

@ -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"))

View File

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

View File

@ -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
}

View File

@ -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)
}
}