Skip to content

Commit 4731aac

Browse files
authored
Merge pull request #19 from kenshin579/feat/#18-caching
[#18] caching with different expiration time
2 parents 9e2b528 + c132c22 commit 4731aac

File tree

4 files changed

+109
-40
lines changed

4 files changed

+109
-40
lines changed

cache.go

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,19 +67,23 @@ type (
6767

6868
Store CacheStore
6969

70-
Expiration time.Duration
71-
IncludePaths []string
72-
ExcludePaths []string
70+
Expiration time.Duration // default expiration
71+
IncludePaths []string
72+
IncludePathsWithExpiration map[string]time.Duration // key: path, value: expiration //IncludePathsWithExpiration has higher priority
73+
ExcludePaths []string
7374
}
7475

7576
// CacheResponse is the cached response data structure.
7677
CacheResponse struct {
77-
// Value is the cached response value.
78-
Value []byte `json:"value"`
78+
// URL is URL
79+
URL string `json:"url"`
7980

8081
// Header is the cached response header.
8182
Header http.Header `json:"header"`
8283

84+
// Body is the cached response value.
85+
Body []byte `json:"body"`
86+
8387
// Expiration is the cached response Expiration date.
8488
Expiration time.Time `json:"expiration"`
8589

@@ -143,7 +147,6 @@ func CacheWithConfig(config CacheConfig) echo.MiddlewareFunc {
143147
}
144148

145149
if c.Request().Method == http.MethodGet {
146-
// isCached := false
147150
sortURLParams(c.Request().URL)
148151
key := generateKey(c.Request().Method, c.Request().URL.String())
149152

@@ -162,7 +165,7 @@ func CacheWithConfig(config CacheConfig) echo.MiddlewareFunc {
162165
c.Response().Header().Set(k, strings.Join(v, ","))
163166
}
164167
c.Response().WriteHeader(http.StatusOK)
165-
c.Response().Write(response.Value)
168+
c.Response().Write(response.Body)
166169
return nil
167170
}
168171
}
@@ -178,18 +181,19 @@ func CacheWithConfig(config CacheConfig) echo.MiddlewareFunc {
178181
}
179182

180183
if writer.statusCode < http.StatusBadRequest {
181-
value := resBody.Bytes()
184+
body := resBody.Bytes()
182185
now := time.Now()
183186

184187
response := CacheResponse{
185-
Value: value,
188+
Body: body,
189+
URL: c.Request().URL.String(),
186190
Header: writer.Header(),
187-
Expiration: now.Add(config.Expiration),
191+
Expiration: config.getExpiration(now, c.Request().URL.String()),
188192
LastAccess: now,
189193
Frequency: 1,
190194
}
191195

192-
if !isAllFieldsEmpty(value) {
196+
if !isAllFieldsEmpty(body) {
193197
config.Store.Set(key, response.bytes(), response.Expiration)
194198
}
195199
}
@@ -206,6 +210,12 @@ func (c *CacheConfig) isIncludePaths(URL string) bool {
206210
return true
207211
}
208212
}
213+
214+
for k, _ := range c.IncludePathsWithExpiration {
215+
if strings.Contains(URL, k) {
216+
return true
217+
}
218+
}
209219
return false
210220
}
211221

@@ -218,6 +228,16 @@ func (c *CacheConfig) isExcludePaths(URL string) bool {
218228
return false
219229
}
220230

231+
func (c *CacheConfig) getExpiration(now time.Time, URL string) time.Time {
232+
for k, v := range c.IncludePathsWithExpiration {
233+
if strings.Contains(URL, k) {
234+
return now.Add(v)
235+
}
236+
}
237+
238+
return now.Add(c.Expiration)
239+
}
240+
221241
type bodyDumpResponseWriter struct {
222242
io.Writer
223243
http.ResponseWriter

cache_test.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func Test_CacheWithConfig(t *testing.T) {
5858
wants wants
5959
}{
6060
{
61-
name: "test IncludePaths",
61+
name: "test IncludePathsWithExpiration",
6262
args: args{
6363
method: http.MethodGet,
6464
url: "http://foo.bar/test-1",
@@ -138,7 +138,7 @@ func Test_CacheWithConfig(t *testing.T) {
138138
var cacheResponse CacheResponse
139139
err := json.Unmarshal(cacheResp, &cacheResponse)
140140
assert.NoError(t, err)
141-
assert.Equal(t, "test", string(cacheResponse.Value))
141+
assert.Equal(t, "test", string(cacheResponse.Body))
142142
}
143143
})
144144
}
@@ -161,7 +161,7 @@ func TestCache_panicBehavior(t *testing.T) {
161161

162162
func Test_toCacheResponse(t *testing.T) {
163163
r := CacheResponse{
164-
Value: []byte("value 1"),
164+
Body: []byte("value 1"),
165165
Expiration: time.Time{},
166166
Frequency: 1,
167167
LastAccess: time.Time{},
@@ -182,21 +182,21 @@ func Test_toCacheResponse(t *testing.T) {
182182
for _, tt := range tests {
183183
t.Run(tt.name, func(t *testing.T) {
184184
got := toCacheResponse(tt.b)
185-
assert.Equal(t, tt.wantValue, string(got.Value))
185+
assert.Equal(t, tt.wantValue, string(got.Body))
186186
})
187187
}
188188
}
189189

190190
func Test_bytes(t *testing.T) {
191191
r := CacheResponse{
192-
Value: []byte("test"),
192+
Body: []byte("test"),
193193
Expiration: time.Time{},
194194
Frequency: 1,
195195
LastAccess: time.Time{},
196196
}
197197

198198
bytes := r.bytes()
199-
assert.Equal(t, `{"value":"dGVzdA==","header":null,"expiration":"0001-01-01T00:00:00Z","lastAccess":"0001-01-01T00:00:00Z","frequency":1}`, string(bytes))
199+
assert.Equal(t, `{"url":"","header":null,"body":"dGVzdA==","expiration":"0001-01-01T00:00:00Z","lastAccess":"0001-01-01T00:00:00Z","frequency":1}`, string(bytes))
200200
}
201201

202202
func Test_keyAsString(t *testing.T) {
@@ -324,3 +324,15 @@ func Test_isAllFieldsEmpty(t *testing.T) {
324324
assert.True(t, isAllFieldsEmpty([]byte(`{"a":"","b":"","c":0}`)))
325325
assert.True(t, isAllFieldsEmpty([]byte(`{"a":"","b":"","c":0.0}`)))
326326
}
327+
328+
func Test_isIncludePaths(t *testing.T) {
329+
config := CacheConfig{
330+
IncludePathsWithExpiration: map[string]time.Duration{
331+
"/test1": time.Duration(1) * time.Second,
332+
"/test2": time.Duration(2) * time.Second,
333+
},
334+
}
335+
assert.False(t, config.isIncludePaths("/test3"))
336+
assert.True(t, config.isIncludePaths("/test1"))
337+
assert.True(t, config.isIncludePaths("/test2"))
338+
}

memory_test.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func TestGet(t *testing.T) {
1515
LRU,
1616
map[uint64][]byte{
1717
14974843192121052621: CacheResponse{
18-
Value: []byte("value 1"),
18+
Body: []byte("value 1"),
1919
Expiration: time.Now(),
2020
LastAccess: time.Now(),
2121
Frequency: 1,
@@ -47,7 +47,7 @@ func TestGet(t *testing.T) {
4747
b, ok := store.Get(tt.key)
4848
assert.Equal(t, tt.ok, ok)
4949

50-
got := toCacheResponse(b).Value
50+
got := toCacheResponse(b).Body
5151
assert.Equal(t, tt.want, got)
5252
})
5353
}
@@ -70,33 +70,33 @@ func TestSet(t *testing.T) {
7070
"sets response cache",
7171
1,
7272
CacheResponse{
73-
Value: []byte("value 1"),
73+
Body: []byte("value 1"),
7474
Expiration: time.Now().Add(1 * time.Minute),
7575
},
7676
},
7777
{
7878
"sets response cache",
7979
2,
8080
CacheResponse{
81-
Value: []byte("value 2"),
81+
Body: []byte("value 2"),
8282
Expiration: time.Now().Add(1 * time.Minute),
8383
},
8484
},
8585
{
8686
"sets response cache",
8787
3,
8888
CacheResponse{
89-
Value: []byte("value 3"),
89+
Body: []byte("value 3"),
9090
Expiration: time.Now().Add(1 * time.Minute),
9191
},
9292
},
9393
}
9494
for _, tt := range tests {
9595
t.Run(tt.name, func(t *testing.T) {
9696
store.Set(tt.key, tt.response.bytes(), tt.response.Expiration)
97-
if toCacheResponse(store.store[tt.key]).Value == nil {
97+
if toCacheResponse(store.store[tt.key]).Body == nil {
9898
t.Errorf(
99-
"memory.Set() error = store[%v] response is not %s", tt.key, tt.response.Value,
99+
"memory.Set() error = store[%v] response is not %s", tt.key, tt.response.Body,
100100
)
101101
}
102102
})
@@ -111,15 +111,15 @@ func TestRelease(t *testing.T) {
111111
map[uint64][]byte{
112112
14974843192121052621: CacheResponse{
113113
Expiration: time.Now().Add(1 * time.Minute),
114-
Value: []byte("value 1"),
114+
Body: []byte("value 1"),
115115
}.bytes(),
116116
14974839893586167988: CacheResponse{
117117
Expiration: time.Now(),
118-
Value: []byte("value 2"),
118+
Body: []byte("value 2"),
119119
}.bytes(),
120120
14974840993097796199: CacheResponse{
121121
Expiration: time.Now(),
122-
Value: []byte("value 3"),
122+
Body: []byte("value 3"),
123123
}.bytes(),
124124
},
125125
}
@@ -185,19 +185,19 @@ func TestEvict(t *testing.T) {
185185
tt.algorithm,
186186
map[uint64][]byte{
187187
14974843192121052621: CacheResponse{
188-
Value: []byte("value 1"),
188+
Body: []byte("value 1"),
189189
Expiration: time.Now().Add(1 * time.Minute),
190190
LastAccess: time.Now().Add(-1 * time.Minute),
191191
Frequency: 2,
192192
}.bytes(),
193193
14974839893586167988: CacheResponse{
194-
Value: []byte("value 2"),
194+
Body: []byte("value 2"),
195195
Expiration: time.Now().Add(1 * time.Minute),
196196
LastAccess: time.Now().Add(-2 * time.Minute),
197197
Frequency: 1,
198198
}.bytes(),
199199
14974840993097796199: CacheResponse{
200-
Value: []byte("value 3"),
200+
Body: []byte("value 3"),
201201
Expiration: time.Now().Add(1 * time.Minute),
202202
LastAccess: time.Now().Add(-3 * time.Minute),
203203
Frequency: 3,

redis_test.go

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ func (suite *cacheRedisStoreTestSuite) SetupSuite() {
4242
Store: store,
4343
Expiration: 5 * time.Second,
4444
IncludePaths: []string{"/test", "/empty"},
45+
IncludePathsWithExpiration: map[string]time.Duration{
46+
"/expired": 1 * time.Minute,
47+
},
4548
}))
4649
}
4750

@@ -70,10 +73,7 @@ func (suite *cacheRedisStoreTestSuite) Test_Redis_CacheStore() {
7073
}
7174

7275
func (suite *cacheRedisStoreTestSuite) Test_Echo_CacheWithConfig() {
73-
actualCalledCountForTestAPI := 0
74-
7576
suite.echo.GET("/test", func(c echo.Context) error {
76-
actualCalledCountForTestAPI++
7777
return c.String(http.StatusOK, "test")
7878
})
7979

@@ -85,6 +85,24 @@ func (suite *cacheRedisStoreTestSuite) Test_Echo_CacheWithConfig() {
8585
return c.String(http.StatusOK, `{"symbolId":"","type":"","price":0.0}`)
8686
})
8787

88+
suite.echo.GET("/expired", func(c echo.Context) error {
89+
return c.String(http.StatusOK, "expired")
90+
})
91+
92+
suite.Run("GET /expired - first call to store response in the cache", func() {
93+
req := httptest.NewRequest(http.MethodGet, "/expired", nil)
94+
rec := httptest.NewRecorder()
95+
96+
suite.echo.ServeHTTP(rec, req)
97+
98+
suite.Equal(http.StatusOK, rec.Code)
99+
suite.Equal(`expired`, rec.Body.String())
100+
101+
key := generateKey(http.MethodGet, "/expired")
102+
_, ok := suite.cacheStore.Get(key)
103+
suite.True(ok)
104+
})
105+
88106
suite.Run("GET /test - return actual response and store in the cache", func() {
89107
req := httptest.NewRequest(http.MethodGet, "/test", nil)
90108
rec := httptest.NewRecorder()
@@ -101,8 +119,8 @@ func (suite *cacheRedisStoreTestSuite) Test_Echo_CacheWithConfig() {
101119
var cacheResponse CacheResponse
102120
err := json.Unmarshal(data, &cacheResponse)
103121
suite.NoError(err)
104-
suite.Equal("test", string(cacheResponse.Value))
105-
suite.Equal(1, actualCalledCountForTestAPI)
122+
suite.Equal("test", string(cacheResponse.Body))
123+
suite.Equal(1, cacheResponse.Frequency)
106124
})
107125

108126
suite.Run("GET /test - not expired. return response from the cache", func() {
@@ -121,8 +139,8 @@ func (suite *cacheRedisStoreTestSuite) Test_Echo_CacheWithConfig() {
121139
var cacheResponse CacheResponse
122140
err := json.Unmarshal(data, &cacheResponse)
123141
suite.NoError(err)
124-
suite.Equal("test", string(cacheResponse.Value))
125-
suite.Equal(1, actualCalledCountForTestAPI)
142+
suite.Equal("test", string(cacheResponse.Body))
143+
suite.Equal(2, cacheResponse.Frequency)
126144
})
127145

128146
suite.Run("GET /test - expired. return actual response", func() {
@@ -143,8 +161,8 @@ func (suite *cacheRedisStoreTestSuite) Test_Echo_CacheWithConfig() {
143161
var cacheResponse CacheResponse
144162
err := json.Unmarshal(data, &cacheResponse)
145163
suite.NoError(err)
146-
suite.Equal("test", string(cacheResponse.Value))
147-
suite.Equal(2, actualCalledCountForTestAPI)
164+
suite.Equal("test", string(cacheResponse.Body))
165+
suite.Equal(1, cacheResponse.Frequency)
148166
})
149167

150168
suite.Run("GET /empty/string", func() {
@@ -170,8 +188,27 @@ func (suite *cacheRedisStoreTestSuite) Test_Echo_CacheWithConfig() {
170188
suite.Equal(http.StatusOK, rec.Code)
171189
suite.Equal(`{"symbolId":"","type":"","price":0.0}`, rec.Body.String())
172190

173-
key := generateKey(http.MethodGet, "/empty2")
191+
key := generateKey(http.MethodGet, "/empty/json")
174192
_, ok := suite.cacheStore.Get(key)
175193
suite.False(ok)
176194
})
195+
196+
suite.Run("GET /expired - second call, still get the response from the cache", func() {
197+
req := httptest.NewRequest(http.MethodGet, "/expired", nil)
198+
rec := httptest.NewRecorder()
199+
200+
suite.echo.ServeHTTP(rec, req)
201+
202+
suite.Equal(http.StatusOK, rec.Code)
203+
suite.Equal(`expired`, rec.Body.String())
204+
205+
key := generateKey(http.MethodGet, "/expired")
206+
data, ok := suite.cacheStore.Get(key)
207+
suite.True(ok)
208+
209+
var cacheResponse CacheResponse
210+
err := json.Unmarshal(data, &cacheResponse)
211+
suite.NoError(err)
212+
suite.Equal(2, cacheResponse.Frequency)
213+
})
177214
}

0 commit comments

Comments
 (0)