mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
add ConfigByMap function for ghttp.Server; improve gsession for file storage
This commit is contained in:
@ -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 {
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
// 查找静态文件的绝对路径
|
||||
|
||||
39
net/ghttp/ghttp_unit_config_test.go
Normal file
39
net/ghttp/ghttp_unit_config_test.go
Normal 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"])
|
||||
})
|
||||
}
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
44
os/gsession/gsession_unit_manager_test.go
Normal file
44
os/gsession/gsession_unit_manager_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
22
os/gsession/gsession_unit_test.go
Normal file
22
os/gsession/gsession_unit_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user