package sessions import ( "errors" "reflect" "sync" "time" "github.com/kataras/iris/v12/context" "github.com/kataras/iris/v12/core/memstore" "github.com/kataras/golog" ) // ErrNotImplemented is returned when a particular feature is not yet implemented yet. // It can be matched directly, i.e: `isNotImplementedError := sessions.ErrNotImplemented.Equal(err)`. var ErrNotImplemented = errors.New("not implemented yet") // Database is the interface which all session databases should implement // By design it doesn't support any type of cookie session like other frameworks. // I want to protect you, believe me. // The scope of the database is to store somewhere the sessions in order to // keep them after restarting the server, nothing more. // // Synchronization are made automatically, you can register one using `UseDatabase`. // // Look the `sessiondb` folder for databases implementations. type Database interface { // SetLogger should inject a logger to this Database. SetLogger(*golog.Logger) // Acquire receives a session's lifetime from the database, // if the return value is LifeTime{} then the session manager sets the life time based on the expiration duration lives in configuration. Acquire(sid string, expires time.Duration) LifeTime // OnUpdateExpiration should re-set the expiration (ttl) of the session entry inside the database, // it is fired on `ShiftExpiration` and `UpdateExpiration`. // If the database does not support change of ttl then the session entry will be cloned to another one // and the old one will be removed, it depends on the chosen database storage. // // Check of error is required, if error returned then the rest session's keys are not proceed. // // If a database does not support this feature then an `ErrNotImplemented` will be returned instead. OnUpdateExpiration(sid string, newExpires time.Duration) error // Set sets a key value of a specific session. // The "immutable" input argument depends on the store, it may not implement it at all. Set(sid string, key string, value interface{}, ttl time.Duration, immutable bool) error // Get retrieves a session value based on the key. Get(sid string, key string) interface{} // Decode binds the "outPtr" to the value associated to the provided "key". Decode(sid, key string, outPtr interface{}) error // Visit loops through all session keys and values. Visit(sid string, cb func(key string, value interface{})) error // Len returns the length of the session's entries (keys). Len(sid string) int // Delete removes a session key value based on its key. Delete(sid string, key string) (deleted bool) // Clear removes all session key values but it keeps the session entry. Clear(sid string) error // Release destroys the session, it clears and removes the session entry, // session manager will create a new session ID on the next request after this call. Release(sid string) error // Close should terminate the database connection. It's called automatically on interrupt signals. Close() error } // DatabaseRequestHandler is an optional interface that a sessions database // can implement. It contains a single EndRequest method which is fired // on the very end of the request life cycle. It should be used to Flush // any local session's values to the client. type DatabaseRequestHandler interface { EndRequest(ctx *context.Context, session *Session) } type mem struct { values map[string]*memstore.Store mu sync.RWMutex } var _ Database = (*mem)(nil) func newMemDB() Database { return &mem{values: make(map[string]*memstore.Store)} } func (s *mem) SetLogger(*golog.Logger) {} func (s *mem) Acquire(sid string, expires time.Duration) LifeTime { s.mu.Lock() s.values[sid] = new(memstore.Store) s.mu.Unlock() return LifeTime{} } // Do nothing, the `LifeTime` of the Session will be managed by the callers automatically on memory-based storage. func (s *mem) OnUpdateExpiration(string, time.Duration) error { return nil } // immutable depends on the store, it may not implement it at all. func (s *mem) Set(sid string, key string, value interface{}, _ time.Duration, immutable bool) error { s.mu.RLock() store, ok := s.values[sid] s.mu.RUnlock() if ok { store.Save(key, value, immutable) } return nil } func (s *mem) Get(sid string, key string) interface{} { s.mu.RLock() store, ok := s.values[sid] s.mu.RUnlock() if ok { return store.Get(key) } return nil } func (s *mem) Decode(sid string, key string, outPtr interface{}) error { v := s.Get(sid, key) if v != nil { reflect.ValueOf(outPtr).Set(reflect.ValueOf(v)) } return nil } func (s *mem) Visit(sid string, cb func(key string, value interface{})) error { s.mu.RLock() store, ok := s.values[sid] s.mu.RUnlock() if ok { store.Visit(cb) } return nil } func (s *mem) Len(sid string) int { s.mu.RLock() store, ok := s.values[sid] s.mu.RUnlock() if ok { return store.Len() } return 0 } func (s *mem) Delete(sid string, key string) (deleted bool) { s.mu.RLock() store, ok := s.values[sid] s.mu.RUnlock() if ok { deleted = store.Remove(key) } return } func (s *mem) Clear(sid string) error { s.mu.RLock() store, ok := s.values[sid] s.mu.RUnlock() if ok { store.Reset() } return nil } func (s *mem) Release(sid string) error { s.mu.Lock() delete(s.values, sid) s.mu.Unlock() return nil } func (s *mem) Close() error { return nil }