2017-02-15 19:06:19 +01:00
package sessions
import (
"sync"
"time"
2018-08-14 15:29:04 +02:00
"github.com/kataras/iris/core/errors"
2017-02-15 19:06:19 +01:00
)
type (
// provider contains the sessions and external databases (load and update).
// It's the session memory manager
provider struct {
// we don't use RWMutex because all actions have read and write at the same action function.
2017-07-10 17:32:42 +02:00
// (or write to a *Session's value which is race if we don't lock)
2017-02-15 19:06:19 +01:00
// narrow locks are fasters but are useless here.
2018-04-22 13:13:40 +02:00
mu sync . Mutex
sessions map [ string ] * Session
db Database
destroyListeners [ ] DestroyListener
2017-02-15 19:06:19 +01:00
}
)
// newProvider returns a new sessions provider
func newProvider ( ) * provider {
return & provider {
2018-04-22 12:52:36 +02:00
sessions : make ( map [ string ] * Session , 0 ) ,
db : newMemDB ( ) ,
2017-02-15 19:06:19 +01:00
}
}
2018-04-22 12:52:36 +02:00
// RegisterDatabase sets a session database.
2017-02-15 19:06:19 +01:00
func ( p * provider ) RegisterDatabase ( db Database ) {
p . mu . Lock ( ) // for any case
2018-04-22 12:52:36 +02:00
p . db = db
2017-02-15 19:06:19 +01:00
p . mu . Unlock ( )
}
2017-08-07 05:04:35 +02:00
// newSession returns a new session from sessionid
func ( p * provider ) newSession ( sid string , expires time . Duration ) * Session {
onExpire := func ( ) {
p . Destroy ( sid )
2017-07-31 20:49:30 +02:00
}
2018-04-22 12:52:36 +02:00
lifetime := p . db . Acquire ( sid , expires )
2017-08-07 05:04:35 +02:00
// 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 )
}
2017-07-31 20:49:30 +02:00
2017-07-10 17:32:42 +02:00
sess := & Session {
2017-02-15 19:06:19 +01:00
sid : sid ,
provider : p ,
flashes : make ( map [ string ] * flashMessage ) ,
2018-04-22 12:52:36 +02:00
Lifetime : lifetime ,
2017-02-15 19:06:19 +01:00
}
return sess
}
// Init creates the session and returns it
2017-07-10 17:32:42 +02:00
func ( p * provider ) Init ( sid string , expires time . Duration ) * Session {
2017-02-15 19:06:19 +01:00
newSession := p . newSession ( sid , expires )
p . mu . Lock ( )
p . sessions [ sid ] = newSession
p . mu . Unlock ( )
return newSession
}
2018-08-14 15:29:04 +02:00
// ErrNotFound can be returned when calling `UpdateExpiration` on a non-existing or invalid session entry.
// It can be matched directly, i.e: `isErrNotFound := sessions.ErrNotFound.Equal(err)`.
var ErrNotFound = errors . New ( "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 {
2017-07-31 20:49:30 +02:00
if expires <= 0 {
2018-08-14 15:29:04 +02:00
return nil
2017-07-31 20:49:30 +02:00
}
p . mu . Lock ( )
sess , found := p . sessions [ sid ]
p . mu . Unlock ( )
if ! found {
2018-08-14 15:29:04 +02:00
return ErrNotFound
2017-07-31 20:49:30 +02:00
}
2018-04-22 12:52:36 +02:00
sess . Lifetime . Shift ( expires )
2018-08-14 15:29:04 +02:00
return p . db . OnUpdateExpiration ( sid , expires )
2017-07-31 20:49:30 +02:00
}
2017-02-15 19:06:19 +01:00
// Read returns the store which sid parameter belongs
2017-07-10 17:32:42 +02:00
func ( p * provider ) Read ( sid string , expires time . Duration ) * Session {
2017-02-15 19:06:19 +01:00
p . mu . Lock ( )
if sess , found := p . sessions [ sid ] ; found {
sess . runFlashGC ( ) // run the flash messages GC, new request here of existing session
p . mu . Unlock ( )
2017-07-31 20:49:30 +02:00
2017-02-15 19:06:19 +01:00
return sess
}
p . mu . Unlock ( )
2017-07-31 20:49:30 +02:00
return p . Init ( sid , expires ) // if not found create new
2017-02-15 19:06:19 +01:00
}
2018-04-22 13:13:40 +02:00
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 )
}
}
2017-02-15 19:06:19 +01:00
// 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 {
2017-08-07 05:04:35 +02:00
p . deleteSession ( sess )
2017-02-15 19:06:19 +01:00
}
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 {
2017-08-07 05:04:35 +02:00
p . deleteSession ( sess )
2017-02-15 19:06:19 +01:00
}
p . mu . Unlock ( )
2017-08-07 05:04:35 +02:00
}
2017-02-15 19:06:19 +01:00
2017-08-07 05:04:35 +02:00
func ( p * provider ) deleteSession ( sess * Session ) {
2018-04-22 13:13:40 +02:00
sid := sess . sid
delete ( p . sessions , sid )
p . db . Release ( sid )
p . fireDestroy ( sid )
2017-02-15 19:06:19 +01:00
}