mirror of
https://github.com/kataras/iris.git
synced 2026-01-10 05:25:58 +00:00
New Context.SetLogoutFunc/Logout and SetFunc/CallFunc methods
Read HISTORY.md
This commit is contained in:
@@ -5162,6 +5162,97 @@ func (ctx *Context) IsRecovered() (*ErrPanicRecovery, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
const (
|
||||
funcsContextPrefixKey = "iris.funcs."
|
||||
funcLogoutContextKey = "auth.logout_func"
|
||||
)
|
||||
|
||||
// SetFunc registers a custom function to this Request.
|
||||
// It's a helper to pass dynamic functions across handlers of the same chain.
|
||||
// For a more complete solution please use Dependency Injection instead.
|
||||
// This is just an easy to way to pass a function to the
|
||||
// next handler like ctx.Values().Set/Get does.
|
||||
// Sometimes is faster and easier to pass the object as a request value
|
||||
// and cast it when you want to use one of its methods instead of using
|
||||
// these `SetFunc` and `CallFunc` methods.
|
||||
// This implementation is suitable for functions that may change inside the
|
||||
// handler chain and the object holding the method is not predictable.
|
||||
//
|
||||
// The "name" argument is the custom name of the function,
|
||||
// you will use its name to call it later on, e.g. "auth.myfunc".
|
||||
//
|
||||
// The second, "fn" argument is the raw function/method you want
|
||||
// to pass through the next handler(s) of the chain.
|
||||
//
|
||||
// The last variadic input argument is optionally, if set
|
||||
// then its arguments are passing into the function's input arguments,
|
||||
// they should be always be the first ones to be accepted by the "fn" inputs,
|
||||
// e.g. an object, a receiver or a static service.
|
||||
//
|
||||
// See its `CallFunc` to call the "fn" on the next handler.
|
||||
//
|
||||
// Example at:
|
||||
// https://github.com/kataras/iris/tree/master/_examples/routing/writing-a-middleware/share-funcs
|
||||
func (ctx *Context) SetFunc(name string, fn interface{}, persistenceArgs ...interface{}) {
|
||||
f := newFunc(name, fn, persistenceArgs...)
|
||||
ctx.values.Set(funcsContextPrefixKey+name, f)
|
||||
}
|
||||
|
||||
// GetFunc returns the context function declaration which holds
|
||||
// some information about the function registered under the given "name" by
|
||||
// the `SetFunc` method.
|
||||
func (ctx *Context) GetFunc(name string) (*Func, bool) {
|
||||
fn := ctx.values.Get(funcsContextPrefixKey + name)
|
||||
if fn == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return fn.(*Func), true
|
||||
}
|
||||
|
||||
// CallFunc calls the function registered by the `SetFunc`.
|
||||
// The input arguments MUST match the expected ones.
|
||||
//
|
||||
// If the registered function was just a handler
|
||||
// or a handler which returns an error
|
||||
// or a simple function
|
||||
// or a simple function which returns an error
|
||||
// then this operation will perform without any serious cost,
|
||||
// otherwise reflection will be used instead, which may slow down the overall performance.
|
||||
//
|
||||
// Retruns ErrNotFound if the function was not registered.
|
||||
//
|
||||
// For a more complete solution without limiations navigate through
|
||||
// the Iris Dependency Injection feature instead.
|
||||
func (ctx *Context) CallFunc(name string, args ...interface{}) ([]reflect.Value, error) {
|
||||
fn, ok := ctx.GetFunc(name)
|
||||
if !ok || fn == nil {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
return fn.call(ctx, args...)
|
||||
}
|
||||
|
||||
// SetLogoutFunc registers a custom logout function that will be
|
||||
// available to use inside the next handler(s). The function
|
||||
// may be registered multiple times but the last one is the valid.
|
||||
// So a logout function may start with basic authentication
|
||||
// and other middleware in the chain may change it to a custom sessions logout one.
|
||||
// This method uses the `SetFunc` method under the hoods.
|
||||
//
|
||||
// See `Logout` method too.
|
||||
func (ctx *Context) SetLogoutFunc(fn interface{}, persistenceArgs ...interface{}) {
|
||||
ctx.SetFunc(funcLogoutContextKey, fn, persistenceArgs...)
|
||||
}
|
||||
|
||||
// Logout calls the registered logout function.
|
||||
// Returns ErrNotFound if a logout function was not specified
|
||||
// by a prior call of `SetLogoutFunc`.
|
||||
func (ctx *Context) Logout(args ...interface{}) error {
|
||||
_, err := ctx.CallFunc(funcLogoutContextKey, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
const idContextKey = "iris.context.id"
|
||||
|
||||
// SetID sets an ID, any value, to the Request Context.
|
||||
|
||||
203
context/context_func.go
Normal file
203
context/context_func.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ErrInvalidArgs fires when the `Context.CallFunc`
|
||||
// is called with invalid number of arguments.
|
||||
var ErrInvalidArgs = errors.New("invalid arguments")
|
||||
|
||||
// Func represents a function registered by the Context.
|
||||
// See its `buildMeta` and `call` internal methods.
|
||||
type Func struct {
|
||||
RegisterName string // the name of which this function is registered, for information only.
|
||||
Raw interface{} // the Raw function, can be used for custom casting.
|
||||
PersistenceArgs []interface{} // the persistence input arguments given on registration.
|
||||
|
||||
once sync.Once // guards build once, on first call.
|
||||
// Available after the first call.
|
||||
Meta *FuncMeta
|
||||
}
|
||||
|
||||
func newFunc(name string, fn interface{}, persistenceArgs ...interface{}) *Func {
|
||||
return &Func{
|
||||
RegisterName: name,
|
||||
Raw: fn,
|
||||
PersistenceArgs: persistenceArgs,
|
||||
}
|
||||
}
|
||||
|
||||
// FuncMeta holds the necessary information about a registered
|
||||
// context function. Built once by the Func.
|
||||
type FuncMeta struct {
|
||||
Handler Handler // when it's just a handler.
|
||||
HandlerWithErr func(*Context) error // when it's just a handler which returns an error.
|
||||
RawFunc func() // when it's just a func.
|
||||
RawFuncWithErr func() error // when it's just a func which returns an error.
|
||||
RawFuncArgs func(...interface{})
|
||||
RawFuncArgsWithErr func(...interface{}) error
|
||||
|
||||
Value reflect.Value
|
||||
Type reflect.Type
|
||||
ExpectedArgumentsLength int
|
||||
PersistenceInputs []reflect.Value
|
||||
AcceptsContext bool // the Context, if exists should be always first argument.
|
||||
ReturnsError bool // when the function's last output argument is error.
|
||||
}
|
||||
|
||||
func (f *Func) buildMeta() {
|
||||
switch fn := f.Raw.(type) {
|
||||
case Handler:
|
||||
f.Meta = &FuncMeta{Handler: fn}
|
||||
return
|
||||
case func(*Context):
|
||||
f.Meta = &FuncMeta{Handler: fn}
|
||||
return
|
||||
case func(*Context) error:
|
||||
f.Meta = &FuncMeta{HandlerWithErr: fn}
|
||||
return
|
||||
case func():
|
||||
f.Meta = &FuncMeta{RawFunc: fn}
|
||||
return
|
||||
case func() error:
|
||||
f.Meta = &FuncMeta{RawFuncWithErr: fn}
|
||||
return
|
||||
case func(...interface{}):
|
||||
f.Meta = &FuncMeta{RawFuncArgs: fn}
|
||||
return
|
||||
case func(...interface{}) error:
|
||||
f.Meta = &FuncMeta{RawFuncArgsWithErr: fn}
|
||||
return
|
||||
}
|
||||
|
||||
fn := f.Raw
|
||||
|
||||
meta := FuncMeta{}
|
||||
if val, ok := fn.(reflect.Value); ok {
|
||||
meta.Value = val
|
||||
} else {
|
||||
meta.Value = reflect.ValueOf(fn)
|
||||
}
|
||||
|
||||
meta.Type = meta.Value.Type()
|
||||
|
||||
if meta.Type.Kind() != reflect.Func {
|
||||
return
|
||||
}
|
||||
|
||||
meta.ExpectedArgumentsLength = meta.Type.NumIn()
|
||||
|
||||
skipInputs := len(meta.PersistenceInputs)
|
||||
if meta.ExpectedArgumentsLength > skipInputs {
|
||||
meta.AcceptsContext = isContext(meta.Type.In(skipInputs))
|
||||
}
|
||||
|
||||
if numOut := meta.Type.NumOut(); numOut > 0 {
|
||||
// error should be the last output.
|
||||
if isError(meta.Type.Out(numOut - 1)) {
|
||||
meta.ReturnsError = true
|
||||
}
|
||||
}
|
||||
|
||||
persistenceArgs := f.PersistenceArgs
|
||||
if len(persistenceArgs) > 0 {
|
||||
inputs := make([]reflect.Value, 0, len(persistenceArgs))
|
||||
for _, arg := range persistenceArgs {
|
||||
if in, ok := arg.(reflect.Value); ok {
|
||||
inputs = append(inputs, in)
|
||||
} else {
|
||||
inputs = append(inputs, reflect.ValueOf(in))
|
||||
}
|
||||
}
|
||||
|
||||
meta.PersistenceInputs = inputs
|
||||
}
|
||||
|
||||
f.Meta = &meta
|
||||
}
|
||||
|
||||
func (f *Func) call(ctx *Context, args ...interface{}) ([]reflect.Value, error) {
|
||||
f.once.Do(f.buildMeta)
|
||||
meta := f.Meta
|
||||
|
||||
if meta.Handler != nil {
|
||||
meta.Handler(ctx)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if meta.HandlerWithErr != nil {
|
||||
return nil, meta.HandlerWithErr(ctx)
|
||||
}
|
||||
|
||||
if meta.RawFunc != nil {
|
||||
meta.RawFunc()
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if meta.RawFuncWithErr != nil {
|
||||
return nil, meta.RawFuncWithErr()
|
||||
}
|
||||
|
||||
if meta.RawFuncArgs != nil {
|
||||
meta.RawFuncArgs(args...)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if meta.RawFuncArgsWithErr != nil {
|
||||
return nil, meta.RawFuncArgsWithErr(args...)
|
||||
}
|
||||
|
||||
inputs := make([]reflect.Value, 0, f.Meta.ExpectedArgumentsLength)
|
||||
inputs = append(inputs, f.Meta.PersistenceInputs...)
|
||||
if f.Meta.AcceptsContext {
|
||||
inputs = append(inputs, reflect.ValueOf(ctx))
|
||||
}
|
||||
|
||||
for _, arg := range args {
|
||||
if in, ok := arg.(reflect.Value); ok {
|
||||
inputs = append(inputs, in)
|
||||
} else {
|
||||
inputs = append(inputs, reflect.ValueOf(arg))
|
||||
}
|
||||
}
|
||||
|
||||
// keep it here, the inptus may contain the context.
|
||||
if f.Meta.ExpectedArgumentsLength != len(inputs) {
|
||||
return nil, ErrInvalidArgs
|
||||
}
|
||||
|
||||
outputs := f.Meta.Value.Call(inputs)
|
||||
return outputs, getError(outputs)
|
||||
}
|
||||
|
||||
var contextType = reflect.TypeOf((*Context)(nil))
|
||||
|
||||
// isContext returns true if the "typ" is a type of Context.
|
||||
func isContext(typ reflect.Type) bool {
|
||||
return typ == contextType
|
||||
}
|
||||
|
||||
var errTyp = reflect.TypeOf((*error)(nil)).Elem()
|
||||
|
||||
// isError returns true if "typ" is type of `error`.
|
||||
func isError(typ reflect.Type) bool {
|
||||
return typ.Implements(errTyp)
|
||||
}
|
||||
|
||||
func getError(outputs []reflect.Value) error {
|
||||
if n := len(outputs); n > 0 {
|
||||
lastOut := outputs[n-1]
|
||||
if isError(lastOut.Type()) {
|
||||
if lastOut.IsNil() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return lastOut.Interface().(error)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user