Skip to content

Commit bdd9e22

Browse files
authored
🐛 bug: Fix Cache middleware handling of Age (#3547)
* Revert benchmark loop changes * test: cover Age header parsing * generate msgp files * Update cache.go * Cast result * Update cache.go
1 parent 55e7b1e commit bdd9e22

File tree

10 files changed

+165
-46
lines changed

10 files changed

+165
-46
lines changed

ctx_interface_gen.go

Lines changed: 22 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/middleware/cache.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ This middleware caches responses with the following status codes according to RF
2626
- `414: URI Too Long`
2727
- `501: Not Implemented`
2828

29-
Additionally, `418: I'm a teapot` is not originally cacheable but is cached by this middleware.
3029
If the status code is other than these, you will always get an `unreachable` cache status.
3130

3231
For more information about cacheable status codes or RFC7231, please refer to the following resources:

middleware/cache/cache.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/gofiber/fiber/v3"
1414
"github.com/gofiber/utils/v2"
15+
"github.com/valyala/fasthttp"
1516
)
1617

1718
// timestampUpdatePeriod is the period which is used to check the cache expiration.
@@ -59,7 +60,6 @@ var cacheableStatusCodes = map[int]bool{
5960
fiber.StatusMethodNotAllowed: true,
6061
fiber.StatusGone: true,
6162
fiber.StatusRequestURITooLong: true,
62-
fiber.StatusTeapot: true,
6363
fiber.StatusNotImplemented: true,
6464
}
6565

@@ -168,8 +168,9 @@ func New(config ...Config) fiber.Handler {
168168
c.Set(fiber.HeaderCacheControl, "public, max-age="+maxAge)
169169
}
170170

171-
// RFC-compliant Age header
172-
age := strconv.FormatUint(e.ttl-(e.exp-ts), 10)
171+
// RFC-compliant Age header (RFC 9111)
172+
resident := e.ttl - (e.exp - ts)
173+
age := strconv.FormatUint(e.age+resident, 10)
173174
c.Response().Header.Set(fiber.HeaderAge, age)
174175

175176
c.Set(cfg.CacheHeader, cacheHit)
@@ -239,9 +240,15 @@ func New(config ...Config) fiber.Handler {
239240
e.ctype = utils.CopyBytes(c.Response().Header.ContentType())
240241
e.cencoding = utils.CopyBytes(c.Response().Header.Peek(fiber.HeaderContentEncoding))
241242

242-
if len(c.Response().Header.Peek(fiber.HeaderAge)) == 0 {
243+
ageVal := uint64(0)
244+
if b := c.Response().Header.Peek(fiber.HeaderAge); len(b) > 0 {
245+
if v, err := fasthttp.ParseUint(b); err == nil {
246+
ageVal = uint64(v) //nolint:gosec //Not a concern
247+
}
248+
} else {
243249
c.Response().Header.Set(fiber.HeaderAge, "0")
244250
}
251+
e.age = ageVal
245252

246253
// Store all response headers
247254
// (more: https://datatracker.ietf.org/doc/html/rfc2616#section-13.5.1)

middleware/cache/cache_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,7 @@ func Test_Cache_UncacheableStatusCodes(t *testing.T) {
10501050
fiber.StatusPreconditionRequired,
10511051
fiber.StatusTooManyRequests,
10521052
fiber.StatusRequestHeaderFieldsTooLarge,
1053+
fiber.StatusTeapot,
10531054
fiber.StatusUnavailableForLegalReasons,
10541055

10551056
// Server error responses
@@ -1092,6 +1093,29 @@ func TestCacheAgeHeader(t *testing.T) {
10921093
require.Positive(t, age)
10931094
}
10941095

1096+
func TestCacheUpstreamAge(t *testing.T) {
1097+
t.Parallel()
1098+
app := fiber.New()
1099+
app.Use(New(Config{Expiration: 3 * time.Second}))
1100+
app.Get("/", func(c fiber.Ctx) error {
1101+
c.Set(fiber.HeaderAge, "5")
1102+
return c.SendString("hi")
1103+
})
1104+
1105+
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
1106+
require.NoError(t, err)
1107+
require.Equal(t, "5", resp.Header.Get(fiber.HeaderAge))
1108+
1109+
time.Sleep(1500 * time.Millisecond)
1110+
1111+
resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
1112+
require.NoError(t, err)
1113+
require.Equal(t, cacheHit, resp.Header.Get("X-Cache"))
1114+
age, err := strconv.Atoi(resp.Header.Get(fiber.HeaderAge))
1115+
require.NoError(t, err)
1116+
require.GreaterOrEqual(t, age, 6)
1117+
}
1118+
10951119
func Test_CacheNoStoreDirective(t *testing.T) {
10961120
t.Parallel()
10971121
app := fiber.New()

middleware/cache/manager.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type item struct {
1717
ctype []byte
1818
cencoding []byte
1919
status int
20+
age uint64
2021
exp uint64
2122
ttl uint64
2223
// used for finding the item in an indexed heap
@@ -63,7 +64,9 @@ func (m *manager) release(e *item) {
6364
e.body = nil
6465
e.ctype = nil
6566
e.status = 0
67+
e.age = 0
6668
e.exp = 0
69+
e.ttl = 0
6770
e.headers = nil
6871
m.pool.Put(e)
6972
}

middleware/cache/manager_msgp.go

Lines changed: 55 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

middleware/cache/manager_msgp_test.go

Lines changed: 10 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)