1
0
mirror of https://github.com/gofiber/fiber.git synced 2025-02-06 10:23:55 +00:00

🐛 fix: Align cache middleware with RFC7231 (#3283)

* 🩹 Fix(v3;middleware/cache): don't cache if status code is not cacheable

* allow 418 TeaPot

* fix test

* fix lint error

* check cacheability with map

* documentation

* fix: markdown lint

---------

Co-authored-by: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
This commit is contained in:
miyamo2 2025-01-20 16:22:51 +09:00 committed by GitHub
parent aa245aeaec
commit 8970f515dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 129 additions and 1 deletions

View File

@ -10,6 +10,31 @@ Request Directives<br />
`Cache-Control: no-cache` will return the up-to-date response but still caches it. You will always get a `miss` cache status.<br />
`Cache-Control: no-store` will refrain from caching. You will always get the up-to-date response.
Cacheable Status Codes<br />
This middleware caches responses with the following status codes according to RFC7231:
- `200: OK`
- `203: Non-Authoritative Information`
- `204: No Content`
- `206: Partial Content`
- `300: Multiple Choices`
- `301: Moved Permanently`
- `404: Not Found`
- `405: Method Not Allowed`
- `410: Gone`
- `414: URI Too Long`
- `501: Not Implemented`
Additionally, `418: I'm a teapot` is not originally cacheable but is cached by this middleware.
If the status code is other than these, you will always get an `unreachable` cache status.
For more information about cacheable status codes or RFC7231, please refer to the following resources:
- [Cacheable - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Glossary/Cacheable)
- [RFC7231 - Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content](https://datatracker.ietf.org/doc/html/rfc7231)
## Signatures
```go

View File

@ -764,7 +764,8 @@ The adaptor middleware has been significantly optimized for performance and effi
### Cache
We are excited to introduce a new option in our caching middleware: Cache Invalidator. This feature provides greater control over cache management, allowing you to define a custom conditions for invalidating cache entries.
We are excited to introduce a new option in our caching middleware: Cache Invalidator. This feature provides greater control over cache management, allowing you to define a custom conditions for invalidating cache entries.
Additionally, the caching middleware has been optimized to avoid caching non-cacheable status codes, as defined by the [HTTP standards](https://datatracker.ietf.org/doc/html/rfc7231#section-6.1). This improvement enhances cache accuracy and reduces unnecessary cache storage usage.
### CORS

View File

@ -48,6 +48,21 @@ var ignoreHeaders = map[string]any{
"Content-Encoding": nil, // already stored explicitly by the cache manager
}
var cacheableStatusCodes = map[int]bool{
fiber.StatusOK: true,
fiber.StatusNonAuthoritativeInformation: true,
fiber.StatusNoContent: true,
fiber.StatusPartialContent: true,
fiber.StatusMultipleChoices: true,
fiber.StatusMovedPermanently: true,
fiber.StatusNotFound: true,
fiber.StatusMethodNotAllowed: true,
fiber.StatusGone: true,
fiber.StatusRequestURITooLong: true,
fiber.StatusTeapot: true,
fiber.StatusNotImplemented: true,
}
// New creates a new middleware handler
func New(config ...Config) fiber.Handler {
// Set default config
@ -170,6 +185,12 @@ func New(config ...Config) fiber.Handler {
return err
}
// Don't cache response if status code is not cacheable
if !cacheableStatusCodes[c.Response().StatusCode()] {
c.Set(cfg.CacheHeader, cacheUnreachable)
return nil
}
// lock entry back and unlock on finish
mux.Lock()
defer mux.Unlock()

View File

@ -918,6 +918,87 @@ func Test_Cache_MaxBytesSizes(t *testing.T) {
}
}
func Test_Cache_UncacheableStatusCodes(t *testing.T) {
t.Parallel()
app := fiber.New()
app.Use(New())
app.Get("/:statusCode", func(c fiber.Ctx) error {
statusCode, err := strconv.Atoi(c.Params("statusCode"))
require.NoError(t, err)
return c.Status(statusCode).SendString("foo")
})
uncacheableStatusCodes := []int{
// Informational responses
fiber.StatusContinue,
fiber.StatusSwitchingProtocols,
fiber.StatusProcessing,
fiber.StatusEarlyHints,
// Successful responses
fiber.StatusCreated,
fiber.StatusAccepted,
fiber.StatusResetContent,
fiber.StatusMultiStatus,
fiber.StatusAlreadyReported,
fiber.StatusIMUsed,
// Redirection responses
fiber.StatusFound,
fiber.StatusSeeOther,
fiber.StatusNotModified,
fiber.StatusUseProxy,
fiber.StatusSwitchProxy,
fiber.StatusTemporaryRedirect,
fiber.StatusPermanentRedirect,
// Client error responses
fiber.StatusBadRequest,
fiber.StatusUnauthorized,
fiber.StatusPaymentRequired,
fiber.StatusForbidden,
fiber.StatusNotAcceptable,
fiber.StatusProxyAuthRequired,
fiber.StatusRequestTimeout,
fiber.StatusConflict,
fiber.StatusLengthRequired,
fiber.StatusPreconditionFailed,
fiber.StatusRequestEntityTooLarge,
fiber.StatusUnsupportedMediaType,
fiber.StatusRequestedRangeNotSatisfiable,
fiber.StatusExpectationFailed,
fiber.StatusMisdirectedRequest,
fiber.StatusUnprocessableEntity,
fiber.StatusLocked,
fiber.StatusFailedDependency,
fiber.StatusTooEarly,
fiber.StatusUpgradeRequired,
fiber.StatusPreconditionRequired,
fiber.StatusTooManyRequests,
fiber.StatusRequestHeaderFieldsTooLarge,
fiber.StatusUnavailableForLegalReasons,
// Server error responses
fiber.StatusInternalServerError,
fiber.StatusBadGateway,
fiber.StatusServiceUnavailable,
fiber.StatusGatewayTimeout,
fiber.StatusHTTPVersionNotSupported,
fiber.StatusVariantAlsoNegotiates,
fiber.StatusInsufficientStorage,
fiber.StatusLoopDetected,
fiber.StatusNotExtended,
fiber.StatusNetworkAuthenticationRequired,
}
for _, v := range uncacheableStatusCodes {
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, fmt.Sprintf("/%d", v), nil))
require.NoError(t, err)
require.Equal(t, cacheUnreachable, resp.Header.Get("X-Cache"))
require.Equal(t, v, resp.StatusCode)
}
}
// go test -v -run=^$ -bench=Benchmark_Cache -benchmem -count=4
func Benchmark_Cache(b *testing.B) {
app := fiber.New()