diff --git a/docs/middleware/cache.md b/docs/middleware/cache.md
index 0723c615dc..08c7ad5989 100644
--- a/docs/middleware/cache.md
+++ b/docs/middleware/cache.md
@@ -10,6 +10,31 @@ Request Directives
`Cache-Control: no-cache` will return the up-to-date response but still caches it. You will always get a `miss` cache status.
`Cache-Control: no-store` will refrain from caching. You will always get the up-to-date response.
+Cacheable Status Codes
+
+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
diff --git a/docs/whats_new.md b/docs/whats_new.md
index 81339d6a48..bb9870ddb6 100644
--- a/docs/whats_new.md
+++ b/docs/whats_new.md
@@ -733,7 +733,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
diff --git a/middleware/cache/cache.go b/middleware/cache/cache.go
index 5c832f0b96..723b5321e2 100644
--- a/middleware/cache/cache.go
+++ b/middleware/cache/cache.go
@@ -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()
diff --git a/middleware/cache/cache_test.go b/middleware/cache/cache_test.go
index 22ab0e2895..2193decb25 100644
--- a/middleware/cache/cache_test.go
+++ b/middleware/cache/cache_test.go
@@ -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()