github.com/gogf/gf@v1.16.9/os/gsession/gsession_storage_file.go (about) 1 // Copyright GoFrame Author(https://goframe.org). All Rights Reserved. 2 // 3 // This Source Code Form is subject to the terms of the MIT License. 4 // If a copy of the MIT was not distributed with this file, 5 // You can obtain one at https://github.com/gogf/gf. 6 7 package gsession 8 9 import ( 10 "context" 11 "github.com/gogf/gf/container/gmap" 12 "github.com/gogf/gf/errors/gcode" 13 "github.com/gogf/gf/errors/gerror" 14 "github.com/gogf/gf/internal/intlog" 15 "github.com/gogf/gf/internal/json" 16 "os" 17 "time" 18 19 "github.com/gogf/gf/crypto/gaes" 20 21 "github.com/gogf/gf/os/gtimer" 22 23 "github.com/gogf/gf/container/gset" 24 "github.com/gogf/gf/encoding/gbinary" 25 26 "github.com/gogf/gf/os/gtime" 27 28 "github.com/gogf/gf/os/gfile" 29 ) 30 31 // StorageFile implements the Session Storage interface with file system. 32 type StorageFile struct { 33 path string 34 cryptoKey []byte 35 cryptoEnabled bool 36 updatingIdSet *gset.StrSet 37 } 38 39 var ( 40 DefaultStorageFilePath = gfile.TempDir("gsessions") 41 DefaultStorageFileCryptoKey = []byte("Session storage file crypto key!") 42 DefaultStorageFileCryptoEnabled = false 43 DefaultStorageFileLoopInterval = 10 * time.Second 44 ) 45 46 // NewStorageFile creates and returns a file storage object for session. 47 func NewStorageFile(path ...string) *StorageFile { 48 storagePath := DefaultStorageFilePath 49 if len(path) > 0 && path[0] != "" { 50 storagePath, _ = gfile.Search(path[0]) 51 if storagePath == "" { 52 panic(gerror.NewCodef(gcode.CodeInvalidParameter, `"%s" does not exist`, path[0])) 53 } 54 if !gfile.IsWritable(storagePath) { 55 panic(gerror.NewCodef(gcode.CodeInvalidParameter, `"%s" is not writable`, path[0])) 56 } 57 } 58 if storagePath != "" { 59 if err := gfile.Mkdir(storagePath); err != nil { 60 panic(gerror.WrapCodef(gcode.CodeInternalError, err, `Mkdir "%s" failed in PWD "%s"`, path, gfile.Pwd())) 61 } 62 } 63 s := &StorageFile{ 64 path: storagePath, 65 cryptoKey: DefaultStorageFileCryptoKey, 66 cryptoEnabled: DefaultStorageFileCryptoEnabled, 67 updatingIdSet: gset.NewStrSet(true), 68 } 69 70 gtimer.AddSingleton(DefaultStorageFileLoopInterval, s.updateSessionTimely) 71 return s 72 } 73 74 // updateSessionTimely batch updates the TTL for sessions timely. 75 func (s *StorageFile) updateSessionTimely() { 76 var ( 77 id string 78 err error 79 ) 80 // Batch updating sessions. 81 for { 82 if id = s.updatingIdSet.Pop(); id == "" { 83 break 84 } 85 if err = s.updateSessionTTl(context.TODO(), id); err != nil { 86 intlog.Error(context.TODO(), err) 87 } 88 } 89 } 90 91 // SetCryptoKey sets the crypto key for session storage. 92 // The crypto key is used when crypto feature is enabled. 93 func (s *StorageFile) SetCryptoKey(key []byte) { 94 s.cryptoKey = key 95 } 96 97 // SetCryptoEnabled enables/disables the crypto feature for session storage. 98 func (s *StorageFile) SetCryptoEnabled(enabled bool) { 99 s.cryptoEnabled = enabled 100 } 101 102 // sessionFilePath returns the storage file path for given session id. 103 func (s *StorageFile) sessionFilePath(id string) string { 104 return gfile.Join(s.path, id) 105 } 106 107 // New creates a session id. 108 // This function can be used for custom session creation. 109 func (s *StorageFile) New(ctx context.Context, ttl time.Duration) (id string, err error) { 110 return "", ErrorDisabled 111 } 112 113 // Get retrieves session value with given key. 114 // It returns nil if the key does not exist in the session. 115 func (s *StorageFile) Get(ctx context.Context, id string, key string) (value interface{}, err error) { 116 return nil, ErrorDisabled 117 } 118 119 // GetMap retrieves all key-value pairs as map from storage. 120 func (s *StorageFile) GetMap(ctx context.Context, id string) (data map[string]interface{}, err error) { 121 return nil, ErrorDisabled 122 } 123 124 // GetSize retrieves the size of key-value pairs from storage. 125 func (s *StorageFile) GetSize(ctx context.Context, id string) (size int, err error) { 126 return -1, ErrorDisabled 127 } 128 129 // Set sets key-value session pair to the storage. 130 // The parameter `ttl` specifies the TTL for the session id (not for the key-value pair). 131 func (s *StorageFile) Set(ctx context.Context, id string, key string, value interface{}, ttl time.Duration) error { 132 return ErrorDisabled 133 } 134 135 // SetMap batch sets key-value session pairs with map to the storage. 136 // The parameter `ttl` specifies the TTL for the session id(not for the key-value pair). 137 func (s *StorageFile) SetMap(ctx context.Context, id string, data map[string]interface{}, ttl time.Duration) error { 138 return ErrorDisabled 139 } 140 141 // Remove deletes key with its value from storage. 142 func (s *StorageFile) Remove(ctx context.Context, id string, key string) error { 143 return ErrorDisabled 144 } 145 146 // RemoveAll deletes all key-value pairs from storage. 147 func (s *StorageFile) RemoveAll(ctx context.Context, id string) error { 148 return ErrorDisabled 149 } 150 151 // GetSession returns the session data as *gmap.StrAnyMap for given session id from storage. 152 // 153 // The parameter `ttl` specifies the TTL for this session, and it returns nil if the TTL is exceeded. 154 // The parameter `data` is the current old session data stored in memory, 155 // and for some storage it might be nil if memory storage is disabled. 156 // 157 // This function is called ever when session starts. 158 func (s *StorageFile) GetSession(ctx context.Context, id string, ttl time.Duration, data *gmap.StrAnyMap) (*gmap.StrAnyMap, error) { 159 if data != nil { 160 return data, nil 161 } 162 path := s.sessionFilePath(id) 163 content := gfile.GetBytes(path) 164 if len(content) > 8 { 165 timestampMilli := gbinary.DecodeToInt64(content[:8]) 166 if timestampMilli+ttl.Nanoseconds()/1e6 < gtime.TimestampMilli() { 167 return nil, nil 168 } 169 var err error 170 content = content[8:] 171 // Decrypt with AES. 172 if s.cryptoEnabled { 173 content, err = gaes.Decrypt(content, DefaultStorageFileCryptoKey) 174 if err != nil { 175 return nil, err 176 } 177 } 178 var m map[string]interface{} 179 if err = json.UnmarshalUseNumber(content, &m); err != nil { 180 return nil, err 181 } 182 if m == nil { 183 return nil, nil 184 } 185 return gmap.NewStrAnyMapFrom(m, true), nil 186 } 187 return nil, nil 188 } 189 190 // SetSession updates the data map for specified session id. 191 // This function is called ever after session, which is changed dirty, is closed. 192 // This copy all session data map from memory to storage. 193 func (s *StorageFile) SetSession(ctx context.Context, id string, data *gmap.StrAnyMap, ttl time.Duration) error { 194 intlog.Printf(ctx, "StorageFile.SetSession: %s, %v, %v", id, data, ttl) 195 path := s.sessionFilePath(id) 196 content, err := json.Marshal(data) 197 if err != nil { 198 return err 199 } 200 // Encrypt with AES. 201 if s.cryptoEnabled { 202 content, err = gaes.Encrypt(content, DefaultStorageFileCryptoKey) 203 if err != nil { 204 return err 205 } 206 } 207 file, err := gfile.OpenWithFlagPerm( 208 path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm, 209 ) 210 if err != nil { 211 return err 212 } 213 defer file.Close() 214 if _, err = file.Write(gbinary.EncodeInt64(gtime.TimestampMilli())); err != nil { 215 return err 216 } 217 if _, err = file.Write(content); err != nil { 218 return err 219 } 220 return nil 221 } 222 223 // UpdateTTL updates the TTL for specified session id. 224 // This function is called ever after session, which is not dirty, is closed. 225 // It just adds the session id to the async handling queue. 226 func (s *StorageFile) UpdateTTL(ctx context.Context, id string, ttl time.Duration) error { 227 intlog.Printf(ctx, "StorageFile.UpdateTTL: %s, %v", id, ttl) 228 if ttl >= DefaultStorageFileLoopInterval { 229 s.updatingIdSet.Add(id) 230 } 231 return nil 232 } 233 234 // updateSessionTTL updates the TTL for specified session id. 235 func (s *StorageFile) updateSessionTTl(ctx context.Context, id string) error { 236 intlog.Printf(ctx, "StorageFile.updateSession: %s", id) 237 path := s.sessionFilePath(id) 238 file, err := gfile.OpenWithFlag(path, os.O_WRONLY) 239 if err != nil { 240 return err 241 } 242 if _, err = file.WriteAt(gbinary.EncodeInt64(gtime.TimestampMilli()), 0); err != nil { 243 return err 244 } 245 return file.Close() 246 }