🚀 [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-1
pull/2169/head
marcmartin13 2022-10-21 16:28:31 +08:00 committed by GitHub
parent 5fb93fdff6
commit c187c6a2f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 176 additions and 1 deletions

View File

@ -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)

View File

@ -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)
}

View File

@ -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()