...

Source file src/github.com/gin-gonic/contrib/cache/redis.go

Documentation: github.com/gin-gonic/contrib/cache

     1  package cache
     2  
     3  import (
     4  	"github.com/garyburd/redigo/redis"
     5  	"time"
     6  )
     7  
     8  // Wraps the Redis client to meet the Cache interface.
     9  type RedisStore struct {
    10  	pool              *redis.Pool
    11  	defaultExpiration time.Duration
    12  }
    13  
    14  // until redigo supports sharding/clustering, only one host will be in hostList
    15  func NewRedisCache(host string, password string, defaultExpiration time.Duration) *RedisStore {
    16  	var pool = &redis.Pool{
    17  		MaxIdle:     5,
    18  		IdleTimeout: 240 * time.Second,
    19  		Dial: func() (redis.Conn, error) {
    20  			// the redis protocol should probably be made sett-able
    21  			c, err := redis.Dial("tcp", host)
    22  			if err != nil {
    23  				return nil, err
    24  			}
    25  			if len(password) > 0 {
    26  				if _, err := c.Do("AUTH", password); err != nil {
    27  					c.Close()
    28  					return nil, err
    29  				}
    30  			} else {
    31  				// check with PING
    32  				if _, err := c.Do("PING"); err != nil {
    33  					c.Close()
    34  					return nil, err
    35  				}
    36  			}
    37  			return c, err
    38  		},
    39  		// custom connection test method
    40  		TestOnBorrow: func(c redis.Conn, t time.Time) error {
    41  			if _, err := c.Do("PING"); err != nil {
    42  				return err
    43  			}
    44  			return nil
    45  		},
    46  	}
    47  	return &RedisStore{pool, defaultExpiration}
    48  }
    49  
    50  func (c *RedisStore) Set(key string, value interface{}, expires time.Duration) error {
    51  	return c.invoke(c.pool.Get().Do, key, value, expires)
    52  }
    53  
    54  func (c *RedisStore) Add(key string, value interface{}, expires time.Duration) error {
    55  	conn := c.pool.Get()
    56  	if exists(conn, key) {
    57  		return ErrNotStored
    58  	}
    59  	return c.invoke(conn.Do, key, value, expires)
    60  }
    61  
    62  func (c *RedisStore) Replace(key string, value interface{}, expires time.Duration) error {
    63  	conn := c.pool.Get()
    64  	if !exists(conn, key) {
    65  		return ErrNotStored
    66  	}
    67  	err := c.invoke(conn.Do, key, value, expires)
    68  	if value == nil {
    69  		return ErrNotStored
    70  	} else {
    71  		return err
    72  	}
    73  }
    74  
    75  func (c *RedisStore) Get(key string, ptrValue interface{}) error {
    76  	conn := c.pool.Get()
    77  	defer conn.Close()
    78  	raw, err := conn.Do("GET", key)
    79  	if raw == nil {
    80  		return ErrCacheMiss
    81  	}
    82  	item, err := redis.Bytes(raw, err)
    83  	if err != nil {
    84  		return err
    85  	}
    86  	return deserialize(item, ptrValue)
    87  }
    88  
    89  func exists(conn redis.Conn, key string) bool {
    90  	retval, _ := redis.Bool(conn.Do("EXISTS", key))
    91  	return retval
    92  }
    93  
    94  func (c *RedisStore) Delete(key string) error {
    95  	conn := c.pool.Get()
    96  	defer conn.Close()
    97  	if !exists(conn, key) {
    98  		return ErrCacheMiss
    99  	}
   100  	_, err := conn.Do("DEL", key)
   101  	return err
   102  }
   103  
   104  func (c *RedisStore) Increment(key string, delta uint64) (uint64, error) {
   105  	conn := c.pool.Get()
   106  	defer conn.Close()
   107  	// Check for existence *before* increment as per the cache contract.
   108  	// redis will auto create the key, and we don't want that. Since we need to do increment
   109  	// ourselves instead of natively via INCRBY (redis doesn't support wrapping), we get the value
   110  	// and do the exists check this way to minimize calls to Redis
   111  	val, err := conn.Do("GET", key)
   112  	if val == nil {
   113  		return 0, ErrCacheMiss
   114  	}
   115  	if err == nil {
   116  		currentVal, err := redis.Int64(val, nil)
   117  		if err != nil {
   118  			return 0, err
   119  		}
   120  		var sum int64 = currentVal + int64(delta)
   121  		_, err = conn.Do("SET", key, sum)
   122  		if err != nil {
   123  			return 0, err
   124  		}
   125  		return uint64(sum), nil
   126  	} else {
   127  		return 0, err
   128  	}
   129  }
   130  
   131  func (c *RedisStore) Decrement(key string, delta uint64) (newValue uint64, err error) {
   132  	conn := c.pool.Get()
   133  	defer conn.Close()
   134  	// Check for existence *before* increment as per the cache contract.
   135  	// redis will auto create the key, and we don't want that, hence the exists call
   136  	if !exists(conn, key) {
   137  		return 0, ErrCacheMiss
   138  	}
   139  	// Decrement contract says you can only go to 0
   140  	// so we go fetch the value and if the delta is greater than the amount,
   141  	// 0 out the value
   142  	currentVal, err := redis.Int64(conn.Do("GET", key))
   143  	if err == nil && delta > uint64(currentVal) {
   144  		tempint, err := redis.Int64(conn.Do("DECRBY", key, currentVal))
   145  		return uint64(tempint), err
   146  	}
   147  	tempint, err := redis.Int64(conn.Do("DECRBY", key, delta))
   148  	return uint64(tempint), err
   149  }
   150  
   151  func (c *RedisStore) Flush() error {
   152  	conn := c.pool.Get()
   153  	defer conn.Close()
   154  	_, err := conn.Do("FLUSHALL")
   155  	return err
   156  }
   157  
   158  func (c *RedisStore) invoke(f func(string, ...interface{}) (interface{}, error),
   159  	key string, value interface{}, expires time.Duration) error {
   160  
   161  	switch expires {
   162  	case DEFAULT:
   163  		expires = c.defaultExpiration
   164  	case FOREVER:
   165  		expires = time.Duration(0)
   166  	}
   167  
   168  	b, err := serialize(value)
   169  	if err != nil {
   170  		return err
   171  	}
   172  	conn := c.pool.Get()
   173  	defer conn.Close()
   174  	if expires > 0 {
   175  		_, err := f("SETEX", key, int32(expires/time.Second), b)
   176  		return err
   177  	} else {
   178  		_, err := f("SET", key, b)
   179  		return err
   180  	}
   181  }
   182  

View as plain text