2017-02-15 19:06:19 +01:00
|
|
|
package redis
|
|
|
|
|
|
|
|
import (
|
2019-10-24 17:57:05 +02:00
|
|
|
"errors"
|
2017-08-20 17:57:19 +02:00
|
|
|
"time"
|
2017-08-08 11:31:42 +02:00
|
|
|
|
2019-10-25 00:27:02 +02:00
|
|
|
"github.com/kataras/iris/v12/sessions"
|
2019-06-22 13:46:20 +02:00
|
|
|
|
|
|
|
"github.com/kataras/golog"
|
2017-02-15 19:06:19 +01:00
|
|
|
)
|
|
|
|
|
2019-08-06 17:40:13 +02:00
|
|
|
const (
|
|
|
|
// DefaultRedisNetwork the redis network option, "tcp".
|
|
|
|
DefaultRedisNetwork = "tcp"
|
|
|
|
// DefaultRedisAddr the redis address option, "127.0.0.1:6379".
|
|
|
|
DefaultRedisAddr = "127.0.0.1:6379"
|
|
|
|
// DefaultRedisTimeout the redis idle timeout option, time.Duration(30) * time.Second
|
|
|
|
DefaultRedisTimeout = time.Duration(30) * time.Second
|
|
|
|
// DefaultDelim ths redis delim option, "-".
|
|
|
|
DefaultDelim = "-"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Config the redis configuration used inside sessions
|
|
|
|
type Config struct {
|
|
|
|
// Network protocol. Defaults to "tcp".
|
|
|
|
Network string
|
2019-08-20 11:29:54 +02:00
|
|
|
// Addr of a single redis server instance.
|
|
|
|
// See "Clusters" field for clusters support.
|
|
|
|
// Defaults to "127.0.0.1:6379".
|
2019-08-06 17:40:13 +02:00
|
|
|
Addr string
|
2019-08-20 11:29:54 +02:00
|
|
|
// Clusters a list of network addresses for clusters.
|
|
|
|
// If not empty "Addr" is ignored.
|
|
|
|
// Currently only Radix() Driver supports it.
|
|
|
|
Clusters []string
|
2019-08-06 17:40:13 +02:00
|
|
|
// Password string .If no password then no 'AUTH'. Defaults to "".
|
|
|
|
Password string
|
|
|
|
// If Database is empty "" then no 'SELECT'. Defaults to "".
|
|
|
|
Database string
|
|
|
|
// MaxActive. Defaults to 10.
|
|
|
|
MaxActive int
|
|
|
|
// Timeout for connect, write and read, defaults to 30 seconds, 0 means no timeout.
|
|
|
|
Timeout time.Duration
|
|
|
|
// Prefix "myprefix-for-this-website". Defaults to "".
|
|
|
|
Prefix string
|
|
|
|
// Delim the delimeter for the keys on the sessiondb. Defaults to "-".
|
|
|
|
Delim string
|
|
|
|
|
|
|
|
// Driver supports `Redigo()` or `Radix()` go clients for redis.
|
|
|
|
// Configure each driver by the return value of their constructors.
|
|
|
|
//
|
|
|
|
// Defaults to `Redigo()`.
|
|
|
|
Driver Driver
|
|
|
|
}
|
|
|
|
|
|
|
|
// DefaultConfig returns the default configuration for Redis service.
|
|
|
|
func DefaultConfig() Config {
|
|
|
|
return Config{
|
|
|
|
Network: DefaultRedisNetwork,
|
|
|
|
Addr: DefaultRedisAddr,
|
|
|
|
Password: "",
|
|
|
|
Database: "",
|
|
|
|
MaxActive: 10,
|
|
|
|
Timeout: DefaultRedisTimeout,
|
|
|
|
Prefix: "",
|
|
|
|
Delim: DefaultDelim,
|
|
|
|
Driver: Redigo(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-10 17:32:42 +02:00
|
|
|
// Database the redis back-end session database for the sessions.
|
2017-02-15 19:06:19 +01:00
|
|
|
type Database struct {
|
2019-08-06 17:40:13 +02:00
|
|
|
c Config
|
2017-02-15 19:06:19 +01:00
|
|
|
}
|
|
|
|
|
2018-04-22 12:52:36 +02:00
|
|
|
var _ sessions.Database = (*Database)(nil)
|
|
|
|
|
2017-07-10 17:32:42 +02:00
|
|
|
// New returns a new redis database.
|
2019-06-22 13:46:20 +02:00
|
|
|
func New(cfg ...Config) *Database {
|
2019-08-06 17:40:13 +02:00
|
|
|
c := DefaultConfig()
|
|
|
|
if len(cfg) > 0 {
|
|
|
|
c = cfg[0]
|
|
|
|
|
|
|
|
if c.Timeout < 0 {
|
|
|
|
c.Timeout = DefaultRedisTimeout
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.Network == "" {
|
|
|
|
c.Network = DefaultRedisNetwork
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.Addr == "" {
|
|
|
|
c.Addr = DefaultRedisAddr
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.MaxActive == 0 {
|
|
|
|
c.MaxActive = 10
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.Delim == "" {
|
|
|
|
c.Delim = DefaultDelim
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.Driver == nil {
|
|
|
|
c.Driver = Redigo()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := c.Driver.Connect(c); err != nil {
|
2019-06-22 13:46:20 +02:00
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2019-08-06 17:40:13 +02:00
|
|
|
db := &Database{c: c}
|
|
|
|
_, err := db.c.Driver.PingPong()
|
2018-04-22 12:52:36 +02:00
|
|
|
if err != nil {
|
|
|
|
golog.Debugf("error connecting to redis: %v", err)
|
|
|
|
return nil
|
|
|
|
}
|
2019-06-22 13:46:20 +02:00
|
|
|
// runtime.SetFinalizer(db, closeDB)
|
2017-08-08 11:31:42 +02:00
|
|
|
return db
|
2017-02-15 19:06:19 +01:00
|
|
|
}
|
|
|
|
|
2017-07-10 17:32:42 +02:00
|
|
|
// Config returns the configuration for the redis server bridge, you can change them.
|
2019-06-22 13:46:20 +02:00
|
|
|
func (db *Database) Config() *Config {
|
2019-08-06 17:40:13 +02:00
|
|
|
return &db.c // 6 Aug 2019 - keep that for no breaking change.
|
2017-08-07 05:04:35 +02:00
|
|
|
}
|
|
|
|
|
2018-04-22 12:52:36 +02:00
|
|
|
// 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 {
|
2019-08-06 17:40:13 +02:00
|
|
|
seconds, hasExpiration, found := db.c.Driver.TTL(sid)
|
2018-04-22 12:52:36 +02:00
|
|
|
if !found {
|
2019-06-22 13:46:20 +02:00
|
|
|
// fmt.Printf("db.Acquire expires: %s. Seconds: %v\n", expires, expires.Seconds())
|
2018-04-22 12:52:36 +02:00
|
|
|
// not found, create an entry with ttl and return an empty lifetime, session manager will do its job.
|
2019-08-06 17:40:13 +02:00
|
|
|
if err := db.c.Driver.Set(sid, sid, int64(expires.Seconds())); err != nil {
|
2018-04-22 12:52:36 +02:00
|
|
|
golog.Debug(err)
|
2017-02-15 19:06:19 +01:00
|
|
|
}
|
2018-04-22 12:52:36 +02:00
|
|
|
|
|
|
|
return sessions.LifeTime{} // session manager will handle the rest.
|
2017-02-15 19:06:19 +01:00
|
|
|
}
|
2017-07-31 20:49:30 +02:00
|
|
|
|
2018-04-22 12:52:36 +02:00
|
|
|
if !hasExpiration {
|
|
|
|
return sessions.LifeTime{}
|
2017-07-31 20:49:30 +02:00
|
|
|
}
|
|
|
|
|
2018-04-22 12:52:36 +02:00
|
|
|
return sessions.LifeTime{Time: time.Now().Add(time.Duration(seconds) * time.Second)}
|
2017-02-15 19:06:19 +01:00
|
|
|
}
|
|
|
|
|
2018-08-14 15:29:04 +02:00
|
|
|
// OnUpdateExpiration will re-set the database's session's entry ttl.
|
|
|
|
// https://redis.io/commands/expire#refreshing-expires
|
|
|
|
func (db *Database) OnUpdateExpiration(sid string, newExpires time.Duration) error {
|
2019-08-06 17:40:13 +02:00
|
|
|
return db.c.Driver.UpdateTTLMany(sid, int64(newExpires.Seconds()))
|
2018-08-14 15:29:04 +02:00
|
|
|
}
|
|
|
|
|
2019-05-30 09:48:07 +02:00
|
|
|
func (db *Database) makeKey(sid, key string) string {
|
2019-08-06 17:40:13 +02:00
|
|
|
return sid + db.c.Delim + key
|
2017-02-15 19:06:19 +01:00
|
|
|
}
|
|
|
|
|
2018-04-22 12:52:36 +02:00
|
|
|
// 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)
|
2017-08-07 05:04:35 +02:00
|
|
|
return
|
|
|
|
}
|
2018-04-22 12:52:36 +02:00
|
|
|
|
2019-06-22 13:46:20 +02:00
|
|
|
// fmt.Println("database.Set")
|
|
|
|
// fmt.Printf("lifetime.DurationUntilExpiration(): %s. Seconds: %v\n", lifetime.DurationUntilExpiration(), lifetime.DurationUntilExpiration().Seconds())
|
2019-08-06 17:40:13 +02:00
|
|
|
if err = db.c.Driver.Set(db.makeKey(sid, key), valueBytes, int64(lifetime.DurationUntilExpiration().Seconds())); err != nil {
|
2018-04-22 12:52:36 +02:00
|
|
|
golog.Debug(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get retrieves a session value based on the key.
|
|
|
|
func (db *Database) Get(sid string, key string) (value interface{}) {
|
2019-05-30 09:48:07 +02:00
|
|
|
db.get(db.makeKey(sid, key), &value)
|
2018-04-22 12:52:36 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *Database) get(key string, outPtr interface{}) {
|
2019-08-06 17:40:13 +02:00
|
|
|
data, err := db.c.Driver.Get(key)
|
2017-08-07 05:04:35 +02:00
|
|
|
if err != nil {
|
2018-04-22 12:52:36 +02:00
|
|
|
// not found.
|
2017-08-07 05:04:35 +02:00
|
|
|
return
|
2017-02-15 19:06:19 +01:00
|
|
|
}
|
|
|
|
|
2018-04-22 12:52:36 +02:00
|
|
|
if err = sessions.DefaultTranscoder.Unmarshal(data.([]byte), outPtr); err != nil {
|
|
|
|
golog.Debugf("unable to unmarshal value of key: '%s': %v", key, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *Database) keys(sid string) []string {
|
2019-08-06 17:40:13 +02:00
|
|
|
keys, err := db.c.Driver.GetKeys(sid)
|
2018-04-22 12:52:36 +02:00
|
|
|
if err != nil {
|
|
|
|
golog.Debugf("unable to get all redis keys of session '%s': %v", sid, err)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return keys
|
|
|
|
}
|
2017-08-20 17:57:19 +02:00
|
|
|
|
2018-04-22 12:52:36 +02:00
|
|
|
// Visit loops through all session keys and values.
|
|
|
|
func (db *Database) Visit(sid string, cb func(key string, value interface{})) {
|
|
|
|
keys := db.keys(sid)
|
|
|
|
for _, key := range keys {
|
|
|
|
var value interface{} // new value each time, we don't know what user will do in "cb".
|
|
|
|
db.get(key, &value)
|
|
|
|
cb(key, value)
|
2017-08-20 17:57:19 +02:00
|
|
|
}
|
2018-04-22 12:52:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Len returns the length of the session's entries (keys).
|
|
|
|
func (db *Database) Len(sid string) (n int) {
|
|
|
|
return len(db.keys(sid))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete removes a session key value based on its key.
|
|
|
|
func (db *Database) Delete(sid string, key string) (deleted bool) {
|
2019-08-06 17:40:13 +02:00
|
|
|
err := db.c.Driver.Delete(db.makeKey(sid, key))
|
2018-04-22 12:52:36 +02:00
|
|
|
if err != nil {
|
|
|
|
golog.Error(err)
|
|
|
|
}
|
|
|
|
return err == nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear removes all session key values but it keeps the session entry.
|
|
|
|
func (db *Database) Clear(sid string) {
|
|
|
|
keys := db.keys(sid)
|
|
|
|
for _, key := range keys {
|
2019-08-06 17:40:13 +02:00
|
|
|
if err := db.c.Driver.Delete(key); err != nil {
|
2018-04-22 12:52:36 +02:00
|
|
|
golog.Debugf("unable to delete session '%s' value of key: '%s': %v", sid, key, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-08-20 17:57:19 +02:00
|
|
|
|
2018-04-22 12:52:36 +02:00
|
|
|
// 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.
|
2020-02-02 15:29:06 +01:00
|
|
|
err := db.c.Driver.Delete(sid)
|
|
|
|
if err != nil {
|
|
|
|
golog.Debugf("Database.Release.Driver.Delete: %s: %v", sid, err)
|
|
|
|
}
|
2017-08-08 11:31:42 +02:00
|
|
|
}
|
|
|
|
|
2018-04-22 12:52:36 +02:00
|
|
|
// Close terminates the redis connection.
|
2017-08-08 11:31:42 +02:00
|
|
|
func (db *Database) Close() error {
|
|
|
|
return closeDB(db)
|
|
|
|
}
|
|
|
|
|
|
|
|
func closeDB(db *Database) error {
|
2019-08-06 17:40:13 +02:00
|
|
|
return db.c.Driver.CloseConnection()
|
2017-02-15 19:06:19 +01:00
|
|
|
}
|
2019-08-06 17:40:13 +02:00
|
|
|
|
|
|
|
var (
|
2019-10-24 17:57:05 +02:00
|
|
|
// ErrRedisClosed an error with message 'redis: already closed'
|
|
|
|
ErrRedisClosed = errors.New("redis: already closed")
|
|
|
|
// ErrKeyNotFound a type of error of non-existing redis keys.
|
|
|
|
// The producers(the library) of this error will dynamically wrap this error(fmt.Errorf) with the key name.
|
|
|
|
// Usage:
|
|
|
|
// if err != nil && errors.Is(err, ErrKeyNotFound) {
|
|
|
|
// [...]
|
|
|
|
// }
|
|
|
|
ErrKeyNotFound = errors.New("key not found")
|
2019-08-06 17:40:13 +02:00
|
|
|
)
|