Skip to content
This repository was archived by the owner on Mar 8, 2024. It is now read-only.

Commit e926f2b

Browse files
Olego.musin
andauthored
feat: added redis storage (#25)
Co-authored-by: o.musin <o.musin@tinkoff.ru>
1 parent bf43fca commit e926f2b

File tree

6 files changed

+234
-21
lines changed

6 files changed

+234
-21
lines changed

cache/cache.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ var (
1212
ErrFailedToSaveToCache = errors.New("Failed to save item")
1313
// ErrCacheMissed will throw if an item can't be retrieved (due to invalid, or missing)
1414
ErrCacheMissed = errors.New("Cache is missing")
15+
// ErrStorageInternal will throw when some internal error in storage occurred
16+
ErrStorageInternal = errors.New("Internal error in storage")
1517
)
1618

1719
// Cache storage type

cache/redis/redis.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package redis
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"time"
8+
9+
"github.com/bxcodec/httpcache/cache"
10+
"github.com/go-redis/redis/v8"
11+
)
12+
13+
// CacheOptions for storing data for Redis connections
14+
type CacheOptions struct {
15+
Addr string
16+
Password string
17+
DB int // 0 for default DB
18+
}
19+
20+
type redisCache struct {
21+
ctx context.Context
22+
cache *redis.Client
23+
expiryTime time.Duration
24+
}
25+
26+
// NewCache will return the redis cache handler
27+
func NewCache(ctx context.Context, c *redis.Client, exptime time.Duration) cache.ICacheInteractor {
28+
return &redisCache{
29+
ctx: ctx,
30+
cache: c,
31+
expiryTime: exptime,
32+
}
33+
}
34+
35+
func (i *redisCache) Set(key string, value cache.CachedResponse) (err error) {
36+
valueJSON, _ := json.Marshal(value)
37+
set := i.cache.Set(i.ctx, key, string(valueJSON), i.expiryTime*time.Second)
38+
if err := set.Err(); err != nil {
39+
fmt.Println(err)
40+
return cache.ErrStorageInternal
41+
}
42+
return nil
43+
}
44+
45+
func (i *redisCache) Get(key string) (res cache.CachedResponse, err error) {
46+
get := i.cache.Do(i.ctx, "get", key)
47+
if err = get.Err(); err != nil {
48+
if err == redis.Nil {
49+
return cache.CachedResponse{}, cache.ErrCacheMissed
50+
}
51+
return cache.CachedResponse{}, cache.ErrStorageInternal
52+
}
53+
val := get.Val().(string)
54+
err = json.Unmarshal([]byte(val), &res)
55+
if err != nil {
56+
return cache.CachedResponse{}, cache.ErrStorageInternal
57+
}
58+
return
59+
}
60+
61+
func (i *redisCache) Delete(key string) (err error) {
62+
// deleting in redis equal to setting expiration time for key to 0
63+
set := i.cache.Set(i.ctx, key, nil, 0)
64+
if err := set.Err(); err != nil {
65+
return cache.ErrStorageInternal
66+
}
67+
return nil
68+
}
69+
70+
func (i *redisCache) Origin() string {
71+
return cache.CacheRedis
72+
}
73+
74+
func (i *redisCache) Flush() error {
75+
flush := i.cache.FlushAll(i.ctx)
76+
if err := flush.Err(); err != nil {
77+
return cache.ErrStorageInternal
78+
}
79+
return nil
80+
}

cache/redis/redis_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package redis_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/alicebob/miniredis"
9+
"github.com/bxcodec/httpcache/cache"
10+
rediscache "github.com/bxcodec/httpcache/cache/redis"
11+
"github.com/go-redis/redis/v8"
12+
)
13+
14+
func TestCacheRedis(t *testing.T) {
15+
s, err := miniredis.Run()
16+
if err != nil {
17+
panic(err)
18+
}
19+
defer s.Close()
20+
c := redis.NewClient(&redis.Options{
21+
Addr: s.Addr(),
22+
Password: "", // no password set
23+
DB: 0, // use default DB
24+
})
25+
26+
cacheObj := rediscache.NewCache(context.Background(), c, 15)
27+
testKey := "KEY"
28+
testVal := cache.CachedResponse{
29+
DumpedResponse: nil,
30+
RequestURI: "http://bxcodec.io",
31+
RequestMethod: "GET",
32+
CachedTime: time.Now(),
33+
}
34+
35+
// Try to SET item
36+
err = cacheObj.Set(testKey, testVal)
37+
if err != nil {
38+
t.Fatalf("expected %v, got %v", nil, err)
39+
}
40+
41+
// try to GET item from cache
42+
res, err := cacheObj.Get(testKey)
43+
if err != nil {
44+
t.Fatalf("expected %v, got %v", nil, err)
45+
}
46+
// assert the content
47+
if res.RequestURI != testVal.RequestURI {
48+
t.Fatalf("expected %v, got %v", testVal.RequestURI, res.RequestURI)
49+
}
50+
// assert the content
51+
if res.RequestMethod != testVal.RequestMethod {
52+
t.Fatalf("expected %v, got %v", testVal.RequestMethod, res.RequestMethod)
53+
}
54+
55+
// try to DELETE the item
56+
err = cacheObj.Delete(testKey)
57+
if err != nil {
58+
t.Fatalf("expected %v, got %v", nil, err)
59+
}
60+
61+
// try to re-GET item from cache after deleted
62+
res, err = cacheObj.Get(testKey)
63+
if err == nil {
64+
t.Fatalf("expected %v, got %v", err, nil)
65+
}
66+
}
Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"time"
88

99
"github.com/bxcodec/httpcache"
10+
"github.com/bxcodec/httpcache/cache/redis"
1011
)
1112

1213
func Example_inMemoryStorageDefault() {
@@ -16,27 +17,42 @@ func Example_inMemoryStorageDefault() {
1617
log.Fatal(err)
1718
}
1819

19-
for i := 0; i < 100; i++ {
20-
startTime := time.Now()
21-
req, err := http.NewRequest("GET", "https://bxcodec.io", nil)
22-
if err != nil {
23-
log.Fatal((err))
24-
}
25-
res, err := client.Do(req)
26-
if err != nil {
27-
log.Fatal(err)
28-
}
29-
fmt.Printf("Response time: %v micro-second\n", time.Since(startTime).Microseconds())
30-
fmt.Println("Status Code", res.StatusCode)
31-
time.Sleep(time.Second * 1)
32-
fmt.Println("Sequence >>> ", i)
33-
if i%5 == 0 {
34-
err := handler.CacheInteractor.Flush()
35-
if err != nil {
36-
log.Fatal(err)
37-
}
38-
}
20+
processCachedRequest(client, handler)
21+
// Example Output:
22+
/*
23+
2020/06/21 13:14:51 Cache item's missing failed to retrieve from cache, trying with a live version
24+
Response time: 940086 micro-second
25+
Status Code 200
26+
Sequence >>> 0
27+
2020/06/21 13:14:53 Cache item's missing failed to retrieve from cache, trying with a live version
28+
Response time: 73679 micro-second
29+
Status Code 200
30+
Sequence >>> 1
31+
Response time: 126 micro-second
32+
Status Code 200
33+
Sequence >>> 2
34+
Response time: 96 micro-second
35+
Status Code 200
36+
Sequence >>> 3
37+
Response time: 102 micro-second
38+
Status Code 200
39+
Sequence >>> 4
40+
Response time: 94 micro-second
41+
Status Code 200
42+
Sequence >>> 5
43+
*/
44+
}
45+
46+
func Example_redisStorage() {
47+
client := &http.Client{}
48+
handler, err := httpcache.NewWithRedisCache(client, true, &redis.CacheOptions{
49+
Addr: "localhost:6379",
50+
}, time.Second*15)
51+
if err != nil {
52+
log.Fatal(err)
3953
}
54+
55+
processCachedRequest(client, handler)
4056
// Example Output:
4157
/*
4258
2020/06/21 13:14:51 Cache item's missing failed to retrieve from cache, trying with a live version
@@ -61,3 +77,27 @@ func Example_inMemoryStorageDefault() {
6177
Sequence >>> 5
6278
*/
6379
}
80+
81+
func processCachedRequest(client *http.Client, handler *httpcache.CacheHandler) {
82+
for i := 0; i < 100; i++ {
83+
startTime := time.Now()
84+
req, err := http.NewRequest("GET", "https://bxcodec.io", nil)
85+
if err != nil {
86+
log.Fatal((err))
87+
}
88+
res, err := client.Do(req)
89+
if err != nil {
90+
log.Fatal(err)
91+
}
92+
fmt.Printf("Response time: %v micro-second\n", time.Since(startTime).Microseconds())
93+
fmt.Println("Status Code", res.StatusCode)
94+
time.Sleep(time.Second * 1)
95+
fmt.Println("Sequence >>> ", i)
96+
if i%5 == 0 {
97+
err := handler.CacheInteractor.Flush()
98+
if err != nil {
99+
log.Fatal(err)
100+
}
101+
}
102+
}
103+
}

go.mod

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ module github.com/bxcodec/httpcache
33
go 1.13
44

55
require (
6+
github.com/alicebob/miniredis v2.5.0+incompatible
7+
github.com/alicebob/miniredis/v2 v2.13.0
68
github.com/bxcodec/gotcha v1.0.0-beta.2
9+
github.com/go-redis/redis/v8 v8.0.0-beta.5
710
github.com/patrickmn/go-cache v2.1.0+incompatible
8-
github.com/stretchr/testify v1.4.0
11+
github.com/stretchr/testify v1.5.1
12+
golang.org/x/net v0.0.0-20190923162816-aa69164e4478
913
)

httpcache.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import (
88
inmemcache "github.com/bxcodec/gotcha/cache"
99
"github.com/bxcodec/httpcache/cache"
1010
"github.com/bxcodec/httpcache/cache/inmem"
11+
rediscache "github.com/bxcodec/httpcache/cache/redis"
12+
"github.com/go-redis/redis/v8"
13+
"golang.org/x/net/context"
1114
)
1215

1316
// NewWithCustomStorageCache will initiate the httpcache with your defined cache storage
@@ -40,3 +43,21 @@ func NewWithInmemoryCache(client *http.Client, rfcCompliance bool, duration ...t
4043

4144
return newClient(client, rfcCompliance, inmem.NewCache(c))
4245
}
46+
47+
// NewWithRedisCache will create a complete cache-support of HTTP client with using redis cache.
48+
// If the duration not set, the cache will use LFU algorithm
49+
func NewWithRedisCache(client *http.Client, rfcCompliance bool, options *rediscache.CacheOptions,
50+
duration ...time.Duration) (cachedHandler *CacheHandler, err error) {
51+
var ctx = context.Background()
52+
var expiryTime time.Duration
53+
if len(duration) > 0 {
54+
expiryTime = duration[0]
55+
}
56+
c := redis.NewClient(&redis.Options{
57+
Addr: options.Addr,
58+
Password: options.Password,
59+
DB: options.DB,
60+
})
61+
62+
return newClient(client, rfcCompliance, rediscache.NewCache(ctx, c, expiryTime))
63+
}

0 commit comments

Comments
 (0)