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  }