mirror of https://github.com/gofiber/fiber.git
🐛 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>
pull/3290/head
parent
aa245aeaec
commit
8970f515dd
|
@ -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-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.
|
`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
|
## Signatures
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|
|
@ -765,6 +765,7 @@ The adaptor middleware has been significantly optimized for performance and effi
|
||||||
### Cache
|
### 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
|
### CORS
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,21 @@ var ignoreHeaders = map[string]any{
|
||||||
"Content-Encoding": nil, // already stored explicitly by the cache manager
|
"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
|
// New creates a new middleware handler
|
||||||
func New(config ...Config) fiber.Handler {
|
func New(config ...Config) fiber.Handler {
|
||||||
// Set default config
|
// Set default config
|
||||||
|
@ -170,6 +185,12 @@ func New(config ...Config) fiber.Handler {
|
||||||
return err
|
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
|
// lock entry back and unlock on finish
|
||||||
mux.Lock()
|
mux.Lock()
|
||||||
defer mux.Unlock()
|
defer mux.Unlock()
|
||||||
|
|
|
@ -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
|
// go test -v -run=^$ -bench=Benchmark_Cache -benchmem -count=4
|
||||||
func Benchmark_Cache(b *testing.B) {
|
func Benchmark_Cache(b *testing.B) {
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
|
|
Loading…
Reference in New Issue