From a205315b2fc17675f9c9acd9fbf0adbb052d62f8 Mon Sep 17 00:00:00 2001 From: Tyler Date: Wed, 2 Oct 2019 00:10:27 -0400 Subject: [PATCH] Initial commit --- README.md | 26 +++++++++++++++++++ cache.go | 48 ++++++++++++++++++++++++++++++++++++ cache_memcache.go | 63 +++++++++++++++++++++++++++++++++++++++++++++++ cache_redis.go | 62 ++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 8 ++++++ go.sum | 4 +++ 6 files changed, 211 insertions(+) create mode 100644 README.md create mode 100644 cache.go create mode 100644 cache_memcache.go create mode 100644 cache_redis.go create mode 100644 go.mod create mode 100644 go.sum diff --git a/README.md b/README.md new file mode 100644 index 0000000..74875c2 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +CacheInterface +-------------- + +An experimental interface to use different caches interchangeably via URIs + +Example Cache URIs: + +``` +redis://server:port?db=0&password=test +memcache://server1:11211,server2:11211 +``` + +Code +#### + +The CacheInterface interface has all methods. All clients will implement this on a best-effort basis. + +```go +type CacheInterface interface { + Has(key string) bool + Get(key string) ([]byte, error) + Set(key string, val interface{}, ttl time.Duration) (err error) + Del(key string) error +} +``` +Note: Set will automatically attempt to store byte arrays and strings directly, and encode the rest with JSON. \ No newline at end of file diff --git a/cache.go b/cache.go new file mode 100644 index 0000000..f976310 --- /dev/null +++ b/cache.go @@ -0,0 +1,48 @@ +package cache + +import ( + "errors" + "net/url" + "strings" + "time" +) + +const ( + Memcache = "memcache" + Redis = "redis" +) + +var ( + ErrInvalidDriver = errors.New("invalid driver") +) + +type CacheInterface interface { + Has(key string) bool + Get(key string) ([]byte, error) + Set(key string, val interface{}, ttl time.Duration) (err error) + Del(key string) error +} + +func New(uri string) (CacheInterface, error) { + u, err := url.Parse(uri) + + if err != nil { + return nil, err + } + + query := u.Query() + + switch u.Scheme { + case Redis: + return NewRedisCache(RedisSettings{ + Address: u.Host, + Password: query.Get("password"), + }) + case Memcache: + return NewMemcacheCache(MemcacheSettings{ + Servers: strings.Split(u.Host, ","), + }) + } + + return nil, ErrInvalidDriver +} diff --git a/cache_memcache.go b/cache_memcache.go new file mode 100644 index 0000000..5526d49 --- /dev/null +++ b/cache_memcache.go @@ -0,0 +1,63 @@ +package cache + +import ( + "encoding/json" + "github.com/bradfitz/gomemcache/memcache" + "time" +) + +type MemcacheSettings struct { + Servers []string +} + +type MemcacheCache struct { + backend *memcache.Client +} + +func NewMemcacheCache(s MemcacheSettings) (CacheInterface, error) { + c := memcache.New(s.Servers...) + + return &MemcacheCache{ + backend: c, + }, nil +} + +func (mc *MemcacheCache) Has(key string) bool { + _, err := mc.backend.Get(key) + + return err != nil +} + +func (mc *MemcacheCache) Get(key string) ([]byte, error) { + item, err := mc.backend.Get(key) + + if err != nil { + return nil, err + } + + return item.Value, nil +} + +func (mc *MemcacheCache) Set(key string, val interface{}, ttl time.Duration) error { + var v []byte + + if b, ok := val.([]byte); ok { + v = b + } else if s, ok := val.(string); ok { + b = []byte(s) + } else { + b, err := json.Marshal(val) + + if err != nil { + return err + } + + v = b + } + + return mc.backend.Set(&memcache.Item{Key: key, Value: v, Expiration: int32(ttl.Seconds())}) +} + +func (mc *MemcacheCache) Del(key string) error { + return mc.backend.Delete(key) +} diff --git a/cache_redis.go b/cache_redis.go new file mode 100644 index 0000000..b92482e --- /dev/null +++ b/cache_redis.go @@ -0,0 +1,62 @@ +package cache + +import ( + "encoding/json" + "github.com/hoisie/redis" + "time" +) + +type RedisSettings struct { + Address string + DB int + Password string +} + +type RedisCache struct { + CacheInterface + + c *redis.Client +} + +func NewRedisCache(c RedisSettings) (CacheInterface, error) { + rc := &redis.Client{Addr: c.Address, Db: c.DB, Password: c.Password} + + return &RedisCache{ + c: rc, + }, nil +} + +func (rc *RedisCache) Has(key string) bool { + b, _ := rc.c.Exists(key) + return b +} + +func (rc *RedisCache) Get(key string) ([]byte, error) { + return rc.c.Get(key) +} + +func (rc *RedisCache) Set(key string, val interface{}, ttl time.Duration) error { + var v []byte + + if b, ok := val.([]byte); ok { + v = b + } else if s, ok := val.(string); ok { + b = []byte(s) + } else { + b, err := json.Marshal(val) + + if err != nil { + return err + } + + v = b + } + + return rc.c.Setex(key, int64(ttl.Seconds()), v) +} + +func (rc *RedisCache) Del(key string) error { + _, err := rc.c.Del(key) + + return err +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..79209e6 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module meow.tf/go/cacheinterface + +go 1.12 + +require ( + github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b + github.com/hoisie/redis v0.0.0-20160730154456-b5c6e81454e0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5207c33 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0= +github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= +github.com/hoisie/redis v0.0.0-20160730154456-b5c6e81454e0 h1:mjZV3MTu2A5gwfT5G9IIiLGdwZNciyVq5qqnmJJZ2JI= +github.com/hoisie/redis v0.0.0-20160730154456-b5c6e81454e0/go.mod h1:pMYMxVaKJqCDC1JUg/XbPJ4/fSazB25zORpFzqsIGIc=