2017-02-15 19:06:19 +01:00
package sessions
import (
2019-10-24 17:57:05 +02:00
"errors"
2017-02-15 19:06:19 +01:00
"sync"
"time"
)
type (
// provider contains the sessions and external databases (load and update).
// It's the session memory manager
provider struct {
2020-05-17 21:08:43 +02:00
mu sync . RWMutex
2018-04-22 13:13:40 +02:00
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 {
2020-05-17 21:08:43 +02:00
p := & provider {
2020-02-02 15:29:06 +01:00
sessions : make ( map [ string ] * Session ) ,
2018-04-22 12:52:36 +02:00
db : newMemDB ( ) ,
2017-02-15 19:06:19 +01:00
}
2020-05-17 21:08:43 +02:00
return p
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 ) {
2020-05-07 06:34:17 +02:00
if db == nil {
return
}
2017-02-15 19:06:19 +01:00
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
2020-04-13 21:15:55 +02:00
func ( p * provider ) newSession ( man * Sessions , sid string , expires time . Duration ) * Session {
2020-05-17 21:08:43 +02:00
sess := & Session {
sid : sid ,
Man : man ,
provider : p ,
flashes : make ( map [ string ] * flashMessage ) ,
}
2017-08-07 05:04:35 +02:00
onExpire := func ( ) {
2020-05-17 21:08:43 +02:00
p . mu . Lock ( )
p . deleteSession ( sess )
p . mu . Unlock ( )
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
2020-05-17 21:08:43 +02:00
sess . Lifetime = lifetime
2017-02-15 19:06:19 +01:00
return sess
}
// Init creates the session and returns it
2020-04-13 21:15:55 +02:00
func ( p * provider ) Init ( man * Sessions , sid string , expires time . Duration ) * Session {
newSession := p . newSession ( man , sid , expires )
2017-02-15 19:06:19 +01:00
p . mu . Lock ( )
p . sessions [ sid ] = newSession
p . mu . Unlock ( )
return newSession
}
2019-10-24 17:57:05 +02:00
// 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" )
2018-08-14 15:29:04 +02:00
// 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
}
2020-05-17 21:08:43 +02:00
p . mu . RLock ( )
2017-07-31 20:49:30 +02:00
sess , found := p . sessions [ sid ]
2020-05-17 21:08:43 +02:00
p . mu . RUnlock ( )
2017-07-31 20:49:30 +02:00
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
2020-04-13 21:15:55 +02:00
func ( p * provider ) Read ( man * Sessions , sid string , expires time . Duration ) * Session {
2020-05-17 21:08:43 +02:00
p . mu . RLock ( )
2017-02-15 19:06:19 +01:00
if sess , found := p . sessions [ sid ] ; found {
sess . runFlashGC ( ) // run the flash messages GC, new request here of existing session
2020-05-17 21:08:43 +02:00
p . mu . RUnlock ( )
2017-07-31 20:49:30 +02:00
2017-02-15 19:06:19 +01:00
return sess
}
2020-05-17 21:08:43 +02:00
p . mu . RUnlock ( )
2017-02-15 19:06:19 +01:00
2020-04-13 21:15:55 +02:00
return p . Init ( man , 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
}