From d5a2773f55e2bd2c3538ef9ffc3cf12e9b47c798 Mon Sep 17 00:00:00 2001 From: marcmartin13 <87841930+marcmartin13@users.noreply.github.com> Date: Tue, 18 Oct 2022 10:06:21 +0800 Subject: [PATCH 01/24] Added noCache field Check if the request header Cache-Control contains no-cache --- middleware/cache/config.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/middleware/cache/config.go b/middleware/cache/config.go index 12f81e2ae8..1787a37f00 100644 --- a/middleware/cache/config.go +++ b/middleware/cache/config.go @@ -2,6 +2,7 @@ package cache import ( "fmt" + "strings" "time" "github.com/gofiber/fiber/v2" @@ -72,6 +73,9 @@ type Config struct { // // Default: []string{fiber.MethodGet, fiber.MethodHead} Methods []string + + // If no-cache exist in request header + NoCache func(c *fiber.Ctx) bool } // ConfigDefault is the default config @@ -88,6 +92,9 @@ var ConfigDefault = Config{ Storage: nil, MaxBytes: 0, Methods: []string{fiber.MethodGet, fiber.MethodHead}, + noCache: func(c *fiber.Ctx) bool { + return strings.Contains(c.Get(fiber.HeaderCacheControl), "no-cache") + }, } // Helper function to set default values @@ -124,5 +131,6 @@ func configDefault(config ...Config) Config { if len(cfg.Methods) == 0 { cfg.Methods = ConfigDefault.Methods } + cfg.noCache = ConfigDefault.noCache return cfg } From fbbd9735f9265d4fd712a0552d96d15d9f52e6bb Mon Sep 17 00:00:00 2001 From: marcmartin13 <87841930+marcmartin13@users.noreply.github.com> Date: Tue, 18 Oct 2022 10:57:56 +0800 Subject: [PATCH 02/24] Update cache.go --- middleware/cache/cache.go | 70 +++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/middleware/cache/cache.go b/middleware/cache/cache.go index a8485f450f..78e4f3ca7b 100644 --- a/middleware/cache/cache.go +++ b/middleware/cache/cache.go @@ -109,43 +109,47 @@ func New(config ...Config) fiber.Handler { // Get timestamp ts := atomic.LoadUint64(×tamp) - // Check if entry is expired - if e.exp != 0 && ts >= e.exp { - deleteKey(key) - if cfg.MaxBytes > 0 { - _, size := heap.remove(e.heapidx) - storedBytes -= size - } - } else if e.exp != 0 { - // Separate body value to avoid msgp serialization - // We can store raw bytes with Storage 👍 - if cfg.Storage != nil { - e.body = manager.getRaw(key + "_body") - } - // Set response headers from cache - c.Response().SetBodyRaw(e.body) - c.Response().SetStatusCode(e.status) - c.Response().Header.SetContentTypeBytes(e.ctype) - if len(e.cencoding) > 0 { - c.Response().Header.SetBytesV(fiber.HeaderContentEncoding, e.cencoding) - } - if e.headers != nil { - for k, v := range e.headers { - c.Response().Header.SetBytesV(k, v) + // Check if no-cache + if !cfg.noCache(c) { + + // Check if entry is expired + if e.exp != 0 && ts >= e.exp { + deleteKey(key) + if cfg.MaxBytes > 0 { + _, size := heap.remove(e.heapidx) + storedBytes -= size + } + } else if e.exp != 0 { + // Separate body value to avoid msgp serialization + // We can store raw bytes with Storage 👍 + if cfg.Storage != nil { + e.body = manager.getRaw(key + "_body") + } + // Set response headers from cache + c.Response().SetBodyRaw(e.body) + c.Response().SetStatusCode(e.status) + c.Response().Header.SetContentTypeBytes(e.ctype) + if len(e.cencoding) > 0 { + c.Response().Header.SetBytesV(fiber.HeaderContentEncoding, e.cencoding) + } + if e.headers != nil { + for k, v := range e.headers { + c.Response().Header.SetBytesV(k, v) + } + } + // Set Cache-Control header if enabled + if cfg.CacheControl { + maxAge := strconv.FormatUint(e.exp-ts, 10) + c.Set(fiber.HeaderCacheControl, "public, max-age="+maxAge) } - } - // Set Cache-Control header if enabled - if cfg.CacheControl { - maxAge := strconv.FormatUint(e.exp-ts, 10) - c.Set(fiber.HeaderCacheControl, "public, max-age="+maxAge) - } - c.Set(cfg.CacheHeader, cacheHit) + c.Set(cfg.CacheHeader, cacheHit) - mux.Unlock() + mux.Unlock() - // Return response - return nil + // Return response + return nil + } } // make sure we're not blocking concurrent requests - do unlock From 342a15edfbaf7fdf587a1f1a1ec1a2dedad319c4 Mon Sep 17 00:00:00 2001 From: marcmartin13 <87841930+marcmartin13@users.noreply.github.com> Date: Tue, 18 Oct 2022 11:04:28 +0800 Subject: [PATCH 03/24] Update config.go --- middleware/cache/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/middleware/cache/config.go b/middleware/cache/config.go index 1787a37f00..8af77eaf71 100644 --- a/middleware/cache/config.go +++ b/middleware/cache/config.go @@ -75,7 +75,7 @@ type Config struct { Methods []string // If no-cache exist in request header - NoCache func(c *fiber.Ctx) bool + noCache func(c *fiber.Ctx) bool } // ConfigDefault is the default config From b398139e1d29ed6135723697c7268b39781671d9 Mon Sep 17 00:00:00 2001 From: marcmartin13 <87841930+marcmartin13@users.noreply.github.com> Date: Wed, 19 Oct 2022 08:33:18 +0800 Subject: [PATCH 04/24] Update cache.go --- middleware/cache/cache.go | 70 ++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/middleware/cache/cache.go b/middleware/cache/cache.go index 78e4f3ca7b..aefb9548ad 100644 --- a/middleware/cache/cache.go +++ b/middleware/cache/cache.go @@ -109,47 +109,43 @@ func New(config ...Config) fiber.Handler { // Get timestamp ts := atomic.LoadUint64(×tamp) - // Check if no-cache - if !cfg.noCache(c) { - - // Check if entry is expired - if e.exp != 0 && ts >= e.exp { - deleteKey(key) - if cfg.MaxBytes > 0 { - _, size := heap.remove(e.heapidx) - storedBytes -= size - } - } else if e.exp != 0 { - // Separate body value to avoid msgp serialization - // We can store raw bytes with Storage 👍 - if cfg.Storage != nil { - e.body = manager.getRaw(key + "_body") - } - // Set response headers from cache - c.Response().SetBodyRaw(e.body) - c.Response().SetStatusCode(e.status) - c.Response().Header.SetContentTypeBytes(e.ctype) - if len(e.cencoding) > 0 { - c.Response().Header.SetBytesV(fiber.HeaderContentEncoding, e.cencoding) - } - if e.headers != nil { - for k, v := range e.headers { - c.Response().Header.SetBytesV(k, v) - } - } - // Set Cache-Control header if enabled - if cfg.CacheControl { - maxAge := strconv.FormatUint(e.exp-ts, 10) - c.Set(fiber.HeaderCacheControl, "public, max-age="+maxAge) + // Check if entry is expired + if e.exp != 0 && ts >= e.exp { + deleteKey(key) + if cfg.MaxBytes > 0 { + _, size := heap.remove(e.heapidx) + storedBytes -= size + } + } else if e.exp != 0 && !cfg.noCache(c) { + // Separate body value to avoid msgp serialization + // We can store raw bytes with Storage 👍 + if cfg.Storage != nil { + e.body = manager.getRaw(key + "_body") + } + // Set response headers from cache + c.Response().SetBodyRaw(e.body) + c.Response().SetStatusCode(e.status) + c.Response().Header.SetContentTypeBytes(e.ctype) + if len(e.cencoding) > 0 { + c.Response().Header.SetBytesV(fiber.HeaderContentEncoding, e.cencoding) + } + if e.headers != nil { + for k, v := range e.headers { + c.Response().Header.SetBytesV(k, v) } + } + // Set Cache-Control header if enabled + if cfg.CacheControl { + maxAge := strconv.FormatUint(e.exp-ts, 10) + c.Set(fiber.HeaderCacheControl, "public, max-age="+maxAge) + } - c.Set(cfg.CacheHeader, cacheHit) + c.Set(cfg.CacheHeader, cacheHit) - mux.Unlock() + mux.Unlock() - // Return response - return nil - } + // Return response + return nil } // make sure we're not blocking concurrent requests - do unlock From b3413f335d476b1e4a09361930831abe01f6b92c Mon Sep 17 00:00:00 2001 From: Marc Martin Date: Wed, 19 Oct 2022 09:36:42 +0800 Subject: [PATCH 05/24] patch-1 --- middleware/cache/cache_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/middleware/cache/cache_test.go b/middleware/cache/cache_test.go index 78aeab4f71..70ee7377ee 100644 --- a/middleware/cache/cache_test.go +++ b/middleware/cache/cache_test.go @@ -107,6 +107,36 @@ func Test_Cache(t *testing.T) { utils.AssertEqual(t, cachedBody, body) } +func Test_CacheWithCacheControlNoCacheRequestHeader(t *testing.T) { + t.Parallel() + + app := fiber.New() + app.Use(New()) + + app.Get("/", func(c *fiber.Ctx) error { + now := fmt.Sprintf("%d", time.Now().UnixNano()) + return c.SendString(now) + }) + + req := httptest.NewRequest("GET", "/", nil) + resp, err := app.Test(req) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, "miss", resp.Header.Get("X-Cache")) + + // Request without Cache-Control: no-cache in request header + cacheReq := httptest.NewRequest("GET", "/", nil) + cacheResp, err := app.Test(cacheReq) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, "hit", cacheResp.Header.Get("X-Cache")) + + // Request with Cache-Control: no-cache in request header + noCacheReq := httptest.NewRequest("GET", "/", nil) + noCacheReq.Header.Set(fiber.HeaderCacheControl, "no-cache") + noCacheResp, err := app.Test(noCacheReq) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, "miss", noCacheResp.Header.Get("X-Cache")) +} + func Test_Cache_WithSeveralRequests(t *testing.T) { t.Parallel() From 53580e7713b60e31fcbfa6711211e54851ac2e4e Mon Sep 17 00:00:00 2001 From: Marc Martin Date: Wed, 19 Oct 2022 09:42:53 +0800 Subject: [PATCH 06/24] patch-1 --- middleware/cache/cache_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/middleware/cache/cache_test.go b/middleware/cache/cache_test.go index 70ee7377ee..40cfcede76 100644 --- a/middleware/cache/cache_test.go +++ b/middleware/cache/cache_test.go @@ -123,18 +123,18 @@ func Test_CacheWithCacheControlNoCacheRequestHeader(t *testing.T) { utils.AssertEqual(t, nil, err) utils.AssertEqual(t, "miss", resp.Header.Get("X-Cache")) - // Request without Cache-Control: no-cache in request header - cacheReq := httptest.NewRequest("GET", "/", nil) - cacheResp, err := app.Test(cacheReq) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "hit", cacheResp.Header.Get("X-Cache")) - // Request with Cache-Control: no-cache in request header noCacheReq := httptest.NewRequest("GET", "/", nil) noCacheReq.Header.Set(fiber.HeaderCacheControl, "no-cache") noCacheResp, err := app.Test(noCacheReq) utils.AssertEqual(t, nil, err) utils.AssertEqual(t, "miss", noCacheResp.Header.Get("X-Cache")) + + // Request without Cache-Control: no-cache in request header + cacheReq := httptest.NewRequest("GET", "/", nil) + cacheResp, err := app.Test(cacheReq) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, "hit", cacheResp.Header.Get("X-Cache")) } func Test_Cache_WithSeveralRequests(t *testing.T) { From f09453b30ab893c51d274da7d92e075e698efa8b Mon Sep 17 00:00:00 2001 From: Marc Martin Date: Wed, 19 Oct 2022 10:34:06 +0800 Subject: [PATCH 07/24] patch-1 --- middleware/cache/cache_test.go | 60 ++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/middleware/cache/cache_test.go b/middleware/cache/cache_test.go index 40cfcede76..f2ab01b8c6 100644 --- a/middleware/cache/cache_test.go +++ b/middleware/cache/cache_test.go @@ -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" @@ -114,8 +115,7 @@ func Test_CacheWithCacheControlNoCacheRequestHeader(t *testing.T) { app.Use(New()) app.Get("/", func(c *fiber.Ctx) error { - now := fmt.Sprintf("%d", time.Now().UnixNano()) - return c.SendString(now) + return c.SendString(c.Query("id", "1")) }) req := httptest.NewRequest("GET", "/", nil) @@ -137,6 +137,62 @@ func Test_CacheWithCacheControlNoCacheRequestHeader(t *testing.T) { utils.AssertEqual(t, "hit", cacheResp.Header.Get("X-Cache")) } +func Test_CacheWithETagAndCacheControlNoCacheRequestHeader(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")) + }) + + req := httptest.NewRequest("GET", "/", nil) + resp, err := app.Test(req) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, "miss", resp.Header.Get("X-Cache")) + + etagToken := resp.Header.Get("Etag") + + // Request with updated response without Cache-Control: no-cache in request header + cacheReq := httptest.NewRequest("GET", "/", nil) + cacheResp, err := app.Test(cacheReq) + fmt.Println(cacheResp) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, "hit", cacheResp.Header.Get("X-Cache")) + + // Request with id equals 2 and etag without Cache-Control: no-cache in request header + cacheReq1 := httptest.NewRequest("GET", "/?id=2", nil) + cacheReq1.Header.Set(fiber.HeaderIfNoneMatch, etagToken) + cacheResp1, err := app.Test(cacheReq1) + fmt.Println(cacheResp1) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, "hit", cacheResp1.Header.Get("X-Cache")) + + // Request with id equals 2 and etag and Cache-Control: no-cache in request header + noCacheReq := httptest.NewRequest("GET", "/?id=2", nil) + noCacheReq.Header.Set(fiber.HeaderCacheControl, "no-cache") + noCacheReq.Header.Set(fiber.HeaderIfNoneMatch, etagToken) + noCacheResp, err := app.Test(noCacheReq) + fmt.Println(noCacheResp) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, "miss", noCacheResp.Header.Get("X-Cache")) + + etagToken = noCacheResp.Header.Get("Etag") + + // Request with id equals 2 and new etag and Cache-Control: no-cache in request header + noCacheReq1 := httptest.NewRequest("GET", "/?id=2", nil) + noCacheReq1.Header.Set(fiber.HeaderCacheControl, "no-cache") + noCacheReq1.Header.Set(fiber.HeaderIfNoneMatch, etagToken) + noCacheResp1, err := app.Test(noCacheReq1) + fmt.Println(noCacheResp1) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, "miss", noCacheResp1.Header.Get("X-Cache")) +} + func Test_Cache_WithSeveralRequests(t *testing.T) { t.Parallel() From 286271c67cd221e5b8425611a73bafb3043018fd Mon Sep 17 00:00:00 2001 From: Marc Martin Date: Wed, 19 Oct 2022 11:41:59 +0800 Subject: [PATCH 08/24] patch-1 --- middleware/cache/cache_test.go | 83 +++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 32 deletions(-) diff --git a/middleware/cache/cache_test.go b/middleware/cache/cache_test.go index f2ab01b8c6..35a9affbbc 100644 --- a/middleware/cache/cache_test.go +++ b/middleware/cache/cache_test.go @@ -108,7 +108,7 @@ func Test_Cache(t *testing.T) { utils.AssertEqual(t, cachedBody, body) } -func Test_CacheWithCacheControlNoCacheRequestHeader(t *testing.T) { +func Test_Cache_WithCacheControlNoCacheRequestHeader(t *testing.T) { t.Parallel() app := fiber.New() @@ -118,26 +118,46 @@ func Test_CacheWithCacheControlNoCacheRequestHeader(t *testing.T) { 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, "miss", resp.Header.Get("X-Cache")) + // Response cached, entry id = 1 - // Request with Cache-Control: no-cache in request header - noCacheReq := httptest.NewRequest("GET", "/", nil) + // Request id = 2 without Cache-Control: no-cache in request header + 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, "hit", 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 in request header + noCacheReq := httptest.NewRequest("GET", "/?id=2", nil) noCacheReq.Header.Set(fiber.HeaderCacheControl, "no-cache") noCacheResp, err := app.Test(noCacheReq) + defer noCacheResp.Body.Close() + noCacheBody, _ := ioutil.ReadAll(noCacheResp.Body) utils.AssertEqual(t, nil, err) utils.AssertEqual(t, "miss", noCacheResp.Header.Get("X-Cache")) - - // Request without Cache-Control: no-cache in request header - cacheReq := httptest.NewRequest("GET", "/", nil) - cacheResp, err := app.Test(cacheReq) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "hit", cacheResp.Header.Get("X-Cache")) + utils.AssertEqual(t, []byte("2"), noCacheBody) + // Response cached, entry = 2 + + // Request id = 1 without Cache-Control: no-cache in request header + 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, "hit", cachedResp1.Header.Get("X-Cache")) + utils.AssertEqual(t, []byte("2"), cachedBody1) + // Response not cached, returns cached response, entry id = 2 } -func Test_CacheWithETagAndCacheControlNoCacheRequestHeader(t *testing.T) { +func Test_Cache_WithETagAndCacheControlNoCacheRequestHeader(t *testing.T) { t.Parallel() app := fiber.New() @@ -150,47 +170,46 @@ func Test_CacheWithETagAndCacheControlNoCacheRequestHeader(t *testing.T) { 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, "miss", resp.Header.Get("X-Cache")) + // Response cached, entry id = 1 + // if success etagToken := resp.Header.Get("Etag") - // Request with updated response without Cache-Control: no-cache in request header - cacheReq := httptest.NewRequest("GET", "/", nil) - cacheResp, err := app.Test(cacheReq) - fmt.Println(cacheResp) - utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "hit", cacheResp.Header.Get("X-Cache")) - - // Request with id equals 2 and etag without Cache-Control: no-cache in request header - cacheReq1 := httptest.NewRequest("GET", "/?id=2", nil) - cacheReq1.Header.Set(fiber.HeaderIfNoneMatch, etagToken) - cacheResp1, err := app.Test(cacheReq1) - fmt.Println(cacheResp1) + // Request id = 2 with ETag but without Cache-Control: no-cache in request header + 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, "hit", cacheResp1.Header.Get("X-Cache")) + utils.AssertEqual(t, "hit", 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 with id equals 2 and etag and Cache-Control: no-cache in request header + // Request id = 2 with ETag and Cache-Control: no-cache in request header noCacheReq := httptest.NewRequest("GET", "/?id=2", nil) noCacheReq.Header.Set(fiber.HeaderCacheControl, "no-cache") noCacheReq.Header.Set(fiber.HeaderIfNoneMatch, etagToken) noCacheResp, err := app.Test(noCacheReq) - fmt.Println(noCacheResp) utils.AssertEqual(t, nil, err) utils.AssertEqual(t, "miss", noCacheResp.Header.Get("X-Cache")) + utils.AssertEqual(t, fiber.StatusOK, noCacheResp.StatusCode) + // Response cached, entry id = 2 + // if success etagToken = noCacheResp.Header.Get("Etag") - // Request with id equals 2 and new etag and Cache-Control: no-cache in request header - noCacheReq1 := httptest.NewRequest("GET", "/?id=2", nil) - noCacheReq1.Header.Set(fiber.HeaderCacheControl, "no-cache") - noCacheReq1.Header.Set(fiber.HeaderIfNoneMatch, etagToken) - noCacheResp1, err := app.Test(noCacheReq1) - fmt.Println(noCacheResp1) + // Request id = 1 with ETag but without Cache-Control: no-cache in request header + cachedReq1 := httptest.NewRequest("GET", "/", nil) + cachedReq1.Header.Set(fiber.HeaderIfNoneMatch, etagToken) + cachedResp1, err := app.Test(cachedReq1) utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "miss", noCacheResp1.Header.Get("X-Cache")) + utils.AssertEqual(t, "hit", cachedResp1.Header.Get("X-Cache")) + utils.AssertEqual(t, fiber.StatusNotModified, cachedResp1.StatusCode) + // Response not cached, returns cached response, entry id = 2, status not modified } func Test_Cache_WithSeveralRequests(t *testing.T) { From 03b68078bb6530498b21f3f2f415d191eee65b0a Mon Sep 17 00:00:00 2001 From: Marc Martin Date: Wed, 19 Oct 2022 12:11:57 +0800 Subject: [PATCH 09/24] patch-1 --- middleware/cache/cache_test.go | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/middleware/cache/cache_test.go b/middleware/cache/cache_test.go index 35a9affbbc..095bc198b3 100644 --- a/middleware/cache/cache_test.go +++ b/middleware/cache/cache_test.go @@ -146,6 +146,18 @@ func Test_Cache_WithCacheControlNoCacheRequestHeader(t *testing.T) { utils.AssertEqual(t, []byte("2"), noCacheBody) // Response cached, entry = 2 + /* If theres an ETag token "If-None-Match" in the request header, this will return 304. */ + // Request id = 2 with Cache-Control: no-cache in request header again + noCacheReq1 := httptest.NewRequest("GET", "/?id=2", nil) + noCacheReq1.Header.Set(fiber.HeaderCacheControl, "no-cache") + noCacheResp1, err := app.Test(noCacheReq1) + defer noCacheResp1.Body.Close() + noCacheBody1, _ := ioutil.ReadAll(noCacheResp1.Body) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, "miss", noCacheResp1.Header.Get("X-Cache")) + utils.AssertEqual(t, []byte("2"), noCacheBody1) + // Response cached, entry = 2 + // Request id = 1 without Cache-Control: no-cache in request header cachedReq1 := httptest.NewRequest("GET", "/", nil) cachedResp1, err := app.Test(cachedReq1) @@ -177,7 +189,7 @@ func Test_Cache_WithETagAndCacheControlNoCacheRequestHeader(t *testing.T) { utils.AssertEqual(t, "miss", resp.Header.Get("X-Cache")) // Response cached, entry id = 1 - // if success + // If success etagToken := resp.Header.Get("Etag") // Request id = 2 with ETag but without Cache-Control: no-cache in request header @@ -199,9 +211,22 @@ func Test_Cache_WithETagAndCacheControlNoCacheRequestHeader(t *testing.T) { utils.AssertEqual(t, fiber.StatusOK, noCacheResp.StatusCode) // Response cached, entry id = 2 - // if success + // If success etagToken = noCacheResp.Header.Get("Etag") + // Request id = 2 with ETag and Cache-Control: no-cache in request header again + noCacheReq1 := httptest.NewRequest("GET", "/?id=2", nil) + noCacheReq1.Header.Set(fiber.HeaderCacheControl, "no-cache") + noCacheReq1.Header.Set(fiber.HeaderIfNoneMatch, etagToken) + noCacheResp1, err := app.Test(noCacheReq1) + utils.AssertEqual(t, nil, err) + utils.AssertEqual(t, "miss", noCacheResp1.Header.Get("X-Cache")) + utils.AssertEqual(t, fiber.StatusNotModified, noCacheResp1.StatusCode) + // Response cached, entry id = 2 + + // if success + etagToken = noCacheResp1.Header.Get("Etag") + // Request id = 1 with ETag but without Cache-Control: no-cache in request header cachedReq1 := httptest.NewRequest("GET", "/", nil) cachedReq1.Header.Set(fiber.HeaderIfNoneMatch, etagToken) From 38cb7bcc4659e45edb1da06b6198821a86886f43 Mon Sep 17 00:00:00 2001 From: Marc Martin Date: Wed, 19 Oct 2022 14:34:20 +0800 Subject: [PATCH 10/24] patch-1 --- middleware/cache/config.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/middleware/cache/config.go b/middleware/cache/config.go index 8af77eaf71..68def2668a 100644 --- a/middleware/cache/config.go +++ b/middleware/cache/config.go @@ -74,7 +74,7 @@ type Config struct { // Default: []string{fiber.MethodGet, fiber.MethodHead} Methods []string - // If no-cache exist in request header + // Check if the request header contains Cache-Control: "no-cache" noCache func(c *fiber.Ctx) bool } @@ -92,7 +92,7 @@ var ConfigDefault = Config{ Storage: nil, MaxBytes: 0, Methods: []string{fiber.MethodGet, fiber.MethodHead}, - noCache: func(c *fiber.Ctx) bool { + noCache: func(c *fiber.Ctx) bool { return strings.Contains(c.Get(fiber.HeaderCacheControl), "no-cache") }, } @@ -132,5 +132,6 @@ func configDefault(config ...Config) Config { cfg.Methods = ConfigDefault.Methods } cfg.noCache = ConfigDefault.noCache + return cfg } From a5084c237d8973a3c7c5b87e06ac81c2796766e5 Mon Sep 17 00:00:00 2001 From: Marc Martin Date: Wed, 19 Oct 2022 14:39:04 +0800 Subject: [PATCH 11/24] patch-1 --- middleware/cache/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/middleware/cache/config.go b/middleware/cache/config.go index 68def2668a..7bc897bdc9 100644 --- a/middleware/cache/config.go +++ b/middleware/cache/config.go @@ -74,7 +74,7 @@ type Config struct { // Default: []string{fiber.MethodGet, fiber.MethodHead} Methods []string - // Check if the request header contains Cache-Control: "no-cache" + // Check if the request header contains Cache-Control: no-cache noCache func(c *fiber.Ctx) bool } From 705928dcb1db7f41daa8773ac584fb069b295627 Mon Sep 17 00:00:00 2001 From: Marc Martin Date: Wed, 19 Oct 2022 15:12:06 +0800 Subject: [PATCH 12/24] patch-1 --- middleware/cache/cache_test.go | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/middleware/cache/cache_test.go b/middleware/cache/cache_test.go index 095bc198b3..51746aa150 100644 --- a/middleware/cache/cache_test.go +++ b/middleware/cache/cache_test.go @@ -144,9 +144,9 @@ func Test_Cache_WithCacheControlNoCacheRequestHeader(t *testing.T) { utils.AssertEqual(t, nil, err) utils.AssertEqual(t, "miss", noCacheResp.Header.Get("X-Cache")) utils.AssertEqual(t, []byte("2"), noCacheBody) - // Response cached, entry = 2 + // Response cached, returns updated response, entry = 2 - /* If theres an ETag token "If-None-Match" in the request header, this will return 304. */ + /* Check Test_Cache_WithETagAndCacheControlNoCacheRequestHeader */ // Request id = 2 with Cache-Control: no-cache in request header again noCacheReq1 := httptest.NewRequest("GET", "/?id=2", nil) noCacheReq1.Header.Set(fiber.HeaderCacheControl, "no-cache") @@ -156,7 +156,7 @@ func Test_Cache_WithCacheControlNoCacheRequestHeader(t *testing.T) { utils.AssertEqual(t, nil, err) utils.AssertEqual(t, "miss", noCacheResp1.Header.Get("X-Cache")) utils.AssertEqual(t, []byte("2"), noCacheBody1) - // Response cached, entry = 2 + // Response cached, returns updated response, entry = 2 // Request id = 1 without Cache-Control: no-cache in request header cachedReq1 := httptest.NewRequest("GET", "/", nil) @@ -187,9 +187,10 @@ func Test_Cache_WithETagAndCacheControlNoCacheRequestHeader(t *testing.T) { resp, err := app.Test(req) utils.AssertEqual(t, nil, err) utils.AssertEqual(t, "miss", resp.Header.Get("X-Cache")) + utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) // Response cached, entry id = 1 - // If success + // If response status 200 etagToken := resp.Header.Get("Etag") // Request id = 2 with ETag but without Cache-Control: no-cache in request header @@ -209,9 +210,9 @@ func Test_Cache_WithETagAndCacheControlNoCacheRequestHeader(t *testing.T) { utils.AssertEqual(t, nil, err) utils.AssertEqual(t, "miss", noCacheResp.Header.Get("X-Cache")) utils.AssertEqual(t, fiber.StatusOK, noCacheResp.StatusCode) - // Response cached, entry id = 2 + // Response cached, returns updated response, entry id = 2 - // If success + // If response status 200 etagToken = noCacheResp.Header.Get("Etag") // Request id = 2 with ETag and Cache-Control: no-cache in request header again @@ -222,19 +223,15 @@ func Test_Cache_WithETagAndCacheControlNoCacheRequestHeader(t *testing.T) { utils.AssertEqual(t, nil, err) utils.AssertEqual(t, "miss", noCacheResp1.Header.Get("X-Cache")) utils.AssertEqual(t, fiber.StatusNotModified, noCacheResp1.StatusCode) - // Response cached, entry id = 2 + // Response cached, returns updated response, entry id = 2, status not modified - // if success - etagToken = noCacheResp1.Header.Get("Etag") - - // Request id = 1 with ETag but without Cache-Control: no-cache in request header + // Request id = 1 without ETag and Cache-Control: no-cache in request header cachedReq1 := httptest.NewRequest("GET", "/", nil) - cachedReq1.Header.Set(fiber.HeaderIfNoneMatch, etagToken) cachedResp1, err := app.Test(cachedReq1) utils.AssertEqual(t, nil, err) utils.AssertEqual(t, "hit", cachedResp1.Header.Get("X-Cache")) - utils.AssertEqual(t, fiber.StatusNotModified, cachedResp1.StatusCode) - // Response not cached, returns cached response, entry id = 2, status not modified + utils.AssertEqual(t, fiber.StatusOK, cachedResp1.StatusCode) + // Response not cached, returns cached response, entry id = 2 } func Test_Cache_WithSeveralRequests(t *testing.T) { From cabd965f6b922f524b1f23abb0f138f4f61457cf Mon Sep 17 00:00:00 2001 From: Marc Martin Date: Wed, 19 Oct 2022 15:21:58 +0800 Subject: [PATCH 13/24] patch-1 --- middleware/cache/cache_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/middleware/cache/cache_test.go b/middleware/cache/cache_test.go index 51746aa150..0dc1c385e7 100644 --- a/middleware/cache/cache_test.go +++ b/middleware/cache/cache_test.go @@ -125,7 +125,7 @@ func Test_Cache_WithCacheControlNoCacheRequestHeader(t *testing.T) { utils.AssertEqual(t, "miss", resp.Header.Get("X-Cache")) // Response cached, entry id = 1 - // Request id = 2 without Cache-Control: no-cache in request header + // Request id = 2 without Cache-Control: no-cache cachedReq := httptest.NewRequest("GET", "/?id=2", nil) cachedResp, err := app.Test(cachedReq) defer cachedResp.Body.Close() @@ -135,7 +135,7 @@ func Test_Cache_WithCacheControlNoCacheRequestHeader(t *testing.T) { utils.AssertEqual(t, []byte("1"), cachedBody) // Response not cached, returns cached response, entry id = 1 - // Request id = 2 with Cache-Control: no-cache in request header + // Request id = 2 with Cache-Control: no-cache noCacheReq := httptest.NewRequest("GET", "/?id=2", nil) noCacheReq.Header.Set(fiber.HeaderCacheControl, "no-cache") noCacheResp, err := app.Test(noCacheReq) @@ -147,7 +147,7 @@ func Test_Cache_WithCacheControlNoCacheRequestHeader(t *testing.T) { // Response cached, returns updated response, entry = 2 /* Check Test_Cache_WithETagAndCacheControlNoCacheRequestHeader */ - // Request id = 2 with Cache-Control: no-cache in request header again + // Request id = 2 with Cache-Control: no-cache again noCacheReq1 := httptest.NewRequest("GET", "/?id=2", nil) noCacheReq1.Header.Set(fiber.HeaderCacheControl, "no-cache") noCacheResp1, err := app.Test(noCacheReq1) @@ -158,7 +158,7 @@ func Test_Cache_WithCacheControlNoCacheRequestHeader(t *testing.T) { utils.AssertEqual(t, []byte("2"), noCacheBody1) // Response cached, returns updated response, entry = 2 - // Request id = 1 without Cache-Control: no-cache in request header + // Request id = 1 without Cache-Control: no-cache cachedReq1 := httptest.NewRequest("GET", "/", nil) cachedResp1, err := app.Test(cachedReq1) defer cachedResp1.Body.Close() @@ -193,7 +193,7 @@ func Test_Cache_WithETagAndCacheControlNoCacheRequestHeader(t *testing.T) { // If response status 200 etagToken := resp.Header.Get("Etag") - // Request id = 2 with ETag but without Cache-Control: no-cache in request header + // 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) @@ -202,7 +202,7 @@ func Test_Cache_WithETagAndCacheControlNoCacheRequestHeader(t *testing.T) { 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 in request header + // Request id = 2 with ETag and Cache-Control: no-cache noCacheReq := httptest.NewRequest("GET", "/?id=2", nil) noCacheReq.Header.Set(fiber.HeaderCacheControl, "no-cache") noCacheReq.Header.Set(fiber.HeaderIfNoneMatch, etagToken) @@ -215,7 +215,7 @@ func Test_Cache_WithETagAndCacheControlNoCacheRequestHeader(t *testing.T) { // If response status 200 etagToken = noCacheResp.Header.Get("Etag") - // Request id = 2 with ETag and Cache-Control: no-cache in request header again + // Request id = 2 with ETag and Cache-Control: no-cache again noCacheReq1 := httptest.NewRequest("GET", "/?id=2", nil) noCacheReq1.Header.Set(fiber.HeaderCacheControl, "no-cache") noCacheReq1.Header.Set(fiber.HeaderIfNoneMatch, etagToken) @@ -225,7 +225,7 @@ func Test_Cache_WithETagAndCacheControlNoCacheRequestHeader(t *testing.T) { 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 in request header + // 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) From cc9033734140f7fe555364cf8aeca0162bb49695 Mon Sep 17 00:00:00 2001 From: Marc Martin Date: Wed, 19 Oct 2022 15:40:09 +0800 Subject: [PATCH 14/24] patch-1 --- middleware/cache/cache.go | 2 +- middleware/cache/config.go | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/middleware/cache/cache.go b/middleware/cache/cache.go index aefb9548ad..af7f881e63 100644 --- a/middleware/cache/cache.go +++ b/middleware/cache/cache.go @@ -116,7 +116,7 @@ func New(config ...Config) fiber.Handler { _, size := heap.remove(e.heapidx) storedBytes -= size } - } else if e.exp != 0 && !cfg.noCache(c) { + } else if e.exp != 0 && !cfg.NoCache(c) { // Separate body value to avoid msgp serialization // We can store raw bytes with Storage 👍 if cfg.Storage != nil { diff --git a/middleware/cache/config.go b/middleware/cache/config.go index 7bc897bdc9..2fff203326 100644 --- a/middleware/cache/config.go +++ b/middleware/cache/config.go @@ -75,7 +75,11 @@ type Config struct { Methods []string // Check if the request header contains Cache-Control: no-cache - noCache func(c *fiber.Ctx) bool + // + // Default: func(c *fiber.Ctx) bool { + // return strings.Contains(c.Get(fiber.HeaderCacheControl), "no-cache") + // }, + NoCache func(c *fiber.Ctx) bool } // ConfigDefault is the default config @@ -92,7 +96,7 @@ var ConfigDefault = Config{ Storage: nil, MaxBytes: 0, Methods: []string{fiber.MethodGet, fiber.MethodHead}, - noCache: func(c *fiber.Ctx) bool { + NoCache: func(c *fiber.Ctx) bool { return strings.Contains(c.Get(fiber.HeaderCacheControl), "no-cache") }, } @@ -131,7 +135,9 @@ func configDefault(config ...Config) Config { if len(cfg.Methods) == 0 { cfg.Methods = ConfigDefault.Methods } - cfg.noCache = ConfigDefault.noCache + if cfg.NoCache == nil { + cfg.NoCache = ConfigDefault.NoCache + } return cfg } From bdb178bee6f887493820e63b0b017ec38b8d894d Mon Sep 17 00:00:00 2001 From: Marc Martin Date: Wed, 19 Oct 2022 16:07:39 +0800 Subject: [PATCH 15/24] patch-1 --- middleware/cache/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/middleware/cache/README.md b/middleware/cache/README.md index a5f3921a7a..d0a7d21bad 100644 --- a/middleware/cache/README.md +++ b/middleware/cache/README.md @@ -131,6 +131,13 @@ type Config struct { // // Default: []string{fiber.MethodGet, fiber.MethodHead} Methods []string + + // Check if the request header contains Cache-Control: no-cache + // + // Default: func(c *fiber.Ctx) bool { + // return strings.Contains(c.Get(fiber.HeaderCacheControl), "no-cache") + // }, + NoCache func(c *fiber.Ctx) bool } ``` @@ -151,5 +158,8 @@ var ConfigDefault = Config{ Storage: nil, MaxBytes: 0, Methods: []string{fiber.MethodGet, fiber.MethodHead}, + NoCache: func(c *fiber.Ctx) bool { + return strings.Contains(c.Get(fiber.HeaderCacheControl), "no-cache") + }, } ``` From 5f612a826ab9000e33ed300c32ac0d1f6c454a25 Mon Sep 17 00:00:00 2001 From: Marc Martin Date: Thu, 20 Oct 2022 09:38:10 +0800 Subject: [PATCH 16/24] patch-1 --- middleware/cache/README.md | 15 +++++---- middleware/cache/cache.go | 14 +++++++- middleware/cache/cache_test.go | 60 +++++++++++++++++++++++++--------- middleware/cache/config.go | 21 ++++++------ 4 files changed, 76 insertions(+), 34 deletions(-) diff --git a/middleware/cache/README.md b/middleware/cache/README.md index d0a7d21bad..06a17bf845 100644 --- a/middleware/cache/README.md +++ b/middleware/cache/README.md @@ -132,12 +132,13 @@ type Config struct { // Default: []string{fiber.MethodGet, fiber.MethodHead} Methods []string - // Check if the request header contains Cache-Control: no-cache - // - // Default: func(c *fiber.Ctx) bool { - // return strings.Contains(c.Get(fiber.HeaderCacheControl), "no-cache") + // Allows you to use no-cache and no-store request directives + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control + // + // Default: func(c *fiber.Ctx, directive string) bool { + // return strings.Contains(c.Get(fiber.HeaderCacheControl), directive) // }, - NoCache func(c *fiber.Ctx) bool + RequestDirective func(c *fiber.Ctx, directive string) bool } ``` @@ -158,8 +159,8 @@ var ConfigDefault = Config{ Storage: nil, MaxBytes: 0, Methods: []string{fiber.MethodGet, fiber.MethodHead}, - NoCache: func(c *fiber.Ctx) bool { - return strings.Contains(c.Get(fiber.HeaderCacheControl), "no-cache") + RequestDirective: func(c *fiber.Ctx, directive string) bool { + return strings.Contains(c.Get(fiber.HeaderCacheControl), directive) }, } ``` diff --git a/middleware/cache/cache.go b/middleware/cache/cache.go index af7f881e63..9ea87f0cac 100644 --- a/middleware/cache/cache.go +++ b/middleware/cache/cache.go @@ -27,6 +27,12 @@ const ( cacheMiss = "miss" ) +// directives +const ( + noCache = "no-cache" + noStore = "no-store" +) + var ignoreHeaders = map[string]interface{}{ "Connection": nil, "Keep-Alive": nil, @@ -96,6 +102,12 @@ func New(config ...Config) fiber.Handler { return c.Next() } + // Refrain from caching + if cfg.RequestDirective(c, noStore) { + c.Set(cfg.CacheHeader, cacheUnreachable) + return c.Next() + } + // Get key from request // TODO(allocation optimization): try to minimize the allocation from 2 to 1 key := cfg.KeyGenerator(c) + "_" + c.Method() @@ -116,7 +128,7 @@ func New(config ...Config) fiber.Handler { _, size := heap.remove(e.heapidx) storedBytes -= size } - } else if e.exp != 0 && !cfg.NoCache(c) { + } else if e.exp != 0 && !cfg.RequestDirective(c, noCache) { // Separate body value to avoid msgp serialization // We can store raw bytes with Storage 👍 if cfg.Storage != nil { diff --git a/middleware/cache/cache_test.go b/middleware/cache/cache_test.go index 0dc1c385e7..b78bd086ab 100644 --- a/middleware/cache/cache_test.go +++ b/middleware/cache/cache_test.go @@ -108,7 +108,8 @@ func Test_Cache(t *testing.T) { utils.AssertEqual(t, cachedBody, body) } -func Test_Cache_WithCacheControlNoCacheRequestHeader(t *testing.T) { +// go test -run Test_Cache_WithNoCacheRequestDirective +func Test_Cache_WithNoCacheRequestDirective(t *testing.T) { t.Parallel() app := fiber.New() @@ -121,8 +122,11 @@ func Test_Cache_WithCacheControlNoCacheRequestHeader(t *testing.T) { // 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, "miss", resp.Header.Get("X-Cache")) + 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 @@ -131,30 +135,30 @@ func Test_Cache_WithCacheControlNoCacheRequestHeader(t *testing.T) { defer cachedResp.Body.Close() cachedBody, _ := ioutil.ReadAll(cachedResp.Body) utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "hit", cachedResp.Header.Get("X-Cache")) + 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, "no-cache") + 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, "miss", noCacheResp.Header.Get("X-Cache")) + 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_WithETagAndCacheControlNoCacheRequestHeader */ // Request id = 2 with Cache-Control: no-cache again noCacheReq1 := httptest.NewRequest("GET", "/?id=2", nil) - noCacheReq1.Header.Set(fiber.HeaderCacheControl, "no-cache") + 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, "miss", noCacheResp1.Header.Get("X-Cache")) + utils.AssertEqual(t, cacheMiss, noCacheResp1.Header.Get("X-Cache")) utils.AssertEqual(t, []byte("2"), noCacheBody1) // Response cached, returns updated response, entry = 2 @@ -164,12 +168,13 @@ func Test_Cache_WithCacheControlNoCacheRequestHeader(t *testing.T) { defer cachedResp1.Body.Close() cachedBody1, _ := ioutil.ReadAll(cachedResp1.Body) utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "hit", cachedResp1.Header.Get("X-Cache")) + utils.AssertEqual(t, cacheHit, cachedResp1.Header.Get("X-Cache")) utils.AssertEqual(t, []byte("2"), cachedBody1) // Response not cached, returns cached response, entry id = 2 } -func Test_Cache_WithETagAndCacheControlNoCacheRequestHeader(t *testing.T) { +// go test -run Test_Cache_WithETagAndNoCacheRequestDirective +func Test_Cache_WithETagAndNoCacheRequestDirective(t *testing.T) { t.Parallel() app := fiber.New() @@ -186,7 +191,7 @@ func Test_Cache_WithETagAndCacheControlNoCacheRequestHeader(t *testing.T) { req := httptest.NewRequest("GET", "/", nil) resp, err := app.Test(req) utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "miss", resp.Header.Get("X-Cache")) + utils.AssertEqual(t, cacheMiss, resp.Header.Get("X-Cache")) utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) // Response cached, entry id = 1 @@ -198,17 +203,17 @@ func Test_Cache_WithETagAndCacheControlNoCacheRequestHeader(t *testing.T) { cachedReq.Header.Set(fiber.HeaderIfNoneMatch, etagToken) cachedResp, err := app.Test(cachedReq) utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "hit", cachedResp.Header.Get("X-Cache")) + 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, "no-cache") + 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, "miss", noCacheResp.Header.Get("X-Cache")) + utils.AssertEqual(t, cacheMiss, noCacheResp.Header.Get("X-Cache")) utils.AssertEqual(t, fiber.StatusOK, noCacheResp.StatusCode) // Response cached, returns updated response, entry id = 2 @@ -217,11 +222,11 @@ func Test_Cache_WithETagAndCacheControlNoCacheRequestHeader(t *testing.T) { // Request id = 2 with ETag and Cache-Control: no-cache again noCacheReq1 := httptest.NewRequest("GET", "/?id=2", nil) - noCacheReq1.Header.Set(fiber.HeaderCacheControl, "no-cache") + 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, "miss", noCacheResp1.Header.Get("X-Cache")) + 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 @@ -229,11 +234,34 @@ func Test_Cache_WithETagAndCacheControlNoCacheRequestHeader(t *testing.T) { cachedReq1 := httptest.NewRequest("GET", "/", nil) cachedResp1, err := app.Test(cachedReq1) utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, "hit", cachedResp1.Header.Get("X-Cache")) + 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, cacheUnreachable, noStoreResp.Header.Get("X-Cache")) + utils.AssertEqual(t, []byte("2"), noStoreBody) + // Response not cached, returns updated response +} + func Test_Cache_WithSeveralRequests(t *testing.T) { t.Parallel() diff --git a/middleware/cache/config.go b/middleware/cache/config.go index 2fff203326..768639bd70 100644 --- a/middleware/cache/config.go +++ b/middleware/cache/config.go @@ -73,13 +73,14 @@ type Config struct { // // Default: []string{fiber.MethodGet, fiber.MethodHead} Methods []string - - // Check if the request header contains Cache-Control: no-cache - // - // Default: func(c *fiber.Ctx) bool { - // return strings.Contains(c.Get(fiber.HeaderCacheControl), "no-cache") + + // Allows you to use no-cache and no-store request directives + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control + // + // Default: func(c *fiber.Ctx, directive string) bool { + // return strings.Contains(c.Get(fiber.HeaderCacheControl), directive) // }, - NoCache func(c *fiber.Ctx) bool + RequestDirective func(c *fiber.Ctx, directive string) bool } // ConfigDefault is the default config @@ -96,8 +97,8 @@ var ConfigDefault = Config{ Storage: nil, MaxBytes: 0, Methods: []string{fiber.MethodGet, fiber.MethodHead}, - NoCache: func(c *fiber.Ctx) bool { - return strings.Contains(c.Get(fiber.HeaderCacheControl), "no-cache") + RequestDirective: func(c *fiber.Ctx, directive string) bool { + return strings.Contains(c.Get(fiber.HeaderCacheControl), directive) }, } @@ -135,8 +136,8 @@ func configDefault(config ...Config) Config { if len(cfg.Methods) == 0 { cfg.Methods = ConfigDefault.Methods } - if cfg.NoCache == nil { - cfg.NoCache = ConfigDefault.NoCache + if cfg.RequestDirective == nil { + cfg.RequestDirective = ConfigDefault.RequestDirective } return cfg From 9f6e442f6dd5f7a8f2164e6d60378e2e3e956e98 Mon Sep 17 00:00:00 2001 From: Marc Martin Date: Thu, 20 Oct 2022 14:34:43 +0800 Subject: [PATCH 17/24] patch-1 --- middleware/cache/cache_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/middleware/cache/cache_test.go b/middleware/cache/cache_test.go index b78bd086ab..bc69bccc06 100644 --- a/middleware/cache/cache_test.go +++ b/middleware/cache/cache_test.go @@ -150,7 +150,7 @@ func Test_Cache_WithNoCacheRequestDirective(t *testing.T) { utils.AssertEqual(t, []byte("2"), noCacheBody) // Response cached, returns updated response, entry = 2 - /* Check Test_Cache_WithETagAndCacheControlNoCacheRequestHeader */ + /* 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) From 8a60d9506d277c6fe08870c2fb2bce231aff0735 Mon Sep 17 00:00:00 2001 From: Marc Martin Date: Fri, 21 Oct 2022 07:31:15 +0800 Subject: [PATCH 18/24] patch-1 --- middleware/cache/README.md | 4 ++-- middleware/cache/cache.go | 4 ++-- middleware/cache/config.go | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/middleware/cache/README.md b/middleware/cache/README.md index 06a17bf845..380e54899e 100644 --- a/middleware/cache/README.md +++ b/middleware/cache/README.md @@ -138,7 +138,7 @@ type Config struct { // Default: func(c *fiber.Ctx, directive string) bool { // return strings.Contains(c.Get(fiber.HeaderCacheControl), directive) // }, - RequestDirective func(c *fiber.Ctx, directive string) bool + requestDirective func(c *fiber.Ctx, directive string) bool } ``` @@ -159,7 +159,7 @@ var ConfigDefault = Config{ Storage: nil, MaxBytes: 0, Methods: []string{fiber.MethodGet, fiber.MethodHead}, - RequestDirective: func(c *fiber.Ctx, directive string) bool { + requestDirective: func(c *fiber.Ctx, directive string) bool { return strings.Contains(c.Get(fiber.HeaderCacheControl), directive) }, } diff --git a/middleware/cache/cache.go b/middleware/cache/cache.go index 9ea87f0cac..0ebe0cf5c6 100644 --- a/middleware/cache/cache.go +++ b/middleware/cache/cache.go @@ -103,7 +103,7 @@ func New(config ...Config) fiber.Handler { } // Refrain from caching - if cfg.RequestDirective(c, noStore) { + if cfg.requestDirective(c, noStore) { c.Set(cfg.CacheHeader, cacheUnreachable) return c.Next() } @@ -128,7 +128,7 @@ func New(config ...Config) fiber.Handler { _, size := heap.remove(e.heapidx) storedBytes -= size } - } else if e.exp != 0 && !cfg.RequestDirective(c, noCache) { + } else if e.exp != 0 && !cfg.requestDirective(c, noCache) { // Separate body value to avoid msgp serialization // We can store raw bytes with Storage 👍 if cfg.Storage != nil { diff --git a/middleware/cache/config.go b/middleware/cache/config.go index 768639bd70..536e2b1260 100644 --- a/middleware/cache/config.go +++ b/middleware/cache/config.go @@ -80,7 +80,7 @@ type Config struct { // Default: func(c *fiber.Ctx, directive string) bool { // return strings.Contains(c.Get(fiber.HeaderCacheControl), directive) // }, - RequestDirective func(c *fiber.Ctx, directive string) bool + requestDirective func(c *fiber.Ctx, directive string) bool } // ConfigDefault is the default config @@ -97,7 +97,7 @@ var ConfigDefault = Config{ Storage: nil, MaxBytes: 0, Methods: []string{fiber.MethodGet, fiber.MethodHead}, - RequestDirective: func(c *fiber.Ctx, directive string) bool { + requestDirective: func(c *fiber.Ctx, directive string) bool { return strings.Contains(c.Get(fiber.HeaderCacheControl), directive) }, } @@ -136,8 +136,8 @@ func configDefault(config ...Config) Config { if len(cfg.Methods) == 0 { cfg.Methods = ConfigDefault.Methods } - if cfg.RequestDirective == nil { - cfg.RequestDirective = ConfigDefault.RequestDirective + if cfg.requestDirective == nil { + cfg.requestDirective = ConfigDefault.requestDirective } return cfg From a066c4be693031bfe5bd2c75ede46eef244c69dd Mon Sep 17 00:00:00 2001 From: Marc Martin Date: Fri, 21 Oct 2022 07:48:56 +0800 Subject: [PATCH 19/24] patch-1 --- middleware/cache/README.md | 11 ----------- middleware/cache/cache.go | 10 ++++++++-- middleware/cache/config.go | 15 --------------- 3 files changed, 8 insertions(+), 28 deletions(-) diff --git a/middleware/cache/README.md b/middleware/cache/README.md index 380e54899e..a5f3921a7a 100644 --- a/middleware/cache/README.md +++ b/middleware/cache/README.md @@ -131,14 +131,6 @@ type Config struct { // // Default: []string{fiber.MethodGet, fiber.MethodHead} Methods []string - - // Allows you to use no-cache and no-store request directives - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control - // - // Default: func(c *fiber.Ctx, directive string) bool { - // return strings.Contains(c.Get(fiber.HeaderCacheControl), directive) - // }, - requestDirective func(c *fiber.Ctx, directive string) bool } ``` @@ -159,8 +151,5 @@ var ConfigDefault = Config{ Storage: nil, MaxBytes: 0, Methods: []string{fiber.MethodGet, fiber.MethodHead}, - requestDirective: func(c *fiber.Ctx, directive string) bool { - return strings.Contains(c.Get(fiber.HeaderCacheControl), directive) - }, } ``` diff --git a/middleware/cache/cache.go b/middleware/cache/cache.go index 0ebe0cf5c6..340abfe59e 100644 --- a/middleware/cache/cache.go +++ b/middleware/cache/cache.go @@ -4,6 +4,7 @@ package cache import ( "strconv" + "strings" "sync" "sync/atomic" "time" @@ -103,7 +104,7 @@ func New(config ...Config) fiber.Handler { } // Refrain from caching - if cfg.requestDirective(c, noStore) { + if hasRequestDirective(c, noStore) { c.Set(cfg.CacheHeader, cacheUnreachable) return c.Next() } @@ -128,7 +129,7 @@ func New(config ...Config) fiber.Handler { _, size := heap.remove(e.heapidx) storedBytes -= size } - } else if e.exp != 0 && !cfg.requestDirective(c, noCache) { + } 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 { @@ -247,3 +248,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) +} diff --git a/middleware/cache/config.go b/middleware/cache/config.go index 536e2b1260..fdfa8b292e 100644 --- a/middleware/cache/config.go +++ b/middleware/cache/config.go @@ -2,7 +2,6 @@ package cache import ( "fmt" - "strings" "time" "github.com/gofiber/fiber/v2" @@ -73,14 +72,6 @@ type Config struct { // // Default: []string{fiber.MethodGet, fiber.MethodHead} Methods []string - - // Allows you to use no-cache and no-store request directives - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control - // - // Default: func(c *fiber.Ctx, directive string) bool { - // return strings.Contains(c.Get(fiber.HeaderCacheControl), directive) - // }, - requestDirective func(c *fiber.Ctx, directive string) bool } // ConfigDefault is the default config @@ -97,9 +88,6 @@ var ConfigDefault = Config{ Storage: nil, MaxBytes: 0, Methods: []string{fiber.MethodGet, fiber.MethodHead}, - requestDirective: func(c *fiber.Ctx, directive string) bool { - return strings.Contains(c.Get(fiber.HeaderCacheControl), directive) - }, } // Helper function to set default values @@ -136,9 +124,6 @@ func configDefault(config ...Config) Config { if len(cfg.Methods) == 0 { cfg.Methods = ConfigDefault.Methods } - if cfg.requestDirective == nil { - cfg.requestDirective = ConfigDefault.requestDirective - } return cfg } From 62110ef20c9ecd1cbcdbcda6c5cc30b61ca1037a Mon Sep 17 00:00:00 2001 From: Marc Martin Date: Fri, 21 Oct 2022 08:02:16 +0800 Subject: [PATCH 20/24] patch-1 --- middleware/cache/config.go | 1 - 1 file changed, 1 deletion(-) diff --git a/middleware/cache/config.go b/middleware/cache/config.go index fdfa8b292e..12f81e2ae8 100644 --- a/middleware/cache/config.go +++ b/middleware/cache/config.go @@ -124,6 +124,5 @@ func configDefault(config ...Config) Config { if len(cfg.Methods) == 0 { cfg.Methods = ConfigDefault.Methods } - return cfg } From 1c10ad87421d585b61dab00ba07835d21f123441 Mon Sep 17 00:00:00 2001 From: Marc Martin Date: Fri, 21 Oct 2022 08:47:21 +0800 Subject: [PATCH 21/24] patch-1 --- middleware/cache/cache.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/middleware/cache/cache.go b/middleware/cache/cache.go index 340abfe59e..f6db49e2cf 100644 --- a/middleware/cache/cache.go +++ b/middleware/cache/cache.go @@ -90,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 { @@ -103,12 +108,6 @@ func New(config ...Config) fiber.Handler { return c.Next() } - // Refrain from caching - if hasRequestDirective(c, noStore) { - c.Set(cfg.CacheHeader, cacheUnreachable) - return c.Next() - } - // Get key from request // TODO(allocation optimization): try to minimize the allocation from 2 to 1 key := cfg.KeyGenerator(c) + "_" + c.Method() From f867159be6a58a70b427fbf36d0fb945d0e207a2 Mon Sep 17 00:00:00 2001 From: Marc Martin Date: Fri, 21 Oct 2022 08:54:56 +0800 Subject: [PATCH 22/24] patch-1 --- middleware/cache/cache_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/middleware/cache/cache_test.go b/middleware/cache/cache_test.go index bc69bccc06..3216a7c48a 100644 --- a/middleware/cache/cache_test.go +++ b/middleware/cache/cache_test.go @@ -257,7 +257,6 @@ func Test_Cache_WithNoStoreRequestDirective(t *testing.T) { defer noStoreResp.Body.Close() noStoreBody, _ := ioutil.ReadAll(noStoreResp.Body) utils.AssertEqual(t, nil, err) - utils.AssertEqual(t, cacheUnreachable, noStoreResp.Header.Get("X-Cache")) utils.AssertEqual(t, []byte("2"), noStoreBody) // Response not cached, returns updated response } From abc103c298424a0e8227a3ccf4f55d05a6e89bd1 Mon Sep 17 00:00:00 2001 From: Marc Martin Date: Fri, 21 Oct 2022 14:57:51 +0800 Subject: [PATCH 23/24] patch-1 --- middleware/cache/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/middleware/cache/README.md b/middleware/cache/README.md index a5f3921a7a..8209be4ed5 100644 --- a/middleware/cache/README.md +++ b/middleware/cache/README.md @@ -2,6 +2,15 @@ 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. +Usage: +Cache-Control: no-cache + +The no-store request directive will refrain from caching. You will always get the up-to-date response. +Usage: +Cache-Control: no-store + ## Table of Contents - [Cache Middleware](#cache-middleware) From 99ea1c3be98fdd0f49d4ec5eccd5aac4d0a07793 Mon Sep 17 00:00:00 2001 From: Marc Martin Date: Fri, 21 Oct 2022 15:08:11 +0800 Subject: [PATCH 24/24] patch-1 --- middleware/cache/README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/middleware/cache/README.md b/middleware/cache/README.md index 8209be4ed5..373446486b 100644 --- a/middleware/cache/README.md +++ b/middleware/cache/README.md @@ -4,12 +4,7 @@ Cache middleware for [Fiber](https://github.com/gofiber/fiber) designed to inter 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. -Usage: -Cache-Control: no-cache - The no-store request directive will refrain from caching. You will always get the up-to-date response. -Usage: -Cache-Control: no-store ## Table of Contents