123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209 |
- package sessions
- import (
- "errors"
- "sync"
- "time"
- "github.com/kataras/iris/v12/context"
- )
- type (
- // provider contains the sessions and external databases (load and update).
- // It's the session memory manager
- provider struct {
- mu sync.RWMutex
- sessions map[string]*Session
- db Database
- dbRequestHandler DatabaseRequestHandler
- destroyListeners []DestroyListener
- }
- )
- // newProvider returns a new sessions provider
- func newProvider() *provider {
- p := &provider{
- sessions: make(map[string]*Session),
- db: newMemDB(),
- }
- return p
- }
- // RegisterDatabase sets a session database.
- func (p *provider) RegisterDatabase(db Database) {
- if db == nil {
- return
- }
- p.mu.Lock() // for any case
- p.db = db
- if dbreq, ok := db.(DatabaseRequestHandler); ok {
- p.dbRequestHandler = dbreq
- }
- p.mu.Unlock()
- }
- // newSession returns a new session from sessionid
- func (p *provider) newSession(man *Sessions, sid string, expires time.Duration) *Session {
- sess := &Session{
- sid: sid,
- Man: man,
- provider: p,
- }
- onExpire := func() {
- p.mu.Lock()
- p.deleteSession(sess)
- p.mu.Unlock()
- }
- lifetime := p.db.Acquire(sid, expires)
- // simple and straight:
- if !lifetime.IsZero() {
- // if stored time is not zero
- // start a timer based on the stored time, if not expired.
- lifetime.Revive(onExpire)
- } else {
- // Remember: if db not exist or it has been expired
- // then the stored time will be zero(see loadSessionFromDB) and the values will be empty.
- //
- // Even if the database has an unlimited session (possible by a previous app run)
- // priority to the "expires" is given,
- // again if <=0 then it does nothing.
- lifetime.Begin(expires, onExpire)
- }
- sess.Lifetime = &lifetime
- return sess
- }
- // Init creates the session and returns it
- func (p *provider) Init(man *Sessions, sid string, expires time.Duration) *Session {
- newSession := p.newSession(man, sid, expires)
- newSession.isNew = true
- p.mu.Lock()
- p.sessions[sid] = newSession
- p.mu.Unlock()
- return newSession
- }
- func (p *provider) EndRequest(ctx *context.Context, session *Session) {
- if p.dbRequestHandler != nil {
- p.dbRequestHandler.EndRequest(ctx, session)
- }
- }
- // ErrNotFound may be returned from `UpdateExpiration` of a non-existing or
- // invalid session entry from memory storage or databases.
- // Usage:
- //
- // if err != nil && err.Is(err, sessions.ErrNotFound) {
- // [handle error...]
- // }
- var ErrNotFound = errors.New("session not found")
- // UpdateExpiration resets the expiration of a session.
- // if expires > 0 then it will try to update the expiration and destroy task is delayed.
- // if expires <= 0 then it does nothing it returns nil, to destroy a session call the `Destroy` func instead.
- //
- // If the session is not found, it returns a `NotFound` error, this can only happen when you restart the server and you used the memory-based storage(default),
- // because the call of the provider's `UpdateExpiration` is always called when the client has a valid session cookie.
- //
- // If a backend database is used then it may return an `ErrNotImplemented` error if the underline database does not support this operation.
- func (p *provider) UpdateExpiration(sid string, expires time.Duration) error {
- if expires <= 0 {
- return nil
- }
- p.mu.RLock()
- sess, found := p.sessions[sid]
- p.mu.RUnlock()
- if !found {
- return ErrNotFound
- }
- sess.Lifetime.Shift(expires)
- return p.db.OnUpdateExpiration(sid, expires)
- }
- // Read returns the store which sid parameter belongs
- func (p *provider) Read(man *Sessions, sid string, expires time.Duration) *Session {
- p.mu.RLock()
- sess, found := p.sessions[sid]
- p.mu.RUnlock()
- if found {
- sess.mu.Lock()
- sess.isNew = false
- sess.mu.Unlock()
- sess.runFlashGC() // run the flash messages GC, new request here of existing session
- return sess
- }
- return p.Init(man, sid, expires) // if not found create new
- }
- func (p *provider) registerDestroyListener(ln DestroyListener) {
- if ln == nil {
- return
- }
- p.destroyListeners = append(p.destroyListeners, ln)
- }
- func (p *provider) fireDestroy(sid string) {
- for _, ln := range p.destroyListeners {
- ln(sid)
- }
- }
- // Destroy destroys the session, removes all sessions and flash values,
- // the session itself and updates the registered session databases,
- // this called from sessionManager which removes the client's cookie also.
- func (p *provider) Destroy(sid string) {
- p.mu.Lock()
- if sess, found := p.sessions[sid]; found {
- p.deleteSession(sess)
- }
- p.mu.Unlock()
- }
- // DestroyAll removes all sessions
- // from the server-side memory (and database if registered).
- // Client's session cookie will still exist but it will be reseted on the next request.
- func (p *provider) DestroyAll() {
- p.mu.Lock()
- for _, sess := range p.sessions {
- p.deleteSession(sess)
- }
- p.mu.Unlock()
- }
- func (p *provider) deleteSession(sess *Session) {
- sid := sess.sid
- delete(p.sessions, sid)
- p.db.Release(sid)
- p.fireDestroy(sid)
- }
- /*
- func (p *provider) regenerateID(ctx *context.Context, oldsid string) {
- p.mu.RLock()
- sess, ok := p.sessions[oldsid]
- p.mu.RUnlock()
- if ok {
- newsid := sess.Man.config.SessionIDGenerator(ctx)
- sess.mu.Lock()
- sess.sid = newsid
- sess.mu.Unlock()
- p.mu.Lock()
- p.sessions[newsid] = sess
- delete(p.sessions, oldsid)
- p.mu.Unlock()
- }
- }
- */
|