mirror of https://github.com/gofiber/fiber.git
🚀 [Feature]: Cache-Control: no-cache (#2159)
* Added noCache field Check if the request header Cache-Control contains no-cache * Update cache.go * Update config.go * Update cache.go * patch-1 * patch-1 * patch-1 * patch-1 * patch-1 * patch-1 * patch-1 * patch-1 * patch-1 * patch-1 * patch-1 * patch-1 * patch-1 * patch-1 * patch-1 * patch-1 * patch-1 * patch-1 * patch-1 * patch-1pull/2169/head
parent
5fb93fdff6
commit
c187c6a2f5
|
@ -2,6 +2,10 @@
|
|||
|
||||
Cache middleware for [Fiber](https://github.com/gofiber/fiber) designed to intercept responses and cache them. This middleware will cache the `Body`, `Content-Type` and `StatusCode` using the `c.Path()` (or a string returned by the Key function) as unique identifier. Special thanks to [@codemicro](https://github.com/codemicro/fiber-cache) for creating this middleware for Fiber core!
|
||||
|
||||
Request Directives
|
||||
The no-cache request directive will return the up-to-date response but still caches it. You will always get a "miss" cache status.
|
||||
The no-store request directive will refrain from caching. You will always get the up-to-date response.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Cache Middleware](#cache-middleware)
|
||||
|
|
|
@ -4,6 +4,7 @@ package cache
|
|||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
@ -27,6 +28,12 @@ const (
|
|||
cacheMiss = "miss"
|
||||
)
|
||||
|
||||
// directives
|
||||
const (
|
||||
noCache = "no-cache"
|
||||
noStore = "no-store"
|
||||
)
|
||||
|
||||
var ignoreHeaders = map[string]interface{}{
|
||||
"Connection": nil,
|
||||
"Keep-Alive": nil,
|
||||
|
@ -83,6 +90,11 @@ func New(config ...Config) fiber.Handler {
|
|||
|
||||
// Return new handler
|
||||
return func(c *fiber.Ctx) error {
|
||||
// Refrain from caching
|
||||
if hasRequestDirective(c, noStore) {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
// Only cache selected methods
|
||||
var isExists bool
|
||||
for _, method := range cfg.Methods {
|
||||
|
@ -116,7 +128,7 @@ func New(config ...Config) fiber.Handler {
|
|||
_, size := heap.remove(e.heapidx)
|
||||
storedBytes -= size
|
||||
}
|
||||
} else if e.exp != 0 {
|
||||
} else if e.exp != 0 && !hasRequestDirective(c, noCache) {
|
||||
// Separate body value to avoid msgp serialization
|
||||
// We can store raw bytes with Storage 👍
|
||||
if cfg.Storage != nil {
|
||||
|
@ -235,3 +247,8 @@ func New(config ...Config) fiber.Handler {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Check if request has directive
|
||||
func hasRequestDirective(c *fiber.Ctx, directive string) bool {
|
||||
return strings.Contains(c.Get(fiber.HeaderCacheControl), directive)
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/etag"
|
||||
"github.com/gofiber/fiber/v2/internal/storage/memory"
|
||||
"github.com/gofiber/fiber/v2/utils"
|
||||
"github.com/valyala/fasthttp"
|
||||
|
@ -107,6 +108,159 @@ func Test_Cache(t *testing.T) {
|
|||
utils.AssertEqual(t, cachedBody, body)
|
||||
}
|
||||
|
||||
// go test -run Test_Cache_WithNoCacheRequestDirective
|
||||
func Test_Cache_WithNoCacheRequestDirective(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app := fiber.New()
|
||||
app.Use(New())
|
||||
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.SendString(c.Query("id", "1"))
|
||||
})
|
||||
|
||||
// Request id = 1
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
resp, err := app.Test(req)
|
||||
defer resp.Body.Close()
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, cacheMiss, resp.Header.Get("X-Cache"))
|
||||
utils.AssertEqual(t, []byte("1"), body)
|
||||
// Response cached, entry id = 1
|
||||
|
||||
// Request id = 2 without Cache-Control: no-cache
|
||||
cachedReq := httptest.NewRequest("GET", "/?id=2", nil)
|
||||
cachedResp, err := app.Test(cachedReq)
|
||||
defer cachedResp.Body.Close()
|
||||
cachedBody, _ := ioutil.ReadAll(cachedResp.Body)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, cacheHit, cachedResp.Header.Get("X-Cache"))
|
||||
utils.AssertEqual(t, []byte("1"), cachedBody)
|
||||
// Response not cached, returns cached response, entry id = 1
|
||||
|
||||
// Request id = 2 with Cache-Control: no-cache
|
||||
noCacheReq := httptest.NewRequest("GET", "/?id=2", nil)
|
||||
noCacheReq.Header.Set(fiber.HeaderCacheControl, noCache)
|
||||
noCacheResp, err := app.Test(noCacheReq)
|
||||
defer noCacheResp.Body.Close()
|
||||
noCacheBody, _ := ioutil.ReadAll(noCacheResp.Body)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, cacheMiss, noCacheResp.Header.Get("X-Cache"))
|
||||
utils.AssertEqual(t, []byte("2"), noCacheBody)
|
||||
// Response cached, returns updated response, entry = 2
|
||||
|
||||
/* Check Test_Cache_WithETagAndNoCacheRequestDirective */
|
||||
// Request id = 2 with Cache-Control: no-cache again
|
||||
noCacheReq1 := httptest.NewRequest("GET", "/?id=2", nil)
|
||||
noCacheReq1.Header.Set(fiber.HeaderCacheControl, noCache)
|
||||
noCacheResp1, err := app.Test(noCacheReq1)
|
||||
defer noCacheResp1.Body.Close()
|
||||
noCacheBody1, _ := ioutil.ReadAll(noCacheResp1.Body)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, cacheMiss, noCacheResp1.Header.Get("X-Cache"))
|
||||
utils.AssertEqual(t, []byte("2"), noCacheBody1)
|
||||
// Response cached, returns updated response, entry = 2
|
||||
|
||||
// Request id = 1 without Cache-Control: no-cache
|
||||
cachedReq1 := httptest.NewRequest("GET", "/", nil)
|
||||
cachedResp1, err := app.Test(cachedReq1)
|
||||
defer cachedResp1.Body.Close()
|
||||
cachedBody1, _ := ioutil.ReadAll(cachedResp1.Body)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, cacheHit, cachedResp1.Header.Get("X-Cache"))
|
||||
utils.AssertEqual(t, []byte("2"), cachedBody1)
|
||||
// Response not cached, returns cached response, entry id = 2
|
||||
}
|
||||
|
||||
// go test -run Test_Cache_WithETagAndNoCacheRequestDirective
|
||||
func Test_Cache_WithETagAndNoCacheRequestDirective(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app := fiber.New()
|
||||
app.Use(
|
||||
etag.New(),
|
||||
New(),
|
||||
)
|
||||
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.SendString(c.Query("id", "1"))
|
||||
})
|
||||
|
||||
// Request id = 1
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
resp, err := app.Test(req)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, cacheMiss, resp.Header.Get("X-Cache"))
|
||||
utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
|
||||
// Response cached, entry id = 1
|
||||
|
||||
// If response status 200
|
||||
etagToken := resp.Header.Get("Etag")
|
||||
|
||||
// Request id = 2 with ETag but without Cache-Control: no-cache
|
||||
cachedReq := httptest.NewRequest("GET", "/?id=2", nil)
|
||||
cachedReq.Header.Set(fiber.HeaderIfNoneMatch, etagToken)
|
||||
cachedResp, err := app.Test(cachedReq)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, cacheHit, cachedResp.Header.Get("X-Cache"))
|
||||
utils.AssertEqual(t, fiber.StatusNotModified, cachedResp.StatusCode)
|
||||
// Response not cached, returns cached response, entry id = 1, status not modified
|
||||
|
||||
// Request id = 2 with ETag and Cache-Control: no-cache
|
||||
noCacheReq := httptest.NewRequest("GET", "/?id=2", nil)
|
||||
noCacheReq.Header.Set(fiber.HeaderCacheControl, noCache)
|
||||
noCacheReq.Header.Set(fiber.HeaderIfNoneMatch, etagToken)
|
||||
noCacheResp, err := app.Test(noCacheReq)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, cacheMiss, noCacheResp.Header.Get("X-Cache"))
|
||||
utils.AssertEqual(t, fiber.StatusOK, noCacheResp.StatusCode)
|
||||
// Response cached, returns updated response, entry id = 2
|
||||
|
||||
// If response status 200
|
||||
etagToken = noCacheResp.Header.Get("Etag")
|
||||
|
||||
// Request id = 2 with ETag and Cache-Control: no-cache again
|
||||
noCacheReq1 := httptest.NewRequest("GET", "/?id=2", nil)
|
||||
noCacheReq1.Header.Set(fiber.HeaderCacheControl, noCache)
|
||||
noCacheReq1.Header.Set(fiber.HeaderIfNoneMatch, etagToken)
|
||||
noCacheResp1, err := app.Test(noCacheReq1)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, cacheMiss, noCacheResp1.Header.Get("X-Cache"))
|
||||
utils.AssertEqual(t, fiber.StatusNotModified, noCacheResp1.StatusCode)
|
||||
// Response cached, returns updated response, entry id = 2, status not modified
|
||||
|
||||
// Request id = 1 without ETag and Cache-Control: no-cache
|
||||
cachedReq1 := httptest.NewRequest("GET", "/", nil)
|
||||
cachedResp1, err := app.Test(cachedReq1)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, cacheHit, cachedResp1.Header.Get("X-Cache"))
|
||||
utils.AssertEqual(t, fiber.StatusOK, cachedResp1.StatusCode)
|
||||
// Response not cached, returns cached response, entry id = 2
|
||||
}
|
||||
|
||||
// go test -run Test_Cache_WithNoStoreRequestDirective
|
||||
func Test_Cache_WithNoStoreRequestDirective(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
app := fiber.New()
|
||||
app.Use(New())
|
||||
|
||||
app.Get("/", func(c *fiber.Ctx) error {
|
||||
return c.SendString(c.Query("id", "1"))
|
||||
})
|
||||
|
||||
// Request id = 2
|
||||
noStoreReq := httptest.NewRequest("GET", "/?id=2", nil)
|
||||
noStoreReq.Header.Set(fiber.HeaderCacheControl, noStore)
|
||||
noStoreResp, err := app.Test(noStoreReq)
|
||||
defer noStoreResp.Body.Close()
|
||||
noStoreBody, _ := ioutil.ReadAll(noStoreResp.Body)
|
||||
utils.AssertEqual(t, nil, err)
|
||||
utils.AssertEqual(t, []byte("2"), noStoreBody)
|
||||
// Response not cached, returns updated response
|
||||
}
|
||||
|
||||
func Test_Cache_WithSeveralRequests(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
Loading…
Reference in New Issue