diff --git a/HISTORY.md b/HISTORY.md index 4a6b5a18..ab79cfcf 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -23,6 +23,8 @@ 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. + - The `cache` sub-package has an update, after 4 years: - Add support for custom storage on `cache` package, through the `Handler#Store` method. diff --git a/_examples/README.md b/_examples/README.md index 7b6f22f2..10539429 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -48,6 +48,7 @@ * [Import from TOML](configuration/from-toml-file/main.go) * [Multi Environment Configuration](configuration/multi-environments) **NEW** * Routing + * [Custom Context](routing/custom-context/main.go) **HOT/NEW** * [Party Controller](routing/party-controller) **NEW** * [Overview](routing/overview/main.go) * [Basic](routing/basic/main.go) diff --git a/_examples/routing/custom-context/main.go b/_examples/routing/custom-context/main.go new file mode 100644 index 00000000..89b9c207 --- /dev/null +++ b/_examples/routing/custom-context/main.go @@ -0,0 +1,74 @@ +package main + +import ( + "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{}) + + // 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` + // methods of the `myCustomContextPool` to get and release the *myCustomContext. + app.Get("/", w.Handler(index)) + + // 4. Start the server. + app.Listen(":8080") +} + +func index(ctx *myCustomContext) { + ctx.HTML("

Hello, World!

") +} + +// Create a custom context. +type myCustomContext struct { + // It's just an embedded field which is set on AcquireFunc, + // so you can use myCustomContext with the same methods as iris.Context, + // override existing iris.Context's methods or add custom methods. + // You can use the `Context` field to access the original context. + iris.Context +} + +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. +} diff --git a/context_wrapper.go b/context_wrapper.go new file mode 100644 index 00000000..4a8236ce --- /dev/null +++ b/context_wrapper.go @@ -0,0 +1,91 @@ +package iris + +// ContextPool is a pool of T. +// +// See `NewContextWrapper` and `ContextPool` for more. +type ContextPool[T any] interface { + Acquire(ctx Context) T + Release(T) +} + +// 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) +} + +// 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 + } + } + + return acquire(ctx) +} + +// 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) {} + } + + release(t) +} + +// ContextWrapper is a wrapper for handlers which expect a T instead of iris.Context. +// +// See the `NewContextWrapper` function for more. +type ContextWrapper[T any] struct { + pool ContextPool[T] +} + +// NewContextWrapper returns a new ContextWrapper. +// If pool is nil, a default pool is used. +// 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. +// +// 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) {}, + } + } + + return &ContextWrapper[T]{ + pool: pool, + } +} + +// Handler wraps the handler with the pool's Acquire and Release methods. +// It returns a new handler which expects a T instead of iris.Context. +// The T is the type of the pool. +// 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. +func (w *ContextWrapper[T]) Handler(handler func(T)) Handler { + return func(ctx Context) { + newT := w.pool.Acquire(ctx) + handler(newT) + w.pool.Release(newT) + } +}