Skip to content

Commit 9e2b528

Browse files
authored
Merge pull request #17 from kenshin579/feat/#16-cache
[#16] not returning response from cache
2 parents b189b30 + 2aafa65 commit 9e2b528

File tree

3 files changed

+86
-44
lines changed

3 files changed

+86
-44
lines changed

cache.go

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,6 @@ import (
4444
"github.com/labstack/echo/v4/middleware"
4545
)
4646

47-
const (
48-
refreshKey = "refreshKey"
49-
)
50-
5147
type (
5248
// CacheStore is the interface to be implemented by custom stores.
5349
CacheStore interface {
@@ -147,36 +143,31 @@ func CacheWithConfig(config CacheConfig) echo.MiddlewareFunc {
147143
}
148144

149145
if c.Request().Method == http.MethodGet {
146+
// isCached := false
150147
sortURLParams(c.Request().URL)
151148
key := generateKey(c.Request().Method, c.Request().URL.String())
152149

153-
params := c.Request().URL.Query()
154-
155-
if _, ok := params[refreshKey]; ok {
156-
refreshKeyValue := params.Get(refreshKey)
157-
delete(params, refreshKeyValue)
158-
config.Store.Release(key)
159-
} else {
160-
if cachedResponse, ok := config.Store.Get(key); ok {
161-
response := toCacheResponse(cachedResponse)
162-
now := time.Now()
163-
if now.After(response.Expiration) {
164-
response.LastAccess = now
165-
response.Frequency++
166-
config.Store.Set(key, response.bytes(), response.Expiration)
167-
168-
for k, v := range response.Header {
169-
c.Response().Header().Set(k, strings.Join(v, ","))
170-
}
171-
c.Response().WriteHeader(http.StatusOK)
172-
c.Response().Write(response.Value)
173-
return nil
174-
}
150+
if cachedResponse, ok := config.Store.Get(key); ok {
151+
response := toCacheResponse(cachedResponse)
152+
now := time.Now()
153+
154+
// not expired. return response from the cache
155+
if !isExpired(now, response.Expiration) {
156+
// restore the response in the cache
157+
response.LastAccess = now
158+
response.Frequency++
175159

176-
config.Store.Release(key)
160+
config.Store.Set(key, response.bytes(), response.Expiration)
161+
for k, v := range response.Header {
162+
c.Response().Header().Set(k, strings.Join(v, ","))
163+
}
164+
c.Response().WriteHeader(http.StatusOK)
165+
c.Response().Write(response.Value)
166+
return nil
177167
}
178168
}
179169

170+
// Response
180171
resBody := new(bytes.Buffer)
181172
mw := io.MultiWriter(c.Response().Writer, resBody)
182173
writer := &bodyDumpResponseWriter{Writer: mw, ResponseWriter: c.Response().Writer}
@@ -186,9 +177,8 @@ func CacheWithConfig(config CacheConfig) echo.MiddlewareFunc {
186177
c.Error(err)
187178
}
188179

189-
statusCode := writer.statusCode
190-
value := resBody.Bytes()
191-
if statusCode < http.StatusBadRequest {
180+
if writer.statusCode < http.StatusBadRequest {
181+
value := resBody.Bytes()
192182
now := time.Now()
193183

194184
response := CacheResponse{
@@ -198,15 +188,13 @@ func CacheWithConfig(config CacheConfig) echo.MiddlewareFunc {
198188
LastAccess: now,
199189
Frequency: 1,
200190
}
191+
201192
if !isAllFieldsEmpty(value) {
202193
config.Store.Set(key, response.bytes(), response.Expiration)
203194
}
204195
}
205196
return nil
206197
}
207-
if err := next(c); err != nil {
208-
c.Error(err)
209-
}
210198
return nil
211199
}
212200
}
@@ -342,3 +330,7 @@ func isMapEmpty(m map[string]any) bool {
342330
}
343331
return true
344332
}
333+
334+
func isExpired(now, expiration time.Time) bool {
335+
return now.After(expiration)
336+
}

cache_test.go

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ func Test_CacheWithConfig(t *testing.T) {
4848
}
4949

5050
type wants struct {
51-
code int
52-
isCached bool
51+
code int
52+
responseBody string
53+
isCached bool
5354
}
5455
tests := []struct {
5556
name string
@@ -71,15 +72,16 @@ func Test_CacheWithConfig(t *testing.T) {
7172
},
7273
},
7374
wants: wants{
74-
code: http.StatusOK,
75-
isCached: true,
75+
code: http.StatusOK,
76+
responseBody: "test",
77+
isCached: true,
7678
},
7779
},
7880
{
7981
name: "test ExcludePaths",
8082
args: args{
8183
method: http.MethodGet,
82-
url: "http://foo.bar/test-1",
84+
url: "http://foo.bar/test-2",
8385
cacheConfig: CacheConfig{
8486
Store: NewCacheMemoryStoreWithConfig(CacheMemoryStoreConfig{
8587
Capacity: 5,
@@ -90,8 +92,9 @@ func Test_CacheWithConfig(t *testing.T) {
9092
},
9193
},
9294
wants: wants{
93-
code: http.StatusOK,
94-
isCached: false,
95+
code: http.StatusOK,
96+
responseBody: "test",
97+
isCached: false,
9598
},
9699
},
97100
{
@@ -109,8 +112,9 @@ func Test_CacheWithConfig(t *testing.T) {
109112
},
110113
},
111114
wants: wants{
112-
code: http.StatusOK,
113-
isCached: false,
115+
code: http.StatusOK,
116+
responseBody: "",
117+
isCached: false,
114118
},
115119
},
116120
}
@@ -125,7 +129,7 @@ func Test_CacheWithConfig(t *testing.T) {
125129
_ = mw(handler)(c)
126130

127131
assert.Equal(t, tt.wants.code, rec.Code)
128-
assert.Equal(t, "test", rec.Body.String())
132+
assert.Equal(t, tt.wants.responseBody, rec.Body.String())
129133

130134
cacheResp, ok := tt.args.cacheConfig.Store.Get(generateKey(tt.args.method, tt.args.url))
131135
assert.Equal(t, tt.wants.isCached, ok)

redis_test.go

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ func (suite *cacheRedisStoreTestSuite) Test_Redis_CacheStore() {
7070
}
7171

7272
func (suite *cacheRedisStoreTestSuite) Test_Echo_CacheWithConfig() {
73+
actualCalledCountForTestAPI := 0
74+
7375
suite.echo.GET("/test", func(c echo.Context) error {
76+
actualCalledCountForTestAPI++
7477
return c.String(http.StatusOK, "test")
7578
})
7679

@@ -82,7 +85,49 @@ func (suite *cacheRedisStoreTestSuite) Test_Echo_CacheWithConfig() {
8285
return c.String(http.StatusOK, `{"symbolId":"","type":"","price":0.0}`)
8386
})
8487

85-
suite.Run("GET /test with non empty body", func() {
88+
suite.Run("GET /test - return actual response and store in the cache", func() {
89+
req := httptest.NewRequest(http.MethodGet, "/test", nil)
90+
rec := httptest.NewRecorder()
91+
92+
suite.echo.ServeHTTP(rec, req)
93+
94+
suite.Equal(http.StatusOK, rec.Code)
95+
suite.Equal("test", rec.Body.String())
96+
97+
key := generateKey(http.MethodGet, "/test")
98+
data, ok := suite.cacheStore.Get(key)
99+
suite.True(ok)
100+
101+
var cacheResponse CacheResponse
102+
err := json.Unmarshal(data, &cacheResponse)
103+
suite.NoError(err)
104+
suite.Equal("test", string(cacheResponse.Value))
105+
suite.Equal(1, actualCalledCountForTestAPI)
106+
})
107+
108+
suite.Run("GET /test - not expired. return response from the cache", func() {
109+
req := httptest.NewRequest(http.MethodGet, "/test", nil)
110+
rec := httptest.NewRecorder()
111+
112+
suite.echo.ServeHTTP(rec, req)
113+
114+
suite.Equal(http.StatusOK, rec.Code)
115+
suite.Equal("test", rec.Body.String())
116+
117+
key := generateKey(http.MethodGet, "/test")
118+
data, ok := suite.cacheStore.Get(key)
119+
suite.True(ok)
120+
121+
var cacheResponse CacheResponse
122+
err := json.Unmarshal(data, &cacheResponse)
123+
suite.NoError(err)
124+
suite.Equal("test", string(cacheResponse.Value))
125+
suite.Equal(1, actualCalledCountForTestAPI)
126+
})
127+
128+
suite.Run("GET /test - expired. return actual response", func() {
129+
time.Sleep(5 * time.Second)
130+
86131
req := httptest.NewRequest(http.MethodGet, "/test", nil)
87132
rec := httptest.NewRecorder()
88133

@@ -99,6 +144,7 @@ func (suite *cacheRedisStoreTestSuite) Test_Echo_CacheWithConfig() {
99144
err := json.Unmarshal(data, &cacheResponse)
100145
suite.NoError(err)
101146
suite.Equal("test", string(cacheResponse.Value))
147+
suite.Equal(2, actualCalledCountForTestAPI)
102148
})
103149

104150
suite.Run("GET /empty/string", func() {

0 commit comments

Comments
 (0)