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:
kataras 2017-06-08 03:39:15 +03:00
parent 5e00c50c37
commit 14e7751d21
14 changed files with 455 additions and 268 deletions

View File

@ -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.

View File

@ -6,7 +6,7 @@ A fast, cross-platform and efficient web framework with robust set of well-desig
[![Report card](https://img.shields.io/badge/report%20card%20-a%2B-F44336.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![Report card](https://img.shields.io/badge/report%20card%20-a%2B-F44336.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris)
[![Support forum](https://img.shields.io/badge/support-page-ec2eb4.svg?style=flat-square)](http://support.iris-go.com) [![Support forum](https://img.shields.io/badge/support-page-ec2eb4.svg?style=flat-square)](http://support.iris-go.com)
[![Examples](https://img.shields.io/badge/howto-examples-3362c2.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples#table-of-contents) [![Examples](https://img.shields.io/badge/howto-examples-3362c2.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples#table-of-contents)
[![Godocs](https://img.shields.io/badge/7.0.1-%20documentation-5272B4.svg?style=flat-square)](https://godoc.org/github.com/kataras/iris) [![Godocs](https://img.shields.io/badge/7.0.2-%20documentation-5272B4.svg?style=flat-square)](https://godoc.org/github.com/kataras/iris)
[![Chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![Chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris)
[![Buy me a cup of coffee](https://img.shields.io/badge/support-%20open--source-F4A460.svg?logo=data:image%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAwIDEwMDAiPjxwYXRoIGZpbGw9InJnYigyMjAsMjIwLDIyMCkiIGQ9Ik04ODYuNiwzMDUuM2MtNDUuNywyMDMuMS0xODcsMzEwLjMtNDA5LjYsMzEwLjNoLTc0LjFsLTUxLjUsMzI2LjloLTYybC0zLjIsMjEuMWMtMi4xLDE0LDguNiwyNi40LDIyLjYsMjYuNGgxNTguNWMxOC44LDAsMzQuNy0xMy42LDM3LjctMzIuMmwxLjUtOGwyOS45LTE4OS4zbDEuOS0xMC4zYzIuOS0xOC42LDE4LjktMzIuMiwzNy43LTMyLjJoMjMuNWMxNTMuNSwwLDI3My43LTYyLjQsMzA4LjktMjQyLjdDOTIxLjYsNDA2LjgsOTE2LjcsMzQ4LjYsODg2LjYsMzA1LjN6Ii8%2BPHBhdGggZmlsbD0icmdiKDIyMCwyMjAsMjIwKSIgZD0iTTc5MS45LDgzLjlDNzQ2LjUsMzIuMiw2NjQuNCwxMCw1NTkuNSwxMEgyNTVjLTIxLjQsMC0zOS44LDE1LjUtNDMuMSwzNi44TDg1LDg1MWMtMi41LDE1LjksOS44LDMwLjIsMjUuOCwzMC4ySDI5OWw0Ny4zLTI5OS42bC0xLjUsOS40YzMuMi0yMS4zLDIxLjQtMzYuOCw0Mi45LTM2LjhINDc3YzE3NS41LDAsMzEzLTcxLjIsMzUzLjItMjc3LjVjMS4yLTYuMSwyLjMtMTIuMSwzLjEtMTcuOEM4NDUuMSwxODIuOCw4MzMuMiwxMzAuOCw3OTEuOSw4My45TDc5MS45LDgzLjl6Ii8%2BPC9zdmc%2B)](https://github.com/kataras/iris#buy-me-a-cup-of-coffee) [![Buy me a cup of coffee](https://img.shields.io/badge/support-%20open--source-F4A460.svg?logo=data:image%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAwIDEwMDAiPjxwYXRoIGZpbGw9InJnYigyMjAsMjIwLDIyMCkiIGQ9Ik04ODYuNiwzMDUuM2MtNDUuNywyMDMuMS0xODcsMzEwLjMtNDA5LjYsMzEwLjNoLTc0LjFsLTUxLjUsMzI2LjloLTYybC0zLjIsMjEuMWMtMi4xLDE0LDguNiwyNi40LDIyLjYsMjYuNGgxNTguNWMxOC44LDAsMzQuNy0xMy42LDM3LjctMzIuMmwxLjUtOGwyOS45LTE4OS4zbDEuOS0xMC4zYzIuOS0xOC42LDE4LjktMzIuMiwzNy43LTMyLjJoMjMuNWMxNTMuNSwwLDI3My43LTYyLjQsMzA4LjktMjQyLjdDOTIxLjYsNDA2LjgsOTE2LjcsMzQ4LjYsODg2LjYsMzA1LjN6Ii8%2BPHBhdGggZmlsbD0icmdiKDIyMCwyMjAsMjIwKSIgZD0iTTc5MS45LDgzLjlDNzQ2LjUsMzIuMiw2NjQuNCwxMCw1NTkuNSwxMEgyNTVjLTIxLjQsMC0zOS44LDE1LjUtNDMuMSwzNi44TDg1LDg1MWMtMi41LDE1LjksOS44LDMwLjIsMjUuOCwzMC4ySDI5OWw0Ny4zLTI5OS42bC0xLjUsOS40YzMuMi0yMS4zLDIxLjQtMzYuOCw0Mi45LTM2LjhINDc3YzE3NS41LDAsMzEzLTcxLjIsMzUzLjItMjc3LjVjMS4yLTYuMSwyLjMtMTIuMSwzLjEtMTcuOEM4NDUuMSwxODIuOCw4MzMuMiwxMzAuOCw3OTEuOSw4My45TDc5MS45LDgzLjl6Ii8%2BPC9zdmc%2B)](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".

View File

@ -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"))

View File

@ -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)

View File

@ -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
} }

View File

@ -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
View 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)
}

View 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")
}
}

View File

@ -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:

View File

@ -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

View File

@ -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 (

View File

@ -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)
}
} }
} }

View File

@ -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()

View File

@ -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()