mirror of
https://github.com/kataras/iris.git
synced 2026-01-08 12:31:58 +00:00
Sessions are now in full sync with the registered database, on acquire(init), set, get, delete, clear, visit, len, release(destroy) as requested by almost everyone. https://github.com/kataras/iris/issues/969
Former-commit-id: 49fcdb93106a78f0a24ad3fb4d8725e35e98451a
This commit is contained in:
@@ -3,6 +3,7 @@ package badger
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/golog"
|
||||
"github.com/kataras/iris/core/errors"
|
||||
@@ -25,6 +26,8 @@ type Database struct {
|
||||
Service *badger.DB
|
||||
}
|
||||
|
||||
var _ sessions.Database = (*Database)(nil)
|
||||
|
||||
// New creates and returns a new badger(key-value file-based) storage
|
||||
// instance based on the "directoryPath".
|
||||
// DirectoryPath should is the directory which the badger database will store the sessions,
|
||||
@@ -32,9 +35,8 @@ type Database struct {
|
||||
//
|
||||
// It will remove any old session files.
|
||||
func New(directoryPath string) (*Database, error) {
|
||||
|
||||
if directoryPath == "" {
|
||||
return nil, errors.New("dir is missing")
|
||||
return nil, errors.New("directoryPath is missing")
|
||||
}
|
||||
|
||||
lindex := directoryPath[len(directoryPath)-1]
|
||||
@@ -57,134 +59,180 @@ func New(directoryPath string) (*Database, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewFromDB(service)
|
||||
return NewFromDB(service), nil
|
||||
}
|
||||
|
||||
// NewFromDB same as `New` but accepts an already-created custom badger connection instead.
|
||||
func NewFromDB(service *badger.DB) (*Database, error) {
|
||||
if service == nil {
|
||||
return nil, errors.New("underline database is missing")
|
||||
}
|
||||
|
||||
func NewFromDB(service *badger.DB) *Database {
|
||||
db := &Database{Service: service}
|
||||
|
||||
runtime.SetFinalizer(db, closeDB)
|
||||
return db, db.Cleanup()
|
||||
}
|
||||
|
||||
// Cleanup removes any invalid(have expired) session entries,
|
||||
// it's being called automatically on `New` as well.
|
||||
func (db *Database) Cleanup() (err error) {
|
||||
rep := errors.NewReporter()
|
||||
|
||||
txn := db.Service.NewTransaction(true)
|
||||
defer txn.Commit(nil)
|
||||
|
||||
iter := txn.NewIterator(badger.DefaultIteratorOptions)
|
||||
defer iter.Close()
|
||||
|
||||
for iter.Rewind(); iter.Valid(); iter.Next() {
|
||||
// Remember that the contents of the returned slice should not be modified, and
|
||||
// only valid until the next call to Next.
|
||||
item := iter.Item()
|
||||
b, err := item.Value()
|
||||
|
||||
if rep.AddErr(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
storeDB, err := sessions.DecodeRemoteStore(b)
|
||||
if rep.AddErr(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
if storeDB.Lifetime.HasExpired() {
|
||||
if err := txn.Delete(item.Key()); err != nil {
|
||||
rep.AddErr(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rep.Return()
|
||||
}
|
||||
|
||||
// Async is DEPRECATED
|
||||
// if it was true then it could use different to update the back-end storage, now it does nothing.
|
||||
func (db *Database) Async(useGoRoutines bool) *Database {
|
||||
return db
|
||||
}
|
||||
|
||||
// Load loads the sessions from the badger(key-value file-based) session storage.
|
||||
func (db *Database) Load(sid string) (storeDB sessions.RemoteStore) {
|
||||
// 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.
|
||||
func (db *Database) Acquire(sid string, expires time.Duration) sessions.LifeTime {
|
||||
txn := db.Service.NewTransaction(true)
|
||||
defer txn.Commit(nil)
|
||||
|
||||
bsid := []byte(sid)
|
||||
|
||||
txn := db.Service.NewTransaction(false)
|
||||
defer txn.Discard()
|
||||
|
||||
item, err := txn.Get(bsid)
|
||||
if err == nil {
|
||||
// found, return the expiration.
|
||||
return sessions.LifeTime{Time: time.Unix(int64(item.ExpiresAt()), 0)}
|
||||
}
|
||||
|
||||
// not found, create an entry with ttl and return an empty lifetime, session manager will do its job.
|
||||
if err != nil {
|
||||
// Key not found, don't report this, session manager will create a new session as it should.
|
||||
if err == badger.ErrKeyNotFound {
|
||||
// create it and set the expiration, we don't care about the value there.
|
||||
err = txn.SetWithTTL(bsid, bsid, expires)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
golog.Error(err)
|
||||
}
|
||||
|
||||
return sessions.LifeTime{} // session manager will handle the rest.
|
||||
}
|
||||
|
||||
var delim = byte('*')
|
||||
|
||||
func makeKey(sid, key string) []byte {
|
||||
return append([]byte(sid), append([]byte(key), delim)...)
|
||||
}
|
||||
|
||||
// Set sets a key value of a specific session.
|
||||
// Ignore the "immutable".
|
||||
func (db *Database) Set(sid string, lifetime sessions.LifeTime, key string, value interface{}, immutable bool) {
|
||||
valueBytes, err := sessions.DefaultTranscoder.Marshal(value)
|
||||
if err != nil {
|
||||
golog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
b, err := item.Value()
|
||||
err = db.Service.Update(func(txn *badger.Txn) error {
|
||||
return txn.SetWithTTL(makeKey(sid, key), valueBytes, lifetime.DurationUntilExpiration())
|
||||
// return txn.Set(makeKey(sid, key), valueBytes)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
golog.Errorf("error while trying to get the serialized session(%s) from the remote store: %v", sid, err)
|
||||
return
|
||||
golog.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
storeDB, err = sessions.DecodeRemoteStore(b) // decode the whole value, as a remote store
|
||||
if err != nil {
|
||||
golog.Errorf("error while trying to load from the remote store: %v", err)
|
||||
// Get retrieves a session value based on the key.
|
||||
func (db *Database) Get(sid string, key string) (value interface{}) {
|
||||
err := db.Service.View(func(txn *badger.Txn) error {
|
||||
item, err := txn.Get(makeKey(sid, key))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// item.ValueCopy
|
||||
valueBytes, err := item.Value()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sessions.DefaultTranscoder.Unmarshal(valueBytes, &value)
|
||||
})
|
||||
|
||||
if err != nil && err != badger.ErrKeyNotFound {
|
||||
golog.Error(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Sync syncs the database with the session's (memory) store.
|
||||
func (db *Database) Sync(p sessions.SyncPayload) {
|
||||
db.sync(p)
|
||||
}
|
||||
// Visit loops through all session keys and values.
|
||||
func (db *Database) Visit(sid string, cb func(key string, value interface{})) {
|
||||
prefix := append([]byte(sid), delim)
|
||||
|
||||
func (db *Database) sync(p sessions.SyncPayload) {
|
||||
bsid := []byte(p.SessionID)
|
||||
txn := db.Service.NewTransaction(false)
|
||||
defer txn.Discard()
|
||||
|
||||
if p.Action == sessions.ActionDestroy {
|
||||
if err := db.destroy(bsid); err != nil {
|
||||
golog.Errorf("error while destroying a session(%s) from badger: %v",
|
||||
p.SessionID, err)
|
||||
iter := txn.NewIterator(badger.DefaultIteratorOptions)
|
||||
defer iter.Close()
|
||||
|
||||
for iter.Rewind(); iter.ValidForPrefix(prefix); iter.Next() {
|
||||
item := iter.Item()
|
||||
valueBytes, err := item.Value()
|
||||
if err != nil {
|
||||
golog.Error(err)
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
s, err := p.Store.Serialize()
|
||||
if err != nil {
|
||||
golog.Errorf("error while serializing the remote store: %v", err)
|
||||
}
|
||||
var value interface{}
|
||||
if err = sessions.DefaultTranscoder.Unmarshal(valueBytes, &value); err != nil {
|
||||
golog.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
txn := db.Service.NewTransaction(true)
|
||||
|
||||
err = txn.Set(bsid, s)
|
||||
if err != nil {
|
||||
txn.Discard()
|
||||
golog.Errorf("error while trying to save the session(%s) to the database: %v", p.SessionID, err)
|
||||
return
|
||||
}
|
||||
if err := txn.Commit(nil); err != nil { // Commit will call the Discard automatically.
|
||||
golog.Errorf("error while committing the session(%s) changes to the database: %v", p.SessionID, err)
|
||||
cb(string(item.Key()), value)
|
||||
}
|
||||
}
|
||||
|
||||
func (db *Database) destroy(bsid []byte) error {
|
||||
txn := db.Service.NewTransaction(true)
|
||||
var iterOptionsNoValues = badger.IteratorOptions{
|
||||
PrefetchValues: false,
|
||||
PrefetchSize: 100,
|
||||
Reverse: false,
|
||||
AllVersions: false,
|
||||
}
|
||||
|
||||
err := txn.Delete(bsid)
|
||||
if err != nil {
|
||||
return err
|
||||
// Len returns the length of the session's entries (keys).
|
||||
func (db *Database) Len(sid string) (n int) {
|
||||
prefix := append([]byte(sid), delim)
|
||||
|
||||
txn := db.Service.NewTransaction(false)
|
||||
iter := txn.NewIterator(iterOptionsNoValues)
|
||||
|
||||
for iter.Rewind(); iter.ValidForPrefix(prefix); iter.Next() {
|
||||
n++
|
||||
}
|
||||
|
||||
return txn.Commit(nil)
|
||||
iter.Close()
|
||||
txn.Discard()
|
||||
return
|
||||
}
|
||||
|
||||
// Delete removes a session key value based on its key.
|
||||
func (db *Database) Delete(sid string, key string) (deleted bool) {
|
||||
txn := db.Service.NewTransaction(true)
|
||||
err := txn.Delete(makeKey(sid, key))
|
||||
if err != nil {
|
||||
golog.Error(err)
|
||||
}
|
||||
txn.Commit(nil)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Clear removes all session key values but it keeps the session entry.
|
||||
func (db *Database) Clear(sid string) {
|
||||
prefix := append([]byte(sid), delim)
|
||||
|
||||
txn := db.Service.NewTransaction(true)
|
||||
defer txn.Commit(nil)
|
||||
|
||||
iter := txn.NewIterator(iterOptionsNoValues)
|
||||
defer iter.Close()
|
||||
|
||||
for iter.Rewind(); iter.ValidForPrefix(prefix); iter.Next() {
|
||||
txn.Delete(iter.Item().Key())
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (db *Database) Release(sid string) {
|
||||
// clear all $sid-$key.
|
||||
db.Clear(sid)
|
||||
// and remove the $sid.
|
||||
txn := db.Service.NewTransaction(true)
|
||||
txn.Delete([]byte(sid))
|
||||
txn.Commit(nil)
|
||||
}
|
||||
|
||||
// Close shutdowns the badger connection.
|
||||
|
||||
Reference in New Issue
Block a user