1
0
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:
Gerasimos Maropoulos
2018-04-22 13:52:36 +03:00
parent f113872b7d
commit b62080c4bb
28 changed files with 496 additions and 1358 deletions

View File

@@ -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.