From 7225e49aa053cbc50015e13d6a385caee546ac2c Mon Sep 17 00:00:00 2001 From: John Date: Tue, 29 Oct 2019 16:45:42 +0800 Subject: [PATCH] add package intlog for GF internal logging usage; improve redis storage feature for gsession --- .../net/ghttp/server/session/session_file.go | 30 +++++++ .../net/ghttp/server/session/session_redis.go | 66 ++------------- .example/other/test.go | 45 ++-------- debug/gdebug/gdebug.go | 6 ++ internal/intlog/intlog.go | 83 +++++++++++++++++++ net/ghttp/ghttp_server.go | 15 ++-- net/ghttp/ghttp_server_config.go | 4 +- net/ghttp/ghttp_server_config_session.go | 2 - os/gcache/gcache_mem_cache.go | 6 +- os/gsession/gsession_manager.go | 3 +- os/gsession/gsession_storage_file.go | 15 +++- os/gsession/gsession_storage_redis.go | 16 +++- .../gsession_storage_redis_hashtable.go | 3 + 13 files changed, 176 insertions(+), 118 deletions(-) create mode 100644 .example/net/ghttp/server/session/session_file.go create mode 100644 internal/intlog/intlog.go diff --git a/.example/net/ghttp/server/session/session_file.go b/.example/net/ghttp/server/session/session_file.go new file mode 100644 index 000000000..6725ecfc3 --- /dev/null +++ b/.example/net/ghttp/server/session/session_file.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/gtime" + "time" +) + +// SessionSet sets a test session key into the session storage. +func SessionSet(r *ghttp.Request) { + r.Session.Set("time", gtime.Second()) + r.Response.WriteJson("ok") +} + +// SessionGet shows all sessions stored. +func SessionGet(r *ghttp.Request) { + r.Response.WriteJson(r.Session.Map()) +} + +func main() { + s := g.Server() + s.SetConfigWithMap(g.Map{ + "SessionMaxAge": 3 * time.Second, + }) + s.BindHandler("/set", SessionSet) + s.BindHandler("/get", SessionGet) + s.SetPort(8199) + s.Run() +} diff --git a/.example/net/ghttp/server/session/session_redis.go b/.example/net/ghttp/server/session/session_redis.go index 2debe6d50..5c32c7466 100644 --- a/.example/net/ghttp/server/session/session_redis.go +++ b/.example/net/ghttp/server/session/session_redis.go @@ -3,80 +3,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" ) -// 测试,SESSION写入 +// SessionSet sets a test session key into the session storage. func SessionSet(r *ghttp.Request) { r.Session.Set("time", gtime.Second()) r.Response.WriteJson("ok") } -// 测试,SESSION读取 +// SessionGet shows all sessions stored. func SessionGet(r *ghttp.Request) { r.Response.WriteJson(r.Session.Map()) } -// 请求处理之前将Redis中的数据读取出来并存储到SESSION对象中。 -func RedisHandlerGet(r *ghttp.Request) { - if !r.IsFileRequest() { - id := r.Cookie.GetSessionId() - if id == "" { - return - } - // 应用服务器一般是多个节点构成的集群, - // 当请求中带有SESSION ID时,自动从Redis读取并恢复数据。 - value, err := g.Redis().DoVar("GET", id) - if err != nil { - panic(err) - } - if !value.IsNil() { - if err := r.Session.Restore(value.Bytes()); err != nil { - panic(err) - } - } - } -} - -// 请求结束时将SESSION数据存储到Redis中,或者在SESSION删除时也删除Redis中的数据。 -func RedisHandlerSet(r *ghttp.Request) { - if !r.IsFileRequest() { - id := r.Cookie.GetSessionId() - if id == "" { - return - } - err := (error)(nil) - value := ([]byte)(nil) - if r.Session.Size() > 0 { - if value, err = r.Session.Export(); err == nil { - if len(value) == 0 { - return - } else if !r.Session.IsDirty() { - // 更新过期时间 - _, err = g.Redis().Do("EXPIRE", id, r.Server.GetSessionMaxAge()) - } else { - // 更新Redis数据 - _, err = g.Redis().Do("SETEX", id, r.Server.GetSessionMaxAge(), value) - } - } - } else { - // 清空SESSION后自动删除Redis数据 - _, err = g.Redis().Do("DEL", id) - } - if err != nil { - panic(err) - } - } -} - func main() { s := g.Server() + s.SetConfigWithMap(g.Map{ + "SessionMaxAge": 3 * time.Second, + "SessionStorage": gsession.NewStorageRedis(g.Redis()), + }) s.BindHandler("/set", SessionSet) s.BindHandler("/get", SessionGet) - s.BindHookHandlerByMap("/*", map[string]ghttp.HandlerFunc{ - ghttp.HOOK_BEFORE_SERVE: RedisHandlerGet, - ghttp.HOOK_AFTER_SERVE: RedisHandlerSet, - }) s.SetPort(8199) s.Run() } diff --git a/.example/other/test.go b/.example/other/test.go index 8e1037ace..02d872135 100644 --- a/.example/other/test.go +++ b/.example/other/test.go @@ -1,47 +1,12 @@ package main import ( - "github.com/gogf/gf/frame/g" - "github.com/gogf/gf/net/ghttp" + "github.com/gogf/gf/internal/intlog" ) -func loadRouter(domain *ghttp.Domain) { - domain.Group("/", func(g *ghttp.RouterGroup) { - g.Group("/app", func(gApp *ghttp.RouterGroup) { - // 该路由规则仅会在GET请求下有效 - gApp.GET("/{table}/list/{page}.html", func(r *ghttp.Request) { - r.Response.WriteJson(r.Router) - }) - // 该路由规则仅会在GET请求及localhost域名下有效 - gApp.GET("/order/info/{order_id}", func(r *ghttp.Request) { - r.Response.WriteJson(r.Router) - }) - // 该路由规则仅会在DELETE请求下有效 - gApp.DELETE("/comment/{id}", func(r *ghttp.Request) { - r.Response.WriteJson(r.Router) - }) - }) - // 该路由规则仅会在GET请求下有效 - g.GET("/{table}/list/{page}.html", func(r *ghttp.Request) { - r.Response.WriteJson(r.Router) - }) - // 该路由规则仅会在GET请求及localhost域名下有效 - g.GET("/order/info/{order_id}", func(r *ghttp.Request) { - r.Response.WriteJson(r.Router) - }) - // 该路由规则仅会在DELETE请求下有效 - g.DELETE("/comment/{id}", func(r *ghttp.Request) { - r.Response.WriteJson(r.Router) - }) - }) -} - func main() { - s := g.Server() - - domain := s.Domain("localhost") - loadRouter(domain) - - s.SetPort(8199) - s.Run() + intlog.Print(1, 2, 3) + intlog.Printf("%d", 1) + intlog.Error(1) + intlog.Errorf("%d", 1) } diff --git a/debug/gdebug/gdebug.go b/debug/gdebug/gdebug.go index ee5d8e1ab..335ef3e59 100644 --- a/debug/gdebug/gdebug.go +++ b/debug/gdebug/gdebug.go @@ -227,3 +227,9 @@ func CallerFileLine() string { _, path, line := Caller() return fmt.Sprintf(`%s:%d`, path, line) } + +// CallerFileLineShort returns the file name along with the line number of the caller. +func CallerFileLineShort() string { + _, path, line := Caller() + return fmt.Sprintf(`%s:%d`, filepath.Base(path), line) +} diff --git a/internal/intlog/intlog.go b/internal/intlog/intlog.go new file mode 100644 index 000000000..1582b27c1 --- /dev/null +++ b/internal/intlog/intlog.go @@ -0,0 +1,83 @@ +// 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 intlog provides internal logging for GF development usage only. +package intlog + +import ( + "fmt" + "github.com/gogf/gf/debug/gdebug" + "os" + "path/filepath" + "time" +) + +const ( + gFILTER_KEY = "/internal/intlog" +) + +var ( + // isInGFDevelop marks whether current environment is in GF development. + isInGFDevelop = false +) + +func init() { + if os.Getenv("GF_DEV") != "" { + isInGFDevelop = true + return + } +} + +// Print prints with newline using fmt.Println. +// The parameter can be multiple variables. +func Print(v ...interface{}) { + if !isInGFDevelop { + return + } + fmt.Println(append([]interface{}{now(), "[INTE]", file()}, v...)...) +} + +// Printf prints with format using fmt.Printf. +// The parameter can be multiple variables. +func Printf(format string, v ...interface{}) { + if !isInGFDevelop { + return + } + fmt.Printf(now()+" [INTE] "+file()+" "+format+"\n", v...) +} + +// Error prints with newline using fmt.Println. +// The parameter can be multiple variables. +func Error(v ...interface{}) { + if !isInGFDevelop { + return + } + array := append([]interface{}{now(), "[INTE]", file()}, v...) + array = append(array, "\n"+gdebug.StackWithFilter(gFILTER_KEY)) + fmt.Println(array...) +} + +// Errorf prints with format using fmt.Printf. +func Errorf(format string, v ...interface{}) { + if !isInGFDevelop { + return + } + fmt.Printf( + now()+" [INTE] "+file()+" "+format+"\n%s\n", + append(v, gdebug.StackWithFilter(gFILTER_KEY))..., + ) +} + +// now returns current time string. +func now() string { + return time.Now().Format("2006-01-02 15:04:05.000") +} + +// file returns caller file name along with its line number. +func file() string { + _, p, l := gdebug.CallerWithFilter(gFILTER_KEY) + return fmt.Sprintf(`%s:%d`, filepath.Base(p), l) +} diff --git a/net/ghttp/ghttp_server.go b/net/ghttp/ghttp_server.go index 7ae6bfb1c..5d226a1f3 100644 --- a/net/ghttp/ghttp_server.go +++ b/net/ghttp/ghttp_server.go @@ -213,10 +213,7 @@ func GetServer(name ...interface{}) *Server { if s := serverMapping.Get(serverName); s != nil { return s.(*Server) } - config := defaultServerConfig - if config.SessionStorage == nil { - config.SessionStorage = gsession.NewStorageFile() - } + c := defaultServerConfig s := &Server{ name: serverName, servers: make([]*gracefulServer, 0), @@ -226,12 +223,11 @@ func GetServer(name ...interface{}) *Server { serveTree: make(map[string]interface{}), serveCache: gcache.New(), routesMap: make(map[string][]registeredRouteItem), - sessionManager: gsession.New(config.SessionMaxAge, config.SessionStorage), servedCount: gtype.NewInt(), logger: glog.New(), } // 初始化时使用默认配置 - s.SetConfig(config) + s.SetConfig(c) // 记录到全局ServerMap中 serverMapping.Set(serverName, s) return s @@ -256,6 +252,13 @@ func (s *Server) Start() error { glog.Fatal("[ghttp] no router set or static feature enabled, did you forget import the router?") } + // Default session storage. + if s.config.SessionStorage == nil { + s.config.SessionStorage = gsession.NewStorageFile() + } + // Initialize session manager when start running. + s.sessionManager = gsession.New(s.config.SessionMaxAge, s.config.SessionStorage) + // 底层http server配置 if s.config.Handler == nil { s.config.Handler = http.HandlerFunc(s.defaultHttpHandle) diff --git a/net/ghttp/ghttp_server_config.go b/net/ghttp/ghttp_server_config.go index d71c04570..d468da3c7 100644 --- a/net/ghttp/ghttp_server_config.go +++ b/net/ghttp/ghttp_server_config.go @@ -61,8 +61,8 @@ type ServerConfig struct { CookiePath string // Cookie: 有效Path(注意同时也会影响SessionID) CookieDomain string // Cookie: 有效Domain(注意同时也会影响SessionID) SessionMaxAge time.Duration // Session: 有效期 - SessionIdName string // Session: SessionId - SessionStorage gsession.Storage // Session: 存储路径 + SessionIdName string // Session: SessionId. + SessionStorage gsession.Storage // Session: Session Storage implementer. DenyIps []string // Security: 不允许访问的ip列表,支持ip前缀过滤,如: 10 将不允许10开头的ip访问 AllowIps []string // Security: 仅允许访问的ip列表,支持ip前缀过滤,如: 10 将仅允许10开头的ip访问 DenyRoutes []string // Security: 不允许访问的路由规则列表 diff --git a/net/ghttp/ghttp_server_config_session.go b/net/ghttp/ghttp_server_config_session.go index aec3d041f..891ea2669 100644 --- a/net/ghttp/ghttp_server_config_session.go +++ b/net/ghttp/ghttp_server_config_session.go @@ -15,7 +15,6 @@ import ( // 设置http server参数 - SessionMaxAge func (s *Server) SetSessionMaxAge(ttl time.Duration) { s.config.SessionMaxAge = ttl - s.sessionManager.SetTTL(ttl) } // 设置http server参数 - SessionIdName @@ -26,7 +25,6 @@ func (s *Server) SetSessionIdName(name string) { // 设置http server参数 - SessionStorage func (s *Server) SetSessionStorage(storage gsession.Storage) { s.config.SessionStorage = storage - s.sessionManager.SetStorage(storage) } // 获取http server参数 - SessionMaxAge diff --git a/os/gcache/gcache_mem_cache.go b/os/gcache/gcache_mem_cache.go index f7ed0c71f..b17a7ff0c 100644 --- a/os/gcache/gcache_mem_cache.go +++ b/os/gcache/gcache_mem_cache.go @@ -27,14 +27,14 @@ type memCache struct { // limits the size of the cache pool. // If the size of the cache exceeds the , - // the cache expiration process is performed according to the LRU algorithm. + // the cache expiration process performs according to the LRU algorithm. // It is 0 in default which means no limits. cap int data map[interface{}]memCacheItem // Underlying cache data which is stored in a hash table. expireTimes map[interface{}]int64 // Expiring key mapping to its timestamp, which is used for quick indexing and deleting. expireSets map[int64]*gset.Set // Expiring timestamp mapping to its key set, which is used for quick indexing and deleting. - lru *memCacheLru // LRU object, which is enabled when > 0. + lru *memCacheLru // LRU manager, which is enabled when > 0. lruGetList *glist.List // LRU history according with Get function. eventList *glist.List // Asynchronous event list for internal data synchronization. closed *gtype.Bool // Is this cache closed or not. @@ -422,7 +422,7 @@ func (c *memCache) syncEventAndClearExpired() { } // clearByKey deletes the key-value pair with given . -// The parameter specifies whether doing this deleting forcely. +// The parameter specifies whether doing this deleting forcedly. func (c *memCache) clearByKey(key interface{}, force ...bool) { c.dataMu.Lock() // Doubly check before really deleting it from cache. diff --git a/os/gsession/gsession_manager.go b/os/gsession/gsession_manager.go index 4891eb770..1e462675e 100644 --- a/os/gsession/gsession_manager.go +++ b/os/gsession/gsession_manager.go @@ -23,11 +23,12 @@ type Manager struct { func New(ttl time.Duration, storage ...Storage) *Manager { m := &Manager{ ttl: ttl, - storage: NewStorageFile(), sessions: gcache.New(), } if len(storage) > 0 && storage[0] != nil { m.storage = storage[0] + } else { + m.storage = NewStorageFile() } return m } diff --git a/os/gsession/gsession_storage_file.go b/os/gsession/gsession_storage_file.go index aa54dcf16..68a479c72 100644 --- a/os/gsession/gsession_storage_file.go +++ b/os/gsession/gsession_storage_file.go @@ -9,6 +9,7 @@ package gsession import ( "encoding/json" "errors" + "github.com/gogf/gf/internal/intlog" "os" "time" @@ -74,13 +75,18 @@ func NewStorageFile(path ...string) *StorageFile { } // Batch updates the TTL for session ids timely. gtimer.AddSingleton(DefaultStorageFileLoopInterval, func() { - id := "" + intlog.Print("StorageFile.timer start") + var id string + var err error for { if id = s.updatingIdSet.Pop(); id == "" { break } - s.doUpdateTTL(id) + if err = s.doUpdateTTL(id); err != nil { + intlog.Error(err) + } } + intlog.Print("StorageFile.timer end") }) return s } @@ -208,12 +214,15 @@ 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 { - s.updatingIdSet.Add(id) + if ttl >= DefaultStorageRedisLoopInterval { + s.updatingIdSet.Add(id) + } return nil } // doUpdateTTL updates the TTL for session id. func (s *StorageFile) doUpdateTTL(id string) error { + intlog.Printf("update StorageFile TTL for session id: %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 9ce7322d5..179921605 100644 --- a/os/gsession/gsession_storage_redis.go +++ b/os/gsession/gsession_storage_redis.go @@ -10,6 +10,7 @@ import ( "encoding/json" "github.com/gogf/gf/container/gmap" "github.com/gogf/gf/database/gredis" + "github.com/gogf/gf/internal/intlog" "time" "github.com/gogf/gf/os/gtimer" @@ -42,15 +43,20 @@ func NewStorageRedis(redis *gredis.Redis, prefix ...string) *StorageRedis { } // Batch updates the TTL for session ids timely. gtimer.AddSingleton(DefaultStorageRedisLoopInterval, func() { + intlog.Print("StorageRedis.timer start") var id string + var err error var ttlSeconds int for { if id, ttlSeconds = s.updatingIdMap.Pop(); id == "" { break } else { - s.doUpdateTTL(id, ttlSeconds) + if err = s.doUpdateTTL(id, ttlSeconds); err != nil { + intlog.Error(err) + } } } + intlog.Print("StorageRedis.timer end") }) return s } @@ -105,7 +111,8 @@ func (s *StorageRedis) RemoveAll(id string) error { func (s *StorageRedis) GetSession(id string, ttl time.Duration) map[string]interface{} { r, err := s.redis.DoVar("GET", s.key(id)) if err != nil { - return nil + // If it does not exist, it returns an empty map to replace the session memory data. + return map[string]interface{}{} } var m map[string]interface{} if err = json.Unmarshal(r.Bytes(), &m); err != nil { @@ -130,12 +137,15 @@ 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 { - s.updatingIdMap.Set(id, int(ttl.Seconds())) + if ttl >= DefaultStorageRedisLoopInterval { + s.updatingIdMap.Set(id, int(ttl.Seconds())) + } return nil } // 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) _, 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 552aae030..df1541cd2 100644 --- a/os/gsession/gsession_storage_redis_hashtable.go +++ b/os/gsession/gsession_storage_redis_hashtable.go @@ -8,6 +8,7 @@ package gsession import ( "github.com/gogf/gf/database/gredis" + "github.com/gogf/gf/internal/intlog" "github.com/gogf/gf/util/gconv" "time" ) @@ -115,6 +116,7 @@ func (s *StorageRedisHashTable) GetSession(id string, ttl time.Duration) map[str // 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 { + intlog.Printf("StorageRedisHashTable.SetSession: %s, %v", id, ttl) _, err := s.redis.Do("EXPIRE", s.key(id), ttl.Seconds()) return err } @@ -123,6 +125,7 @@ func (s *StorageRedisHashTable) SetSession(id string, data map[string]interface{ // 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 *StorageRedisHashTable) UpdateTTL(id string, ttl time.Duration) error { + intlog.Printf("StorageRedisHashTable.UpdateTTL: %s, %v", id, ttl) _, err := s.redis.Do("EXPIRE", s.key(id), ttl.Seconds()) return err }