From a63875c4d1159a2df38947cc08dd3d462f25f170 Mon Sep 17 00:00:00 2001 From: darkweak Date: Wed, 19 Jul 2023 15:11:38 +0200 Subject: [PATCH 1/2] feat(dependencies): bump to Souin v1.6.39 --- Caddyfile | 52 ++++++++++++++++++++++++ README.md | 2 + configuration.go | 45 +++++++++++++++++++-- go.mod | 4 +- go.sum | 8 ++-- httpcache_test.go | 101 +++++++++++++++++++++++++++++++++++++++++++++- 6 files changed, 202 insertions(+), 10 deletions(-) diff --git a/Caddyfile b/Caddyfile index 5f2d521..fde988b 100644 --- a/Caddyfile +++ b/Caddyfile @@ -287,6 +287,15 @@ route /custom-key/without-* { respond "Hello to the authenticated user." } +route /must-revalidate { + cache { + ttl 5s + stale 5s + } + header Cache-Control "must-revalidate" + reverse_proxy 127.0.0.1:81 +} + route /cache-authorization { cache { cache_keys { @@ -299,6 +308,49 @@ route /cache-authorization { respond "Hello to the authenticated user." } +route /bypass { + cache { + mode bypass + } + + header Cache-Control "no-store" + respond "Hello bypass" +} + +route /bypass_request { + cache { + mode bypass_request + } + + respond "Hello bypass_request" +} + +route /bypass_response { + cache { + mode bypass_response + } + + header Cache-Control "no-cache, no-store" + respond "Hello bypass_response" +} + +route /strict_request { + cache { + mode strict + } + + respond "Hello strict" +} + +route /strict_response { + cache { + mode strict + } + + header Cache-Control "no-cache, no-store" + respond "Hello strict" +} + cache @souin-api { } diff --git a/README.md b/README.md index 8f3dbef..d36631c 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ Here are all the available options for the global options headers Content-Type Authorization } log_level debug + mode bypass nuts { path /path/to/the/storage } @@ -384,6 +385,7 @@ What does these directives mean? | `key.disable_query` | Disable the query string part in the key | `true`

`(default: false)` | | `key.headers` | Add headers to the key matching the regexp | `Authorization Content-Type X-Additional-Header` | | `key.hide` | Prevent the key from being exposed in the `Cache-Status` HTTP response header | `true`

`(default: false)` | +| `mode` | Bypass the RFC respect | One of `bypass` `bypass_request` `bypass_response` `strict` (default `strict`) | | `nuts` | Configure the Nuts cache storage | | | `nuts.path` | Set the Nuts file path storage | `/anywhere/nuts/storage` | | `nuts.configuration` | Configure Nuts directly in the Caddyfile or your JSON caddy configuration | [See the Nuts configuration for the options](https://github.com/nutsdb/nutsdb#default-options) | diff --git a/configuration.go b/configuration.go index df4f612..93c0f06 100644 --- a/configuration.go +++ b/configuration.go @@ -28,6 +28,8 @@ type DefaultCache struct { Headers []string `json:"headers"` // Configure the global key generation. Key configurationtypes.Key `json:"key"` + // Mode defines if strict or bypass. + Mode string `json:"mode"` // Olric provider configuration. Olric configurationtypes.CacheProvider `json:"olric"` // Redis provider configuration. @@ -86,6 +88,11 @@ func (d *DefaultCache) GetEtcd() configurationtypes.CacheProvider { return d.Etcd } +// GetMode returns mdoe configuration +func (d *DefaultCache) GetMode() string { + return d.Mode +} + // GetNuts returns nuts configuration func (d *DefaultCache) GetNuts() configurationtypes.CacheProvider { return d.Nuts @@ -274,6 +281,8 @@ func parseConfiguration(cfg *Configuration, h *caddyfile.Dispenser, isBlocking b switch directive { case "basepath": apiConfiguration.Debug.BasePath = h.RemainingArgs()[0] + default: + return h.Errf("unsupported debug directive: %s", directive) } } case "prometheus": @@ -284,6 +293,8 @@ func parseConfiguration(cfg *Configuration, h *caddyfile.Dispenser, isBlocking b switch directive { case "basepath": apiConfiguration.Prometheus.BasePath = h.RemainingArgs()[0] + default: + return h.Errf("unsupported prometheus directive: %s", directive) } } case "souin": @@ -294,8 +305,12 @@ func parseConfiguration(cfg *Configuration, h *caddyfile.Dispenser, isBlocking b switch directive { case "basepath": apiConfiguration.Souin.BasePath = h.RemainingArgs()[0] + default: + return h.Errf("unsupported souin directive: %s", directive) } } + default: + return h.Errf("unsupported api directive: %s", directive) } } cfg.API = apiConfiguration @@ -310,6 +325,8 @@ func parseConfiguration(cfg *Configuration, h *caddyfile.Dispenser, isBlocking b case "configuration": provider.Configuration = parseCaddyfileRecursively(h) provider.Configuration = parseBadgerConfiguration(provider.Configuration.(map[string]interface{})) + default: + return h.Errf("unsupported badger directive: %s", directive) } } cfg.DefaultCache.Badger = provider @@ -337,6 +354,8 @@ func parseConfiguration(cfg *Configuration, h *caddyfile.Dispenser, isBlocking b ck.Hide = true case "headers": ck.Headers = h.RemainingArgs() + default: + return h.Errf("unsupported cache_keys (%s) directive: %s", rg, directive) } } @@ -367,6 +386,8 @@ func parseConfiguration(cfg *Configuration, h *caddyfile.Dispenser, isBlocking b cdn.Provider = h.RemainingArgs()[0] case "strategy": cdn.Strategy = h.RemainingArgs()[0] + default: + return h.Errf("unsupported cdn directive: %s", directive) } } cfg.DefaultCache.CDN = cdn @@ -381,6 +402,8 @@ func parseConfiguration(cfg *Configuration, h *caddyfile.Dispenser, isBlocking b switch directive { case "configuration": provider.Configuration = parseCaddyfileRecursively(h) + default: + return h.Errf("unsupported etcd directive: %s", directive) } } cfg.DefaultCache.Etcd = provider @@ -403,12 +426,20 @@ func parseConfiguration(cfg *Configuration, h *caddyfile.Dispenser, isBlocking b config_key.Hide = true case "headers": config_key.Headers = h.RemainingArgs() + default: + return h.Errf("unsupported key directive: %s", directive) } } cfg.DefaultCache.Key = config_key case "log_level": args := h.RemainingArgs() cfg.LogLevel = args[0] + case "mode": + args := h.RemainingArgs() + if len(args) > 1 { + return h.Errf("mode must contains only one arg: %s given", args) + } + cfg.DefaultCache.Mode = args[0] case "nuts": provider := configurationtypes.CacheProvider{} for nesting := h.Nesting(); h.NextBlock(nesting); { @@ -422,6 +453,8 @@ func parseConfiguration(cfg *Configuration, h *caddyfile.Dispenser, isBlocking b provider.Path = urlArgs[0] case "configuration": provider.Configuration = parseCaddyfileRecursively(h) + default: + return h.Errf("unsupported nuts directive: %s", directive) } } cfg.DefaultCache.Nuts = provider @@ -439,6 +472,8 @@ func parseConfiguration(cfg *Configuration, h *caddyfile.Dispenser, isBlocking b provider.Path = urlArgs[0] case "configuration": provider.Configuration = parseCaddyfileRecursively(h) + default: + return h.Errf("unsupported olric directive: %s", directive) } } cfg.DefaultCache.Olric = provider @@ -457,6 +492,8 @@ func parseConfiguration(cfg *Configuration, h *caddyfile.Dispenser, isBlocking b case "configuration": provider.Configuration = parseCaddyfileRecursively(h) provider.Configuration = parseRedisConfiguration(provider.Configuration.(map[string]interface{})) + default: + return h.Errf("unsupported redis directive: %s", directive) } } cfg.DefaultCache.Redis = provider @@ -466,6 +503,8 @@ func parseConfiguration(cfg *Configuration, h *caddyfile.Dispenser, isBlocking b switch directive { case "exclude": cfg.DefaultCache.Regex.Exclude = h.RemainingArgs()[0] + default: + return h.Errf("unsupported regex directive: %s", directive) } } case "stale": @@ -493,6 +532,8 @@ func parseConfiguration(cfg *Configuration, h *caddyfile.Dispenser, isBlocking b d.Duration = ttl } timeout.Cache = d + default: + return h.Errf("unsupported timeout directive: %s", directive) } } cfg.DefaultCache.Timeout = timeout @@ -503,9 +544,7 @@ func parseConfiguration(cfg *Configuration, h *caddyfile.Dispenser, isBlocking b cfg.DefaultCache.TTL.Duration = ttl } default: - if isBlocking { - return h.Errf("unsupported root directive: %s", rootOption) - } + return h.Errf("unsupported root directive: %s", rootOption) } } } diff --git a/go.mod b/go.mod index e4b8d28..31a10b6 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.19 require ( github.com/buraksezer/olric v0.5.4 github.com/caddyserver/caddy/v2 v2.6.4 - github.com/darkweak/souin v1.6.36 + github.com/darkweak/souin v1.6.39 go.uber.org/zap v1.24.0 ) @@ -102,7 +102,7 @@ require ( github.com/mschoch/smat v0.2.0 // indirect github.com/onsi/ginkgo/v2 v2.9.0 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pquerna/cachecontrol v0.1.0 // indirect + github.com/pquerna/cachecontrol v0.1.1-0.20230415224848-baaf0ee61529 // indirect github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.41.0 // indirect diff --git a/go.sum b/go.sum index bf16fec..75d9334 100644 --- a/go.sum +++ b/go.sum @@ -656,8 +656,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/darkweak/go-esi v0.0.5 h1:b9LHI8Tz46R+i6p8avKPHAIBRQUCZDebNmKm5w/Zrns= github.com/darkweak/go-esi v0.0.5/go.mod h1:koCJqwum1u6mslyZuq/Phm6hfG1K3ZK5Y7jrUBTH654= -github.com/darkweak/souin v1.6.36 h1:T+7xZFjnDgbjOPF/1jEFj3cBcTMK6hzdG0X3iQiGkGg= -github.com/darkweak/souin v1.6.36/go.mod h1:/C9ISWex+bkdmHgAT2wZpq9VQddcMLb0i8/ykrapN40= +github.com/darkweak/souin v1.6.39 h1:uVO18+pvy5N8dJH+CCfuBD3gx7AXWaBv+RjC9ee1pCk= +github.com/darkweak/souin v1.6.39/go.mod h1:7B7VqPbFrF7AWJDRP6FCbE+zGiRUUqHAmWOs3Q2n4hk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -1115,8 +1115,8 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= -github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= +github.com/pquerna/cachecontrol v0.1.1-0.20230415224848-baaf0ee61529 h1:wcNVCAIsWcLpEJ5FhXHKC7dBi6SIZQukrOTY5eHes0M= +github.com/pquerna/cachecontrol v0.1.1-0.20230415224848-baaf0ee61529/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= diff --git a/httpcache_test.go b/httpcache_test.go index 41d6ecf..9093be7 100644 --- a/httpcache_test.go +++ b/httpcache_test.go @@ -248,7 +248,7 @@ func TestNotHandledRoute(t *testing.T) { }`, "caddyfile") resp1, _ := tester.AssertGetResponse(`http://localhost:9080/not-handled`, 200, "Hello, Age header!") - if resp1.Header.Get("Cache-Status") != "Souin; fwd=uri-miss; detail=EXCLUDED-REQUEST-URI" { + if resp1.Header.Get("Cache-Status") != "Souin; fwd=bypass; detail=EXCLUDED-REQUEST-URI" { t.Errorf("unexpected Cache-Status header value %v", resp1.Header.Get("Cache-Status")) } } @@ -329,3 +329,102 @@ func TestAuthenticatedRoute(t *testing.T) { t.Errorf("unexpected Cache-Status header %v", respAuthVaryBypassAlice2.Header.Get("Cache-Status")) } } + +type testErrorHandler struct { + iterator int +} + +func (t *testErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + t.iterator++ + if t.iterator%2 == 0 { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Header().Set("Cache-Control", "must-revalidate") + w.WriteHeader(http.StatusOK) + w.Write([]byte("Hello must-revalidate!")) +} + +func TestMustRevalidate(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + admin localhost:2999 + order cache before rewrite + http_port 9080 + cache { + ttl 5s + stale 5s + } + } + localhost:9080 { + route /cache-default { + cache + reverse_proxy localhost:9081 + } + }`, "caddyfile") + + errorHandler := testErrorHandler{} + go http.ListenAndServe(":9081", &errorHandler) + time.Sleep(time.Second) + resp1, _ := tester.AssertGetResponse(`http://localhost:9080/cache-default`, http.StatusOK, "Hello must-revalidate!") + resp2, _ := tester.AssertGetResponse(`http://localhost:9080/cache-default`, http.StatusOK, "Hello must-revalidate!") + time.Sleep(6 * time.Second) + staleReq, _ := http.NewRequest(http.MethodGet, "http://localhost:9080/cache-default", nil) + staleReq.Header = http.Header{"Cache-Control": []string{"max-stale=3, stale-if-error=84600"}} + resp3, _ := tester.AssertResponse(staleReq, http.StatusOK, "Hello must-revalidate!") + + if resp1.Header.Get("Cache-Control") != "must-revalidate" { + t.Errorf("unexpected resp1 Cache-Control header %v", resp1.Header.Get("Cache-Control")) + } + if resp1.Header.Get("Cache-Status") != "Souin; fwd=uri-miss; stored; key=GET-http-localhost:9080-/cache-default" { + t.Errorf("unexpected resp1 Cache-Status header %v", resp1.Header.Get("Cache-Status")) + } + if resp1.Header.Get("Age") != "" { + t.Errorf("unexpected resp1 Age header %v", resp1.Header.Get("Age")) + } + + if resp2.Header.Get("Cache-Control") != "must-revalidate" { + t.Errorf("unexpected resp2 Cache-Control header %v", resp2.Header.Get("Cache-Control")) + } + if resp2.Header.Get("Cache-Status") != "Souin; hit; ttl=4; key=GET-http-localhost:9080-/cache-default" { + t.Errorf("unexpected resp2 Cache-Status header %v", resp2.Header.Get("Cache-Status")) + } + if resp2.Header.Get("Age") != "1" { + t.Errorf("unexpected resp2 Age header %v", resp2.Header.Get("Age")) + } + + if resp3.Header.Get("Cache-Control") != "must-revalidate" { + t.Errorf("unexpected resp3 Cache-Control header %v", resp3.Header.Get("Cache-Control")) + } + if resp3.Header.Get("Cache-Status") != "Souin; hit; ttl=-2; key=GET-http-localhost:9080-/cache-default; fwd=stale; fwd-status=500" { + t.Errorf("unexpected resp3 Cache-Status header %v", resp3.Header.Get("Cache-Status")) + } + if resp3.Header.Get("Age") != "7" { + t.Errorf("unexpected resp3 Age header %v", resp3.Header.Get("Age")) + } + + resp4, _ := tester.AssertGetResponse(`http://localhost:9080/cache-default`, http.StatusOK, "Hello must-revalidate!") + if resp4.Header.Get("Cache-Control") != "must-revalidate" { + t.Errorf("unexpected resp4 Cache-Control header %v", resp4.Header.Get("Cache-Control")) + } + if resp4.Header.Get("Cache-Status") != "Souin; fwd=uri-miss; stored; key=GET-http-localhost:9080-/cache-default" { + t.Errorf("unexpected resp4 Cache-Status header %v", resp4.Header.Get("Cache-Status")) + } + if resp4.Header.Get("Age") != "" { + t.Errorf("unexpected resp4 Age header %v", resp4.Header.Get("Age")) + } + + time.Sleep(6 * time.Second) + staleReq, _ = http.NewRequest(http.MethodGet, "http://localhost:9080/cache-default", nil) + staleReq.Header = http.Header{"Cache-Control": []string{"max-stale=3"}} + resp5, _ := tester.AssertResponse(staleReq, http.StatusGatewayTimeout, "") + + if resp5.Header.Get("Cache-Status") != "Souin; fwd=request; fwd-status=500; key=GET-http-localhost:9080-/cache-default; detail=REQUEST-REVALIDATION" { + t.Errorf("unexpected resp5 Cache-Status header %v", resp4.Header.Get("Cache-Status")) + } + if resp5.Header.Get("Age") != "" { + t.Errorf("unexpected resp5 Age header %v", resp4.Header.Get("Age")) + } +} From a9778c21cd354a5b25db41760a6187e8416d1b47 Mon Sep 17 00:00:00 2001 From: darkweak Date: Wed, 19 Jul 2023 15:16:58 +0200 Subject: [PATCH 2/2] fix(ci): lint --- httpcache_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/httpcache_test.go b/httpcache_test.go index 9093be7..1eb9a4a 100644 --- a/httpcache_test.go +++ b/httpcache_test.go @@ -343,7 +343,7 @@ func (t *testErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "must-revalidate") w.WriteHeader(http.StatusOK) - w.Write([]byte("Hello must-revalidate!")) + _, _ = w.Write([]byte("Hello must-revalidate!")) } func TestMustRevalidate(t *testing.T) { @@ -366,7 +366,9 @@ func TestMustRevalidate(t *testing.T) { }`, "caddyfile") errorHandler := testErrorHandler{} - go http.ListenAndServe(":9081", &errorHandler) + go func(teh *testErrorHandler) { + _ = http.ListenAndServe(":9081", teh) + }(&errorHandler) time.Sleep(time.Second) resp1, _ := tester.AssertGetResponse(`http://localhost:9080/cache-default`, http.StatusOK, "Hello must-revalidate!") resp2, _ := tester.AssertGetResponse(`http://localhost:9080/cache-default`, http.StatusOK, "Hello must-revalidate!")