Skip to content

Commit

Permalink
Merge pull request #11 from kenshin579/feat/#10-empty
Browse files Browse the repository at this point in the history
[#10] not to store empty response in the cache
  • Loading branch information
kenshin579 committed Jul 13, 2023
2 parents f87cd62 + 41e3e2b commit 6ff89a9
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 16 deletions.
35 changes: 28 additions & 7 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"net"
"net/http"
"net/url"
"reflect"
"sort"
"strconv"
"strings"
Expand Down Expand Up @@ -84,7 +85,7 @@ type (
Header http.Header `json:"header"`

// Expiration is the cached response Expiration date.
Expiration time.Time `json:"Expiration"`
Expiration time.Time `json:"expiration"`

// LastAccess is the last date a cached response was accessed.
// Used by LRU and MRU algorithms.
Expand Down Expand Up @@ -129,7 +130,7 @@ func CacheWithConfig(config CacheConfig) echo.MiddlewareFunc {
panic("Store configuration must be provided")
}
if config.Expiration < 1 {
panic("Cache Expiration must be provided")
panic("Cache expiration must be provided")
}

return func(next echo.HandlerFunc) echo.HandlerFunc {
Expand Down Expand Up @@ -157,12 +158,11 @@ func CacheWithConfig(config CacheConfig) echo.MiddlewareFunc {
config.Store.Release(key)
} else {
if cachedResponse, ok := config.Store.Get(key); ok {
response := bytesToResponse(cachedResponse)
response := toCacheResponse(cachedResponse)
now := time.Now()
if response.Expiration.After(now) {
response.LastAccess = now
response.Frequency++

config.Store.Set(key, response.bytes(), response.Expiration)

for k, v := range response.Header {
Expand Down Expand Up @@ -198,7 +198,9 @@ func CacheWithConfig(config CacheConfig) echo.MiddlewareFunc {
LastAccess: now,
Frequency: 1,
}
config.Store.Set(key, response.bytes(), response.Expiration)
if !isAllFieldsEmpty(value) {
config.Store.Set(key, response.bytes(), response.Expiration)
}
}
return nil
}
Expand Down Expand Up @@ -257,8 +259,8 @@ func (r CacheResponse) bytes() []byte {
return data
}

// bytesToResponse converts bytes array into CacheResponse data structure.
func bytesToResponse(b []byte) CacheResponse {
// toCacheResponse converts bytes array into CacheResponse data structure.
func toCacheResponse(b []byte) CacheResponse {
var r CacheResponse
dec := json.NewDecoder(bytes.NewReader(b))
dec.Decode(&r)
Expand Down Expand Up @@ -287,3 +289,22 @@ func generateKey(method, URL string) uint64 {

return hash.Sum64()
}

func isAllFieldsEmpty(inter any) bool {
val := reflect.ValueOf(inter)
if val.IsZero() {
return true
}

if val.Kind() != reflect.Struct {
return false
}

for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
if !field.IsZero() {
return false
}
}
return true
}
8 changes: 4 additions & 4 deletions cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func TestCache_panicBehavior(t *testing.T) {
})
}

func Test_bytesToResponse(t *testing.T) {
func Test_toCacheResponse(t *testing.T) {
r := CacheResponse{
Value: []byte("value 1"),
Expiration: time.Time{},
Expand All @@ -177,13 +177,13 @@ func Test_bytesToResponse(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := bytesToResponse(tt.b)
got := toCacheResponse(tt.b)
assert.Equal(t, tt.wantValue, string(got.Value))
})
}
}

func Test_nytes(t *testing.T) {
func Test_bytes(t *testing.T) {
r := CacheResponse{
Value: []byte("test"),
Expiration: time.Time{},
Expand All @@ -192,7 +192,7 @@ func Test_nytes(t *testing.T) {
}

bytes := r.bytes()
assert.Equal(t, `{"value":"dGVzdA==","header":null,"Expiration":"0001-01-01T00:00:00Z","lastAccess":"0001-01-01T00:00:00Z","frequency":1}`, string(bytes))
assert.Equal(t, `{"value":"dGVzdA==","header":null,"expiration":"0001-01-01T00:00:00Z","lastAccess":"0001-01-01T00:00:00Z","frequency":1}`, string(bytes))
}

func Test_keyAsString(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func (store *CacheMemoryStore) evict() {
}

for k, v := range store.store {
r := bytesToResponse(v)
r := toCacheResponse(v)
switch store.algorithm {
case LRU:
if r.LastAccess.Before(lastAccess) {
Expand Down
4 changes: 2 additions & 2 deletions memory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func TestGet(t *testing.T) {
b, ok := store.Get(tt.key)
assert.Equal(t, tt.ok, ok)

got := bytesToResponse(b).Value
got := toCacheResponse(b).Value
assert.Equal(t, tt.want, got)
})
}
Expand Down Expand Up @@ -94,7 +94,7 @@ func TestSet(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
store.Set(tt.key, tt.response.bytes(), tt.response.Expiration)
if bytesToResponse(store.store[tt.key]).Value == nil {
if toCacheResponse(store.store[tt.key]).Value == nil {
t.Errorf(
"memory.Set() error = store[%v] response is not %s", tt.key, tt.response.Value,
)
Expand Down
22 changes: 20 additions & 2 deletions redis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (suite *cacheRedisStoreTestSuite) SetupSuite() {
suite.echo.Use(CacheWithConfig(CacheConfig{
Store: store,
Expiration: 5 * time.Second,
IncludePaths: []string{"/test"},
IncludePaths: []string{"/test", "/empty"},
}))
}

Expand Down Expand Up @@ -74,7 +74,11 @@ func (suite *cacheRedisStoreTestSuite) Test_Echo_CacheWithConfig() {
return c.String(http.StatusOK, "test")
})

suite.Run("echo cache test with redis store", func() {
suite.echo.GET("/empty", func(c echo.Context) error {
return c.String(http.StatusOK, "")
})

suite.Run("GET /test with non empty body", func() {
req := httptest.NewRequest(http.MethodGet, "/test", nil)
rec := httptest.NewRecorder()

Expand All @@ -92,4 +96,18 @@ func (suite *cacheRedisStoreTestSuite) Test_Echo_CacheWithConfig() {
suite.NoError(err)
suite.Equal("test", string(cacheResponse.Value))
})

suite.Run("GET /empty", func() {
req := httptest.NewRequest(http.MethodGet, "/empty", nil)
rec := httptest.NewRecorder()

suite.echo.ServeHTTP(rec, req)

suite.Equal(http.StatusOK, rec.Code)
suite.Equal("", rec.Body.String())

key := generateKey(http.MethodGet, "/empty")
_, ok := suite.cacheStore.Get(key)
suite.False(ok)
})
}

0 comments on commit 6ff89a9

Please sign in to comment.