mirror of
https://github.com/kataras/iris.git
synced 2025-03-14 08:16:28 +01:00
SetImmutable
for sessions and context's values. https://github.com/iris-contrib/community-board/issues/5
New package "memstore" created, read it to see how I made it. Former-commit-id: 9edc344b938786b2ef68defec03c44259a2d539c
This commit is contained in:
parent
5e00c50c37
commit
14e7751d21
|
@ -29,6 +29,10 @@ Thanks to [Santosh Anand](https://github.com/santoshanand) the http://iris-go.co
|
||||||
|
|
||||||
The amount of the next two or three donations you'll send they will be immediately transferred to his own account balance, so be generous please!
|
The amount of the next two or three donations you'll send they will be immediately transferred to his own account balance, so be generous please!
|
||||||
|
|
||||||
|
# Th, 08 June 2017 | v7.0.2
|
||||||
|
|
||||||
|
- Able to set **immutable** data on sessions and context's storage. Aligned to fix an issue on slices and maps as reported [here](https://github.com/iris-contrib/community-board/issues/5).
|
||||||
|
|
||||||
# We, 07 June 2017 | v7.0.1
|
# We, 07 June 2017 | v7.0.1
|
||||||
|
|
||||||
- Proof of concept of an internal release generator, navigate [here](https://github.com/iris-contrib/community-board/issues/2) to read more.
|
- Proof of concept of an internal release generator, navigate [here](https://github.com/iris-contrib/community-board/issues/2) to read more.
|
||||||
|
|
|
@ -6,7 +6,7 @@ A fast, cross-platform and efficient web framework with robust set of well-desig
|
||||||
[](http://goreportcard.com/report/kataras/iris)
|
[](http://goreportcard.com/report/kataras/iris)
|
||||||
[](http://support.iris-go.com)
|
[](http://support.iris-go.com)
|
||||||
[](https://github.com/kataras/iris/tree/master/_examples#table-of-contents)
|
[](https://github.com/kataras/iris/tree/master/_examples#table-of-contents)
|
||||||
[](https://godoc.org/github.com/kataras/iris)
|
[](https://godoc.org/github.com/kataras/iris)
|
||||||
[](https://kataras.rocket.chat/channel/iris)
|
[](https://kataras.rocket.chat/channel/iris)
|
||||||
[](https://github.com/kataras/iris#buy-me-a-cup-of-coffee)
|
[](https://github.com/kataras/iris#buy-me-a-cup-of-coffee)
|
||||||
|
|
||||||
|
@ -394,7 +394,7 @@ Besides the fact that we have a [community chat][Chat] for questions or reports
|
||||||
Version
|
Version
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Current: v7.0.1
|
Current: v7.0.2
|
||||||
|
|
||||||
Each new release is pushed to the master. It stays there until the next version. When a next version is released then the previous version goes to its own branch with `gopkg.in` as its import path (and its own vendor folder), in order to keep it working "for-ever".
|
Each new release is pushed to the master. It stays there until the next version. When a next version is released then the previous version goes to its own branch with `gopkg.in` as its import path (and its own vendor folder), in order to keep it working "for-ever".
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ func main() {
|
||||||
})
|
})
|
||||||
|
|
||||||
app.Get("/u/{firstname:alphabetical}", func(ctx context.Context) {
|
app.Get("/u/{firstname:alphabetical}", func(ctx context.Context) {
|
||||||
ctx.Writef("Hello %s", ctx.Values().GetString("firstname"))
|
ctx.Writef("Hello %s", ctx.Params().Get("firstname"))
|
||||||
})
|
})
|
||||||
|
|
||||||
app.Run(iris.Addr(":8080"))
|
app.Run(iris.Addr(":8080"))
|
||||||
|
|
|
@ -8,6 +8,10 @@ import (
|
||||||
"github.com/kataras/iris/sessions"
|
"github.com/kataras/iris/sessions"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type myobject struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
// enable all (error) logs
|
// enable all (error) logs
|
||||||
|
@ -29,11 +33,6 @@ func main() {
|
||||||
// want to be crazy safe? Take a look at the "securecookie" example folder.
|
// want to be crazy safe? Take a look at the "securecookie" example folder.
|
||||||
})
|
})
|
||||||
|
|
||||||
// OPTIONALLY:
|
|
||||||
// import "github.com/kataras/iris/sessions/sessiondb/redis"
|
|
||||||
// or import "github.com/kataras/go-sessions/sessiondb/$any_available_community_database"
|
|
||||||
// mySessions.UseDatabase(redis.New(...))
|
|
||||||
|
|
||||||
app.AttachSessionManager(mySessions) // Attach the session manager we just created.
|
app.AttachSessionManager(mySessions) // Attach the session manager we just created.
|
||||||
|
|
||||||
app.Get("/", func(ctx context.Context) {
|
app.Get("/", func(ctx context.Context) {
|
||||||
|
@ -41,15 +40,24 @@ func main() {
|
||||||
})
|
})
|
||||||
app.Get("/set", func(ctx context.Context) {
|
app.Get("/set", func(ctx context.Context) {
|
||||||
|
|
||||||
//set session values
|
//set session values.
|
||||||
|
|
||||||
ctx.Session().Set("name", "iris")
|
ctx.Session().Set("name", "iris")
|
||||||
|
|
||||||
//test if setted here
|
//test if setted here
|
||||||
ctx.Writef("All ok session setted to: %s", ctx.Session().GetString("name"))
|
ctx.Writef("All ok session setted to: %s", ctx.Session().GetString("name"))
|
||||||
|
|
||||||
|
// Set will set the value as-it-is,
|
||||||
|
// if it's a slice or map
|
||||||
|
// you will be able to change it on .Get directly!
|
||||||
|
// Keep note that I don't recommend saving big data neither slices or maps on a session
|
||||||
|
// but if you really need it then use the `SetImmutable` instead of `Set`.
|
||||||
|
// Use `SetImmutable` consistently, it's slower.
|
||||||
|
// Read more about muttable and immutable go types: https://stackoverflow.com/a/8021081
|
||||||
})
|
})
|
||||||
|
|
||||||
app.Get("/get", func(ctx context.Context) {
|
app.Get("/get", func(ctx context.Context) {
|
||||||
// get a specific key, as string, if no found returns just an empty string
|
// get a specific value, as string, if no found returns just an empty string
|
||||||
name := ctx.Session().GetString("name")
|
name := ctx.Session().GetString("name")
|
||||||
|
|
||||||
ctx.Writef("The name on the /set was: %s", name)
|
ctx.Writef("The name on the /set was: %s", name)
|
||||||
|
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"github.com/russross/blackfriday"
|
"github.com/russross/blackfriday"
|
||||||
|
|
||||||
"github.com/kataras/iris/core/errors"
|
"github.com/kataras/iris/core/errors"
|
||||||
|
"github.com/kataras/iris/core/memstore"
|
||||||
"github.com/kataras/iris/sessions"
|
"github.com/kataras/iris/sessions"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -73,6 +74,64 @@ func (u UnmarshalerFunc) Unmarshal(data []byte, v interface{}) error {
|
||||||
return u(data, v)
|
return u(data, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequestParams is a key string - value string storage which context's request params should implement.
|
||||||
|
// RequestValues is for communication between middleware, RequestParams cannot be changed, are setted at the routing
|
||||||
|
// time, stores the dynamic named parameters, can be empty if the route is static.
|
||||||
|
type RequestParams struct {
|
||||||
|
store memstore.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set shouldn't be used as a local storage, context's values store
|
||||||
|
// is the local storage, not params.
|
||||||
|
func (r *RequestParams) Set(key, value string) {
|
||||||
|
r.store.Set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visit accepts a visitor which will be filled
|
||||||
|
// by the key-value params.
|
||||||
|
func (r *RequestParams) Visit(visitor func(key string, value interface{})) {
|
||||||
|
r.store.Visit(visitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a path parameter's value based on its route's dynamic path key.
|
||||||
|
func (r RequestParams) Get(key string) string {
|
||||||
|
return r.store.GetString(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt returns the param's value as int, based on its key.
|
||||||
|
func (r RequestParams) GetInt(key string) (int, error) {
|
||||||
|
return r.store.GetInt(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt64 returns the user's value as int64, based on its key.
|
||||||
|
func (r RequestParams) GetInt64(key string) (int64, error) {
|
||||||
|
return r.store.GetInt64(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDecoded returns the url-query-decoded user's value based on its key.
|
||||||
|
func (r RequestParams) GetDecoded(key string) string {
|
||||||
|
return DecodeQuery(DecodeQuery(r.Get(key)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIntUnslashed same as Get but it removes the first slash if found.
|
||||||
|
// Usage: Get an id from a wildcard path.
|
||||||
|
//
|
||||||
|
// Returns -1 with an error if the parameter couldn't be found.
|
||||||
|
func (r RequestParams) GetIntUnslashed(key string) (int, error) {
|
||||||
|
v := r.Get(key)
|
||||||
|
if v != "" {
|
||||||
|
if len(v) > 1 {
|
||||||
|
if v[0] == '/' {
|
||||||
|
v = v[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strconv.Atoi(v)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1, memstore.ErrIntParse.Format(v)
|
||||||
|
}
|
||||||
|
|
||||||
// Context is the midle-man server's "object" for the clients.
|
// Context is the midle-man server's "object" for the clients.
|
||||||
//
|
//
|
||||||
// A New context is being acquired from a sync.Pool on each connection.
|
// A New context is being acquired from a sync.Pool on each connection.
|
||||||
|
@ -163,9 +222,7 @@ type Context interface {
|
||||||
// Params returns the current url's named parameters key-value storage.
|
// Params returns the current url's named parameters key-value storage.
|
||||||
// Named path parameters are being saved here.
|
// Named path parameters are being saved here.
|
||||||
// This storage, as the whole Context, is per-request lifetime.
|
// This storage, as the whole Context, is per-request lifetime.
|
||||||
Params() RequestParams
|
Params() *RequestParams
|
||||||
// SetParams sets/or/overrides the request's url named parameters key-value storage.
|
|
||||||
SetParams(RequestParams)
|
|
||||||
|
|
||||||
// Values returns the current "user" storage.
|
// Values returns the current "user" storage.
|
||||||
// Named path parameters and any optional data can be saved here.
|
// Named path parameters and any optional data can be saved here.
|
||||||
|
@ -173,7 +230,7 @@ type Context interface {
|
||||||
//
|
//
|
||||||
// You can use this function to Set and Get local values
|
// You can use this function to Set and Get local values
|
||||||
// that can be used to share information between handlers and middleware.
|
// that can be used to share information between handlers and middleware.
|
||||||
Values() *RequestValues
|
Values() *memstore.Store
|
||||||
// Translate is the i18n (localization) middleware's function,
|
// Translate is the i18n (localization) middleware's function,
|
||||||
// it calls the Get("translate") to return the translated value.
|
// it calls the Get("translate") to return the translated value.
|
||||||
//
|
//
|
||||||
|
@ -684,8 +741,8 @@ type context struct {
|
||||||
// the original http.Request
|
// the original http.Request
|
||||||
request *http.Request
|
request *http.Request
|
||||||
// the local key-value storage
|
// the local key-value storage
|
||||||
params RequestParams // url named parameters
|
params RequestParams // url named parameters
|
||||||
values RequestValues // generic storage, middleware communication
|
values memstore.Store // generic storage, middleware communication
|
||||||
|
|
||||||
// the underline application framework
|
// the underline application framework
|
||||||
framework Application
|
framework Application
|
||||||
|
@ -720,7 +777,7 @@ func (ctx *context) BeginRequest(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx.handlers = nil // will be filled by router.Serve/HTTP
|
ctx.handlers = nil // will be filled by router.Serve/HTTP
|
||||||
ctx.session = nil // >> >> by sessions.Session()
|
ctx.session = nil // >> >> by sessions.Session()
|
||||||
ctx.values = ctx.values[0:0] // >> >> by context.Values().Set
|
ctx.values = ctx.values[0:0] // >> >> by context.Values().Set
|
||||||
ctx.params = ctx.params[0:0]
|
ctx.params.store = ctx.params.store[0:0]
|
||||||
ctx.request = r
|
ctx.request = r
|
||||||
ctx.currentHandlerIndex = 0
|
ctx.currentHandlerIndex = 0
|
||||||
ctx.writer = AcquireResponseWriter()
|
ctx.writer = AcquireResponseWriter()
|
||||||
|
@ -861,13 +918,8 @@ func (ctx *context) IsStopped() bool {
|
||||||
// Params returns the current url's named parameters key-value storage.
|
// Params returns the current url's named parameters key-value storage.
|
||||||
// Named path parameters are being saved here.
|
// Named path parameters are being saved here.
|
||||||
// This storage, as the whole context, is per-request lifetime.
|
// This storage, as the whole context, is per-request lifetime.
|
||||||
func (ctx *context) Params() RequestParams {
|
func (ctx *context) Params() *RequestParams {
|
||||||
return ctx.params
|
return &ctx.params
|
||||||
}
|
|
||||||
|
|
||||||
// SetParams sets/or/overrides the request's url named parameters key-value storage.
|
|
||||||
func (ctx *context) SetParams(params RequestParams) {
|
|
||||||
ctx.params = params
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Values returns the current "user" storage.
|
// Values returns the current "user" storage.
|
||||||
|
@ -876,7 +928,7 @@ func (ctx *context) SetParams(params RequestParams) {
|
||||||
//
|
//
|
||||||
// You can use this function to Set and Get local values
|
// You can use this function to Set and Get local values
|
||||||
// that can be used to share information between handlers and middleware.
|
// that can be used to share information between handlers and middleware.
|
||||||
func (ctx *context) Values() *RequestValues {
|
func (ctx *context) Values() *memstore.Store {
|
||||||
return &ctx.values
|
return &ctx.values
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,193 +0,0 @@
|
||||||
package context
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/kataras/iris/core/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
// RequestValue is the entry of the context storage RequestValues - .Values()
|
|
||||||
RequestValue struct {
|
|
||||||
key string
|
|
||||||
value interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestValues is just a key-value storage which context's request values should implement.
|
|
||||||
RequestValues []RequestValue
|
|
||||||
)
|
|
||||||
|
|
||||||
// RequestValuesReadOnly the request values with read-only access.
|
|
||||||
type RequestValuesReadOnly struct {
|
|
||||||
RequestValues
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set does nothing.
|
|
||||||
func (r RequestValuesReadOnly) Set(string, interface{}) {}
|
|
||||||
|
|
||||||
// Set sets a value to the key-value context storage, can be familiar as "User Values".
|
|
||||||
func (r *RequestValues) Set(key string, value interface{}) {
|
|
||||||
args := *r
|
|
||||||
n := len(args)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
kv := &args[i]
|
|
||||||
if kv.key == key {
|
|
||||||
kv.value = value
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c := cap(args)
|
|
||||||
if c > n {
|
|
||||||
args = args[:n+1]
|
|
||||||
kv := &args[n]
|
|
||||||
kv.key = key
|
|
||||||
kv.value = value
|
|
||||||
*r = args
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
kv := RequestValue{}
|
|
||||||
kv.key = key
|
|
||||||
kv.value = value
|
|
||||||
*r = append(args, kv)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the user's value based on its key.
|
|
||||||
func (r *RequestValues) Get(key string) interface{} {
|
|
||||||
args := *r
|
|
||||||
n := len(args)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
kv := &args[i]
|
|
||||||
if kv.key == key {
|
|
||||||
return kv.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Visit accepts a visitor which will be filled
|
|
||||||
// by the key-value objects, the caller should not try to change the underline values.
|
|
||||||
func (r *RequestValues) Visit(visitor func(key string, value interface{})) {
|
|
||||||
args := *r
|
|
||||||
for i, n := 0, len(args); i < n; i++ {
|
|
||||||
visitor(args[i].key, args[i].value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetString returns the user's value as string, based on its key.
|
|
||||||
func (r *RequestValues) GetString(key string) string {
|
|
||||||
if v, ok := r.Get(key).(string); ok {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var errIntParse = errors.New("unable to find or parse the integer, found: %#v")
|
|
||||||
|
|
||||||
// GetInt returns the user's value as int, based on its key.
|
|
||||||
func (r *RequestValues) GetInt(key string) (int, error) {
|
|
||||||
v := r.Get(key)
|
|
||||||
if vint, ok := v.(int); ok {
|
|
||||||
return vint, nil
|
|
||||||
} else if vstring, sok := v.(string); sok {
|
|
||||||
return strconv.Atoi(vstring)
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1, errIntParse.Format(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInt64 returns the user's value as int64, based on its key.
|
|
||||||
func (r *RequestValues) GetInt64(key string) (int64, error) {
|
|
||||||
return strconv.ParseInt(r.GetString(key), 10, 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset clears all the request values.
|
|
||||||
func (r *RequestValues) Reset() {
|
|
||||||
*r = (*r)[0:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadOnly returns a new request values with read-only access.
|
|
||||||
func (r *RequestValues) ReadOnly() RequestValuesReadOnly {
|
|
||||||
args := *r
|
|
||||||
values := make(RequestValues, len(args))
|
|
||||||
copy(values, args)
|
|
||||||
return RequestValuesReadOnly{values}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the full length of the values.
|
|
||||||
func (r *RequestValues) Len() int {
|
|
||||||
args := *r
|
|
||||||
return len(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestParam is the entry of RequestParams, request's url named parameters are storaged here.
|
|
||||||
type RequestParam struct {
|
|
||||||
Key string
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestParams is a key string - value string storage which context's request params should implement.
|
|
||||||
// RequestValues is for communication between middleware, RequestParams cannot be changed, are setted at the routing
|
|
||||||
// time, stores the dynamic named parameters, can be empty if the route is static.
|
|
||||||
type RequestParams []RequestParam
|
|
||||||
|
|
||||||
// Get returns the param's value based on its key.
|
|
||||||
func (r RequestParams) Get(key string) string {
|
|
||||||
for _, p := range r {
|
|
||||||
if p.Key == key {
|
|
||||||
return p.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Visit accepts a visitor which will be filled
|
|
||||||
// by the key and value.
|
|
||||||
// The caller should not try to change the underline values.
|
|
||||||
func (r RequestParams) Visit(visitor func(key string, value string)) {
|
|
||||||
for i, n := 0, len(r); i < n; i++ {
|
|
||||||
visitor(r[i].Key, r[i].Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInt returns the user's value as int, based on its key.
|
|
||||||
func (r RequestParams) GetInt(key string) (int, error) {
|
|
||||||
v := r.Get(key)
|
|
||||||
return strconv.Atoi(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInt64 returns the user's value as int64, based on its key.
|
|
||||||
func (r RequestParams) GetInt64(key string) (int64, error) {
|
|
||||||
return strconv.ParseInt(r.Get(key), 10, 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDecoded returns the url-query-decoded user's value based on its key.
|
|
||||||
func (r RequestParams) GetDecoded(key string) string {
|
|
||||||
return DecodeQuery(DecodeQuery(r.Get(key)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIntUnslashed same as Get but it removes the first slash if found.
|
|
||||||
// Usage: Get an id from a wildcard path.
|
|
||||||
//
|
|
||||||
// Returns -1 with an error if the parameter couldn't be found.
|
|
||||||
func (r RequestParams) GetIntUnslashed(key string) (int, error) {
|
|
||||||
v := r.Get(key)
|
|
||||||
if v != "" {
|
|
||||||
if len(v) > 1 {
|
|
||||||
if v[0] == '/' {
|
|
||||||
v = v[1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strconv.Atoi(v)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1, errIntParse.Format(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the full length of the params.
|
|
||||||
func (r RequestParams) Len() int {
|
|
||||||
return len(r)
|
|
||||||
}
|
|
210
core/memstore/memstore.go
Normal file
210
core/memstore/memstore.go
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
// Package memstore contains a store which is just
|
||||||
|
// a collection of key-value entries with immutability capabilities.
|
||||||
|
//
|
||||||
|
// Created after that proposal: https://github.com/iris-contrib/community-board/issues/5
|
||||||
|
// and it's being used by session entries (session lifetime) and context's entries (per-request).
|
||||||
|
//
|
||||||
|
// Developers can use that storage to their own apps if they like its behavior.
|
||||||
|
// It's fast and in the same time you get read-only access (safety) when you need it.
|
||||||
|
package memstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/kataras/iris/core/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Entry is the entry of the context storage Store - .Values()
|
||||||
|
Entry struct {
|
||||||
|
Key string
|
||||||
|
value interface{}
|
||||||
|
immutable bool // if true then it can't change by its caller.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store is a collection of key-value entries with immutability capabilities.
|
||||||
|
Store []Entry
|
||||||
|
)
|
||||||
|
|
||||||
|
// Value returns the value of the entry,
|
||||||
|
// respects the immutable.
|
||||||
|
func (e Entry) Value() interface{} {
|
||||||
|
if e.immutable {
|
||||||
|
// take its value, no pointer even if setted with a rreference.
|
||||||
|
vv := reflect.Indirect(reflect.ValueOf(e.value))
|
||||||
|
|
||||||
|
// return copy of that slice
|
||||||
|
if vv.Type().Kind() == reflect.Slice {
|
||||||
|
newSlice := reflect.MakeSlice(vv.Type(), vv.Len(), vv.Cap())
|
||||||
|
reflect.Copy(newSlice, vv)
|
||||||
|
return newSlice.Interface()
|
||||||
|
}
|
||||||
|
// return a copy of that map
|
||||||
|
if vv.Type().Kind() == reflect.Map {
|
||||||
|
newMap := reflect.MakeMap(vv.Type())
|
||||||
|
for _, k := range vv.MapKeys() {
|
||||||
|
newMap.SetMapIndex(k, vv.MapIndex(k))
|
||||||
|
}
|
||||||
|
return newMap.Interface()
|
||||||
|
}
|
||||||
|
// if was *value it will return value{}.
|
||||||
|
return vv.Interface()
|
||||||
|
}
|
||||||
|
return e.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// the id is immutable(true or false)+key
|
||||||
|
// so the users will be able to use the same key
|
||||||
|
// to store two different entries (one immutable and other mutable).
|
||||||
|
// or no? better no, that will confuse and maybe result on unexpected results.
|
||||||
|
// I will just replace the value and the immutable bool value when Set if
|
||||||
|
// a key is already exists.
|
||||||
|
// func (e Entry) identifier() string {}
|
||||||
|
|
||||||
|
func (r *Store) save(key string, value interface{}, immutable bool) {
|
||||||
|
args := *r
|
||||||
|
n := len(args)
|
||||||
|
|
||||||
|
// replace if we can, else just return
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
kv := &args[i]
|
||||||
|
if kv.Key == key {
|
||||||
|
if immutable && kv.immutable {
|
||||||
|
// if called by `SetImmutable`
|
||||||
|
// then allow the update, maybe it's a slice that user wants to update by SetImmutable method,
|
||||||
|
// we should allow this
|
||||||
|
kv.value = value
|
||||||
|
kv.immutable = immutable
|
||||||
|
} else if kv.immutable == false {
|
||||||
|
// if it was not immutable then user can alt it via `Set` and `SetImmutable`
|
||||||
|
kv.value = value
|
||||||
|
kv.immutable = immutable
|
||||||
|
}
|
||||||
|
// else it was immutable and called by `Set` then disallow the update
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// expand slice to add it
|
||||||
|
c := cap(args)
|
||||||
|
if c > n {
|
||||||
|
args = args[:n+1]
|
||||||
|
kv := &args[n]
|
||||||
|
kv.Key = key
|
||||||
|
kv.value = value
|
||||||
|
kv.immutable = immutable
|
||||||
|
*r = args
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// add
|
||||||
|
kv := Entry{
|
||||||
|
Key: key,
|
||||||
|
value: value,
|
||||||
|
immutable: immutable,
|
||||||
|
}
|
||||||
|
*r = append(args, kv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set saves a value to the key-value storage.
|
||||||
|
// See `SetImmutable` and `Get`.
|
||||||
|
func (r *Store) Set(key string, value interface{}) {
|
||||||
|
r.save(key, value, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetImmutable saves a value to the key-value storage.
|
||||||
|
// Unlike `Set`, the output value cannot be changed by the caller later on (when .Get OR .Set)
|
||||||
|
//
|
||||||
|
// An Immutable entry should be only changed with a `SetImmutable`, simple `Set` will not work
|
||||||
|
// if the entry was immutable, for your own safety.
|
||||||
|
//
|
||||||
|
// Use it consistently, it's far slower than `Set`.
|
||||||
|
// Read more about muttable and immutable go types: https://stackoverflow.com/a/8021081
|
||||||
|
func (r *Store) SetImmutable(key string, value interface{}) {
|
||||||
|
r.save(key, value, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the entry's value based on its key.
|
||||||
|
func (r *Store) Get(key string) interface{} {
|
||||||
|
args := *r
|
||||||
|
n := len(args)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
kv := &args[i]
|
||||||
|
if kv.Key == key {
|
||||||
|
return kv.Value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visit accepts a visitor which will be filled
|
||||||
|
// by the key-value objects.
|
||||||
|
func (r *Store) Visit(visitor func(key string, value interface{})) {
|
||||||
|
args := *r
|
||||||
|
for i, n := 0, len(args); i < n; i++ {
|
||||||
|
kv := args[i]
|
||||||
|
visitor(kv.Key, kv.Value())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetString returns the entry's value as string, based on its key.
|
||||||
|
func (r *Store) GetString(key string) string {
|
||||||
|
if v, ok := r.Get(key).(string); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrIntParse returns an error message when int parse failed
|
||||||
|
// it's not statical error, it depends on the failed value.
|
||||||
|
var ErrIntParse = errors.New("unable to find or parse the integer, found: %#v")
|
||||||
|
|
||||||
|
// GetInt returns the entry's value as int, based on its key.
|
||||||
|
func (r *Store) GetInt(key string) (int, error) {
|
||||||
|
v := r.Get(key)
|
||||||
|
if vint, ok := v.(int); ok {
|
||||||
|
return vint, nil
|
||||||
|
} else if vstring, sok := v.(string); sok {
|
||||||
|
return strconv.Atoi(vstring)
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1, ErrIntParse.Format(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt64 returns the entry's value as int64, based on its key.
|
||||||
|
func (r *Store) GetInt64(key string) (int64, error) {
|
||||||
|
return strconv.ParseInt(r.GetString(key), 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove deletes an entry linked to that "key",
|
||||||
|
// returns true if an entry is actually removed.
|
||||||
|
func (r *Store) Remove(key string) bool {
|
||||||
|
args := *r
|
||||||
|
n := len(args)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
kv := &args[i]
|
||||||
|
if kv.Key == key {
|
||||||
|
// we found the index,
|
||||||
|
// let's remove the item by appending to the temp and
|
||||||
|
// after set the pointer of the slice to this temp args
|
||||||
|
args = append(args[:i], args[i+1:]...)
|
||||||
|
*r = args
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset clears all the request entries.
|
||||||
|
func (r *Store) Reset() {
|
||||||
|
*r = (*r)[0:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the full length of the entries.
|
||||||
|
func (r *Store) Len() int {
|
||||||
|
args := *r
|
||||||
|
return len(args)
|
||||||
|
}
|
93
core/memstore/memstore_test.go
Normal file
93
core/memstore/memstore_test.go
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
package memstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type myTestObject struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMuttable(t *testing.T) {
|
||||||
|
var p Store
|
||||||
|
|
||||||
|
// slice
|
||||||
|
p.Set("slice", []myTestObject{{"value 1"}, {"value 2"}})
|
||||||
|
v := p.Get("slice").([]myTestObject)
|
||||||
|
v[0].name = "modified"
|
||||||
|
|
||||||
|
vv := p.Get("slice").([]myTestObject)
|
||||||
|
if vv[0].name != "modified" {
|
||||||
|
t.Fatalf("expected slice to be muttable but caller was not able to change its value")
|
||||||
|
}
|
||||||
|
|
||||||
|
// map
|
||||||
|
|
||||||
|
p.Set("map", map[string]myTestObject{"key 1": myTestObject{"value 1"}, "key 2": myTestObject{"value 2"}})
|
||||||
|
vMap := p.Get("map").(map[string]myTestObject)
|
||||||
|
vMap["key 1"] = myTestObject{"modified"}
|
||||||
|
|
||||||
|
vvMap := p.Get("map").(map[string]myTestObject)
|
||||||
|
if vvMap["key 1"].name != "modified" {
|
||||||
|
t.Fatalf("expected map to be muttable but caller was not able to change its value")
|
||||||
|
}
|
||||||
|
|
||||||
|
// object pointer of a value, it can change like maps or slices and arrays.
|
||||||
|
p.Set("objp", &myTestObject{"value"})
|
||||||
|
// we expect pointer here, as we set it.
|
||||||
|
vObjP := p.Get("objp").(*myTestObject)
|
||||||
|
|
||||||
|
vObjP.name = "modified"
|
||||||
|
|
||||||
|
vvObjP := p.Get("objp").(*myTestObject)
|
||||||
|
if vvObjP.name != "modified" {
|
||||||
|
t.Fatalf("expected objp to be muttable but caller was able to change its value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImmutable(t *testing.T) {
|
||||||
|
var p Store
|
||||||
|
|
||||||
|
// slice
|
||||||
|
p.SetImmutable("slice", []myTestObject{{"value 1"}, {"value 2"}})
|
||||||
|
v := p.Get("slice").([]myTestObject)
|
||||||
|
v[0].name = "modified"
|
||||||
|
|
||||||
|
vv := p.Get("slice").([]myTestObject)
|
||||||
|
if vv[0].name == "modified" {
|
||||||
|
t.Fatalf("expected slice to be immutable but caller was able to change its value")
|
||||||
|
}
|
||||||
|
|
||||||
|
// map
|
||||||
|
p.SetImmutable("map", map[string]myTestObject{"key 1": myTestObject{"value 1"}, "key 2": myTestObject{"value 2"}})
|
||||||
|
vMap := p.Get("map").(map[string]myTestObject)
|
||||||
|
vMap["key 1"] = myTestObject{"modified"}
|
||||||
|
|
||||||
|
vvMap := p.Get("map").(map[string]myTestObject)
|
||||||
|
if vvMap["key 1"].name == "modified" {
|
||||||
|
t.Fatalf("expected map to be immutable but caller was able to change its value")
|
||||||
|
}
|
||||||
|
|
||||||
|
// object value, it's immutable at all cases.
|
||||||
|
p.SetImmutable("obj", myTestObject{"value"})
|
||||||
|
vObj := p.Get("obj").(myTestObject)
|
||||||
|
vObj.name = "modified"
|
||||||
|
|
||||||
|
vvObj := p.Get("obj").(myTestObject)
|
||||||
|
if vvObj.name == "modified" {
|
||||||
|
t.Fatalf("expected obj to be immutable but caller was able to change its value")
|
||||||
|
}
|
||||||
|
|
||||||
|
// object pointer of a value, it's immutable at all cases.
|
||||||
|
p.SetImmutable("objp", &myTestObject{"value"})
|
||||||
|
// we expect no pointer here if SetImmutable.
|
||||||
|
// so it can't be changed by-design
|
||||||
|
vObjP := p.Get("objp").(myTestObject)
|
||||||
|
|
||||||
|
vObjP.name = "modified"
|
||||||
|
|
||||||
|
vvObjP := p.Get("objp").(myTestObject)
|
||||||
|
if vvObjP.name == "modified" {
|
||||||
|
t.Fatalf("expected objp to be immutable but caller was able to change its value")
|
||||||
|
}
|
||||||
|
}
|
|
@ -173,9 +173,8 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handlers, params, mustRedirect := t.Entry.ResolveRoute(ctx)
|
handlers, mustRedirect := t.Entry.ResolveRoute(ctx)
|
||||||
if len(handlers) > 0 {
|
if len(handlers) > 0 {
|
||||||
ctx.SetParams(params)
|
|
||||||
ctx.SetHandlers(handlers)
|
ctx.SetHandlers(handlers)
|
||||||
ctx.Handlers()[0](ctx)
|
ctx.Handlers()[0](ctx)
|
||||||
// to remove the .Next(maybe not a good idea), reduces the performance a bit:
|
// to remove the .Next(maybe not a good idea), reduces the performance a bit:
|
||||||
|
|
|
@ -329,11 +329,9 @@ func (n *Node) insertChild(numParams uint8, path, fullPath string, handle contex
|
||||||
// given context.
|
// given context.
|
||||||
//
|
//
|
||||||
// ResolveRoute finds the correct registered route from the Node when the ctx.Handlers() > 0.
|
// ResolveRoute finds the correct registered route from the Node when the ctx.Handlers() > 0.
|
||||||
func (n *Node) ResolveRoute(ctx context.Context) (handlers context.Handlers, p context.RequestParams, tsr bool) { //(p context.RequestParams, tsr bool) {
|
func (n *Node) ResolveRoute(ctx context.Context) (handlers context.Handlers, tsr bool) { //(p context.RequestParams, tsr bool) {
|
||||||
path := ctx.Request().URL.Path
|
path := ctx.Request().URL.Path
|
||||||
handlers = ctx.Handlers()
|
handlers = ctx.Handlers()
|
||||||
// values := ctx.Values()
|
|
||||||
p = ctx.Params()
|
|
||||||
walk: // outer loop for walking the tree
|
walk: // outer loop for walking the tree
|
||||||
for {
|
for {
|
||||||
if len(path) > len(n.path) {
|
if len(path) > len(n.path) {
|
||||||
|
@ -370,14 +368,7 @@ walk: // outer loop for walking the tree
|
||||||
}
|
}
|
||||||
|
|
||||||
// save param value
|
// save param value
|
||||||
if cap(p) < int(n.maxParams) {
|
ctx.Params().Set(n.path[1:], path[:end])
|
||||||
p = make(context.RequestParams, 0, n.maxParams)
|
|
||||||
}
|
|
||||||
i := len(p)
|
|
||||||
p = p[:i+1] // expand
|
|
||||||
p[i].Key = n.path[1:]
|
|
||||||
p[i].Value = path[:end]
|
|
||||||
|
|
||||||
// we need to go deeper!
|
// we need to go deeper!
|
||||||
if end < len(path) {
|
if end < len(path) {
|
||||||
if len(n.children) > 0 {
|
if len(n.children) > 0 {
|
||||||
|
@ -403,15 +394,7 @@ walk: // outer loop for walking the tree
|
||||||
return
|
return
|
||||||
|
|
||||||
case catchAll:
|
case catchAll:
|
||||||
// save param value
|
ctx.Params().Set(n.path[2:], path)
|
||||||
if cap(p) < int(n.maxParams) {
|
|
||||||
p = make(context.RequestParams, 0, n.maxParams)
|
|
||||||
}
|
|
||||||
i := len(p)
|
|
||||||
p = p[:i+1] // expand
|
|
||||||
|
|
||||||
p[i].Key = n.path[2:]
|
|
||||||
p[i].Value = path
|
|
||||||
handlers = n.handle
|
handlers = n.handle
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
2
iris.go
2
iris.go
|
@ -40,7 +40,7 @@ const (
|
||||||
// Version is the current version number of the Iris Web framework.
|
// Version is the current version number of the Iris Web framework.
|
||||||
//
|
//
|
||||||
// Look https://github.com/kataras/iris#where-can-i-find-older-versions for older versions.
|
// Look https://github.com/kataras/iris#where-can-i-find-older-versions for older versions.
|
||||||
Version = "7.0.1"
|
Version = "7.0.2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -7,6 +7,8 @@ package sessions
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/kataras/iris/core/memstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -44,7 +46,7 @@ func (p *provider) newSession(sid string, expires time.Duration) *session {
|
||||||
sess := &session{
|
sess := &session{
|
||||||
sid: sid,
|
sid: sid,
|
||||||
provider: p,
|
provider: p,
|
||||||
values: p.loadSessionValues(sid),
|
values: p.loadSessionValuesFromDB(sid),
|
||||||
flashes: make(map[string]*flashMessage),
|
flashes: make(map[string]*flashMessage),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,20 +62,32 @@ func (p *provider) newSession(sid string, expires time.Duration) *session {
|
||||||
return sess
|
return sess
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *provider) loadSessionValues(sid string) map[string]interface{} {
|
// can return nil
|
||||||
|
func (p *provider) loadSessionValuesFromDB(sid string) memstore.Store {
|
||||||
|
var store memstore.Store
|
||||||
|
|
||||||
for i, n := 0, len(p.databases); i < n; i++ {
|
for i, n := 0, len(p.databases); i < n; i++ {
|
||||||
if dbValues := p.databases[i].Load(sid); dbValues != nil && len(dbValues) > 0 {
|
if dbValues := p.databases[i].Load(sid); dbValues != nil && len(dbValues) > 0 {
|
||||||
return dbValues // return the first non-empty from the registered stores.
|
for k, v := range dbValues {
|
||||||
|
store.Set(k, v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
values := make(map[string]interface{})
|
return store
|
||||||
return values
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *provider) updateDatabases(sid string, newValues map[string]interface{}) {
|
func (p *provider) updateDatabases(sid string, store memstore.Store) {
|
||||||
for i, n := 0, len(p.databases); i < n; i++ {
|
|
||||||
p.databases[i].Update(sid, newValues)
|
if l := store.Len(); l > 0 {
|
||||||
|
mapValues := make(map[string]interface{}, l)
|
||||||
|
|
||||||
|
store.Visit(func(k string, v interface{}) {
|
||||||
|
mapValues[k] = v
|
||||||
|
})
|
||||||
|
|
||||||
|
for i, n := 0, len(p.databases); i < n; i++ {
|
||||||
|
p.databases[i].Update(sid, mapValues)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kataras/iris/core/errors"
|
"github.com/kataras/iris/core/errors"
|
||||||
|
"github.com/kataras/iris/core/memstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -18,7 +19,7 @@ type (
|
||||||
// implements the context.Session interface
|
// implements the context.Session interface
|
||||||
session struct {
|
session struct {
|
||||||
sid string
|
sid string
|
||||||
values map[string]interface{} // here are the real values
|
values memstore.Store // here are the real values
|
||||||
// we could set the flash messages inside values but this will bring us more problems
|
// we could set the flash messages inside values but this will bring us more problems
|
||||||
// because of session databases and because of
|
// because of session databases and because of
|
||||||
// users may want to get all sessions and save them or display them
|
// users may want to get all sessions and save them or display them
|
||||||
|
@ -45,10 +46,10 @@ func (s *session) ID() string {
|
||||||
return s.sid
|
return s.sid
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the value of an entry by its key
|
// Get returns a value based on its "key".
|
||||||
func (s *session) Get(key string) interface{} {
|
func (s *session) Get(key string) interface{} {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
value := s.values[key]
|
value := s.values.Get(key)
|
||||||
s.mu.RUnlock()
|
s.mu.RUnlock()
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
@ -215,8 +216,8 @@ func (s *session) GetBoolean(key string) (bool, error) {
|
||||||
func (s *session) GetAll() map[string]interface{} {
|
func (s *session) GetAll() map[string]interface{} {
|
||||||
items := make(map[string]interface{}, len(s.values))
|
items := make(map[string]interface{}, len(s.values))
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
for key, v := range s.values {
|
for _, kv := range s.values {
|
||||||
items[key] = v
|
items[kv.Key] = kv.Value()
|
||||||
}
|
}
|
||||||
s.mu.RUnlock()
|
s.mu.RUnlock()
|
||||||
return items
|
return items
|
||||||
|
@ -237,21 +238,36 @@ func (s *session) GetFlashes() map[string]interface{} {
|
||||||
|
|
||||||
// VisitAll loop each one entry and calls the callback function func(key,value)
|
// VisitAll loop each one entry and calls the callback function func(key,value)
|
||||||
func (s *session) VisitAll(cb func(k string, v interface{})) {
|
func (s *session) VisitAll(cb func(k string, v interface{})) {
|
||||||
for key := range s.values {
|
s.values.Visit(cb)
|
||||||
cb(key, s.values[key])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set fills the session with an entry, it receives a key and a value
|
func (s *session) set(key string, value interface{}, immutable bool) {
|
||||||
// returns an error, which is always nil
|
|
||||||
func (s *session) Set(key string, value interface{}) {
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.values[key] = value
|
if immutable {
|
||||||
|
s.values.Set(key, value)
|
||||||
|
} else {
|
||||||
|
s.values.SetImmutable(key, value)
|
||||||
|
}
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
||||||
s.updateDatabases()
|
s.updateDatabases()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set fills the session with an entry"value", based on its "key".
|
||||||
|
func (s *session) Set(key string, value interface{}) {
|
||||||
|
s.set(key, value, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetImmutable fills the session with an entry "value", based on its "key".
|
||||||
|
// Unlike `Set`, the output value cannot be changed by the caller later on (when .Get)
|
||||||
|
// An Immutable entry should be only changed with a `SetImmutable`, simple `Set` will not work
|
||||||
|
// if the entry was immutable, for your own safety.
|
||||||
|
// Use it consistently, it's far slower than `Set`.
|
||||||
|
// Read more about muttable and immutable go types: https://stackoverflow.com/a/8021081
|
||||||
|
func (s *session) SetImmutable(key string, value interface{}) {
|
||||||
|
s.set(key, value, true)
|
||||||
|
}
|
||||||
|
|
||||||
// SetFlash sets a flash message by its key.
|
// SetFlash sets a flash message by its key.
|
||||||
//
|
//
|
||||||
// A flash message is used in order to keep a message in session through one or several requests of the same user.
|
// A flash message is used in order to keep a message in session through one or several requests of the same user.
|
||||||
|
@ -278,13 +294,15 @@ func (s *session) SetFlash(key string, value interface{}) {
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete removes an entry by its key
|
// Delete removes an entry by its key,
|
||||||
func (s *session) Delete(key string) {
|
// returns true if actually something was removed.
|
||||||
|
func (s *session) Delete(key string) bool {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
delete(s.values, key)
|
removed := s.values.Remove(key)
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
||||||
s.updateDatabases()
|
s.updateDatabases()
|
||||||
|
return removed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *session) updateDatabases() {
|
func (s *session) updateDatabases() {
|
||||||
|
@ -301,9 +319,7 @@ func (s *session) DeleteFlash(key string) {
|
||||||
// Clear removes all entries
|
// Clear removes all entries
|
||||||
func (s *session) Clear() {
|
func (s *session) Clear() {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
for key := range s.values {
|
s.values.Reset()
|
||||||
delete(s.values, key)
|
|
||||||
}
|
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
||||||
s.updateDatabases()
|
s.updateDatabases()
|
||||||
|
|
|
@ -47,8 +47,9 @@ type (
|
||||||
GetFlashes() map[string]interface{}
|
GetFlashes() map[string]interface{}
|
||||||
VisitAll(cb func(k string, v interface{}))
|
VisitAll(cb func(k string, v interface{}))
|
||||||
Set(string, interface{})
|
Set(string, interface{})
|
||||||
|
SetImmutable(key string, value interface{})
|
||||||
SetFlash(string, interface{})
|
SetFlash(string, interface{})
|
||||||
Delete(string)
|
Delete(string) bool
|
||||||
DeleteFlash(string)
|
DeleteFlash(string)
|
||||||
Clear()
|
Clear()
|
||||||
ClearFlashes()
|
ClearFlashes()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user