add ConfigByMap function for ghttp.Server; improve gsession for file storage

This commit is contained in:
John
2019-09-13 20:49:16 +08:00
parent aa44c0fb11
commit 141bee9c49
10 changed files with 195 additions and 44 deletions

View File

@ -13,6 +13,8 @@ import (
"strconv"
"time"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/os/gsession"
"github.com/gogf/gf/os/gview"
@ -110,7 +112,14 @@ func Config() ServerConfig {
return defaultServerConfig
}
// http server setting设置
// 通过Map创建Config配置对象Map没有传递的属性将会使用模块的默认值
func ConfigFromMap(m map[string]interface{}) ServerConfig {
config := defaultServerConfig
gconv.Struct(m, &config)
return config
}
// http server setting设置。
// 注意使用该方法进行http server配置时需要配置所有的配置项否则没有配置的属性将会默认变量为空
func (s *Server) SetConfig(c ServerConfig) {
if s.Status() == SERVER_STATUS_RUNNING {
@ -127,6 +136,16 @@ func (s *Server) SetConfig(c ServerConfig) {
}
}
// 通过map设置http server setting。
// 注意使用该方法进行http server配置时需要配置所有的配置项否则没有配置的属性将会默认变量为空
func (s *Server) SetConfigWithMap(m map[string]interface{}) {
if s.Status() == SERVER_STATUS_RUNNING {
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
return
}
s.SetConfig(ConfigFromMap(m))
}
// 设置http server参数 - Addr
func (s *Server) SetAddr(itemFunc string) {
if s.Status() == SERVER_STATUS_RUNNING {

View File

@ -140,7 +140,7 @@ func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {
s.callHookHandler(HOOK_BEFORE_OUTPUT, request)
}
// 设置Session Id到Cookie中
if request.Session.Id() != "" && request.GetSessionId() != request.Session.Id() {
if request.Session.IsDirty() && request.Session.Id() != request.GetSessionId() {
request.Cookie.SetSessionId(request.Session.Id())
}
// 输出Cookie
@ -151,8 +151,8 @@ func (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {
if !request.IsExited() {
s.callHookHandler(HOOK_AFTER_OUTPUT, request)
}
// 更新Session会话超时时间
request.Session.UpdateTTL()
// 关闭当前Session,并更新会话超时时间
request.Session.Close()
}
// 查找静态文件的绝对路径

View File

@ -0,0 +1,39 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package ghttp_test
import (
"testing"
"time"
"github.com/gogf/gf/util/gconv"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/test/gtest"
)
func Test_ConfigFromMap(t *testing.T) {
gtest.Case(t, func() {
m := g.Map{
"addr": ":8199",
"readTimeout": "60s",
"indexFiles": g.Slice{"index.php", "main.php"},
"errorLogEnabled": true,
"cookieMaxAge": "1y",
}
config := ghttp.ConfigFromMap(m)
d1, _ := time.ParseDuration(gconv.String(m["readTimeout"]))
d2, _ := time.ParseDuration(gconv.String(m["cookieMaxAge"]))
gtest.Assert(config.Addr, m["addr"])
gtest.Assert(config.ReadTimeout, d1)
gtest.Assert(config.CookieMaxAge, d2)
gtest.Assert(config.IndexFiles, m["indexFiles"])
gtest.Assert(config.ErrorLogEnabled, m["errorLogEnabled"])
})
}

View File

@ -15,7 +15,8 @@ import (
"github.com/gogf/gf/util/grand"
)
// NewSessionId creates and returns a new and unique session id string.
// NewSessionId creates and returns a new and unique session id string,
// the length of which is 18 bytes.
func NewSessionId() string {
return strings.ToUpper(strconv.FormatInt(gtime.Nanosecond(), 36) + grand.Str(6))
}

View File

@ -39,7 +39,7 @@ func (m *Manager) New(sessionId ...string) *Session {
id = sessionId[0]
}
// NOTE:
// We CANNOT creates and stores it directory to manager
// We CANNOT creates and stores it directly to manager
// as it might be a fake and invalid session id
// which would consumes your memory as much as possible.
return &Session{
@ -58,8 +58,8 @@ func (m *Manager) TTL() time.Duration {
return m.ttl
}
// UpdateTTL updates the ttl for given session.
// UpdateSessionTTL updates the ttl for given session.
// If this session is dirty, it also exports it to storage.
func (m *Manager) UpdateTTL(id string, session *Session) {
func (m *Manager) UpdateSessionTTL(id string, session *Session) {
m.sessions.Set(id, session, m.ttl)
}

View File

@ -7,7 +7,6 @@
package gsession
import (
"encoding/json"
"time"
"github.com/gogf/gf/container/gtype"
@ -29,7 +28,9 @@ type Session struct {
// init does the delay initialization for session.
// It here to initialization real session if necessary.
func (s *Session) init() {
s.dirty = gtype.NewBool(false)
if s.dirty == nil {
s.dirty = gtype.NewBool(false)
}
if len(s.id) > 0 && s.data == nil {
if data := s.manager.storage.Get(s.id); data != nil {
if s.data = gmap.NewStrAnyMapFrom(data, true); s.data == nil {
@ -44,13 +45,16 @@ func (s *Session) init() {
}
if len(s.id) == 0 {
s.id = NewSessionId()
}
if s.data == nil {
s.data = gmap.NewStrAnyMap(true)
}
}
// Id returns the session id for this session.
// It might be empty if session is not actually used.
// It create and returns a new session id if the session id is not passed in initialization.
func (s *Session) Id() string {
s.init()
return s.id
}
@ -95,6 +99,9 @@ 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()
}
@ -105,18 +112,6 @@ func (s *Session) Remove(key string) {
s.dirty.Set(true)
}
// Restore un-serializes the data and restore the session from it.
func (s *Session) Restore(data []byte) (err error) {
if len(data) == 0 {
return nil
}
s.init()
s.data.LockFunc(func(m map[string]interface{}) {
err = json.Unmarshal(data, &m)
})
return
}
// Clear deletes all key-value pairs from this session.
func (s *Session) Clear() {
if len(s.id) > 0 {
@ -126,9 +121,11 @@ func (s *Session) Clear() {
}
}
// UpdateTTL updates the ttl of the session.
// Close closes current session and updates its ttl in the session manager.
// If this session is dirty, it also exports it to storage.
func (s *Session) UpdateTTL() {
//
// 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) {
@ -143,7 +140,7 @@ func (s *Session) UpdateTTL() {
}
}
}
s.manager.UpdateTTL(s.id, s)
s.manager.UpdateSessionTTL(s.id, s)
}
}

View File

@ -29,13 +29,16 @@ import (
type StorageFile struct {
ttl time.Duration
path string
cryptoKey []byte
cryptoEnabled bool
updatingIdSet *gset.StrSet
}
var (
DefaultStorageFilePath = gfile.Join(gfile.TempDir(), "gsessions")
DefaultStorageFileCryptoKey = []byte("Session storage file crypto key!")
DefaultStorageFileLoopInterval = 5 * time.Second
DefaultStorageFilePath = gfile.Join(gfile.TempDir(), "gsessions")
DefaultStorageFileCryptoKey = []byte("Session storage file crypto key!")
DefaultStorageFileCryptoEnabled = false
DefaultStorageFileLoopInterval = time.Minute
)
func init() {
@ -45,6 +48,7 @@ func init() {
}
}
// NewStorageFile creates and returns a file storage object for session.
func NewStorageFile(ttl time.Duration, path ...string) *StorageFile {
storagePath := DefaultStorageFilePath
if len(path) > 0 && path[0] != "" {
@ -64,17 +68,30 @@ func NewStorageFile(ttl time.Duration, path ...string) *StorageFile {
s := &StorageFile{
ttl: ttl,
path: storagePath,
cryptoKey: DefaultStorageFileCryptoKey,
cryptoEnabled: DefaultStorageFileCryptoEnabled,
updatingIdSet: gset.NewStrSet(true),
}
gtimer.AddSingleton(DefaultStorageFileLoopInterval, func() {
s.updatingIdSet.Iterator(func(v string) bool {
s.doUpdateTTL(v)
return true
})
for _, id := range s.updatingIdSet.Slice() {
s.doUpdateTTL(id)
}
})
return s
}
// SetCryptoKey sets the crypto key for session storage.
// The crypto key is used when crypto feature is enabled.
func (s *StorageFile) SetCryptoKey(key []byte) {
s.cryptoKey = key
}
// SetCryptoEnabled enables/disables the crypto feature for session storage.
func (s *StorageFile) SetCryptoEnabled(enabled bool) {
s.cryptoEnabled = enabled
}
// sessionFilePath returns the storage file path for given session id.
func (s *StorageFile) sessionFilePath(id string) string {
return gfile.Join(s.path, id)
}
@ -84,17 +101,21 @@ func (s *StorageFile) Get(id string) map[string]interface{} {
path := s.sessionFilePath(id)
data := gfile.GetBytes(path)
if len(data) > 8 {
timestamp := gbinary.DecodeToInt64(data[:8])
if timestamp+int64(s.ttl.Seconds()) < gtime.Second() {
timestampMilli := gbinary.DecodeToInt64(data[:8])
if timestampMilli+s.ttl.Nanoseconds()/1e6 < gtime.Millisecond() {
return nil
}
var err error
content := data[8:]
// Decrypt with AES.
content, err := gaes.Decrypt(data[8:], DefaultStorageFileCryptoKey)
if err != nil {
return nil
if s.cryptoEnabled {
content, err = gaes.Decrypt(data[8:], DefaultStorageFileCryptoKey)
if err != nil {
return nil
}
}
var m map[string]interface{}
if err := json.Unmarshal(content, &m); err != nil {
if err = json.Unmarshal(content, &m); err != nil {
return nil
}
return m
@ -111,15 +132,17 @@ func (s *StorageFile) Set(id string, data map[string]interface{}) error {
return err
}
// Encrypt with AES.
content, err = gaes.Encrypt(content, DefaultStorageFileCryptoKey)
if err != nil {
return err
if s.cryptoEnabled {
content, err = gaes.Encrypt(content, DefaultStorageFileCryptoKey)
if err != nil {
return err
}
}
file, err := gfile.OpenWithFlag(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
if err != nil {
return err
}
if _, err = file.Write(gbinary.EncodeInt64(gtime.Second())); err != nil {
if _, err = file.Write(gbinary.EncodeInt64(gtime.Millisecond())); err != nil {
return err
}
if _, err = file.Write(content); err != nil {
@ -142,7 +165,7 @@ func (s *StorageFile) doUpdateTTL(id string) error {
if err != nil {
return err
}
if _, err = file.Write(gbinary.EncodeInt64(gtime.Second())); err != nil {
if _, err = file.WriteAt(gbinary.EncodeInt64(gtime.Millisecond()), 0); err != nil {
return err
}
return file.Close()

View File

@ -0,0 +1,44 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gsession
import (
"testing"
"time"
"github.com/gogf/gf/test/gtest"
)
func Test_Manager_Basic(t *testing.T) {
ttl := time.Second
storage := NewStorageFile(ttl)
manager := New(ttl, storage)
sessionId := ""
gtest.Case(t, func() {
session := manager.New()
defer session.Close()
session.Set("k1", "v1")
session.Set("k2", "v2")
gtest.Assert(session.Get("k1"), "v1")
gtest.Assert(session.Get("k2"), "v2")
sessionId = session.Id()
})
time.Sleep(500 * time.Millisecond)
gtest.Case(t, func() {
gtest.AssertNE(sessionId, "")
gtest.Assert(manager.New(sessionId).Get("k1"), "v1")
gtest.Assert(manager.New(sessionId).Get("k2"), "v2")
})
time.Sleep(1000 * time.Millisecond)
gtest.Case(t, func() {
gtest.AssertNE(sessionId, "")
gtest.Assert(manager.New(sessionId).Get("k1"), nil)
gtest.Assert(manager.New(sessionId).Get("k2"), nil)
})
}

View File

@ -0,0 +1,22 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gsession
import (
"testing"
"github.com/gogf/gf/test/gtest"
)
func Test_NewSessionId(t *testing.T) {
gtest.Case(t, func() {
id1 := NewSessionId()
id2 := NewSessionId()
gtest.AssertNE(id1, id2)
gtest.Assert(len(id1), 18)
})
}

View File

@ -1,3 +1,9 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gspath_test
import (