mirror of
https://github.com/kataras/iris.git
synced 2025-01-23 10:41:03 +01:00
New Context.SetLogoutFunc/Logout and SetFunc/CallFunc methods
Read HISTORY.md
This commit is contained in:
parent
2d62d49fdb
commit
f6905a3f79
|
@ -28,6 +28,9 @@ The codebase for Dependency Injection, Internationalization and localization and
|
||||||
|
|
||||||
## Fixes and Improvements
|
## Fixes and Improvements
|
||||||
|
|
||||||
|
- A `Context.Logout` method is added, can be used to invalidate [basicauth](https://github.com/kataras/iris/blob/master/_examples/auth/basicauth/main.go) client credentials.
|
||||||
|
- Add the ability to [share functions](https://github.com/kataras/iris/tree/master/_examples/routing/writing-a-middleware/share-funcs) between handlers chain and add an [example](https://github.com/kataras/iris/tree/master/_examples/routing/writing-a-middleware/share-services) on sharing Go structures (aka services).
|
||||||
|
|
||||||
- Add the new `Party.UseOnce` method to the `*Route`
|
- Add the new `Party.UseOnce` method to the `*Route`
|
||||||
- Add a new `*Route.RemoveHandler(interface{}) int`, deletes a handler from begin, main and done handlers based on its name or the handler pc function. Returns the total amount of handlers removed.
|
- Add a new `*Route.RemoveHandler(interface{}) int`, deletes a handler from begin, main and done handlers based on its name or the handler pc function. Returns the total amount of handlers removed.
|
||||||
|
|
||||||
|
@ -306,6 +309,8 @@ var dirOpts = iris.DirOptions{
|
||||||
|
|
||||||
## New Context Methods
|
## New Context Methods
|
||||||
|
|
||||||
|
- `Context.SetLogoutFunc(fn interface{}, persistenceArgs ...interface{})` and `Logout(args ...interface{}) error` methods to allow different kind of auth middlewares to be able to set a "logout" a user/client feature with a single function, the route handler may not be aware of the implementation of the authentication used.
|
||||||
|
- `Context.SetFunc(name string, fn interface{}, persistenceArgs ...interface{})` and `Context.CallFunc(name string, args ...interface{}) ([]reflect.Value, error)` to allow middlewares to share functions dynamically when the type of the function is not predictable, see the [example](https://github.com/kataras/iris/tree/master/_examples/routing/writing-a-middleware/share-funcs) for more.
|
||||||
- `Context.TextYAML(interface{}) error` same as `Context.YAML` but with set the Content-Type to `text/yaml` instead (Google Chrome renders it as text).
|
- `Context.TextYAML(interface{}) error` same as `Context.YAML` but with set the Content-Type to `text/yaml` instead (Google Chrome renders it as text).
|
||||||
- `Context.IsDebug() bool` reports whether the application is running under debug/development mode. It is a shortcut of Application.Logger().Level >= golog.DebugLevel.
|
- `Context.IsDebug() bool` reports whether the application is running under debug/development mode. It is a shortcut of Application.Logger().Level >= golog.DebugLevel.
|
||||||
- `Context.IsRecovered() bool` reports whether the current request was recovered from the [recover middleware](https://github.com/kataras/iris/tree/master/middleware/recover). Also the `iris.IsErrPrivate` function and `iris.ErrPrivate` interface have been introduced.
|
- `Context.IsRecovered() bool` reports whether the current request was recovered from the [recover middleware](https://github.com/kataras/iris/tree/master/middleware/recover). Also the `iris.IsErrPrivate` function and `iris.ErrPrivate` interface have been introduced.
|
||||||
|
|
|
@ -31,6 +31,7 @@ With your help, we can improve Open Source web development for everyone!
|
||||||
> Donations from **China** are now accepted!
|
> Donations from **China** are now accepted!
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
<a href="https://github.com/mizzlespot"><img src="https://avatars1.githubusercontent.com/u/2654538?v=4" alt ="Jasper" title="mizzlespot" with="75" style="width:75px;max-width:75px;height:75px" height="75" /></a>
|
||||||
<a href="https://github.com/wiener01mu"><img src="https://avatars1.githubusercontent.com/u/41128011?v=4" alt ="Simranjit Singh" title="wiener01mu" with="75" style="width:75px;max-width:75px;height:75px" height="75" /></a>
|
<a href="https://github.com/wiener01mu"><img src="https://avatars1.githubusercontent.com/u/41128011?v=4" alt ="Simranjit Singh" title="wiener01mu" with="75" style="width:75px;max-width:75px;height:75px" height="75" /></a>
|
||||||
<a href="https://github.com/theantichris"><img src="https://avatars1.githubusercontent.com/u/1486502?v=4" alt ="Christopher Lamm" title="theantichris" with="75" style="width:75px;max-width:75px;height:75px" height="75" /></a>
|
<a href="https://github.com/theantichris"><img src="https://avatars1.githubusercontent.com/u/1486502?v=4" alt ="Christopher Lamm" title="theantichris" with="75" style="width:75px;max-width:75px;height:75px" height="75" /></a>
|
||||||
<a href="https://github.com/L-M-Sherlock"><img src="https://avatars1.githubusercontent.com/u/32575846?v=4" alt ="叶峻峣" title="L-M-Sherlock" with="75" style="width:75px;max-width:75px;height:75px" height="75" /></a>
|
<a href="https://github.com/L-M-Sherlock"><img src="https://avatars1.githubusercontent.com/u/32575846?v=4" alt ="叶峻峣" title="L-M-Sherlock" with="75" style="width:75px;max-width:75px;height:75px" height="75" /></a>
|
||||||
|
|
|
@ -48,6 +48,9 @@
|
||||||
* Middleware
|
* Middleware
|
||||||
* [Per Route](routing/writing-a-middleware/per-route/main.go)
|
* [Per Route](routing/writing-a-middleware/per-route/main.go)
|
||||||
* [Globally](routing/writing-a-middleware/globally/main.go)
|
* [Globally](routing/writing-a-middleware/globally/main.go)
|
||||||
|
* Share Values
|
||||||
|
* [Share Services](routing/writing-a-middleware/share-services/main.go)
|
||||||
|
* [Share Functions](routing/writing-a-middleware/share-funcs/main.go)
|
||||||
* [Handlers Execution Rule](routing/route-handlers-execution-rules/main.go)
|
* [Handlers Execution Rule](routing/route-handlers-execution-rules/main.go)
|
||||||
* [Route Register Rule](routing/route-register-rule/main.go)
|
* [Route Register Rule](routing/route-register-rule/main.go)
|
||||||
* Convert net/http Handlers
|
* Convert net/http Handlers
|
||||||
|
|
|
@ -37,6 +37,8 @@ func newApp() *iris.Application {
|
||||||
|
|
||||||
// http://localhost:8080/admin/settings
|
// http://localhost:8080/admin/settings
|
||||||
needAuth.Get("/settings", h)
|
needAuth.Get("/settings", h)
|
||||||
|
|
||||||
|
needAuth.Get("/logout", logout)
|
||||||
}
|
}
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
@ -55,3 +57,11 @@ func h(ctx iris.Context) {
|
||||||
|
|
||||||
ctx.Writef("%s %s:%s", ctx.Path(), username, password)
|
ctx.Writef("%s %s:%s", ctx.Path(), username, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func logout(ctx iris.Context) {
|
||||||
|
err := ctx.Logout() // fires 401, invalidates the basic auth.
|
||||||
|
if err != nil {
|
||||||
|
ctx.Application().Logger().Errorf("Logout error: %v", err)
|
||||||
|
}
|
||||||
|
ctx.Redirect("/admin", iris.StatusTemporaryRedirect)
|
||||||
|
}
|
||||||
|
|
109
_examples/routing/writing-a-middleware/share-funcs/main.go
Normal file
109
_examples/routing/writing-a-middleware/share-funcs/main.go
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
// Package main shows how you can share a
|
||||||
|
// function between handlers of the same chain.
|
||||||
|
// Note that, this case is very rarely used and it exists,
|
||||||
|
// mostly, for 3rd-party middleware creators.
|
||||||
|
//
|
||||||
|
// The middleware creator registers a dynamic function by Context.SetFunc and
|
||||||
|
// the route handler just needs to call Context.CallFunc(funcName, arguments),
|
||||||
|
// without knowning what is the specific middleware's implementation or who was the creator
|
||||||
|
// of that function, it may be a basicauth middleware's logout or session's logout.
|
||||||
|
//
|
||||||
|
// See Context.SetLogoutFunc and Context.Logout methods too (these are not covered here).
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/kataras/iris/v12"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := newApp()
|
||||||
|
|
||||||
|
// GET: http://localhost:8080
|
||||||
|
app.Listen(":8080")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newApp() *iris.Application {
|
||||||
|
app := iris.New()
|
||||||
|
app.Use(middleware)
|
||||||
|
// OR app.UseRouter(middleware)
|
||||||
|
// to register it everywhere,
|
||||||
|
// including the HTTP errors.
|
||||||
|
|
||||||
|
app.Get("/", handler)
|
||||||
|
app.Get("/2", middleware2, handler2)
|
||||||
|
app.Get("/3", middleware3, handler3)
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume: this is a middleware which does not export
|
||||||
|
// the 'hello' function for several reasons
|
||||||
|
// but we offer a 'greeting' optional feature to the route handler.
|
||||||
|
func middleware(ctx iris.Context) {
|
||||||
|
ctx.SetFunc("greet", hello)
|
||||||
|
ctx.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume: this is a handler which needs to "greet" the client but
|
||||||
|
// the function for that job is not predictable,
|
||||||
|
// it may change - dynamically (SetFunc) - depending on
|
||||||
|
// the middlewares registered before this route handler.
|
||||||
|
// E.g. it may be a "Hello $name" or "Greetings $Name".
|
||||||
|
func handler(ctx iris.Context) {
|
||||||
|
outputs, err := ctx.CallFunc("greet", "Gophers")
|
||||||
|
if err != nil {
|
||||||
|
ctx.StopWithError(iris.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := outputs[0].Interface().(string)
|
||||||
|
ctx.WriteString(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func middleware2(ctx iris.Context) {
|
||||||
|
ctx.SetFunc("greet", sayHello)
|
||||||
|
ctx.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handler2(ctx iris.Context) {
|
||||||
|
_, err := ctx.CallFunc("greet", "Gophers [2]")
|
||||||
|
if err != nil {
|
||||||
|
ctx.StopWithError(iris.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func middleware3(ctx iris.Context) {
|
||||||
|
ctx.SetFunc("job", function3)
|
||||||
|
ctx.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handler3(ctx iris.Context) {
|
||||||
|
_, err := ctx.CallFunc("job")
|
||||||
|
if err != nil {
|
||||||
|
ctx.StopWithError(iris.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.WriteString("OK, job was executed.\nSee the command prompt.")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
| ------------------------ |
|
||||||
|
| function implementations |
|
||||||
|
| ------------------------ |
|
||||||
|
*/
|
||||||
|
|
||||||
|
func hello(name string) string {
|
||||||
|
return fmt.Sprintf("Hello, %s!", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sayHello(ctx iris.Context, name string) {
|
||||||
|
ctx.WriteString(hello(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func function3() {
|
||||||
|
fmt.Printf("function3 called\n")
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kataras/iris/v12/httptest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShareFuncs(t *testing.T) {
|
||||||
|
app := newApp()
|
||||||
|
e := httptest.New(t, app)
|
||||||
|
|
||||||
|
e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("Hello, Gophers!")
|
||||||
|
e.GET("/2").Expect().Status(httptest.StatusOK).Body().Equal("Hello, Gophers [2]!")
|
||||||
|
e.GET("/3").Expect().Status(httptest.StatusOK).Body().Equal("OK, job was executed.\nSee the command prompt.")
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/kataras/iris/v12"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := newApp()
|
||||||
|
|
||||||
|
// GET: http://localhost:8080
|
||||||
|
app.Listen(":8080")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newApp() *iris.Application {
|
||||||
|
app := iris.New()
|
||||||
|
app.Use(middleware)
|
||||||
|
// OR app.UseRouter(middleware)
|
||||||
|
// to register it everywhere,
|
||||||
|
// including the HTTP errors.
|
||||||
|
|
||||||
|
app.Get("/", handler)
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
func middleware(ctx iris.Context) {
|
||||||
|
service := &helloService{
|
||||||
|
Greeting: "Hello",
|
||||||
|
}
|
||||||
|
setService(ctx, service)
|
||||||
|
|
||||||
|
ctx.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handler(ctx iris.Context) {
|
||||||
|
service, ok := getService(ctx)
|
||||||
|
if !ok {
|
||||||
|
ctx.StopWithStatus(iris.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := service.Greet("Gophers")
|
||||||
|
ctx.WriteString(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
| ---------------------- |
|
||||||
|
| service implementation |
|
||||||
|
| ---------------------- |
|
||||||
|
*/
|
||||||
|
|
||||||
|
const serviceContextKey = "app.service"
|
||||||
|
|
||||||
|
func setService(ctx iris.Context, service GreetService) {
|
||||||
|
ctx.Values().Set(serviceContextKey, service)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getService(ctx iris.Context) (GreetService, bool) {
|
||||||
|
v := ctx.Values().Get(serviceContextKey)
|
||||||
|
if v == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
service, ok := v.(GreetService)
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return service, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// A GreetService example.
|
||||||
|
type GreetService interface {
|
||||||
|
Greet(name string) string
|
||||||
|
}
|
||||||
|
|
||||||
|
type helloService struct {
|
||||||
|
Greeting string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *helloService) Greet(name string) string {
|
||||||
|
return fmt.Sprintf("%s, %s!", m.Greeting, name)
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kataras/iris/v12/httptest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShareServices(t *testing.T) {
|
||||||
|
app := newApp()
|
||||||
|
e := httptest.New(t, app)
|
||||||
|
|
||||||
|
e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("Hello, Gophers!")
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ func main() {
|
||||||
Password: "",
|
Password: "",
|
||||||
Database: "",
|
Database: "",
|
||||||
Prefix: "myapp-",
|
Prefix: "myapp-",
|
||||||
Driver: redis.GoRedis(), // defautls.
|
Driver: redis.GoRedis(), // defaults.
|
||||||
})
|
})
|
||||||
|
|
||||||
// Optionally configure the underline driver:
|
// Optionally configure the underline driver:
|
||||||
|
|
|
@ -5162,6 +5162,97 @@ func (ctx *Context) IsRecovered() (*ErrPanicRecovery, bool) {
|
||||||
return nil, false
|
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"
|
const idContextKey = "iris.context.id"
|
||||||
|
|
||||||
// SetID sets an ID, any value, to the Request Context.
|
// 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
|
||||||
|
}
|
|
@ -21,15 +21,15 @@ type (
|
||||||
HeaderValue string
|
HeaderValue string
|
||||||
Username string
|
Username string
|
||||||
logged bool
|
logged bool
|
||||||
|
forceLogout bool // in order to be able to invalidate and use a redirect response.
|
||||||
expires time.Time
|
expires time.Time
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
encodedUsers []*encodedUser
|
|
||||||
|
|
||||||
basicAuthMiddleware struct {
|
basicAuthMiddleware struct {
|
||||||
config Config
|
config *Config
|
||||||
// these are filled from the config.Users map at the startup
|
// these are filled from the config.Users map at the startup
|
||||||
auth encodedUsers
|
auth []*encodedUser
|
||||||
realmHeaderValue string
|
realmHeaderValue string
|
||||||
|
|
||||||
// The below can be removed but they are here because on the future we may add dynamic options for those two fields,
|
// The below can be removed but they are here because on the future we may add dynamic options for those two fields,
|
||||||
|
@ -54,7 +54,7 @@ func New(c Config) context.Handler {
|
||||||
config.Expires = c.Expires
|
config.Expires = c.Expires
|
||||||
config.OnAsk = c.OnAsk
|
config.OnAsk = c.OnAsk
|
||||||
|
|
||||||
b := &basicAuthMiddleware{config: config}
|
b := &basicAuthMiddleware{config: &config}
|
||||||
b.init()
|
b.init()
|
||||||
return b.Serve
|
return b.Serve
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ func Default(users map[string]string) context.Handler {
|
||||||
|
|
||||||
func (b *basicAuthMiddleware) init() {
|
func (b *basicAuthMiddleware) init() {
|
||||||
// pass the encoded users from the user's config's Users value
|
// pass the encoded users from the user's config's Users value
|
||||||
b.auth = make(encodedUsers, 0, len(b.config.Users))
|
b.auth = make([]*encodedUser, 0, len(b.config.Users))
|
||||||
|
|
||||||
for k, v := range b.config.Users {
|
for k, v := range b.config.Users {
|
||||||
fullUser := k + ":" + v
|
fullUser := k + ":" + v
|
||||||
|
@ -109,12 +109,19 @@ func (b *basicAuthMiddleware) askForCredentials(ctx *context.Context) {
|
||||||
// Serve the actual middleware
|
// Serve the actual middleware
|
||||||
func (b *basicAuthMiddleware) Serve(ctx *context.Context) {
|
func (b *basicAuthMiddleware) Serve(ctx *context.Context) {
|
||||||
auth, found := b.findAuth(ctx.GetHeader("Authorization"))
|
auth, found := b.findAuth(ctx.GetHeader("Authorization"))
|
||||||
if !found {
|
if !found || auth.forceLogout {
|
||||||
|
if auth != nil {
|
||||||
|
auth.mu.Lock()
|
||||||
|
auth.forceLogout = false
|
||||||
|
auth.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
b.askForCredentials(ctx)
|
b.askForCredentials(ctx)
|
||||||
ctx.StopExecution()
|
ctx.StopExecution()
|
||||||
return
|
return
|
||||||
// don't continue to the next handler
|
// don't continue to the next handler
|
||||||
}
|
}
|
||||||
|
|
||||||
// all ok
|
// all ok
|
||||||
if b.expireEnabled {
|
if b.expireEnabled {
|
||||||
if !auth.logged {
|
if !auth.logged {
|
||||||
|
@ -136,5 +143,26 @@ func (b *basicAuthMiddleware) Serve(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !b.config.DisableLogoutFunc {
|
||||||
|
ctx.SetLogoutFunc(b.Logout)
|
||||||
|
}
|
||||||
|
|
||||||
ctx.Next() // continue
|
ctx.Next() // continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logout sends a 401 so the browser/client can invalidate the
|
||||||
|
// Basic Authentication and also sets the underline user's logged field to false,
|
||||||
|
// so its expiration resets when re-ask for credentials.
|
||||||
|
//
|
||||||
|
// End-developers should call the `Context.Logout()` method
|
||||||
|
// to fire this method as this structure is hidden.
|
||||||
|
func (b *basicAuthMiddleware) Logout(ctx *context.Context) {
|
||||||
|
ctx.StatusCode(401)
|
||||||
|
if auth, found := b.findAuth(ctx.GetHeader("Authorization")); found {
|
||||||
|
auth.mu.Lock()
|
||||||
|
auth.logged = false
|
||||||
|
auth.forceLogout = true
|
||||||
|
auth.mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -42,11 +42,14 @@ type Config struct {
|
||||||
//
|
//
|
||||||
// Defaults to nil.
|
// Defaults to nil.
|
||||||
OnAsk context.Handler
|
OnAsk context.Handler
|
||||||
|
|
||||||
|
// DisableLogoutFunc disables the registration of the custom basicauth Context.Logout.
|
||||||
|
DisableLogoutFunc bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultConfig returns the default configs for the BasicAuth middleware
|
// DefaultConfig returns the default configs for the BasicAuth middleware
|
||||||
func DefaultConfig() Config {
|
func DefaultConfig() Config {
|
||||||
return Config{make(map[string]string), DefaultBasicAuthRealm, 0, nil}
|
return Config{make(map[string]string), DefaultBasicAuthRealm, 0, nil, false}
|
||||||
}
|
}
|
||||||
|
|
||||||
// User returns the user from context key same as ctx.Request().BasicAuth().
|
// User returns the user from context key same as ctx.Request().BasicAuth().
|
||||||
|
|
Loading…
Reference in New Issue
Block a user