diff --git a/.example/os/gsession/storage-file/file.go b/.example/os/gsession/storage-file/file.go new file mode 100644 index 000000000..f8662135a --- /dev/null +++ b/.example/os/gsession/storage-file/file.go @@ -0,0 +1,28 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/os/gtime" + "time" +) + +func main() { + s := g.Server() + s.SetConfigWithMap(g.Map{ + "SessionMaxAge": time.Minute, + }) + s.BindHandler("/set", func(r *ghttp.Request) { + r.Session.Set("time", gtime.Second()) + r.Response.Write("ok") + }) + s.BindHandler("/get", func(r *ghttp.Request) { + r.Response.Write(r.Session.Map()) + }) + s.BindHandler("/del", func(r *ghttp.Request) { + r.Session.Clear() + r.Response.Write("ok") + }) + s.SetPort(8199) + s.Run() +} diff --git a/.example/os/gsession/storage-redis-hashtable/config.toml b/.example/os/gsession/storage-redis-hashtable/config.toml new file mode 100644 index 000000000..5632e6444 --- /dev/null +++ b/.example/os/gsession/storage-redis-hashtable/config.toml @@ -0,0 +1,4 @@ +# Redis数据库配置 +[redis] + default = "127.0.0.1:6379,0" + cache = "127.0.0.1:6379,1" \ No newline at end of file diff --git a/.example/os/gsession/storage-redis-hashtable/redis-hashtable.go b/.example/os/gsession/storage-redis-hashtable/redis-hashtable.go new file mode 100644 index 000000000..3e5d77190 --- /dev/null +++ b/.example/os/gsession/storage-redis-hashtable/redis-hashtable.go @@ -0,0 +1,30 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/os/gsession" + "github.com/gogf/gf/os/gtime" + "time" +) + +func main() { + s := g.Server() + s.SetConfigWithMap(g.Map{ + "SessionMaxAge": 60 * time.Minute, + "SessionStorage": gsession.NewStorageRedisHashTable(g.Redis()), + }) + s.BindHandler("/set", func(r *ghttp.Request) { + r.Session.Set("time", gtime.Second()) + r.Response.Write("ok") + }) + s.BindHandler("/get", func(r *ghttp.Request) { + r.Response.Write(r.Session.Map()) + }) + s.BindHandler("/del", func(r *ghttp.Request) { + r.Session.Clear() + r.Response.Write("ok") + }) + s.SetPort(8199) + s.Run() +} diff --git a/.example/os/gsession/storage-redis/config.toml b/.example/os/gsession/storage-redis/config.toml new file mode 100644 index 000000000..5632e6444 --- /dev/null +++ b/.example/os/gsession/storage-redis/config.toml @@ -0,0 +1,4 @@ +# Redis数据库配置 +[redis] + default = "127.0.0.1:6379,0" + cache = "127.0.0.1:6379,1" \ No newline at end of file diff --git a/.example/os/gsession/storage-redis/redis.go b/.example/os/gsession/storage-redis/redis.go new file mode 100644 index 000000000..16bbbe2c0 --- /dev/null +++ b/.example/os/gsession/storage-redis/redis.go @@ -0,0 +1,30 @@ +package main + +import ( + "github.com/gogf/gf/frame/g" + "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/os/gsession" + "github.com/gogf/gf/os/gtime" + "time" +) + +func main() { + s := g.Server() + s.SetConfigWithMap(g.Map{ + "SessionMaxAge": time.Minute, + "SessionStorage": gsession.NewStorageRedis(g.Redis()), + }) + s.BindHandler("/set", func(r *ghttp.Request) { + r.Session.Set("time", gtime.Second()) + r.Response.Write("ok") + }) + s.BindHandler("/get", func(r *ghttp.Request) { + r.Response.Write(r.Session.Map()) + }) + s.BindHandler("/del", func(r *ghttp.Request) { + r.Session.Clear() + r.Response.Write("ok") + }) + s.SetPort(8199) + s.Run() +} diff --git a/.example/other/test.go b/.example/other/test.go index 31633195c..9cca30a1e 100644 --- a/.example/other/test.go +++ b/.example/other/test.go @@ -1,22 +1,17 @@ package main import ( - "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/net/ghttp" + "fmt" + "github.com/gogf/gf/container/gmap" ) -func main() { - s := g.Server() - s.BindHandler("/", func(r *ghttp.Request) { - - }) - s.BindHandler("/user", func(r *ghttp.Request) { - - }) - s.BindHandler("/user/:id", func(r *ghttp.Request) { - r.Response.Write(r.GetRouterString("id")) - }) - s.EnablePprof() - s.SetPort(3000) - s.Run() +func Test(data *gmap.Map) { + data = gmap.New() + fmt.Println(data) +} +func main() { + var m *gmap.Map + fmt.Println(m) + Test(m) + fmt.Println(m) } diff --git a/container/gmap/gmap_hash_any_any_map.go b/container/gmap/gmap_hash_any_any_map.go index 928e798a7..d1e43342c 100644 --- a/container/gmap/gmap_hash_any_any_map.go +++ b/container/gmap/gmap_hash_any_any_map.go @@ -368,6 +368,13 @@ func (m *AnyAnyMap) Clear() { m.mu.Unlock() } +// Replace the data of the map with given . +func (m *AnyAnyMap) Replace(data map[interface{}]interface{}) { + m.mu.Lock() + m.data = data + m.mu.Unlock() +} + // LockFunc locks writing with given callback function within RWMutex.Lock. func (m *AnyAnyMap) LockFunc(f func(m map[interface{}]interface{})) { m.mu.Lock() diff --git a/container/gmap/gmap_hash_int_any_map.go b/container/gmap/gmap_hash_int_any_map.go index 4b51e9985..600065c7e 100644 --- a/container/gmap/gmap_hash_int_any_map.go +++ b/container/gmap/gmap_hash_int_any_map.go @@ -366,6 +366,13 @@ func (m *IntAnyMap) Clear() { m.mu.Unlock() } +// Replace the data of the map with given . +func (m *IntAnyMap) Replace(data map[int]interface{}) { + m.mu.Lock() + m.data = data + m.mu.Unlock() +} + // LockFunc locks writing with given callback function within RWMutex.Lock. func (m *IntAnyMap) LockFunc(f func(m map[int]interface{})) { m.mu.Lock() diff --git a/container/gmap/gmap_hash_int_int_map.go b/container/gmap/gmap_hash_int_int_map.go index 78c22f502..249553ec1 100644 --- a/container/gmap/gmap_hash_int_int_map.go +++ b/container/gmap/gmap_hash_int_int_map.go @@ -343,6 +343,13 @@ func (m *IntIntMap) Clear() { m.mu.Unlock() } +// Replace the data of the map with given . +func (m *IntIntMap) Replace(data map[int]int) { + m.mu.Lock() + m.data = data + m.mu.Unlock() +} + // LockFunc locks writing with given callback function within RWMutex.Lock. func (m *IntIntMap) LockFunc(f func(m map[int]int)) { m.mu.Lock() diff --git a/container/gmap/gmap_hash_int_str_map.go b/container/gmap/gmap_hash_int_str_map.go index cdd63955b..ea4ac883a 100644 --- a/container/gmap/gmap_hash_int_str_map.go +++ b/container/gmap/gmap_hash_int_str_map.go @@ -344,6 +344,13 @@ func (m *IntStrMap) Clear() { m.mu.Unlock() } +// Replace the data of the map with given . +func (m *IntStrMap) Replace(data map[int]string) { + m.mu.Lock() + m.data = data + m.mu.Unlock() +} + // LockFunc locks writing with given callback function within RWMutex.Lock. func (m *IntStrMap) LockFunc(f func(m map[int]string)) { m.mu.Lock() diff --git a/container/gmap/gmap_hash_str_any_map.go b/container/gmap/gmap_hash_str_any_map.go index 2a9491437..054c4c90e 100644 --- a/container/gmap/gmap_hash_str_any_map.go +++ b/container/gmap/gmap_hash_str_any_map.go @@ -362,6 +362,13 @@ func (m *StrAnyMap) Clear() { m.mu.Unlock() } +// Replace the data of the map with given . +func (m *StrAnyMap) Replace(data map[string]interface{}) { + m.mu.Lock() + m.data = data + m.mu.Unlock() +} + // LockFunc locks writing with given callback function within RWMutex.Lock. func (m *StrAnyMap) LockFunc(f func(m map[string]interface{})) { m.mu.Lock() diff --git a/container/gmap/gmap_hash_str_int_map.go b/container/gmap/gmap_hash_str_int_map.go index c2d338bb5..66734c19e 100644 --- a/container/gmap/gmap_hash_str_int_map.go +++ b/container/gmap/gmap_hash_str_int_map.go @@ -345,6 +345,13 @@ func (m *StrIntMap) Clear() { m.mu.Unlock() } +// Replace the data of the map with given . +func (m *StrIntMap) Replace(data map[string]int) { + m.mu.Lock() + m.data = data + m.mu.Unlock() +} + // LockFunc locks writing with given callback function within RWMutex.Lock. func (m *StrIntMap) LockFunc(f func(m map[string]int)) { m.mu.Lock() diff --git a/container/gmap/gmap_hash_str_str_map.go b/container/gmap/gmap_hash_str_str_map.go index 58d8d9551..026de7ecb 100644 --- a/container/gmap/gmap_hash_str_str_map.go +++ b/container/gmap/gmap_hash_str_str_map.go @@ -346,6 +346,13 @@ func (m *StrStrMap) Clear() { m.mu.Unlock() } +// Replace the data of the map with given . +func (m *StrStrMap) Replace(data map[string]string) { + m.mu.Lock() + m.data = data + m.mu.Unlock() +} + // LockFunc locks writing with given callback function within RWMutex.Lock. func (m *StrStrMap) LockFunc(f func(m map[string]string)) { m.mu.Lock() diff --git a/container/gmap/gmap_list_map.go b/container/gmap/gmap_list_map.go index a74ac24d4..bc40b3826 100644 --- a/container/gmap/gmap_list_map.go +++ b/container/gmap/gmap_list_map.go @@ -92,6 +92,21 @@ func (m *ListMap) Clear() { m.mu.Unlock() } +// Replace the data of the map with given . +func (m *ListMap) Replace(data map[interface{}]interface{}) { + m.mu.Lock() + m.data = make(map[interface{}]*glist.Element) + m.list = glist.New() + for key, value := range data { + if e, ok := m.data[key]; !ok { + m.data[key] = m.list.PushBack(&gListMapNode{key, value}) + } else { + e.Value = &gListMapNode{key, value} + } + } + m.mu.Unlock() +} + // Map returns a copy of the data of the map. func (m *ListMap) Map() map[interface{}]interface{} { m.mu.RLock() diff --git a/container/gtree/gtree_avltree.go b/container/gtree/gtree_avltree.go index 3d26390f4..377f33671 100644 --- a/container/gtree/gtree_avltree.go +++ b/container/gtree/gtree_avltree.go @@ -386,6 +386,17 @@ func (tree *AVLTree) Clear() { tree.size = 0 } +// Replace the data of the tree with given . +func (tree *AVLTree) Replace(data map[interface{}]interface{}) { + tree.mu.Lock() + defer tree.mu.Unlock() + tree.root = nil + tree.size = 0 + for key, value := range data { + tree.put(key, value, nil, &tree.root) + } +} + // String returns a string representation of container func (tree *AVLTree) String() string { tree.mu.RLock() diff --git a/container/gtree/gtree_btree.go b/container/gtree/gtree_btree.go index 6b41ce9f0..3fcad5faf 100644 --- a/container/gtree/gtree_btree.go +++ b/container/gtree/gtree_btree.go @@ -324,6 +324,17 @@ func (tree *BTree) Clear() { tree.size = 0 } +// Replace the data of the tree with given . +func (tree *BTree) Replace(data map[interface{}]interface{}) { + tree.mu.Lock() + defer tree.mu.Unlock() + tree.root = nil + tree.size = 0 + for k, v := range data { + tree.doSet(k, v) + } +} + // Height returns the height of the tree. func (tree *BTree) Height() int { tree.mu.RLock() diff --git a/container/gtree/gtree_redblacktree.go b/container/gtree/gtree_redblacktree.go index 4abb72684..34e94f9f9 100644 --- a/container/gtree/gtree_redblacktree.go +++ b/container/gtree/gtree_redblacktree.go @@ -605,6 +605,17 @@ func (tree *RedBlackTree) Clear() { tree.size = 0 } +// Replace the data of the tree with given . +func (tree *RedBlackTree) Replace(data map[interface{}]interface{}) { + tree.mu.Lock() + defer tree.mu.Unlock() + tree.root = nil + tree.size = 0 + for k, v := range data { + tree.doSet(k, v) + } +} + // String returns a string representation of container. func (tree *RedBlackTree) String() string { tree.mu.RLock() diff --git a/net/ghttp/ghttp_server_handler.go b/net/ghttp/ghttp_server_handler.go index 998aad231..f5d1481d4 100644 --- a/net/ghttp/ghttp_server_handler.go +++ b/net/ghttp/ghttp_server_handler.go @@ -77,6 +77,8 @@ func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) { } // access log s.handleAccessLog(request) + // 关闭当前Session,并更新会话超时时间 + request.Session.Close() }() // ============================================================ @@ -168,8 +170,6 @@ func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) { if !request.IsExited() { s.callHookHandler(HOOK_AFTER_OUTPUT, request) } - // 关闭当前Session,并更新会话超时时间 - request.Session.Close() } // 查找静态文件的绝对路径 diff --git a/os/gsession/gsession_manager.go b/os/gsession/gsession_manager.go index 1e462675e..12a05e047 100644 --- a/os/gsession/gsession_manager.go +++ b/os/gsession/gsession_manager.go @@ -7,6 +7,7 @@ package gsession import ( + "github.com/gogf/gf/container/gmap" "time" "github.com/gogf/gf/os/gcache" @@ -14,16 +15,16 @@ import ( // Manager for sessions. type Manager struct { - ttl time.Duration // TTL for sessions. - storage Storage // Storage interface for session storage Set/Get. - sessions *gcache.Cache // Session cache for session TTL. + ttl time.Duration // TTL for sessions. + storage Storage // Storage interface for session storage Set/Get. + sessionData *gcache.Cache // Session data cache for session TTL. } // New creates and returns a new session manager. func New(ttl time.Duration, storage ...Storage) *Manager { m := &Manager{ - ttl: ttl, - sessions: gcache.New(), + ttl: ttl, + sessionData: gcache.New(), } if len(storage) > 0 && storage[0] != nil { m.storage = storage[0] @@ -39,12 +40,6 @@ func (m *Manager) New(sessionId ...string) *Session { if len(sessionId) > 0 && sessionId[0] != "" { id = sessionId[0] } - // NOTE: - // We CANNOT creates and stores session directly to manager - // as it might be a fake and invalid session id - // which would consumes your memory as much as possible. - // - // We here create a temporary tiny session struct. return &Session{ id: id, manager: m, @@ -67,7 +62,6 @@ func (m *Manager) TTL() time.Duration { } // UpdateSessionTTL updates the ttl for given session. -// If this session is dirty, it also exports it to storage. -func (m *Manager) UpdateSessionTTL(id string, session *Session) { - m.sessions.Set(id, session, m.ttl) +func (m *Manager) UpdateSessionTTL(id string, data *gmap.StrAnyMap) { + m.sessionData.Set(id, data, m.ttl) } diff --git a/os/gsession/gsession_session.go b/os/gsession/gsession_session.go index 8d45bfdf2..c417fbb9e 100644 --- a/os/gsession/gsession_session.go +++ b/os/gsession/gsession_session.go @@ -7,10 +7,9 @@ package gsession import ( + "github.com/gogf/gf/internal/intlog" "time" - "github.com/gogf/gf/container/gtype" - "github.com/gogf/gf/container/gmap" "github.com/gogf/gf/container/gvar" "github.com/gogf/gf/os/gtime" @@ -21,25 +20,33 @@ import ( type Session struct { id string // Session id. data *gmap.StrAnyMap // Session data. - dirty *gtype.Bool // Used to mark session is modified. + dirty bool // Used to mark session is modified. + start bool // Used to mark session is started. manager *Manager // Parent manager. } // init does the delay initialization for session. -// It here to initialization real session if necessary. +// It here initializes real session if necessary. func (s *Session) init() { - if s.dirty == nil { - s.dirty = gtype.NewBool(false) + if s.start { + return } - if len(s.id) > 0 && s.data == nil { - if data := s.manager.storage.GetSession(s.id, s.manager.ttl); data != nil { - if s.data = gmap.NewStrAnyMapFrom(data, true); s.data == nil { - panic("session restoring failed for id:" + s.id) + if len(s.id) > 0 { + var err error + // Retrieve memory session data from manager. + if r := s.manager.sessionData.Get(s.id); r != nil { + s.data = r.(*gmap.StrAnyMap) + intlog.Print("session init data:", s.data) + } + // Retrieve stored session data from storage. + if s.manager.storage != nil { + if s.data, err = s.manager.storage.GetSession(s.id, s.manager.ttl, s.data); err != nil { + intlog.Errorf("session restoring failed for id '%s': %v", s.id, err) } - return - } else { - // Invalid or expired session id, - // it should create a new one. + } + // If it's an invalid or expired session id, + // it should create a new session id. + if s.data == nil { s.id = "" } } @@ -52,6 +59,31 @@ func (s *Session) init() { if s.data == nil { s.data = gmap.NewStrAnyMap(true) } + s.start = true +} + +// Close closes current session and updates its ttl in the session manager. +// If this session is dirty, it also exports it to storage. +// +// NOTE that this function must be called ever after a session request done. +func (s *Session) Close() { + if s.start && len(s.id) > 0 { + size := s.data.Size() + if s.manager.storage != nil { + if s.dirty { + if err := s.manager.storage.SetSession(s.id, s.data, s.manager.ttl); err != nil { + panic(err) + } + } else if size > 0 { + if err := s.manager.storage.UpdateTTL(s.id, s.manager.ttl); err != nil { + panic(err) + } + } + } + if s.dirty || size > 0 { + s.manager.UpdateSessionTTL(s.id, s.data) + } + } } // Set sets key-value pair to this session. @@ -64,7 +96,7 @@ func (s *Session) Set(key string, value interface{}) error { return err } } - s.dirty.Set(true) + s.dirty = true return nil } @@ -78,7 +110,7 @@ func (s *Session) Sets(data map[string]interface{}) error { return err } } - s.dirty.Set(true) + s.dirty = true return nil } @@ -95,7 +127,7 @@ func (s *Session) Remove(key string) error { return err } } - s.dirty.Set(true) + s.dirty = true return nil } @@ -117,7 +149,7 @@ func (s *Session) RemoveAll() error { return err } } - s.dirty.Set(true) + s.dirty = true return nil } @@ -161,33 +193,7 @@ func (s *Session) Contains(key string) bool { // IsDirty checks whether there's any data changes in the session. func (s *Session) IsDirty() bool { - if s.dirty == nil { - return false - } - return s.dirty.Val() -} - -// Close closes current session and updates its ttl in the session manager. -// If this session is dirty, it also exports it to storage. -// -// NOTE that this function must be called ever after a session request done. -func (s *Session) Close() { - if len(s.id) > 0 && s.data != nil { - if s.manager.storage != nil { - if s.dirty.Cas(true, false) { - s.data.RLockFunc(func(m map[string]interface{}) { - if err := s.manager.storage.SetSession(s.id, m, s.manager.ttl); err != nil { - panic(err) - } - }) - } else { - if err := s.manager.storage.UpdateTTL(s.id, s.manager.ttl); err != nil { - panic(err) - } - } - } - s.manager.UpdateSessionTTL(s.id, s) - } + return s.dirty } // Get retrieves session value with given key. diff --git a/os/gsession/gsession_storage.go b/os/gsession/gsession_storage.go index 7eee2c221..17ab8e0c8 100644 --- a/os/gsession/gsession_storage.go +++ b/os/gsession/gsession_storage.go @@ -6,7 +6,11 @@ package gsession -import "time" +import ( + "errors" + "github.com/gogf/gf/container/gmap" + "time" +) type Storage interface { // New creates a custom session id. @@ -37,17 +41,25 @@ type Storage interface { // RemoveAll deletes all key-value pairs from storage. RemoveAll(id string) error - // GetSession returns the session data as map for given session id. - // The parameter specifies the TTL for this session. - // It returns nil if the TTL is exceeded. - GetSession(id string, ttl time.Duration) map[string]interface{} + // GetSession returns the session data as *gmap.StrAnyMap for given session id from storage. + // + // The parameter specifies the TTL for this session, and it returns nil if the TTL is exceeded. + // The parameter is the current old session data stored in memory, + // and for some storage it might be nil if memory storage is disabled. + // + // This function is called ever when session starts. + GetSession(id string, ttl time.Duration, data *gmap.StrAnyMap) (*gmap.StrAnyMap, error) - // SetSession updates the data map for specified session id. + // SetSession updates the data for specified session id. // This function is called ever after session, which is changed dirty, is closed. // This copy all session data map from memory to storage. - SetSession(id string, data map[string]interface{}, ttl time.Duration) error + SetSession(id string, data *gmap.StrAnyMap, ttl time.Duration) error // UpdateTTL updates the TTL for specified session id. // This function is called ever after session, which is not dirty, is closed. UpdateTTL(id string, ttl time.Duration) error } + +var ( + ErrorDisabled = errors.New("this feature is disabled in this storage") +) diff --git a/os/gsession/gsession_storage_file.go b/os/gsession/gsession_storage_file.go index 68a479c72..8611f947c 100644 --- a/os/gsession/gsession_storage_file.go +++ b/os/gsession/gsession_storage_file.go @@ -8,7 +8,7 @@ package gsession import ( "encoding/json" - "errors" + "github.com/gogf/gf/container/gmap" "github.com/gogf/gf/internal/intlog" "os" "time" @@ -40,7 +40,6 @@ var ( DefaultStorageFileCryptoKey = []byte("Session storage file crypto key!") DefaultStorageFileCryptoEnabled = false DefaultStorageFileLoopInterval = time.Minute - ErrorDisabled = errors.New("this feature is disabled in this storage") ) func init() { @@ -151,39 +150,51 @@ func (s *StorageFile) RemoveAll(id string) error { return ErrorDisabled } -// GetSession returns the session data as map for given session id. -// The parameter specifies the TTL for this session. -// It returns nil if the TTL is exceeded. -func (s *StorageFile) GetSession(id string, ttl time.Duration) map[string]interface{} { +// GetSession returns the session data as *gmap.StrAnyMap for given session id from storage. +// +// The parameter specifies the TTL for this session, and it returns nil if the TTL is exceeded. +// The parameter is the current old session data stored in memory, +// and for some storage it might be nil if memory storage is disabled. +// +// This function is called ever when session starts. +func (s *StorageFile) GetSession(id string, ttl time.Duration, data *gmap.StrAnyMap) (*gmap.StrAnyMap, error) { + if data != nil { + return data, nil + } + intlog.Printf("StorageFile.GetSession: %s, %v", id, ttl) path := s.sessionFilePath(id) - data := gfile.GetBytes(path) - if len(data) > 8 { - timestampMilli := gbinary.DecodeToInt64(data[:8]) + content := gfile.GetBytes(path) + if len(content) > 8 { + timestampMilli := gbinary.DecodeToInt64(content[:8]) if timestampMilli+ttl.Nanoseconds()/1e6 < gtime.Millisecond() { - return nil + return nil, nil } var err error - content := data[8:] + content = content[8:] // Decrypt with AES. if s.cryptoEnabled { - content, err = gaes.Decrypt(data[8:], DefaultStorageFileCryptoKey) + content, err = gaes.Decrypt(content, DefaultStorageFileCryptoKey) if err != nil { - return nil + return nil, err } } var m map[string]interface{} if err = json.Unmarshal(content, &m); err != nil { - return nil + return nil, err } - return m + if m == nil { + return nil, nil + } + return gmap.NewStrAnyMapFrom(m, true), nil } - return nil + return nil, nil } // SetSession updates the data map for specified session id. // This function is called ever after session, which is changed dirty, is closed. // This copy all session data map from memory to storage. -func (s *StorageFile) SetSession(id string, data map[string]interface{}, ttl time.Duration) error { +func (s *StorageFile) SetSession(id string, data *gmap.StrAnyMap, ttl time.Duration) error { + intlog.Printf("StorageFile.SetSession: %s, %v, %v", id, data, ttl) path := s.sessionFilePath(id) content, err := json.Marshal(data) if err != nil { @@ -214,6 +225,7 @@ func (s *StorageFile) SetSession(id string, data map[string]interface{}, ttl tim // This function is called ever after session, which is not dirty, is closed. // It just adds the session id to the async handling queue. func (s *StorageFile) UpdateTTL(id string, ttl time.Duration) error { + intlog.Printf("StorageFile.UpdateTTL: %s, %v", id, ttl) if ttl >= DefaultStorageRedisLoopInterval { s.updatingIdSet.Add(id) } @@ -222,7 +234,7 @@ func (s *StorageFile) UpdateTTL(id string, ttl time.Duration) error { // doUpdateTTL updates the TTL for session id. func (s *StorageFile) doUpdateTTL(id string) error { - intlog.Printf("update StorageFile TTL for session id: %s", id) + intlog.Printf("StorageFile.doUpdateTTL: %s", id) path := s.sessionFilePath(id) file, err := gfile.OpenWithFlag(path, os.O_WRONLY) if err != nil { diff --git a/os/gsession/gsession_storage_redis.go b/os/gsession/gsession_storage_redis.go index 179921605..5cb36976d 100644 --- a/os/gsession/gsession_storage_redis.go +++ b/os/gsession/gsession_storage_redis.go @@ -32,6 +32,7 @@ var ( // NewStorageRedis creates and returns a redis storage object for session. func NewStorageRedis(redis *gredis.Redis, prefix ...string) *StorageRedis { if redis == nil { + panic("redis instance for storage cannot be empty") return nil } s := &StorageRedis{ @@ -105,26 +106,43 @@ func (s *StorageRedis) RemoveAll(id string) error { return ErrorDisabled } -// GetSession returns the session data as map for given session id. -// The parameter specifies the TTL for this session. -// It returns nil if the TTL is exceeded. -func (s *StorageRedis) GetSession(id string, ttl time.Duration) map[string]interface{} { +// GetSession returns the session data as *gmap.StrAnyMap for given session id from storage. +// +// The parameter specifies the TTL for this session, and it returns nil if the TTL is exceeded. +// The parameter is the current old session data stored in memory, +// and for some storage it might be nil if memory storage is disabled. +// +// This function is called ever when session starts. +func (s *StorageRedis) GetSession(id string, ttl time.Duration, data *gmap.StrAnyMap) (*gmap.StrAnyMap, error) { + intlog.Printf("StorageRedis.GetSession: %s, %v", id, ttl) r, err := s.redis.DoVar("GET", s.key(id)) if err != nil { - // If it does not exist, it returns an empty map to replace the session memory data. - return map[string]interface{}{} + return nil, err + } + content := r.Bytes() + if len(content) == 0 { + return nil, nil } var m map[string]interface{} - if err = json.Unmarshal(r.Bytes(), &m); err != nil { - return nil + if err = json.Unmarshal(content, &m); err != nil { + return nil, err } - return m + if m == nil { + return nil, nil + } + if data == nil { + return gmap.NewStrAnyMapFrom(m, true), nil + } else { + data.Replace(m) + } + return data, nil } // SetSession updates the data map for specified session id. // This function is called ever after session, which is changed dirty, is closed. // This copy all session data map from memory to storage. -func (s *StorageRedis) SetSession(id string, data map[string]interface{}, ttl time.Duration) error { +func (s *StorageRedis) SetSession(id string, data *gmap.StrAnyMap, ttl time.Duration) error { + intlog.Printf("StorageRedis.SetSession: %s, %v, %v", id, data, ttl) content, err := json.Marshal(data) if err != nil { return err @@ -137,6 +155,7 @@ func (s *StorageRedis) SetSession(id string, data map[string]interface{}, ttl ti // This function is called ever after session, which is not dirty, is closed. // It just adds the session id to the async handling queue. func (s *StorageRedis) UpdateTTL(id string, ttl time.Duration) error { + intlog.Printf("StorageRedis.UpdateTTL: %s, %v", id, ttl) if ttl >= DefaultStorageRedisLoopInterval { s.updatingIdMap.Set(id, int(ttl.Seconds())) } @@ -145,7 +164,7 @@ func (s *StorageRedis) UpdateTTL(id string, ttl time.Duration) error { // doUpdateTTL updates the TTL for session id. func (s *StorageRedis) doUpdateTTL(id string, ttlSeconds int) error { - intlog.Printf("update StorageRedis TTL for session id: %s, %d", id, ttlSeconds) + intlog.Printf("StorageRedis.doUpdateTTL: %s, %d", id, ttlSeconds) _, err := s.redis.DoVar("EXPIRE", s.key(id), ttlSeconds) return err } diff --git a/os/gsession/gsession_storage_redis_hashtable.go b/os/gsession/gsession_storage_redis_hashtable.go index df1541cd2..f7537f671 100644 --- a/os/gsession/gsession_storage_redis_hashtable.go +++ b/os/gsession/gsession_storage_redis_hashtable.go @@ -7,6 +7,7 @@ package gsession import ( + "github.com/gogf/gf/container/gmap" "github.com/gogf/gf/database/gredis" "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/util/gconv" @@ -22,6 +23,7 @@ type StorageRedisHashTable struct { // NewStorageRedisHashTable creates and returns a redis hash table storage object for session. func NewStorageRedisHashTable(redis *gredis.Redis, prefix ...string) *StorageRedisHashTable { if redis == nil { + panic("redis instance for storage cannot be empty") return nil } s := &StorageRedisHashTable{ @@ -42,8 +44,8 @@ func (s *StorageRedisHashTable) New(ttl time.Duration) (id string) { // Get retrieves session value with given key. // It returns nil if the key does not exist in the session. func (s *StorageRedisHashTable) Get(id string, key string) interface{} { - r, _ := s.redis.Do("HGET", s.key(id), key) - return r + r, _ := s.redis.DoVar("HGET", s.key(id), key) + return r.String() } // GetMap retrieves all key-value pairs as map from storage. @@ -55,7 +57,7 @@ func (s *StorageRedisHashTable) GetMap(id string) map[string]interface{} { array := r.Interfaces() m := make(map[string]interface{}) for i := 0; i < len(array); i += 2 { - m[gconv.String(array[i])] = array[i+1] + m[gconv.String(array[i])] = gconv.String(array[i+1]) } return m } @@ -101,21 +103,29 @@ func (s *StorageRedisHashTable) RemoveAll(id string) error { return err } -// GetSession returns the session data as map for given session id. -// The parameter specifies the TTL for this session. -// It returns nil if the TTL is exceeded. -func (s *StorageRedisHashTable) GetSession(id string, ttl time.Duration) map[string]interface{} { - r, _ := s.redis.DoVar("EXISTS", s.key(id)) - if r.Bool() { - return map[string]interface{}{} +// GetSession returns the session data as *gmap.StrAnyMap for given session id from storage. +// +// The parameter specifies the TTL for this session, and it returns nil if the TTL is exceeded. +// The parameter is the current old session data stored in memory, +// and for some storage it might be nil if memory storage is disabled. +// +// This function is called ever when session starts. +func (s *StorageRedisHashTable) GetSession(id string, ttl time.Duration, data *gmap.StrAnyMap) (*gmap.StrAnyMap, error) { + intlog.Printf("StorageRedisHashTable.GetSession: %s, %v", id, ttl) + r, err := s.redis.DoVar("EXISTS", s.key(id)) + if err != nil { + return nil, err } - return nil + if r.Bool() { + return gmap.NewStrAnyMap(true), nil + } + return nil, nil } // SetSession updates the data map for specified session id. // This function is called ever after session, which is changed dirty, is closed. // This copy all session data map from memory to storage. -func (s *StorageRedisHashTable) SetSession(id string, data map[string]interface{}, ttl time.Duration) error { +func (s *StorageRedisHashTable) SetSession(id string, data *gmap.StrAnyMap, ttl time.Duration) error { intlog.Printf("StorageRedisHashTable.SetSession: %s, %v", id, ttl) _, err := s.redis.Do("EXPIRE", s.key(id), ttl.Seconds()) return err