Skip to content

Commit

Permalink
improvements of the new ContextWrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
kataras committed Nov 3, 2023
1 parent ec69670 commit 4d13ff3
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 86 deletions.
2 changes: 1 addition & 1 deletion HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene

Changes apply to `main` branch.

- A new way to customize the handler's parameter among with the `hero` and `mvc` packages. New `iris.NewContextWrapper` method and `iris.DefaultContextPool` struct were added to wrap a handler and use a custom context instead of the iris.Context directly.
- A new way to customize the handler's parameter among with the `hero` and `mvc` packages. New `iris.NewContextWrapper` and `iris.NewContextPool` methods were added to wrap a handler and use a custom context instead of the iris.Context directly. Example at: https://github.com/kataras/iris/tree/main/_examples/routing/custom-context.

- The `cache` sub-package has an update, after 4 years:

Expand Down
78 changes: 45 additions & 33 deletions _examples/routing/custom-context/main.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,21 @@
package main

import (
"sync"

"github.com/kataras/iris/v12"
)

func main() {
// 1. Create the iris app instance.
app := iris.New()

/*
w := iris.NewContextWrapper(&iris.DefaultContextPool[*myCustomContext]{
AcquireFunc: func(ctx iris.Context) *myCustomContext {
return &myCustomContext{
Context: ctx,
// custom fields here...
}
},
ReleaseFunc: func(t *myCustomContext) {
// do nothing
},
})
OR: */
// 2. Create the Context Wrapper which will be used to wrap the handlers
// that expect a *myCustomContext instead of iris.Context.
w := iris.NewContextWrapper(&myCustomContextPool{})
// OR:
// w := iris.NewContextWrapper(iris.NewContextPool[myCustomContext, *myCustomContext]())
// The example custom context pool operates exactly the same as the result of iris.NewContextPool.

// 3. Register the handler(s) which expects a *myCustomContext instead of iris.Context.
// The `w.Handler` will wrap the handler and will call the `Acquire` and `Release`
Expand All @@ -38,6 +30,38 @@ func index(ctx *myCustomContext) {
ctx.HTML("<h1>Hello, World!</h1>")
}

/*
Custom Context Pool
*/
// Create the context sync pool for our custom context,
// the pool must implement Acquire() T and Release(T) methods to satisfy the iris.ContextPool interface.
type myCustomContextPool struct {
pool sync.Pool
}

// Acquire returns a new custom context from the pool.
func (p *myCustomContextPool) Acquire(ctx iris.Context) *myCustomContext {
v := p.pool.Get()
if v == nil {
v = &myCustomContext{
Context: ctx,
// custom fields here...
}
}

return v.(*myCustomContext)
}

// Release puts a custom context back to the pool.
func (p *myCustomContextPool) Release(t *myCustomContext) {
// You can take advantage of this method to clear the context
// and re-use it on the Acquire method, use the sync.Pool.
p.pool.Put(t)
}

/*
Custom Context
*/
// Create a custom context.
type myCustomContext struct {
// It's just an embedded field which is set on AcquireFunc,
Expand All @@ -47,28 +71,16 @@ type myCustomContext struct {
iris.Context
}

// SetContext sets the original iris.Context,
// should be implemented by custom context type(s) when
// the ContextWrapper uses a context Pool through the iris.NewContextPool function.
// Comment line 15, uncomment line 17 and the method below.
func (c *myCustomContext) SetContext(ctx iris.Context) {
c.Context = ctx
}

func (c *myCustomContext) HTML(format string, args ...interface{}) (int, error) {
c.Application().Logger().Info("HTML was called from custom Context")

return c.Context.HTML(format, args...)
}

// Create the context memory pool for your custom context,
// the pool must contain Acquire() T and Release(T) methods.
type myCustomContextPool struct{}

// Acquire returns a new custom context from the pool.
func (p *myCustomContextPool) Acquire(ctx iris.Context) *myCustomContext {
return &myCustomContext{
Context: ctx,
// custom fields here...
}
}

// Release puts a custom context back to the pool.
func (p *myCustomContextPool) Release(t *myCustomContext) {
// You can take advantage of this method to clear the context
// and re-use it on the Acquire method, use the sync.Pool.
//
// We do nothing for the shake of the exampel.
}
132 changes: 88 additions & 44 deletions context_wrapper.go
Original file line number Diff line number Diff line change
@@ -1,51 +1,101 @@
package iris

import (
"sync"

"github.com/kataras/iris/v12/context"
)

// ContextPool is a pool of T.
//
// See `NewContextWrapper` and `ContextPool` for more.
type ContextPool[T any] interface {
Acquire(ctx Context) T
Release(T)
}
type (
// ContextSetter is an interface which can be implemented by a struct
// to set the iris.Context to the struct.
// The receiver must be a pointer of the struct.
ContextSetter interface {
// SetContext sets the iris.Context to the struct.
SetContext(Context)
}

// DefaultContextPool is a pool of T.
// It's used to acquire and release T.
// The T is acquired from the pool and released back to the pool after the handler's execution.
// The T is passed to the handler as an argument.
// The T is not shared between requests.
type DefaultContextPool[T any] struct {
AcquireFunc func(Context) T
ReleaseFunc func(T)
}
// ContextSetterPtr is a pointer of T which implements the `ContextSetter` interface.
// The T must be a struct.
ContextSetterPtr[T any] interface {
*T
ContextSetter
}

// emptyContextSetter is an empty struct which implements the `ContextSetter` interface.
emptyContextSetter struct{}
)

// SetContext method implements `ContextSetter` interface.
func (*emptyContextSetter) SetContext(Context) {}

// Ensure that DefaultContextPool[T] implements ContextPool[T].
var _ ContextPool[any] = (*DefaultContextPool[any])(nil)

// Acquire returns a new T from the pool's AcquireFunc.
func (p *DefaultContextPool[T]) Acquire(ctx Context) T {
acquire := p.AcquireFunc
if p.AcquireFunc == nil {
acquire = func(ctx Context) T {
var t T
return t
}
// ContextPool is a pool of T. It's used to acquire and release custom context.
// Use of custom implementation or `NewContextPool`.
//
// See `NewContextWrapper` and `NewContextPool` for more.
type (
ContextPool[T any] interface {
// Acquire must return a new T from a pool.
Acquire(ctx Context) T
// Release must put the T back to the pool.
Release(T)
}

return acquire(ctx)
}
// syncContextPool is a sync pool implementation of T.
// It's used to acquire and release T.
// The contextPtr is acquired from the sync pool and released back to the sync pool after the handler's execution.
// The contextPtr is passed to the handler as an argument.
// ThecontextPtr is not shared between requests.
// The contextPtr must implement the `ContextSetter` interface.
// The T must be a struct.
// The contextPtr must be a pointer of T.
syncContextPool[T any, contextPtr ContextSetterPtr[T]] struct {
pool *sync.Pool
}
)

// Release does nothing if the pool's ReleaseFunc is nil.
func (p *DefaultContextPool[T]) Release(t T) {
release := p.ReleaseFunc
if p.ReleaseFunc == nil {
release = func(t T) {}
// Ensure that syncContextPool implements ContextPool.
var _ ContextPool[*emptyContextSetter] = (*syncContextPool[emptyContextSetter, *emptyContextSetter])(nil)

// NewContextPool returns a new ContextPool default implementation which
// uses sync.Pool to implement its Acquire and Release methods.
// The contextPtr is acquired from the sync pool and released back to the sync pool after the handler's execution.
// The contextPtr is passed to the handler as an argument.
// ThecontextPtr is not shared between requests.
// The contextPtr must implement the `ContextSetter` interface.
// The T must be a struct.
// The contextPtr must be a pointer of T.
//
// Example:
// w := iris.NewContextWrapper(iris.NewContextPool[myCustomContext, *myCustomContext]())
func NewContextPool[T any, contextPtr ContextSetterPtr[T]]() ContextPool[contextPtr] {
return &syncContextPool[T, contextPtr]{
pool: &sync.Pool{
New: func() interface{} {
var t contextPtr = new(T)
return t
},
},
}
}

// Acquire returns a new T from the sync pool.
func (p *syncContextPool[T, contextPtr]) Acquire(ctx Context) contextPtr {
// var t contextPtr
// if v := p.pool.Get(); v == nil {
// t = new(T)
// } else {
// t = v.(contextPtr)
// }

t := p.pool.Get().(contextPtr)
t.SetContext(ctx)
return t
}

release(t)
// Release puts the T back to the sync pool.
func (p *syncContextPool[T, contextPtr]) Release(t contextPtr) {
p.pool.Put(t)
}

// ContextWrapper is a wrapper for handlers which expect a T instead of iris.Context.
Expand All @@ -60,19 +110,13 @@ type ContextWrapper[T any] struct {
// The default pool's AcquireFunc returns a zero value of T.
// The default pool's ReleaseFunc does nothing.
// The default pool is used when the pool is nil.
// Use the `&iris.DefaultContextPool{...}` to pass a simple context pool.
// Use the `iris.NewContextPool[T, *T]()` to pass a simple context pool.
// Then, use the `Handler` method to wrap custom handlers to iris ones.
//
// See the `Handler` method for more.
// Example: https://github.com/kataras/iris/tree/main/_examples/routing/custom-context
func NewContextWrapper[T any](pool ContextPool[T]) *ContextWrapper[T] {
if pool == nil {
pool = &DefaultContextPool[T]{
AcquireFunc: func(ctx Context) T {
var t T
return t
},
ReleaseFunc: func(t T) {},
}
panic("pool cannot be nil")
}

return &ContextWrapper[T]{
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ require (
github.com/redis/go-redis/v9 v9.3.0
github.com/schollz/closestmatch v2.1.0+incompatible
github.com/shirou/gopsutil/v3 v3.23.10
github.com/tdewolff/minify/v2 v2.20.5
github.com/tdewolff/minify/v2 v2.20.6
github.com/vmihailenco/msgpack/v5 v5.4.1
github.com/yosssi/ace v0.0.5
go.etcd.io/bbolt v1.3.8
Expand Down Expand Up @@ -95,7 +95,7 @@ require (
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/tdewolff/parse/v2 v2.7.3-0.20231031132452-e7c20a5d77ab // indirect
github.com/tdewolff/parse/v2 v2.7.4 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
Expand Down
12 changes: 6 additions & 6 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 4d13ff3

Please sign in to comment.