2017-02-15 19:06:19 +01:00
package service
import (
2018-04-22 12:52:36 +02:00
"fmt"
2017-02-15 19:06:19 +01:00
"time"
2018-04-22 12:52:36 +02:00
"github.com/gomodule/redigo/redis"
Publish the new version :airplane: | Look description please!
# FAQ
### Looking for free support?
http://support.iris-go.com
https://kataras.rocket.chat/channel/iris
### Looking for previous versions?
https://github.com/kataras/iris#version
### Should I upgrade my Iris?
Developers are not forced to upgrade if they don't really need it. Upgrade whenever you feel ready.
> Iris uses the [vendor directory](https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo) feature, so you get truly reproducible builds, as this method guards against upstream renames and deletes.
**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`.
For further installation support, please click [here](http://support.iris-go.com/d/16-how-to-install-iris-web-framework).
### About our new home page
http://iris-go.com
Thanks to [Santosh Anand](https://github.com/santoshanand) the http://iris-go.com has been upgraded and it's really awesome!
[Santosh](https://github.com/santoshanand) is a freelancer, he has a great knowledge of nodejs and express js, Android, iOS, React Native, Vue.js etc, if you need a developer to find or create a solution for your problem or task, please contact with him.
The amount of the next two or three donations you'll send they will be immediately transferred to his own account balance, so be generous please!
Read more at https://github.com/kataras/iris/blob/master/HISTORY.md
Former-commit-id: eec2d71bbe011d6b48d2526eb25919e36e5ad94e
2017-06-03 22:22:52 +02:00
"github.com/kataras/iris/core/errors"
2017-02-15 19:06:19 +01:00
)
var (
// ErrRedisClosed an error with message 'Redis is already closed'
ErrRedisClosed = errors . New ( "Redis is already closed" )
// ErrKeyNotFound an error with message 'Key $thekey doesn't found'
ErrKeyNotFound = errors . New ( "Key '%s' doesn't found" )
)
// Service the Redis service, contains the config and the redis pool
type Service struct {
// Connected is true when the Service has already connected
Connected bool
// Config the redis config for this redis
Config * Config
pool * redis . Pool
}
// PingPong sends a ping and receives a pong, if no pong received then returns false and filled error
func ( r * Service ) PingPong ( ) ( bool , error ) {
c := r . pool . Get ( )
defer c . Close ( )
msg , err := c . Do ( "PING" )
if err != nil || msg == nil {
return false , err
}
return ( msg == "PONG" ) , nil
}
// CloseConnection closes the redis connection
func ( r * Service ) CloseConnection ( ) error {
if r . pool != nil {
return r . pool . Close ( )
}
return ErrRedisClosed
}
2017-08-07 05:04:35 +02:00
// Set sets a key-value to the redis store.
// The expiration is setted by the MaxAgeSeconds.
2018-04-22 12:52:36 +02:00
func ( r * Service ) Set ( key string , value interface { } , secondsLifetime int64 ) ( err error ) {
2017-02-15 19:06:19 +01:00
c := r . pool . Get ( )
defer c . Close ( )
2017-08-07 05:04:35 +02:00
if c . Err ( ) != nil {
return c . Err ( )
2017-02-15 19:06:19 +01:00
}
2017-08-07 05:04:35 +02:00
2017-08-08 11:31:42 +02:00
// if has expiration, then use the "EX" to delete the key automatically.
if secondsLifetime > 0 {
_ , err = c . Do ( "SETEX" , r . Config . Prefix + key , secondsLifetime , value )
} else {
_ , err = c . Do ( "SET" , r . Config . Prefix + key , value )
}
return
2017-02-15 19:06:19 +01:00
}
// Get returns value, err by its key
2017-08-07 05:04:35 +02:00
//returns nil and a filled error if something bad happened.
2017-02-15 19:06:19 +01:00
func ( r * Service ) Get ( key string ) ( interface { } , error ) {
c := r . pool . Get ( )
defer c . Close ( )
if err := c . Err ( ) ; err != nil {
return nil , err
}
redisVal , err := c . Do ( "GET" , r . Config . Prefix + key )
if err != nil {
return nil , err
}
if redisVal == nil {
return nil , ErrKeyNotFound . Format ( key )
}
return redisVal , nil
}
2018-04-22 12:52:36 +02:00
// TTL returns the seconds to expire, if the key has expiration and error if action failed.
// Read more at: https://redis.io/commands/ttl
func ( r * Service ) TTL ( key string ) ( seconds int64 , hasExpiration bool , ok bool ) {
c := r . pool . Get ( )
defer c . Close ( )
redisVal , err := c . Do ( "TTL" , r . Config . Prefix + key )
if err != nil {
return - 2 , false , false
}
seconds = redisVal . ( int64 )
// if -1 means the key has unlimited life time.
hasExpiration = seconds == - 1
// if -2 means key does not exist.
ok = ( c . Err ( ) != nil || seconds == - 2 )
return
}
2018-08-14 15:29:04 +02:00
func ( r * Service ) updateTTLConn ( c redis . Conn , key string , newSecondsLifeTime int64 ) error {
reply , err := c . Do ( "EXPIRE" , r . Config . Prefix + key , newSecondsLifeTime )
if err != nil {
return err
}
// https://redis.io/commands/expire#return-value
//
// 1 if the timeout was set.
// 0 if key does not exist.
if hadTTLOrExists , ok := reply . ( int ) ; ok {
if hadTTLOrExists == 1 {
return nil
} else if hadTTLOrExists == 0 {
return fmt . Errorf ( "unable to update expiration, the key '%s' was stored without ttl" , key )
} // do not check for -1.
}
return nil
}
// UpdateTTL will update the ttl of a key.
// Using the "EXPIRE" command.
// Read more at: https://redis.io/commands/expire#refreshing-expires
func ( r * Service ) UpdateTTL ( key string , newSecondsLifeTime int64 ) error {
c := r . pool . Get ( )
defer c . Close ( )
err := c . Err ( )
if err != nil {
return err
}
return r . updateTTLConn ( c , key , newSecondsLifeTime )
}
// UpdateTTLMany like `UpdateTTL` but for all keys starting with that "prefix",
// it is a bit faster operation if you need to update all sessions keys (although it can be even faster if we used hash but this will limit other features),
// look the `sessions/Database#OnUpdateExpiration` for example.
func ( r * Service ) UpdateTTLMany ( prefix string , newSecondsLifeTime int64 ) error {
c := r . pool . Get ( )
defer c . Close ( )
if err := c . Err ( ) ; err != nil {
return err
}
keys , err := r . getKeysConn ( c , prefix )
if err != nil {
return err
}
for _ , key := range keys {
if err = r . updateTTLConn ( c , key , newSecondsLifeTime ) ; err != nil { // fail on first error.
return err
}
}
return err
}
2017-08-07 05:04:35 +02:00
// GetAll returns all redis entries using the "SCAN" command (2.8+).
func ( r * Service ) GetAll ( ) ( interface { } , error ) {
2017-02-15 19:06:19 +01:00
c := r . pool . Get ( )
defer c . Close ( )
if err := c . Err ( ) ; err != nil {
return nil , err
}
2017-08-07 05:04:35 +02:00
redisVal , err := c . Do ( "SCAN" , 0 ) // 0 -> cursor
2017-02-15 19:06:19 +01:00
if err != nil {
return nil , err
}
if redisVal == nil {
return nil , err
}
2017-08-07 05:04:35 +02:00
return redisVal , nil
2017-02-15 19:06:19 +01:00
}
2018-08-14 15:29:04 +02:00
func ( r * Service ) getKeysConn ( c redis . Conn , prefix string ) ( [ ] string , error ) {
2018-04-22 12:52:36 +02:00
if err := c . Send ( "SCAN" , 0 , "MATCH" , r . Config . Prefix + prefix + "*" , "COUNT" , 9999999999 ) ; err != nil {
return nil , err
}
if err := c . Flush ( ) ; err != nil {
return nil , err
}
reply , err := c . Receive ( )
if err != nil || reply == nil {
return nil , err
}
// it returns []interface, with two entries, the first one is "0" and the second one is a slice of the keys as []interface{uint8....}.
if keysInterface , ok := reply . ( [ ] interface { } ) ; ok {
if len ( keysInterface ) == 2 {
// take the second, it must contain the slice of keys.
if keysSliceAsBytes , ok := keysInterface [ 1 ] . ( [ ] interface { } ) ; ok {
keys := make ( [ ] string , len ( keysSliceAsBytes ) , len ( keysSliceAsBytes ) )
for i , k := range keysSliceAsBytes {
keys [ i ] = fmt . Sprintf ( "%s" , k )
}
return keys , nil
}
}
}
return nil , nil
}
2018-08-14 15:29:04 +02:00
// GetKeys returns all redis keys using the "SCAN" with MATCH command.
// Read more at: https://redis.io/commands/scan#the-match-option.
func ( r * Service ) GetKeys ( prefix string ) ( [ ] string , error ) {
c := r . pool . Get ( )
defer c . Close ( )
if err := c . Err ( ) ; err != nil {
return nil , err
}
return r . getKeysConn ( c , prefix )
}
2017-08-07 05:04:35 +02:00
// GetBytes returns value, err by its key
// you can use utils.Deserialize((.GetBytes("yourkey"),&theobject{})
//returns nil and a filled error if something wrong happens
func ( r * Service ) GetBytes ( key string ) ( [ ] byte , error ) {
2017-02-15 19:06:19 +01:00
c := r . pool . Get ( )
defer c . Close ( )
if err := c . Err ( ) ; err != nil {
return nil , err
}
2017-08-07 05:04:35 +02:00
redisVal , err := c . Do ( "GET" , r . Config . Prefix + key )
2017-02-15 19:06:19 +01:00
if err != nil {
return nil , err
}
2017-08-07 05:04:35 +02:00
if redisVal == nil {
return nil , ErrKeyNotFound . Format ( key )
2017-02-15 19:06:19 +01:00
}
2017-08-07 05:04:35 +02:00
return redis . Bytes ( redisVal , err )
2017-02-15 19:06:19 +01:00
}
// Delete removes redis entry by specific key
func ( r * Service ) Delete ( key string ) error {
c := r . pool . Get ( )
defer c . Close ( )
2018-03-08 19:55:58 +01:00
_ , err := c . Do ( "DEL" , r . Config . Prefix + key )
return err
2017-02-15 19:06:19 +01:00
}
func dial ( network string , addr string , pass string ) ( redis . Conn , error ) {
if network == "" {
network = DefaultRedisNetwork
}
if addr == "" {
addr = DefaultRedisAddr
}
c , err := redis . Dial ( network , addr )
if err != nil {
return nil , err
}
if pass != "" {
if _ , err = c . Do ( "AUTH" , pass ) ; err != nil {
c . Close ( )
return nil , err
}
}
return c , err
}
// Connect connects to the redis, called only once
func ( r * Service ) Connect ( ) {
c := r . Config
if c . IdleTimeout <= 0 {
c . IdleTimeout = DefaultRedisIdleTimeout
}
if c . Network == "" {
c . Network = DefaultRedisNetwork
}
if c . Addr == "" {
c . Addr = DefaultRedisAddr
}
pool := & redis . Pool { IdleTimeout : DefaultRedisIdleTimeout , MaxIdle : c . MaxIdle , MaxActive : c . MaxActive }
pool . TestOnBorrow = func ( c redis . Conn , t time . Time ) error {
_ , err := c . Do ( "PING" )
return err
}
if c . Database != "" {
pool . Dial = func ( ) ( redis . Conn , error ) {
red , err := dial ( c . Network , c . Addr , c . Password )
if err != nil {
return nil , err
}
if _ , err = red . Do ( "SELECT" , c . Database ) ; err != nil {
red . Close ( )
return nil , err
}
return red , err
}
} else {
pool . Dial = func ( ) ( redis . Conn , error ) {
return dial ( c . Network , c . Addr , c . Password )
}
}
r . Connected = true
r . pool = pool
}
// New returns a Redis service filled by the passed config
2017-07-10 17:32:42 +02:00
// to connect call the .Connect().
2017-02-15 19:06:19 +01:00
func New ( cfg ... Config ) * Service {
2017-07-10 17:32:42 +02:00
c := DefaultConfig ( )
if len ( cfg ) > 0 {
c = cfg [ 0 ]
}
2017-02-15 19:06:19 +01:00
r := & Service { pool : & redis . Pool { } , Config : & c }
return r
}