Skip to content

Commit aedffa1

Browse files
authored
🧹 chore: Improve Cache middleware defaults (#3740)
* Make CacheControl boolean * docs(cache): align defaults with config * Limit cache entries to 1MB and restore docs * Rename cache control option
1 parent cc3b007 commit aedffa1

File tree

4 files changed

+52
-36
lines changed

4 files changed

+52
-36
lines changed

docs/middleware/cache.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ id: cache
66

77
Cache middleware for [Fiber](https://github.com/gofiber/fiber) that intercepts responses and stores the body, `Content-Type`, and status code under a key derived from the request path and method. Special thanks to [@codemicro](https://github.com/codemicro/fiber-cache) for contributing this middleware to Fiber core.
88

9+
By default, cached responses expire after five minutes and the middleware stores up to 1 MB of response bodies.
10+
911
Request directives
1012

1113
- `Cache-Control: no-cache` returns the latest response while still caching it, so the status is always `miss`.
@@ -67,7 +69,7 @@ app.Use(cache.New(cache.Config{
6769
return fiber.Query[bool](c, "noCache")
6870
},
6971
Expiration: 30 * time.Minute,
70-
CacheControl: true,
72+
DisableCacheControl: true,
7173
}))
7274
```
7375

@@ -107,33 +109,33 @@ app.Use(cache.New(cache.Config{
107109
| Property | Type | Description | Default |
108110
| :------------------- | :--------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------- |
109111
| Next | `func(fiber.Ctx) bool` | Next defines a function that is executed before creating the cache entry and can be used to execute the request without cache creation. If an entry already exists, it will be used. If you want to completely bypass the cache functionality in certain cases, you should use the [skip middleware](skip.md). | `nil` |
110-
| Expiration | `time.Duration` | Expiration is the time that a cached response will live. | `1 * time.Minute` |
112+
| Expiration | `time.Duration` | Expiration is the time that a cached response will live. | `5 * time.Minute` |
111113
| CacheHeader | `string` | CacheHeader is the header on the response header that indicates the cache status, with the possible return values "hit," "miss," or "unreachable." | `X-Cache` |
112-
| CacheControl | `bool` | CacheControl enables client-side caching if set to true. | `false` |
113-
| CacheInvalidator | `func(fiber.Ctx) bool` | CacheInvalidator defines a function that is executed before checking the cache entry. It can be used to invalidate the existing cache manually by returning true. | `nil` |
114-
| KeyGenerator | `func(fiber.Ctx) string` | Key allows you to generate custom keys. The HTTP method is appended automatically. | `func(c fiber.Ctx) string { return utils.CopyString(c.Path()) }` |
114+
| DisableCacheControl | `bool` | DisableCacheControl omits the `Cache-Control` header when set to `true`. | `false` |
115+
| CacheInvalidator | `func(fiber.Ctx) bool` | CacheInvalidator defines a function that is executed before checking the cache entry. It can be used to invalidate the existing cache manually by returning true. | `nil` |
116+
| KeyGenerator | `func(fiber.Ctx) string` | KeyGenerator allows you to generate custom keys. The HTTP method is appended automatically. | `func(c fiber.Ctx) string { return utils.CopyString(c.Path()) }` |
115117
| ExpirationGenerator | `func(fiber.Ctx, *cache.Config) time.Duration` | ExpirationGenerator allows you to generate custom expiration keys based on the request. | `nil` |
116118
| Storage | `fiber.Storage` | Storage is used to store the state of the middleware. | In-memory store |
117119
| StoreResponseHeaders | `bool` | StoreResponseHeaders allows you to store additional headers generated by next middlewares & handler. | `false` |
118-
| MaxBytes | `uint` | MaxBytes is the maximum number of bytes of response bodies simultaneously stored in cache. | `0` (No limit) |
120+
| MaxBytes | `uint` | MaxBytes is the maximum number of bytes of response bodies simultaneously stored in cache. | `1 * 1024 * 1024` (~1 MB) |
119121
| Methods | `[]string` | Methods specifies the HTTP methods to cache. | `[]string{fiber.MethodGet, fiber.MethodHead}` |
120122

121123
## Default Config
122124

123125
```go
124126
var ConfigDefault = Config{
125127
Next: nil,
126-
Expiration: 1 * time.Minute,
128+
Expiration: 5 * time.Minute,
127129
CacheHeader: "X-Cache",
128-
CacheControl: false,
130+
DisableCacheControl: false,
129131
CacheInvalidator: nil,
130132
KeyGenerator: func(c fiber.Ctx) string {
131133
return utils.CopyString(c.Path())
132134
},
133135
ExpirationGenerator: nil,
134136
StoreResponseHeaders: false,
135137
Storage: nil,
136-
MaxBytes: 0,
138+
MaxBytes: 1 * 1024 * 1024,
137139
Methods: []string{fiber.MethodGet, fiber.MethodHead},
138140
}
139141
```

middleware/cache/cache.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,10 @@ func New(config ...Config) fiber.Handler {
9090

9191
// Update timestamp in the configured interval
9292
go func() {
93-
for {
93+
ticker := time.NewTicker(timestampUpdatePeriod)
94+
defer ticker.Stop()
95+
for range ticker.C {
9496
atomic.StoreUint64(&timestamp, uint64(time.Now().Unix())) //nolint:gosec //Not a concern
95-
time.Sleep(timestampUpdatePeriod)
9697
}
9798
}()
9899

@@ -163,8 +164,8 @@ func New(config ...Config) fiber.Handler {
163164
for k, v := range e.headers {
164165
c.Response().Header.SetBytesV(k, v)
165166
}
166-
// Set Cache-Control header if enabled and not already set
167-
if cfg.CacheControl && len(c.Response().Header.Peek(fiber.HeaderCacheControl)) == 0 {
167+
// Set Cache-Control header if not disabled and not already set
168+
if !cfg.DisableCacheControl && len(c.Response().Header.Peek(fiber.HeaderCacheControl)) == 0 {
168169
maxAge := strconv.FormatUint(e.exp-ts, 10)
169170
c.Set(fiber.HeaderCacheControl, "public, max-age="+maxAge)
170171
}

middleware/cache/cache_test.go

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,28 @@ func Test_Cache_CacheControl(t *testing.T) {
2626

2727
app := fiber.New()
2828

29+
app.Use(New(Config{Expiration: 10 * time.Second}))
30+
31+
app.Get("/", func(c fiber.Ctx) error {
32+
return c.SendString("Hello, World!")
33+
})
34+
35+
_, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
36+
require.NoError(t, err)
37+
38+
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
39+
require.NoError(t, err)
40+
require.Equal(t, "public, max-age=10", resp.Header.Get(fiber.HeaderCacheControl))
41+
}
42+
43+
func Test_Cache_CacheControl_Disabled(t *testing.T) {
44+
t.Parallel()
45+
46+
app := fiber.New()
47+
2948
app.Use(New(Config{
30-
CacheControl: true,
31-
Expiration: 10 * time.Second,
49+
Expiration: 10 * time.Second,
50+
DisableCacheControl: true,
3251
}))
3352

3453
app.Get("/", func(c fiber.Ctx) error {
@@ -40,7 +59,7 @@ func Test_Cache_CacheControl(t *testing.T) {
4059

4160
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
4261
require.NoError(t, err)
43-
require.Equal(t, "public, max-age=10", resp.Header.Get(fiber.HeaderCacheControl))
62+
require.Empty(t, resp.Header.Get(fiber.HeaderCacheControl))
4463
}
4564

4665
func Test_Cache_Expired(t *testing.T) {
@@ -341,8 +360,7 @@ func Test_Cache_WithSeveralRequests(t *testing.T) {
341360
app := fiber.New()
342361

343362
app.Use(New(Config{
344-
CacheControl: true,
345-
Expiration: 10 * time.Second,
363+
Expiration: 10 * time.Second,
346364
}))
347365

348366
app.Get("/:id", func(c fiber.Ctx) error {
@@ -522,7 +540,6 @@ func Test_Cache_CustomNext(t *testing.T) {
522540
Next: func(c fiber.Ctx) bool {
523541
return c.Response().StatusCode() != fiber.StatusOK
524542
},
525-
CacheControl: true,
526543
}))
527544

528545
count := 0
@@ -792,7 +809,6 @@ func Test_CacheInvalidation(t *testing.T) {
792809

793810
app := fiber.New()
794811
app.Use(New(Config{
795-
CacheControl: true,
796812
CacheInvalidator: func(c fiber.Ctx) bool {
797813
return fiber.Query[bool](c, "invalidate")
798814
},
@@ -830,7 +846,6 @@ func Test_CacheInvalidation_noCacheEntry(t *testing.T) {
830846
app := fiber.New()
831847
cacheInvalidatorExecuted := false
832848
app.Use(New(Config{
833-
CacheControl: true,
834849
CacheInvalidator: func(c fiber.Ctx) bool {
835850
cacheInvalidatorExecuted = true
836851
return fiber.Query[bool](c, "invalidate")
@@ -849,7 +864,6 @@ func Test_CacheInvalidation_removeFromHeap(t *testing.T) {
849864
t.Parallel()
850865
app := fiber.New()
851866
app.Use(New(Config{
852-
CacheControl: true,
853867
CacheInvalidator: func(c fiber.Ctx) bool {
854868
return fiber.Query[bool](c, "invalidate")
855869
},
@@ -886,9 +900,8 @@ func Test_CacheStorage_CustomHeaders(t *testing.T) {
886900
t.Parallel()
887901
app := fiber.New()
888902
app.Use(New(Config{
889-
CacheControl: true,
890-
Storage: memory.New(),
891-
MaxBytes: 10 * 1024 * 1024,
903+
Storage: memory.New(),
904+
MaxBytes: 10 * 1024 * 1024,
892905
}))
893906

894907
app.Get("/", func(c fiber.Ctx) error {
@@ -1137,7 +1150,7 @@ func Test_CacheNoStoreDirective(t *testing.T) {
11371150
func Test_CacheControlNotOverwritten(t *testing.T) {
11381151
t.Parallel()
11391152
app := fiber.New()
1140-
app.Use(New(Config{CacheControl: true, Expiration: 10 * time.Second, StoreResponseHeaders: true}))
1153+
app.Use(New(Config{Expiration: 10 * time.Second, StoreResponseHeaders: true}))
11411154
app.Get("/", func(c fiber.Ctx) error {
11421155
c.Set(fiber.HeaderCacheControl, "private")
11431156
return c.SendString("ok")

middleware/cache/config.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,20 @@ type Config struct {
5252

5353
// Expiration is the time that an cached response will live
5454
//
55-
// Optional. Default: 1 * time.Minute
55+
// Optional. Default: 5 * time.Minute
5656
Expiration time.Duration
5757

5858
// Max number of bytes of response bodies simultaneously stored in cache. When limit is reached,
5959
// entries with the nearest expiration are deleted to make room for new.
6060
// 0 means no limit
6161
//
62-
// Default: 0
62+
// Optional. Default: 1 * 1024 * 1024
6363
MaxBytes uint
6464

65-
// CacheControl enables client side caching if set to true
65+
// DisableCacheControl disables client side caching if set to true
6666
//
6767
// Optional. Default: false
68-
CacheControl bool
68+
DisableCacheControl bool
6969

7070
// StoreResponseHeaders allows you to store additional headers generated by
7171
// next middlewares and handlers.
@@ -76,18 +76,18 @@ type Config struct {
7676

7777
// ConfigDefault is the default config
7878
var ConfigDefault = Config{
79-
Next: nil,
80-
Expiration: 1 * time.Minute,
81-
CacheHeader: "X-Cache",
82-
CacheControl: false,
83-
CacheInvalidator: nil,
79+
Next: nil,
80+
Expiration: 5 * time.Minute,
81+
CacheHeader: "X-Cache",
82+
DisableCacheControl: false,
83+
CacheInvalidator: nil,
8484
KeyGenerator: func(c fiber.Ctx) string {
8585
return utils.CopyString(c.Path())
8686
},
8787
ExpirationGenerator: nil,
8888
StoreResponseHeaders: false,
8989
Storage: nil,
90-
MaxBytes: 0,
90+
MaxBytes: 1 * 1024 * 1024,
9191
Methods: []string{fiber.MethodGet, fiber.MethodHead},
9292
}
9393

0 commit comments

Comments
 (0)