From 278c9f1327233a6033d7db95fa2cafa5919a8e86 Mon Sep 17 00:00:00 2001 From: darkweak Date: Mon, 28 Oct 2024 13:40:02 +0100 Subject: [PATCH] fix(multiple): october 2024 wave (#558) * fix(multiple): october 2024 wave * feat(middleware): serve stale content if not strict * fix(ci): mod * feat(deps): bump storages version * fix(chore): stale-if-error * feat(global): implement simplefs storage & add missing cdn props support * fix(deny-request): text/event-stream drop closes #562 * feat(caddy): allow to configure TLSConfig in Caddyfile * fix(api): delete by regexp * bump(deps): use storages core v0.0.11 * fix(traefik): resolves #558, full rewrite to match the actual cache behavior. Yaegi sucks! * fix(chore): bypass on some cases --- configurationtypes/types.go | 9 +- go.mod | 2 +- go.sum | 4 +- pkg/api/souin.go | 5 +- pkg/middleware/middleware.go | 56 +- pkg/rfc/age.go | 16 +- pkg/rfc/age_test.go | 10 +- pkg/surrogate/providers/common.go | 8 +- plugins/beego/go.mod | 7 +- plugins/beego/go.sum | 6 +- plugins/caddy/configuration.go | 66 ++- plugins/caddy/dispatch.go | 36 +- plugins/caddy/go.mod | 2 +- plugins/caddy/go.sum | 4 +- plugins/caddy/httpcache.go | 10 +- plugins/caddy/httpcache_test.go | 158 ++++++ plugins/chi/go.mod | 9 +- plugins/chi/go.sum | 6 +- plugins/dotweb/go.mod | 9 +- plugins/dotweb/go.sum | 6 +- plugins/echo/go.mod | 9 +- plugins/echo/go.sum | 6 +- plugins/echo/souin.go | 3 - plugins/fiber/go.mod | 9 +- plugins/fiber/go.sum | 6 +- plugins/gin/go.mod | 9 +- plugins/gin/go.sum | 6 +- .../examples/internal/handler/routes.go | 2 +- plugins/go-zero/go.mod | 9 +- plugins/go-zero/go.sum | 6 +- plugins/goa/go.mod | 9 +- plugins/goa/go.sum | 6 +- plugins/goyave/go.mod | 9 +- plugins/goyave/go.sum | 6 +- plugins/hertz/go.mod | 9 +- plugins/hertz/go.sum | 6 +- plugins/kratos/configuration.go | 21 + plugins/kratos/go.mod | 9 +- plugins/kratos/go.sum | 6 +- plugins/roadrunner/go.mod | 7 +- plugins/roadrunner/go.sum | 6 +- plugins/skipper/go.mod | 9 +- plugins/skipper/go.sum | 6 +- .../souin/agnostic/configuration_parser.go | 17 + plugins/souin/go.mod | 4 +- plugins/souin/go.sum | 4 +- plugins/souin/storages/go.mod | 2 +- plugins/souin/storages/go.sum | 4 +- plugins/traefik/go.mod | 8 +- plugins/traefik/go.sum | 4 +- .../override/configurationtypes/types.go | 17 +- plugins/traefik/override/context/cache.go | 4 + plugins/traefik/override/context/graphql.go | 26 + plugins/traefik/override/context/key.go | 128 +++-- plugins/traefik/override/context/method.go | 4 + plugins/traefik/override/context/mode.go | 6 +- plugins/traefik/override/context/now.go | 4 + plugins/traefik/override/context/timeout.go | 6 +- plugins/traefik/override/context/types.go | 5 +- .../traefik/override/middleware/middleware.go | 521 +++++++++++++----- plugins/traefik/override/middleware/writer.go | 41 +- plugins/traefik/override/rfc/revalidation.go | 141 ++++- .../override/storage/abstractProvider.go | 2 +- .../traefik/override/storage/cacheProvider.go | 48 +- .../traefik/override/storage/types/types.go | 28 +- .../souin/configurationtypes/types.go | 17 +- .../darkweak/souin/context/cache.go | 4 + .../darkweak/souin/context/graphql.go | 26 + .../github.com/darkweak/souin/context/key.go | 128 +++-- .../darkweak/souin/context/method.go | 4 + .../github.com/darkweak/souin/context/mode.go | 6 +- .../github.com/darkweak/souin/context/now.go | 4 + .../darkweak/souin/context/timeout.go | 6 +- .../darkweak/souin/context/types.go | 5 +- .../souin/pkg/middleware/middleware.go | 521 +++++++++++++----- .../darkweak/souin/pkg/middleware/writer.go | 41 +- .../github.com/darkweak/souin/pkg/rfc/age.go | 16 +- .../darkweak/souin/pkg/rfc/revalidation.go | 141 ++++- .../souin/pkg/storage/abstractProvider.go | 2 +- .../souin/pkg/storage/cacheProvider.go | 48 +- .../darkweak/souin/pkg/storage/types/types.go | 28 +- plugins/traefik/vendor/modules.txt | 2 +- plugins/tyk/go.mod | 4 +- plugins/tyk/go.sum | 4 +- plugins/tyk/main.go | 2 +- plugins/webgo/go.mod | 9 +- plugins/webgo/go.sum | 6 +- tests/mock.go | 44 +- 88 files changed, 2006 insertions(+), 689 deletions(-) diff --git a/configurationtypes/types.go b/configurationtypes/types.go index 016aae3a5..c6a60b8ed 100644 --- a/configurationtypes/types.go +++ b/configurationtypes/types.go @@ -246,6 +246,7 @@ type DefaultCache struct { Redis CacheProvider `json:"redis" yaml:"redis"` Port Port `json:"port" yaml:"port"` Regex Regex `json:"regex" yaml:"regex"` + SimpleFS CacheProvider `json:"simplefs" yaml:"simplefs"` Stale Duration `json:"stale" yaml:"stale"` Storers []string `json:"storers" yaml:"storers"` Timeout Timeout `json:"timeout" yaml:"timeout"` @@ -300,7 +301,7 @@ func (d *DefaultCache) GetMode() string { return d.Mode } -// GetNats returns nuts configuration +// GetNats returns nats configuration func (d *DefaultCache) GetNats() CacheProvider { return d.Nats } @@ -340,6 +341,11 @@ func (d *DefaultCache) GetTTL() time.Duration { return d.TTL.Duration } +// GetSimpleFS returns simplefs configuration +func (d *DefaultCache) GetSimpleFS() CacheProvider { + return d.SimpleFS +} + // GetStale returns the stale duration func (d *DefaultCache) GetStale() time.Duration { return d.Stale.Duration @@ -382,6 +388,7 @@ type DefaultCacheInterface interface { GetHeaders() []string GetKey() Key GetRegex() Regex + GetSimpleFS() CacheProvider GetStale() time.Duration GetStorers() []string GetTimeout() Timeout diff --git a/go.mod b/go.mod index c4968c0ec..be5411f27 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.1 require ( github.com/caddyserver/caddy/v2 v2.8.4 github.com/cespare/xxhash/v2 v2.2.0 - github.com/darkweak/storages/core v0.0.8 + github.com/darkweak/storages/core v0.0.11 github.com/google/uuid v1.6.0 github.com/pierrec/lz4/v4 v4.1.21 github.com/pquerna/cachecontrol v0.2.0 diff --git a/go.sum b/go.sum index b93fe1e84..6674733f3 100644 --- a/go.sum +++ b/go.sum @@ -98,8 +98,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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/storages/core v0.0.8 h1:9e7rOxHiJwnvADDVCZ7LFRnUnOHGT+UMpNOFlR8BOiw= -github.com/darkweak/storages/core v0.0.8/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= +github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew= +github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= 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= diff --git a/pkg/api/souin.go b/pkg/api/souin.go index a14671b2a..0c94aa479 100644 --- a/pkg/api/souin.go +++ b/pkg/api/souin.go @@ -158,6 +158,7 @@ var storageToInfiniteTTLMap = map[string]time.Duration{ "OLRIC": types.OneYearDuration, "OTTER": types.OneYearDuration, "REDIS": -1, + "SIMPLEFS": 0, types.DefaultStorageName: types.OneYearDuration, } @@ -305,7 +306,9 @@ func (s *SouinAPI) HandleRequest(w http.ResponseWriter, r *http.Request) { s.purgeMapping() } else { submatch := keysRg.FindAllStringSubmatch(r.RequestURI, -1)[0][1] - s.BulkDelete(submatch, true) + for _, current := range s.storers { + current.DeleteMany(submatch) + } } } else { ck, _ := s.surrogateStorage.Purge(r.Header) diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go index 8e02dbe76..4b4295d74 100644 --- a/pkg/middleware/middleware.go +++ b/pkg/middleware/middleware.go @@ -67,7 +67,7 @@ func NewHTTPCacheHandler(c configurationtypes.AbstractConfigurationInterface) *S storers := []types.Storer{} if len(storedStorers) != 0 { dc := c.GetDefaultCache() - for _, s := range []string{dc.GetBadger().Uuid, dc.GetEtcd().Uuid, dc.GetNats().Uuid, dc.GetNuts().Uuid, dc.GetOlric().Uuid, dc.GetOtter().Uuid, dc.GetRedis().Uuid} { + for _, s := range []string{dc.GetBadger().Uuid, dc.GetEtcd().Uuid, dc.GetNats().Uuid, dc.GetNuts().Uuid, dc.GetOlric().Uuid, dc.GetOtter().Uuid, dc.GetRedis().Uuid, dc.GetSimpleFS().Uuid} { if s != "" { if st := core.GetRegisteredStorer(s); st != nil { storers = append(storers, st.(types.Storer)) @@ -244,7 +244,7 @@ func (s *SouinBaseHandler) Store( ma = time.Duration(responseCc.SMaxAge) * time.Second } else if responseCc.MaxAge >= 0 { ma = time.Duration(responseCc.MaxAge) * time.Second - } else if customWriter.Header().Get("Expires") != "" { + } else if !modeContext.Bypass_response && customWriter.Header().Get("Expires") != "" { exp, err := time.Parse(time.RFC1123, customWriter.Header().Get("Expires")) if err != nil { return nil @@ -569,7 +569,7 @@ func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, n req := s.context.SetBaseContext(rq) cacheName := req.Context().Value(context.CacheName).(string) - if rq.Header.Get("Upgrade") == "websocket" || (s.ExcludeRegex != nil && s.ExcludeRegex.MatchString(rq.RequestURI)) { + if rq.Header.Get("Upgrade") == "websocket" || rq.Header.Get("Accept") == "text/event-stream" || (s.ExcludeRegex != nil && s.ExcludeRegex.MatchString(rq.RequestURI)) { rw.Header().Set("Cache-Status", cacheName+"; fwd=bypass; detail=EXCLUDED-REQUEST-URI") return next(rw, req) } @@ -673,13 +673,13 @@ func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, n return nil } - if validator.NeedRevalidation { + if !modeContext.Bypass_request && validator.NeedRevalidation { err := s.Revalidate(validator, next, customWriter, req, requestCc, cachedKey, uri) _, _ = customWriter.Send() return err } - if resCc, _ := cacheobject.ParseResponseCacheControl(rfc.HeaderAllCommaSepValuesString(response.Header, headerName)); resCc.NoCachePresent { + if resCc, _ := cacheobject.ParseResponseCacheControl(rfc.HeaderAllCommaSepValuesString(response.Header, headerName)); !modeContext.Bypass_response && resCc.NoCachePresent { prometheus.Increment(prometheus.NoCachedResponseCounter) err := s.Revalidate(validator, next, customWriter, req, requestCc, cachedKey, uri) _, _ = customWriter.Send() @@ -726,7 +726,7 @@ func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, n return err } - if responseCc.MustRevalidate || responseCc.NoCachePresent || validator.NeedRevalidation { + if modeContext.Bypass_response || responseCc.MustRevalidate || responseCc.NoCachePresent || validator.NeedRevalidation { req.Header["If-None-Match"] = append(req.Header["If-None-Match"], validator.ResponseETag) err := s.Revalidate(validator, next, customWriter, req, requestCc, cachedKey, uri) statusCode := customWriter.GetStatusCode() @@ -737,6 +737,7 @@ func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, n response.Header.Set("Cache-Status", response.Header.Get("Cache-Status")+code) maps.Copy(customWriter.Header(), response.Header) customWriter.WriteHeader(response.StatusCode) + customWriter.Buf.Reset() _, _ = io.Copy(customWriter.Buf, response.Body) _, err := customWriter.Send() @@ -774,7 +775,7 @@ func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, n return err } - if rfc.ValidateMaxAgeCachedStaleResponse(requestCc, response, int(addTime.Seconds())) != nil { + if !modeContext.Strict || rfc.ValidateMaxAgeCachedStaleResponse(requestCc, responseCc, response, int(addTime.Seconds())) != nil { customWriter.WriteHeader(response.StatusCode) rfc.HitStaleCache(&response.Header) maps.Copy(customWriter.Header(), response.Header) @@ -784,6 +785,47 @@ func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, n return err } } + } else if stale != nil { + response := stale + + if !modeContext.Strict { + rfc.SetCacheStatusHeader(response, storerName) + customWriter.WriteHeader(response.StatusCode) + rfc.HitStaleCache(&response.Header) + maps.Copy(customWriter.Header(), response.Header) + _, _ = io.Copy(customWriter.Buf, response.Body) + _, err := customWriter.Send() + + return err + } + + addTime, _ := time.ParseDuration(response.Header.Get(rfc.StoredTTLHeader)) + responseCc, _ := cacheobject.ParseResponseCacheControl(rfc.HeaderAllCommaSepValuesString(response.Header, "Cache-Control")) + + if !modeContext.Strict || rfc.ValidateMaxAgeCachedStaleResponse(requestCc, responseCc, response, int(addTime.Seconds())) != nil { + _, _ = time.ParseDuration(response.Header.Get(rfc.StoredTTLHeader)) + rfc.SetCacheStatusHeader(response, storerName) + + responseCc, _ := cacheobject.ParseResponseCacheControl(rfc.HeaderAllCommaSepValuesString(response.Header, "Cache-Control")) + + if responseCc.StaleIfError > -1 || requestCc.StaleIfError > 0 { + err := s.Revalidate(validator, next, customWriter, req, requestCc, cachedKey, uri) + statusCode := customWriter.GetStatusCode() + if err != nil { + code := fmt.Sprintf("; fwd-status=%d", statusCode) + rfc.HitStaleCache(&response.Header) + response.Header.Set("Cache-Status", response.Header.Get("Cache-Status")+code) + maps.Copy(customWriter.Header(), response.Header) + customWriter.WriteHeader(response.StatusCode) + customWriter.Buf.Reset() + _, _ = io.Copy(customWriter.Buf, response.Body) + _, err := customWriter.Send() + + return err + } + } + + } } } diff --git a/pkg/rfc/age.go b/pkg/rfc/age.go index 03b8593a9..03f12acbc 100644 --- a/pkg/rfc/age.go +++ b/pkg/rfc/age.go @@ -31,11 +31,25 @@ func ValidateMaxAgeCachedResponse(co *cacheobject.RequestCacheDirectives, res *h return validateMaxAgeCachedResponse(res, int(ma), 0) } -func ValidateMaxAgeCachedStaleResponse(co *cacheobject.RequestCacheDirectives, res *http.Response, addTime int) *http.Response { +func ValidateMaxAgeCachedStaleResponse(co *cacheobject.RequestCacheDirectives, resCo *cacheobject.ResponseCacheDirectives, res *http.Response, addTime int) *http.Response { if co.MaxStaleSet { return res } + if resCo != nil && (resCo.StaleIfError > -1 || co.StaleIfError > 0) { + if resCo.StaleIfError > -1 { + if response := validateMaxAgeCachedResponse(res, int(resCo.StaleIfError), addTime); response != nil { + return response + } + } + + if co.StaleIfError > 0 { + if response := validateMaxAgeCachedResponse(res, int(co.StaleIfError), addTime); response != nil { + return response + } + } + } + if co.MaxStale < 0 { return nil } diff --git a/pkg/rfc/age_test.go b/pkg/rfc/age_test.go index c62e3f1e0..b1fc5fe3d 100644 --- a/pkg/rfc/age_test.go +++ b/pkg/rfc/age_test.go @@ -63,20 +63,20 @@ func Test_ValidateMaxStaleCachedResponse(t *testing.T) { }, } - if ValidateMaxAgeCachedStaleResponse(&coWithoutMaxStale, &expiredMaxAge, 3) != nil { + if ValidateMaxAgeCachedStaleResponse(&coWithoutMaxStale, nil, &expiredMaxAge, 3) != nil { t.Errorf("The max-stale validation should return nil instead of the response with the given parameters:\nRequestCacheDirectives: %+v\nResponse: %+v\n", coWithoutMaxStale, expiredMaxAge) } - if ValidateMaxAgeCachedStaleResponse(&coWithoutMaxStale, &validMaxAge, 14) != nil { + if ValidateMaxAgeCachedStaleResponse(&coWithoutMaxStale, nil, &validMaxAge, 14) != nil { t.Errorf("The max-stale validation should return the response instead of nil with the given parameters:\nRequestCacheDirectives: %+v\nResponse: %+v\n", coWithoutMaxStale, validMaxAge) } - if ValidateMaxAgeCachedStaleResponse(&coWithMaxStale, &expiredMaxAge, 0) != nil { + if ValidateMaxAgeCachedStaleResponse(&coWithMaxStale, nil, &expiredMaxAge, 0) != nil { t.Errorf("The max-stale validation should return nil instead of the response with the given parameters:\nRequestCacheDirectives: %+v\nResponse: %+v\n", coWithMaxStale, expiredMaxAge) } - if ValidateMaxAgeCachedStaleResponse(&coWithMaxStaleSet, &expiredMaxAge, 0) == nil { + if ValidateMaxAgeCachedStaleResponse(&coWithMaxStaleSet, nil, &expiredMaxAge, 0) == nil { t.Errorf("The max-stale validation should return the response instead of nil with the given parameters:\nRequestCacheDirectives: %+v\nResponse: %+v\n", coWithMaxStaleSet, expiredMaxAge) } - if ValidateMaxAgeCachedStaleResponse(&coWithMaxStale, &validMaxAge, 5) == nil { + if ValidateMaxAgeCachedStaleResponse(&coWithMaxStale, nil, &validMaxAge, 5) == nil { t.Errorf("The max-stale validation should return the response instead of nil with the given parameters:\nRequestCacheDirectives: %+v\nResponse: %+v\n", coWithMaxStale, expiredMaxAge) } } diff --git a/pkg/surrogate/providers/common.go b/pkg/surrogate/providers/common.go index 01f39f59a..1830d06e2 100644 --- a/pkg/surrogate/providers/common.go +++ b/pkg/surrogate/providers/common.go @@ -37,11 +37,17 @@ var storageToInfiniteTTLMap = map[string]time.Duration{ "OLRIC": types.OneYearDuration, "OTTER": types.OneYearDuration, "REDIS": -1, + "SIMPLEFS": 0, types.DefaultStorageName: types.OneYearDuration, } func (s *baseStorage) ParseHeaders(value string) []string { - return strings.Split(value, s.parent.getHeaderSeparator()) + res := strings.Split(value, s.parent.getHeaderSeparator()) + for i, v := range res { + res[i] = strings.TrimSpace(v) + } + + return res } func getCandidateHeader(header http.Header, getCandidates func() []string) (string, string) { diff --git a/plugins/beego/go.mod b/plugins/beego/go.mod index 4606b4a44..474687251 100644 --- a/plugins/beego/go.mod +++ b/plugins/beego/go.mod @@ -7,8 +7,8 @@ toolchain go1.22.4 require ( github.com/beego/beego/v2 v2.1.1 github.com/darkweak/souin v1.7.2 - github.com/darkweak/souin/plugins/souin v0.0.0-00010101000000-000000000000 - github.com/darkweak/souin/plugins/souin/storages v0.0.0-00010101000000-000000000000 + github.com/darkweak/souin/plugins/souin v1.7.2 + github.com/darkweak/souin/plugins/souin/storages v1.7.2 ) require ( @@ -41,7 +41,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/darkweak/go-esi v0.0.5 // indirect github.com/darkweak/storages/badger v0.0.8 // indirect - github.com/darkweak/storages/core v0.0.8 // indirect + github.com/darkweak/storages/core v0.0.11 // indirect github.com/darkweak/storages/etcd v0.0.8 // indirect github.com/darkweak/storages/nats v0.0.8 // indirect github.com/darkweak/storages/nuts v0.0.8 // indirect @@ -186,5 +186,4 @@ require ( replace ( github.com/darkweak/souin v1.7.2 => ../.. github.com/darkweak/souin/plugins/souin => ../souin - github.com/darkweak/souin/plugins/souin/storages => ../souin/storages ) diff --git a/plugins/beego/go.sum b/plugins/beego/go.sum index 1c21746fc..ebaedf0eb 100644 --- a/plugins/beego/go.sum +++ b/plugins/beego/go.sum @@ -141,10 +141,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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/plugins/souin/storages v1.7.2 h1:vA1oFap6sbWO+Ebbq6NGtjmCFuCRJOZeG+XXPhhSIWA= +github.com/darkweak/souin/plugins/souin/storages v1.7.2/go.mod h1:VfkwGN+ubAuluSwbjGHqImbUjxdEA0N9xGJUTCcFBV0= github.com/darkweak/storages/badger v0.0.8 h1:rKVXrasVA74xgiqGRgW0kH11NUIlWwn9HiFyHUok85k= github.com/darkweak/storages/badger v0.0.8/go.mod h1:ZmrNmKkFzyu/B3+1nsvVeTvyg2I2mOV5yTpT46mZ06o= -github.com/darkweak/storages/core v0.0.8 h1:9e7rOxHiJwnvADDVCZ7LFRnUnOHGT+UMpNOFlR8BOiw= -github.com/darkweak/storages/core v0.0.8/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= +github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew= +github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= github.com/darkweak/storages/etcd v0.0.8 h1:Guzv6zgxkQJLjak36KsbtQqkmwMRJoZZI0B7ztZKIik= github.com/darkweak/storages/etcd v0.0.8/go.mod h1:Yw9xJramKAzIRoC7tizVMYPSwUBHqxY5BPTh8OgyISY= github.com/darkweak/storages/nats v0.0.8 h1:HRS3i2zzzIq1Qb3yoOUWD6MoRQgGV1NbECF1ex4wZjg= diff --git a/plugins/caddy/configuration.go b/plugins/caddy/configuration.go index a8b803ec2..df18a107d 100644 --- a/plugins/caddy/configuration.go +++ b/plugins/caddy/configuration.go @@ -52,6 +52,8 @@ type DefaultCache struct { Timeout configurationtypes.Timeout `json:"timeout"` // Time to live. TTL configurationtypes.Duration `json:"ttl"` + // SimpleFS provider configuration. + SimpleFS configurationtypes.CacheProvider `json:"simplefs"` // Stale time to live. Stale configurationtypes.Duration `json:"stale"` // Disable the coalescing system. @@ -103,7 +105,7 @@ func (d *DefaultCache) GetMode() string { return d.Mode } -// GetNats returns nuts configuration +// GetNats returns nats configuration func (d *DefaultCache) GetNats() configurationtypes.CacheProvider { return d.Nats } @@ -133,6 +135,11 @@ func (d *DefaultCache) GetRegex() configurationtypes.Regex { return d.Regex } +// GetSimpleFS returns simplefs configuration +func (d *DefaultCache) GetSimpleFS() configurationtypes.CacheProvider { + return d.SimpleFS +} + // GetStorers returns the chianed storers func (d *DefaultCache) GetStorers() []string { return d.Storers @@ -303,6 +310,45 @@ func parseRedisConfiguration(c map[string]interface{}) map[string]interface{} { } case "ConnWriteTimeout", "MaxFlushDelay", "MinRetryBackoff", "MaxRetryBackoff", "DialTimeout", "ReadTimeout", "WriteTimeout", "PoolTimeout", "ConnMaxIdleTime", "ConnMaxLifetime": c[k], _ = time.ParseDuration(v.(string)) + case "MaxVersion", "MinVersion": + strV, _ := v.(string) + if strings.HasPrefix(strV, "TLS") { + strV = strings.Trim(strings.TrimPrefix(strV, "TLS"), " ") + } + + switch strV { + case "0x0300", "SSLv3": + c[k] = 0x0300 + case "0x0301", "1.0": + c[k] = 0x0301 + case "0x0302", "1.1": + c[k] = 0x0302 + case "0x0303", "1.2": + c[k] = 0x0303 + case "0x0304", "1.3": + c[k] = 0x0304 + } + case "TLSConfig": + c[k] = parseRedisConfiguration(v.(map[string]interface{})) + } + } + + return c +} + +func parseSimpleFSConfiguration(c map[string]interface{}) map[string]interface{} { + for k, v := range c { + switch k { + case "path": + c[k] = v + case "size": + if v == false { + c[k] = 0 + } else if v == true { + c[k] = 1 + } else { + c[k], _ = strconv.Atoi(v.(string)) + } } } @@ -537,7 +583,7 @@ func parseConfiguration(cfg *Configuration, h *caddyfile.Dispenser, isGlobal boo return h.Errf("unsupported nats directive: %s", directive) } } - cfg.DefaultCache.Nuts = provider + cfg.DefaultCache.Nats = provider case "nuts": provider := configurationtypes.CacheProvider{Found: true} for nesting := h.Nesting(); h.NextBlock(nesting); { @@ -617,6 +663,22 @@ func parseConfiguration(cfg *Configuration, h *caddyfile.Dispenser, isGlobal boo return h.Errf("unsupported regex directive: %s", directive) } } + case "simplefs": + provider := configurationtypes.CacheProvider{Found: true} + for nesting := h.Nesting(); h.NextBlock(nesting); { + directive := h.Val() + switch directive { + case "path": + urlArgs := h.RemainingArgs() + provider.Path = urlArgs[0] + case "configuration": + provider.Configuration = parseCaddyfileRecursively(h) + provider.Configuration = parseSimpleFSConfiguration(provider.Configuration.(map[string]interface{})) + default: + return h.Errf("unsupported simplefs directive: %s", directive) + } + } + cfg.DefaultCache.SimpleFS = provider case "stale": args := h.RemainingArgs() stale, err := time.ParseDuration(args[0]) diff --git a/plugins/caddy/dispatch.go b/plugins/caddy/dispatch.go index 63574e806..76e5a75ba 100644 --- a/plugins/caddy/dispatch.go +++ b/plugins/caddy/dispatch.go @@ -2,6 +2,7 @@ package httpcache import ( "fmt" + "os" "strings" "github.com/caddyserver/caddy/v2" @@ -73,7 +74,7 @@ func (s *SouinCaddyMiddleware) parseStorages(ctx caddy.Context) { if e != nil { s.logger.Errorf("Error during Nats init, did you include the Nats storage (--with github.com/darkweak/storages/nats/caddy)? %v", e) } else { - s.Configuration.DefaultCache.Nuts.Uuid = fmt.Sprintf("NATS-%s-%s", s.Configuration.DefaultCache.Nats.URL, s.Configuration.DefaultCache.GetStale()) + s.Configuration.DefaultCache.Nats.Uuid = fmt.Sprintf("NATS-%s-%s", s.Configuration.DefaultCache.Nats.URL, s.Configuration.DefaultCache.GetStale()) } } if s.Configuration.DefaultCache.Nuts.Found { @@ -101,7 +102,7 @@ func (s *SouinCaddyMiddleware) parseStorages(ctx caddy.Context) { if e != nil { s.logger.Errorf("Error during Olric init, did you include the Olric storage (--with github.com/darkweak/storages/olric/caddy)? %v", e) } else { - s.Configuration.DefaultCache.Nuts.Uuid = fmt.Sprintf("OLRIC-%s-%s", s.Configuration.DefaultCache.Olric.URL, s.Configuration.DefaultCache.GetStale()) + s.Configuration.DefaultCache.Olric.Uuid = fmt.Sprintf("OLRIC-%s-%s", s.Configuration.DefaultCache.Olric.URL, s.Configuration.DefaultCache.GetStale()) } } if s.Configuration.DefaultCache.Otter.Found { @@ -172,4 +173,35 @@ func (s *SouinCaddyMiddleware) parseStorages(ctx caddy.Context) { ) } } + if s.Configuration.DefaultCache.SimpleFS.Found { + e := dispatchStorage(ctx, "simplefs", s.Configuration.DefaultCache.SimpleFS, s.Configuration.DefaultCache.GetStale()) + if e != nil { + s.logger.Errorf("Error during SimpleFS init, did you include the SimpleFS storage (--with github.com/darkweak/storages/simplefs/caddy)? %v", e) + } else { + simplefs := s.Configuration.DefaultCache.SimpleFS + path := simplefs.Path + size := "0" + if c := simplefs.Configuration; c != nil { + p, ok := c.(map[string]interface{}) + if ok { + if d, ok := p["path"]; path == "" && ok { + path = fmt.Sprint(d) + } + if d, ok := p["size"]; ok { + size = fmt.Sprint(d) + } + } + } + + if path == "" { + path, _ = os.Getwd() + } + + s.Configuration.DefaultCache.SimpleFS.Uuid = fmt.Sprintf( + "SIMPLEFS-%s-%s", + path, + size, + ) + } + } } diff --git a/plugins/caddy/go.mod b/plugins/caddy/go.mod index eb31950f1..fa0c38fc7 100644 --- a/plugins/caddy/go.mod +++ b/plugins/caddy/go.mod @@ -5,7 +5,7 @@ go 1.22.1 require ( github.com/caddyserver/caddy/v2 v2.8.4 github.com/darkweak/souin v1.7.2 - github.com/darkweak/storages/core v0.0.8 + github.com/darkweak/storages/core v0.0.11 ) require ( diff --git a/plugins/caddy/go.sum b/plugins/caddy/go.sum index 459564b7f..177dac7ec 100644 --- a/plugins/caddy/go.sum +++ b/plugins/caddy/go.sum @@ -109,8 +109,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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/storages/core v0.0.8 h1:9e7rOxHiJwnvADDVCZ7LFRnUnOHGT+UMpNOFlR8BOiw= -github.com/darkweak/storages/core v0.0.8/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= +github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew= +github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= 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= diff --git a/plugins/caddy/httpcache.go b/plugins/caddy/httpcache.go index d72efd6ed..7d4c4f464 100644 --- a/plugins/caddy/httpcache.go +++ b/plugins/caddy/httpcache.go @@ -48,6 +48,8 @@ type SouinCaddyMiddleware struct { Key configurationtypes.Key `json:"key,omitempty"` // Override the cache key generation matching the pattern. CacheKeys configurationtypes.CacheKeys `json:"cache_keys,omitempty"` + // Configure the Nats cache storage. + Nats configurationtypes.CacheProvider `json:"nats,omitempty"` // Configure the Nuts cache storage. Nuts configurationtypes.CacheProvider `json:"nuts,omitempty"` // Configure the Otter cache storage. @@ -62,6 +64,8 @@ type SouinCaddyMiddleware struct { Timeout configurationtypes.Timeout `json:"timeout,omitempty"` // Time to live for a key, using time.duration. TTL configurationtypes.Duration `json:"ttl,omitempty"` + // Configure the SimpleFS cache storage. + SimpleFS configurationtypes.CacheProvider `json:"simplefs,omitempty"` // Time to live for a stale key, using time.duration. Stale configurationtypes.Duration `json:"stale,omitempty"` // Storage providers chaining and order. @@ -91,7 +95,9 @@ func (s *SouinCaddyMiddleware) configurationPropertyMapper() error { if s.Configuration.GetDefaultCache() == nil { defaultCache := DefaultCache{ Badger: s.Badger, + Nats: s.Nats, Nuts: s.Nuts, + SimpleFS: s.SimpleFS, Otter: s.Otter, Key: s.Key, DefaultCacheControl: s.DefaultCacheControl, @@ -203,14 +209,16 @@ func (s *SouinCaddyMiddleware) FromApp(app *SouinApp) error { if dc.CacheName == "" { s.Configuration.DefaultCache.CacheName = appDc.CacheName } - if isProviderEmpty(dc.Badger) && isProviderEmpty(dc.Etcd) && isProviderEmpty(dc.Nats) && isProviderEmpty(dc.Nuts) && isProviderEmpty(dc.Olric) && isProviderEmpty(dc.Otter) && isProviderEmpty(dc.Redis) { + if isProviderEmpty(dc.Badger) && isProviderEmpty(dc.Etcd) && isProviderEmpty(dc.Nats) && isProviderEmpty(dc.Nuts) && isProviderEmpty(dc.Olric) && isProviderEmpty(dc.Otter) && isProviderEmpty(dc.Redis) && isProviderEmpty(dc.SimpleFS) { s.Configuration.DefaultCache.Distributed = appDc.Distributed s.Configuration.DefaultCache.Olric = appDc.Olric s.Configuration.DefaultCache.Redis = appDc.Redis s.Configuration.DefaultCache.Etcd = appDc.Etcd s.Configuration.DefaultCache.Badger = appDc.Badger + s.Configuration.DefaultCache.Nats = appDc.Nats s.Configuration.DefaultCache.Nuts = appDc.Nuts s.Configuration.DefaultCache.Otter = appDc.Otter + s.Configuration.DefaultCache.SimpleFS = appDc.SimpleFS } if dc.Regex.Exclude == "" { s.Configuration.DefaultCache.Regex.Exclude = appDc.Regex.Exclude diff --git a/plugins/caddy/httpcache_test.go b/plugins/caddy/httpcache_test.go index c5eed1f3e..b25a5c6ed 100644 --- a/plugins/caddy/httpcache_test.go +++ b/plugins/caddy/httpcache_test.go @@ -602,6 +602,106 @@ func TestMustRevalidate(t *testing.T) { } } +type staleIfErrorHandler struct { + iterator int +} + +func (t *staleIfErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if t.iterator > 0 { + w.WriteHeader(http.StatusInternalServerError) + return + } + + t.iterator++ + w.Header().Set("Cache-Control", "stale-if-error=86400") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("Hello stale-if-error!")) +} + +func TestStaleIfError(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + admin localhost:2999 + http_port 9080 + cache { + ttl 5s + stale 5s + } + } + localhost:9080 { + route /stale-if-error { + cache + reverse_proxy localhost:9085 + } + }`, "caddyfile") + + go func() { + staleIfErrorHandler := staleIfErrorHandler{} + _ = http.ListenAndServe(":9085", &staleIfErrorHandler) + }() + time.Sleep(time.Second) + resp1, _ := tester.AssertGetResponse(`http://localhost:9080/stale-if-error`, http.StatusOK, "Hello stale-if-error!") + resp2, _ := tester.AssertGetResponse(`http://localhost:9080/stale-if-error`, http.StatusOK, "Hello stale-if-error!") + + if resp1.Header.Get("Cache-Control") != "stale-if-error=86400" { + 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-/stale-if-error" { + 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") != "stale-if-error=86400" { + 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-/stale-if-error; detail=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")) + } + + time.Sleep(6 * time.Second) + staleReq, _ := http.NewRequest(http.MethodGet, "http://localhost:9080/stale-if-error", nil) + staleReq.Header = http.Header{"Cache-Control": []string{"stale-if-error=86400"}} + resp3, _ := tester.AssertResponse(staleReq, http.StatusOK, "Hello stale-if-error!") + + if resp3.Header.Get("Cache-Control") != "stale-if-error=86400" { + 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-/stale-if-error; detail=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/stale-if-error`, http.StatusOK, "Hello stale-if-error!") + + if resp4.Header.Get("Cache-Status") != "Souin; hit; ttl=-2; key=GET-http-localhost:9080-/stale-if-error; detail=DEFAULT; fwd=stale; fwd-status=500" && + resp4.Header.Get("Cache-Status") != "Souin; hit; ttl=-3; key=GET-http-localhost:9080-/stale-if-error; detail=DEFAULT; fwd=stale; fwd-status=500" { + t.Errorf("unexpected resp4 Cache-Status header %v", resp4.Header.Get("Cache-Status")) + } + + if resp4.Header.Get("Age") != "7" && resp4.Header.Get("Age") != "8" { + t.Errorf("unexpected resp4 Age header %v", resp4.Header.Get("Age")) + } + + time.Sleep(6 * time.Second) + resp5, _ := tester.AssertGetResponse(`http://localhost:9080/stale-if-error`, http.StatusInternalServerError, "") + + if resp5.Header.Get("Cache-Status") != "Souin; fwd=uri-miss; key=GET-http-localhost:9080-/stale-if-error; detail=UNCACHEABLE-STATUS-CODE" { + t.Errorf("unexpected resp5 Cache-Status header %v", resp5.Header.Get("Cache-Status")) + } + + if resp5.Header.Get("Age") != "" { + t.Errorf("unexpected resp5 Age header %v", resp5.Header.Get("Age")) + } +} + type testETagsHandler struct{} const etagValue = "AAA-BBB" @@ -1086,3 +1186,61 @@ func TestComplexQuery(t *testing.T) { cacheChecker(caddyTester, "fields[]=id&pagination=true", 9) cacheChecker(caddyTester, "fields[]=id&pagination=false", 9) } + +func TestBypassWithExpiresAndRevalidate(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + debug + admin localhost:2999 + http_port 9080 + https_port 9443 + cache { + ttl 5s + stale 5s + mode bypass + } + } + localhost:9080 { + route /bypass-with-expires-and-revalidate { + cache + header Expires 0 + header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate" + respond "Hello, expires and revalidate!" + } + }`, "caddyfile") + + respStored1, _ := tester.AssertGetResponse(`http://localhost:9080/bypass-with-expires-and-revalidate`, 200, "Hello, expires and revalidate!") + if respStored1.Header.Get("Cache-Status") != "Souin; fwd=uri-miss; stored; key=GET-http-localhost:9080-/bypass-with-expires-and-revalidate" { + t.Errorf("unexpected Cache-Status header value %v", respStored1.Header.Get("Cache-Status")) + } + if respStored1.Header.Get("Age") != "" { + t.Errorf("unexpected Age header %v", respStored1.Header.Get("Age")) + } + + respStored2, _ := tester.AssertGetResponse(`http://localhost:9080/bypass-with-expires-and-revalidate`, 200, "Hello, expires and revalidate!") + if respStored2.Header.Get("Cache-Status") != "Souin; hit; ttl=4; key=GET-http-localhost:9080-/bypass-with-expires-and-revalidate; detail=DEFAULT" { + t.Errorf("unexpected Cache-Status header value %v", respStored2.Header.Get("Cache-Status")) + } + if respStored2.Header.Get("Age") == "" { + t.Error("Age header should be present") + } + + time.Sleep(5 * time.Second) + respStored3, _ := tester.AssertGetResponse(`http://localhost:9080/bypass-with-expires-and-revalidate`, 200, "Hello, expires and revalidate!") + if respStored3.Header.Get("Cache-Status") != "Souin; hit; ttl=-1; key=GET-http-localhost:9080-/bypass-with-expires-and-revalidate; detail=DEFAULT; fwd=stale" { + t.Errorf("unexpected Cache-Status header value %v", respStored3.Header.Get("Cache-Status")) + } + if respStored3.Header.Get("Age") == "" { + t.Error("Age header should be present") + } + + time.Sleep(5 * time.Second) + respStored4, _ := tester.AssertGetResponse(`http://localhost:9080/bypass-with-expires-and-revalidate`, 200, "Hello, expires and revalidate!") + if respStored4.Header.Get("Cache-Status") != "Souin; fwd=uri-miss; stored; key=GET-http-localhost:9080-/bypass-with-expires-and-revalidate" { + t.Errorf("unexpected Cache-Status header value %v", respStored4.Header.Get("Cache-Status")) + } + if respStored4.Header.Get("Age") != "" { + t.Errorf("unexpected Age header %v", respStored4.Header.Get("Age")) + } +} diff --git a/plugins/chi/go.mod b/plugins/chi/go.mod index 9dd91c219..813aa5784 100644 --- a/plugins/chi/go.mod +++ b/plugins/chi/go.mod @@ -4,7 +4,7 @@ go 1.22.1 require ( github.com/darkweak/souin v1.7.2 - github.com/darkweak/souin/plugins/souin/storages v0.0.0-00010101000000-000000000000 + github.com/darkweak/souin/plugins/souin/storages v1.7.2 github.com/go-chi/chi/v5 v5.0.12 ) @@ -38,7 +38,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/darkweak/go-esi v0.0.6 // indirect github.com/darkweak/storages/badger v0.0.8 // indirect - github.com/darkweak/storages/core v0.0.8 // indirect + github.com/darkweak/storages/core v0.0.11 // indirect github.com/darkweak/storages/etcd v0.0.8 // indirect github.com/darkweak/storages/nats v0.0.8 // indirect github.com/darkweak/storages/nuts v0.0.8 // indirect @@ -178,7 +178,4 @@ require ( howett.net/plist v1.0.0 // indirect ) -replace ( - github.com/darkweak/souin v1.7.2 => ../.. - github.com/darkweak/souin/plugins/souin/storages => ../souin/storages -) +replace github.com/darkweak/souin v1.7.2 => ../.. diff --git a/plugins/chi/go.sum b/plugins/chi/go.sum index 3e5ea8613..cfb5c3d3f 100644 --- a/plugins/chi/go.sum +++ b/plugins/chi/go.sum @@ -139,10 +139,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/darkweak/go-esi v0.0.6 h1:eVHCJfqrZwOHPfRK7JTlSYG9F8lfpX/d4lz/41RQkd8= github.com/darkweak/go-esi v0.0.6/go.mod h1:IJSayeQZDUh5R5ayyDC3wUEBykti12aUa0eUxZZeodk= +github.com/darkweak/souin/plugins/souin/storages v1.7.2 h1:vA1oFap6sbWO+Ebbq6NGtjmCFuCRJOZeG+XXPhhSIWA= +github.com/darkweak/souin/plugins/souin/storages v1.7.2/go.mod h1:VfkwGN+ubAuluSwbjGHqImbUjxdEA0N9xGJUTCcFBV0= github.com/darkweak/storages/badger v0.0.8 h1:rKVXrasVA74xgiqGRgW0kH11NUIlWwn9HiFyHUok85k= github.com/darkweak/storages/badger v0.0.8/go.mod h1:ZmrNmKkFzyu/B3+1nsvVeTvyg2I2mOV5yTpT46mZ06o= -github.com/darkweak/storages/core v0.0.8 h1:9e7rOxHiJwnvADDVCZ7LFRnUnOHGT+UMpNOFlR8BOiw= -github.com/darkweak/storages/core v0.0.8/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= +github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew= +github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= github.com/darkweak/storages/etcd v0.0.8 h1:Guzv6zgxkQJLjak36KsbtQqkmwMRJoZZI0B7ztZKIik= github.com/darkweak/storages/etcd v0.0.8/go.mod h1:Yw9xJramKAzIRoC7tizVMYPSwUBHqxY5BPTh8OgyISY= github.com/darkweak/storages/nats v0.0.8 h1:HRS3i2zzzIq1Qb3yoOUWD6MoRQgGV1NbECF1ex4wZjg= diff --git a/plugins/dotweb/go.mod b/plugins/dotweb/go.mod index 1a61ab0a1..0201a2e03 100644 --- a/plugins/dotweb/go.mod +++ b/plugins/dotweb/go.mod @@ -4,7 +4,7 @@ go 1.22.1 require ( github.com/darkweak/souin v1.7.2 - github.com/darkweak/souin/plugins/souin/storages v0.0.0-00010101000000-000000000000 + github.com/darkweak/souin/plugins/souin/storages v1.7.2 github.com/devfeel/dotweb v1.7.21 ) @@ -38,7 +38,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/darkweak/go-esi v0.0.6 // indirect github.com/darkweak/storages/badger v0.0.8 // indirect - github.com/darkweak/storages/core v0.0.8 // indirect + github.com/darkweak/storages/core v0.0.11 // indirect github.com/darkweak/storages/etcd v0.0.8 // indirect github.com/darkweak/storages/nats v0.0.8 // indirect github.com/darkweak/storages/nuts v0.0.8 // indirect @@ -179,7 +179,4 @@ require ( howett.net/plist v1.0.0 // indirect ) -replace ( - github.com/darkweak/souin v1.7.2 => ../.. - github.com/darkweak/souin/plugins/souin/storages => ../souin/storages -) +replace github.com/darkweak/souin v1.7.2 => ../.. diff --git a/plugins/dotweb/go.sum b/plugins/dotweb/go.sum index da857c7f0..de06e2c98 100644 --- a/plugins/dotweb/go.sum +++ b/plugins/dotweb/go.sum @@ -139,10 +139,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/darkweak/go-esi v0.0.6 h1:eVHCJfqrZwOHPfRK7JTlSYG9F8lfpX/d4lz/41RQkd8= github.com/darkweak/go-esi v0.0.6/go.mod h1:IJSayeQZDUh5R5ayyDC3wUEBykti12aUa0eUxZZeodk= +github.com/darkweak/souin/plugins/souin/storages v1.7.2 h1:vA1oFap6sbWO+Ebbq6NGtjmCFuCRJOZeG+XXPhhSIWA= +github.com/darkweak/souin/plugins/souin/storages v1.7.2/go.mod h1:VfkwGN+ubAuluSwbjGHqImbUjxdEA0N9xGJUTCcFBV0= github.com/darkweak/storages/badger v0.0.8 h1:rKVXrasVA74xgiqGRgW0kH11NUIlWwn9HiFyHUok85k= github.com/darkweak/storages/badger v0.0.8/go.mod h1:ZmrNmKkFzyu/B3+1nsvVeTvyg2I2mOV5yTpT46mZ06o= -github.com/darkweak/storages/core v0.0.8 h1:9e7rOxHiJwnvADDVCZ7LFRnUnOHGT+UMpNOFlR8BOiw= -github.com/darkweak/storages/core v0.0.8/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= +github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew= +github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= github.com/darkweak/storages/etcd v0.0.8 h1:Guzv6zgxkQJLjak36KsbtQqkmwMRJoZZI0B7ztZKIik= github.com/darkweak/storages/etcd v0.0.8/go.mod h1:Yw9xJramKAzIRoC7tizVMYPSwUBHqxY5BPTh8OgyISY= github.com/darkweak/storages/nats v0.0.8 h1:HRS3i2zzzIq1Qb3yoOUWD6MoRQgGV1NbECF1ex4wZjg= diff --git a/plugins/echo/go.mod b/plugins/echo/go.mod index 196d515b7..bf46b223e 100644 --- a/plugins/echo/go.mod +++ b/plugins/echo/go.mod @@ -4,8 +4,8 @@ go 1.22.1 require ( github.com/darkweak/souin v1.7.2 - github.com/darkweak/souin/plugins/souin/storages v0.0.0-00010101000000-000000000000 - github.com/darkweak/storages/core v0.0.8 + github.com/darkweak/souin/plugins/souin/storages v1.7.2 + github.com/darkweak/storages/core v0.0.11 github.com/labstack/echo/v4 v4.11.1 ) @@ -181,7 +181,4 @@ require ( howett.net/plist v1.0.0 // indirect ) -replace ( - github.com/darkweak/souin v1.7.2 => ../.. - github.com/darkweak/souin/plugins/souin/storages => ../souin/storages -) +replace github.com/darkweak/souin v1.7.2 => ../.. diff --git a/plugins/echo/go.sum b/plugins/echo/go.sum index 3962b23a1..79af93a7e 100644 --- a/plugins/echo/go.sum +++ b/plugins/echo/go.sum @@ -139,10 +139,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/darkweak/go-esi v0.0.6 h1:eVHCJfqrZwOHPfRK7JTlSYG9F8lfpX/d4lz/41RQkd8= github.com/darkweak/go-esi v0.0.6/go.mod h1:IJSayeQZDUh5R5ayyDC3wUEBykti12aUa0eUxZZeodk= +github.com/darkweak/souin/plugins/souin/storages v1.7.2 h1:vA1oFap6sbWO+Ebbq6NGtjmCFuCRJOZeG+XXPhhSIWA= +github.com/darkweak/souin/plugins/souin/storages v1.7.2/go.mod h1:VfkwGN+ubAuluSwbjGHqImbUjxdEA0N9xGJUTCcFBV0= github.com/darkweak/storages/badger v0.0.8 h1:rKVXrasVA74xgiqGRgW0kH11NUIlWwn9HiFyHUok85k= github.com/darkweak/storages/badger v0.0.8/go.mod h1:ZmrNmKkFzyu/B3+1nsvVeTvyg2I2mOV5yTpT46mZ06o= -github.com/darkweak/storages/core v0.0.8 h1:9e7rOxHiJwnvADDVCZ7LFRnUnOHGT+UMpNOFlR8BOiw= -github.com/darkweak/storages/core v0.0.8/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= +github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew= +github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= github.com/darkweak/storages/etcd v0.0.8 h1:Guzv6zgxkQJLjak36KsbtQqkmwMRJoZZI0B7ztZKIik= github.com/darkweak/storages/etcd v0.0.8/go.mod h1:Yw9xJramKAzIRoC7tizVMYPSwUBHqxY5BPTh8OgyISY= github.com/darkweak/storages/nats v0.0.8 h1:HRS3i2zzzIq1Qb3yoOUWD6MoRQgGV1NbECF1ex4wZjg= diff --git a/plugins/echo/souin.go b/plugins/echo/souin.go index f1ec9ad38..77b86e2ab 100644 --- a/plugins/echo/souin.go +++ b/plugins/echo/souin.go @@ -34,9 +34,6 @@ var ( Regex: configurationtypes.Regex{ Exclude: "/excluded", }, - // Nuts: configurationtypes.CacheProvider{ - // Path: "./tmp", - // }, TTL: configurationtypes.Duration{ Duration: 5 * time.Second, }, diff --git a/plugins/fiber/go.mod b/plugins/fiber/go.mod index 0783e081b..ef6ad96f4 100644 --- a/plugins/fiber/go.mod +++ b/plugins/fiber/go.mod @@ -4,7 +4,7 @@ go 1.22.1 require ( github.com/darkweak/souin v1.7.2 - github.com/darkweak/souin/plugins/souin/storages v0.0.0-00010101000000-000000000000 + github.com/darkweak/souin/plugins/souin/storages v1.7.2 github.com/gofiber/fiber/v2 v2.52.1 github.com/valyala/fasthttp v1.51.0 ) @@ -40,7 +40,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/darkweak/go-esi v0.0.6 // indirect github.com/darkweak/storages/badger v0.0.8 // indirect - github.com/darkweak/storages/core v0.0.8 // indirect + github.com/darkweak/storages/core v0.0.11 // indirect github.com/darkweak/storages/etcd v0.0.8 // indirect github.com/darkweak/storages/nats v0.0.8 // indirect github.com/darkweak/storages/nuts v0.0.8 // indirect @@ -184,7 +184,4 @@ require ( howett.net/plist v1.0.0 // indirect ) -replace ( - github.com/darkweak/souin v1.7.2 => ../.. - github.com/darkweak/souin/plugins/souin/storages => ../souin/storages -) +replace github.com/darkweak/souin v1.7.2 => ../.. diff --git a/plugins/fiber/go.sum b/plugins/fiber/go.sum index 2f04c5be6..eb966e247 100644 --- a/plugins/fiber/go.sum +++ b/plugins/fiber/go.sum @@ -141,10 +141,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/darkweak/go-esi v0.0.6 h1:eVHCJfqrZwOHPfRK7JTlSYG9F8lfpX/d4lz/41RQkd8= github.com/darkweak/go-esi v0.0.6/go.mod h1:IJSayeQZDUh5R5ayyDC3wUEBykti12aUa0eUxZZeodk= +github.com/darkweak/souin/plugins/souin/storages v1.7.2 h1:vA1oFap6sbWO+Ebbq6NGtjmCFuCRJOZeG+XXPhhSIWA= +github.com/darkweak/souin/plugins/souin/storages v1.7.2/go.mod h1:VfkwGN+ubAuluSwbjGHqImbUjxdEA0N9xGJUTCcFBV0= github.com/darkweak/storages/badger v0.0.8 h1:rKVXrasVA74xgiqGRgW0kH11NUIlWwn9HiFyHUok85k= github.com/darkweak/storages/badger v0.0.8/go.mod h1:ZmrNmKkFzyu/B3+1nsvVeTvyg2I2mOV5yTpT46mZ06o= -github.com/darkweak/storages/core v0.0.8 h1:9e7rOxHiJwnvADDVCZ7LFRnUnOHGT+UMpNOFlR8BOiw= -github.com/darkweak/storages/core v0.0.8/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= +github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew= +github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= github.com/darkweak/storages/etcd v0.0.8 h1:Guzv6zgxkQJLjak36KsbtQqkmwMRJoZZI0B7ztZKIik= github.com/darkweak/storages/etcd v0.0.8/go.mod h1:Yw9xJramKAzIRoC7tizVMYPSwUBHqxY5BPTh8OgyISY= github.com/darkweak/storages/nats v0.0.8 h1:HRS3i2zzzIq1Qb3yoOUWD6MoRQgGV1NbECF1ex4wZjg= diff --git a/plugins/gin/go.mod b/plugins/gin/go.mod index f8b61cd01..4fa8d0763 100644 --- a/plugins/gin/go.mod +++ b/plugins/gin/go.mod @@ -4,7 +4,7 @@ go 1.22.1 require ( github.com/darkweak/souin v1.7.2 - github.com/darkweak/souin/plugins/souin/storages v0.0.0-00010101000000-000000000000 + github.com/darkweak/souin/plugins/souin/storages v1.7.2 github.com/gin-gonic/gin v1.9.1 ) @@ -41,7 +41,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/darkweak/go-esi v0.0.6 // indirect github.com/darkweak/storages/badger v0.0.8 // indirect - github.com/darkweak/storages/core v0.0.8 // indirect + github.com/darkweak/storages/core v0.0.11 // indirect github.com/darkweak/storages/etcd v0.0.8 // indirect github.com/darkweak/storages/nats v0.0.8 // indirect github.com/darkweak/storages/nuts v0.0.8 // indirect @@ -195,7 +195,4 @@ require ( howett.net/plist v1.0.0 // indirect ) -replace ( - github.com/darkweak/souin v1.7.2 => ../.. - github.com/darkweak/souin/plugins/souin/storages => ../souin/storages -) +replace github.com/darkweak/souin v1.7.2 => ../.. diff --git a/plugins/gin/go.sum b/plugins/gin/go.sum index 8869821f7..1a078848a 100644 --- a/plugins/gin/go.sum +++ b/plugins/gin/go.sum @@ -149,10 +149,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/darkweak/go-esi v0.0.6 h1:eVHCJfqrZwOHPfRK7JTlSYG9F8lfpX/d4lz/41RQkd8= github.com/darkweak/go-esi v0.0.6/go.mod h1:IJSayeQZDUh5R5ayyDC3wUEBykti12aUa0eUxZZeodk= +github.com/darkweak/souin/plugins/souin/storages v1.7.2 h1:vA1oFap6sbWO+Ebbq6NGtjmCFuCRJOZeG+XXPhhSIWA= +github.com/darkweak/souin/plugins/souin/storages v1.7.2/go.mod h1:VfkwGN+ubAuluSwbjGHqImbUjxdEA0N9xGJUTCcFBV0= github.com/darkweak/storages/badger v0.0.8 h1:rKVXrasVA74xgiqGRgW0kH11NUIlWwn9HiFyHUok85k= github.com/darkweak/storages/badger v0.0.8/go.mod h1:ZmrNmKkFzyu/B3+1nsvVeTvyg2I2mOV5yTpT46mZ06o= -github.com/darkweak/storages/core v0.0.8 h1:9e7rOxHiJwnvADDVCZ7LFRnUnOHGT+UMpNOFlR8BOiw= -github.com/darkweak/storages/core v0.0.8/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= +github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew= +github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= github.com/darkweak/storages/etcd v0.0.8 h1:Guzv6zgxkQJLjak36KsbtQqkmwMRJoZZI0B7ztZKIik= github.com/darkweak/storages/etcd v0.0.8/go.mod h1:Yw9xJramKAzIRoC7tizVMYPSwUBHqxY5BPTh8OgyISY= github.com/darkweak/storages/nats v0.0.8 h1:HRS3i2zzzIq1Qb3yoOUWD6MoRQgGV1NbECF1ex4wZjg= diff --git a/plugins/go-zero/examples/internal/handler/routes.go b/plugins/go-zero/examples/internal/handler/routes.go index 664356b03..3bb44eec0 100644 --- a/plugins/go-zero/examples/internal/handler/routes.go +++ b/plugins/go-zero/examples/internal/handler/routes.go @@ -1,5 +1,5 @@ // Code generated by goctl. DO NOT EDIT. -// goctl 1.7.2 +// goctl 1.7.3 package handler diff --git a/plugins/go-zero/go.mod b/plugins/go-zero/go.mod index a0696a11b..dcb7e42c3 100644 --- a/plugins/go-zero/go.mod +++ b/plugins/go-zero/go.mod @@ -4,7 +4,7 @@ go 1.22.1 require ( github.com/darkweak/souin v1.7.2 - github.com/darkweak/souin/plugins/souin/storages v0.0.0-00010101000000-000000000000 + github.com/darkweak/souin/plugins/souin/storages v1.7.2 github.com/zeromicro/go-zero v1.6.2 ) @@ -39,7 +39,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/darkweak/go-esi v0.0.5 // indirect github.com/darkweak/storages/badger v0.0.8 // indirect - github.com/darkweak/storages/core v0.0.8 // indirect + github.com/darkweak/storages/core v0.0.11 // indirect github.com/darkweak/storages/etcd v0.0.8 // indirect github.com/darkweak/storages/nats v0.0.8 // indirect github.com/darkweak/storages/nuts v0.0.8 // indirect @@ -198,7 +198,4 @@ require ( howett.net/plist v1.0.0 // indirect ) -replace ( - github.com/darkweak/souin v1.7.2 => ../.. - github.com/darkweak/souin/plugins/souin/storages => ../souin/storages -) +replace github.com/darkweak/souin v1.7.2 => ../.. diff --git a/plugins/go-zero/go.sum b/plugins/go-zero/go.sum index bcf254b05..1c90478e4 100644 --- a/plugins/go-zero/go.sum +++ b/plugins/go-zero/go.sum @@ -141,10 +141,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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/plugins/souin/storages v1.7.2 h1:vA1oFap6sbWO+Ebbq6NGtjmCFuCRJOZeG+XXPhhSIWA= +github.com/darkweak/souin/plugins/souin/storages v1.7.2/go.mod h1:VfkwGN+ubAuluSwbjGHqImbUjxdEA0N9xGJUTCcFBV0= github.com/darkweak/storages/badger v0.0.8 h1:rKVXrasVA74xgiqGRgW0kH11NUIlWwn9HiFyHUok85k= github.com/darkweak/storages/badger v0.0.8/go.mod h1:ZmrNmKkFzyu/B3+1nsvVeTvyg2I2mOV5yTpT46mZ06o= -github.com/darkweak/storages/core v0.0.8 h1:9e7rOxHiJwnvADDVCZ7LFRnUnOHGT+UMpNOFlR8BOiw= -github.com/darkweak/storages/core v0.0.8/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= +github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew= +github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= github.com/darkweak/storages/etcd v0.0.8 h1:Guzv6zgxkQJLjak36KsbtQqkmwMRJoZZI0B7ztZKIik= github.com/darkweak/storages/etcd v0.0.8/go.mod h1:Yw9xJramKAzIRoC7tizVMYPSwUBHqxY5BPTh8OgyISY= github.com/darkweak/storages/nats v0.0.8 h1:HRS3i2zzzIq1Qb3yoOUWD6MoRQgGV1NbECF1ex4wZjg= diff --git a/plugins/goa/go.mod b/plugins/goa/go.mod index 94874c695..6a50858d9 100644 --- a/plugins/goa/go.mod +++ b/plugins/goa/go.mod @@ -4,8 +4,8 @@ go 1.22.1 require ( github.com/darkweak/souin v1.7.2 - github.com/darkweak/souin/plugins/souin/storages v0.0.0-00010101000000-000000000000 - github.com/darkweak/storages/core v0.0.8 + github.com/darkweak/souin/plugins/souin/storages v1.7.2 + github.com/darkweak/storages/core v0.0.11 goa.design/goa/v3 v3.12.3 ) @@ -180,7 +180,4 @@ require ( howett.net/plist v1.0.0 // indirect ) -replace ( - github.com/darkweak/souin v1.7.2 => ../.. - github.com/darkweak/souin/plugins/souin/storages => ../souin/storages -) +replace github.com/darkweak/souin v1.7.2 => ../.. diff --git a/plugins/goa/go.sum b/plugins/goa/go.sum index c5de68b74..3d6487503 100644 --- a/plugins/goa/go.sum +++ b/plugins/goa/go.sum @@ -139,10 +139,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/darkweak/go-esi v0.0.6 h1:eVHCJfqrZwOHPfRK7JTlSYG9F8lfpX/d4lz/41RQkd8= github.com/darkweak/go-esi v0.0.6/go.mod h1:IJSayeQZDUh5R5ayyDC3wUEBykti12aUa0eUxZZeodk= +github.com/darkweak/souin/plugins/souin/storages v1.7.2 h1:vA1oFap6sbWO+Ebbq6NGtjmCFuCRJOZeG+XXPhhSIWA= +github.com/darkweak/souin/plugins/souin/storages v1.7.2/go.mod h1:VfkwGN+ubAuluSwbjGHqImbUjxdEA0N9xGJUTCcFBV0= github.com/darkweak/storages/badger v0.0.8 h1:rKVXrasVA74xgiqGRgW0kH11NUIlWwn9HiFyHUok85k= github.com/darkweak/storages/badger v0.0.8/go.mod h1:ZmrNmKkFzyu/B3+1nsvVeTvyg2I2mOV5yTpT46mZ06o= -github.com/darkweak/storages/core v0.0.8 h1:9e7rOxHiJwnvADDVCZ7LFRnUnOHGT+UMpNOFlR8BOiw= -github.com/darkweak/storages/core v0.0.8/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= +github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew= +github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= github.com/darkweak/storages/etcd v0.0.8 h1:Guzv6zgxkQJLjak36KsbtQqkmwMRJoZZI0B7ztZKIik= github.com/darkweak/storages/etcd v0.0.8/go.mod h1:Yw9xJramKAzIRoC7tizVMYPSwUBHqxY5BPTh8OgyISY= github.com/darkweak/storages/nats v0.0.8 h1:HRS3i2zzzIq1Qb3yoOUWD6MoRQgGV1NbECF1ex4wZjg= diff --git a/plugins/goyave/go.mod b/plugins/goyave/go.mod index 063cd29d4..42ae4655a 100644 --- a/plugins/goyave/go.mod +++ b/plugins/goyave/go.mod @@ -4,7 +4,7 @@ go 1.22.1 require ( github.com/darkweak/souin v1.7.2 - github.com/darkweak/souin/plugins/souin/storages v0.0.0-00010101000000-000000000000 + github.com/darkweak/souin/plugins/souin/storages v1.7.2 goyave.dev/goyave/v4 v4.4.11 ) @@ -39,7 +39,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/darkweak/go-esi v0.0.6 // indirect github.com/darkweak/storages/badger v0.0.8 // indirect - github.com/darkweak/storages/core v0.0.8 // indirect + github.com/darkweak/storages/core v0.0.11 // indirect github.com/darkweak/storages/etcd v0.0.8 // indirect github.com/darkweak/storages/nats v0.0.8 // indirect github.com/darkweak/storages/nuts v0.0.8 // indirect @@ -185,7 +185,4 @@ require ( howett.net/plist v1.0.0 // indirect ) -replace ( - github.com/darkweak/souin v1.7.2 => ../.. - github.com/darkweak/souin/plugins/souin/storages => ../souin/storages -) +replace github.com/darkweak/souin v1.7.2 => ../.. diff --git a/plugins/goyave/go.sum b/plugins/goyave/go.sum index 47d2bce90..e699c554e 100644 --- a/plugins/goyave/go.sum +++ b/plugins/goyave/go.sum @@ -141,10 +141,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/darkweak/go-esi v0.0.6 h1:eVHCJfqrZwOHPfRK7JTlSYG9F8lfpX/d4lz/41RQkd8= github.com/darkweak/go-esi v0.0.6/go.mod h1:IJSayeQZDUh5R5ayyDC3wUEBykti12aUa0eUxZZeodk= +github.com/darkweak/souin/plugins/souin/storages v1.7.2 h1:vA1oFap6sbWO+Ebbq6NGtjmCFuCRJOZeG+XXPhhSIWA= +github.com/darkweak/souin/plugins/souin/storages v1.7.2/go.mod h1:VfkwGN+ubAuluSwbjGHqImbUjxdEA0N9xGJUTCcFBV0= github.com/darkweak/storages/badger v0.0.8 h1:rKVXrasVA74xgiqGRgW0kH11NUIlWwn9HiFyHUok85k= github.com/darkweak/storages/badger v0.0.8/go.mod h1:ZmrNmKkFzyu/B3+1nsvVeTvyg2I2mOV5yTpT46mZ06o= -github.com/darkweak/storages/core v0.0.8 h1:9e7rOxHiJwnvADDVCZ7LFRnUnOHGT+UMpNOFlR8BOiw= -github.com/darkweak/storages/core v0.0.8/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= +github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew= +github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= github.com/darkweak/storages/etcd v0.0.8 h1:Guzv6zgxkQJLjak36KsbtQqkmwMRJoZZI0B7ztZKIik= github.com/darkweak/storages/etcd v0.0.8/go.mod h1:Yw9xJramKAzIRoC7tizVMYPSwUBHqxY5BPTh8OgyISY= github.com/darkweak/storages/nats v0.0.8 h1:HRS3i2zzzIq1Qb3yoOUWD6MoRQgGV1NbECF1ex4wZjg= diff --git a/plugins/hertz/go.mod b/plugins/hertz/go.mod index ad5b4030e..2511b3c85 100644 --- a/plugins/hertz/go.mod +++ b/plugins/hertz/go.mod @@ -4,7 +4,7 @@ go 1.22.1 require ( github.com/darkweak/souin v1.7.2 - github.com/darkweak/souin/plugins/souin/storages v0.0.0-00010101000000-000000000000 + github.com/darkweak/souin/plugins/souin/storages v1.7.2 ) require ( @@ -33,7 +33,7 @@ require ( github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/darkweak/storages/badger v0.0.8 // indirect - github.com/darkweak/storages/core v0.0.8 // indirect + github.com/darkweak/storages/core v0.0.11 // indirect github.com/darkweak/storages/etcd v0.0.8 // indirect github.com/darkweak/storages/nats v0.0.8 // indirect github.com/darkweak/storages/nuts v0.0.8 // indirect @@ -194,7 +194,4 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace ( - github.com/darkweak/souin v1.7.2 => ../.. - github.com/darkweak/souin/plugins/souin/storages => ../souin/storages -) +replace github.com/darkweak/souin v1.7.2 => ../.. diff --git a/plugins/hertz/go.sum b/plugins/hertz/go.sum index b541c2b57..ef677af3b 100644 --- a/plugins/hertz/go.sum +++ b/plugins/hertz/go.sum @@ -153,10 +153,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/darkweak/go-esi v0.0.6 h1:eVHCJfqrZwOHPfRK7JTlSYG9F8lfpX/d4lz/41RQkd8= github.com/darkweak/go-esi v0.0.6/go.mod h1:IJSayeQZDUh5R5ayyDC3wUEBykti12aUa0eUxZZeodk= +github.com/darkweak/souin/plugins/souin/storages v1.7.2 h1:vA1oFap6sbWO+Ebbq6NGtjmCFuCRJOZeG+XXPhhSIWA= +github.com/darkweak/souin/plugins/souin/storages v1.7.2/go.mod h1:VfkwGN+ubAuluSwbjGHqImbUjxdEA0N9xGJUTCcFBV0= github.com/darkweak/storages/badger v0.0.8 h1:rKVXrasVA74xgiqGRgW0kH11NUIlWwn9HiFyHUok85k= github.com/darkweak/storages/badger v0.0.8/go.mod h1:ZmrNmKkFzyu/B3+1nsvVeTvyg2I2mOV5yTpT46mZ06o= -github.com/darkweak/storages/core v0.0.8 h1:9e7rOxHiJwnvADDVCZ7LFRnUnOHGT+UMpNOFlR8BOiw= -github.com/darkweak/storages/core v0.0.8/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= +github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew= +github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= github.com/darkweak/storages/etcd v0.0.8 h1:Guzv6zgxkQJLjak36KsbtQqkmwMRJoZZI0B7ztZKIik= github.com/darkweak/storages/etcd v0.0.8/go.mod h1:Yw9xJramKAzIRoC7tizVMYPSwUBHqxY5BPTh8OgyISY= github.com/darkweak/storages/nats v0.0.8 h1:HRS3i2zzzIq1Qb3yoOUWD6MoRQgGV1NbECF1ex4wZjg= diff --git a/plugins/kratos/configuration.go b/plugins/kratos/configuration.go index edc20e372..2bb9d30ec 100644 --- a/plugins/kratos/configuration.go +++ b/plugins/kratos/configuration.go @@ -178,14 +178,20 @@ func parseDefaultCache(dcConfiguration map[string]config.Value) *configurationty cdn.APIKey, _ = cdnConfigurationV.String() case "dynamic": cdn.Dynamic, _ = cdnConfigurationV.Bool() + case "email": + cdn.Email, _ = cdnConfigurationV.String() case "hostname": cdn.Hostname, _ = cdnConfigurationV.String() case "network": cdn.Network, _ = cdnConfigurationV.String() case "provider": cdn.Provider, _ = cdnConfigurationV.String() + case "service_id": + cdn.ServiceID, _ = cdnConfigurationV.String() case "strategy": cdn.Strategy, _ = cdnConfigurationV.String() + case "zone_id": + cdn.ZoneID, _ = cdnConfigurationV.String() } } dc.CDN = cdn @@ -300,6 +306,21 @@ func parseDefaultCache(dcConfiguration map[string]config.Value) *configurationty if exclude != "" { dc.Regex = configurationtypes.Regex{Exclude: exclude} } + case "simplefs": + provider := configurationtypes.CacheProvider{} + simplefsConfiguration, _ := defaultCacheV.Map() + for simplefsConfigurationK, simplefsConfigurationV := range simplefsConfiguration { + switch simplefsConfigurationK { + case path: + provider.Path, _ = simplefsConfigurationV.String() + case configurationPK: + configMap, e := simplefsConfigurationV.Map() + if e == nil { + provider.Configuration = parseRecursively(configMap) + } + } + } + dc.SimpleFS = provider case "timeout": timeout := configurationtypes.Timeout{} timeoutConfiguration, _ := defaultCacheV.Map() diff --git a/plugins/kratos/go.mod b/plugins/kratos/go.mod index 2a6506c45..833375ad8 100644 --- a/plugins/kratos/go.mod +++ b/plugins/kratos/go.mod @@ -4,7 +4,7 @@ go 1.22.1 require ( github.com/darkweak/souin v1.7.2 - github.com/darkweak/souin/plugins/souin/storages v0.0.0-00010101000000-000000000000 + github.com/darkweak/souin/plugins/souin/storages v1.7.2 github.com/go-kratos/kratos/v2 v2.7.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -39,7 +39,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/darkweak/go-esi v0.0.6 // indirect github.com/darkweak/storages/badger v0.0.8 // indirect - github.com/darkweak/storages/core v0.0.8 // indirect + github.com/darkweak/storages/core v0.0.11 // indirect github.com/darkweak/storages/etcd v0.0.8 // indirect github.com/darkweak/storages/nats v0.0.8 // indirect github.com/darkweak/storages/nuts v0.0.8 // indirect @@ -182,7 +182,4 @@ require ( howett.net/plist v1.0.0 // indirect ) -replace ( - github.com/darkweak/souin v1.7.2 => ../.. - github.com/darkweak/souin/plugins/souin/storages => ../souin/storages -) +replace github.com/darkweak/souin v1.7.2 => ../.. diff --git a/plugins/kratos/go.sum b/plugins/kratos/go.sum index 38bc65b1f..b0f9c77b4 100644 --- a/plugins/kratos/go.sum +++ b/plugins/kratos/go.sum @@ -139,10 +139,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/darkweak/go-esi v0.0.6 h1:eVHCJfqrZwOHPfRK7JTlSYG9F8lfpX/d4lz/41RQkd8= github.com/darkweak/go-esi v0.0.6/go.mod h1:IJSayeQZDUh5R5ayyDC3wUEBykti12aUa0eUxZZeodk= +github.com/darkweak/souin/plugins/souin/storages v1.7.2 h1:vA1oFap6sbWO+Ebbq6NGtjmCFuCRJOZeG+XXPhhSIWA= +github.com/darkweak/souin/plugins/souin/storages v1.7.2/go.mod h1:VfkwGN+ubAuluSwbjGHqImbUjxdEA0N9xGJUTCcFBV0= github.com/darkweak/storages/badger v0.0.8 h1:rKVXrasVA74xgiqGRgW0kH11NUIlWwn9HiFyHUok85k= github.com/darkweak/storages/badger v0.0.8/go.mod h1:ZmrNmKkFzyu/B3+1nsvVeTvyg2I2mOV5yTpT46mZ06o= -github.com/darkweak/storages/core v0.0.8 h1:9e7rOxHiJwnvADDVCZ7LFRnUnOHGT+UMpNOFlR8BOiw= -github.com/darkweak/storages/core v0.0.8/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= +github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew= +github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= github.com/darkweak/storages/etcd v0.0.8 h1:Guzv6zgxkQJLjak36KsbtQqkmwMRJoZZI0B7ztZKIik= github.com/darkweak/storages/etcd v0.0.8/go.mod h1:Yw9xJramKAzIRoC7tizVMYPSwUBHqxY5BPTh8OgyISY= github.com/darkweak/storages/nats v0.0.8 h1:HRS3i2zzzIq1Qb3yoOUWD6MoRQgGV1NbECF1ex4wZjg= diff --git a/plugins/roadrunner/go.mod b/plugins/roadrunner/go.mod index ca349989a..7a26c2c6a 100644 --- a/plugins/roadrunner/go.mod +++ b/plugins/roadrunner/go.mod @@ -4,8 +4,8 @@ go 1.22.1 require ( github.com/darkweak/souin v1.7.2 - github.com/darkweak/souin/plugins/souin v0.0.0-00010101000000-000000000000 - github.com/darkweak/souin/plugins/souin/storages v0.0.0-00010101000000-000000000000 + github.com/darkweak/souin/plugins/souin v1.7.2 + github.com/darkweak/souin/plugins/souin/storages v1.7.2 github.com/roadrunner-server/errors v1.3.0 go.uber.org/zap v1.27.0 gopkg.in/yaml.v3 v3.0.1 @@ -41,7 +41,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/darkweak/go-esi v0.0.6 // indirect github.com/darkweak/storages/badger v0.0.8 // indirect - github.com/darkweak/storages/core v0.0.8 // indirect + github.com/darkweak/storages/core v0.0.11 // indirect github.com/darkweak/storages/etcd v0.0.8 // indirect github.com/darkweak/storages/nats v0.0.8 // indirect github.com/darkweak/storages/nuts v0.0.8 // indirect @@ -182,5 +182,4 @@ require ( replace ( github.com/darkweak/souin v1.7.2 => ../.. github.com/darkweak/souin/plugins/souin => ../souin - github.com/darkweak/souin/plugins/souin/storages => ../souin/storages ) diff --git a/plugins/roadrunner/go.sum b/plugins/roadrunner/go.sum index 99289d1b5..c91f3507f 100644 --- a/plugins/roadrunner/go.sum +++ b/plugins/roadrunner/go.sum @@ -139,10 +139,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/darkweak/go-esi v0.0.6 h1:eVHCJfqrZwOHPfRK7JTlSYG9F8lfpX/d4lz/41RQkd8= github.com/darkweak/go-esi v0.0.6/go.mod h1:IJSayeQZDUh5R5ayyDC3wUEBykti12aUa0eUxZZeodk= +github.com/darkweak/souin/plugins/souin/storages v1.7.2 h1:vA1oFap6sbWO+Ebbq6NGtjmCFuCRJOZeG+XXPhhSIWA= +github.com/darkweak/souin/plugins/souin/storages v1.7.2/go.mod h1:VfkwGN+ubAuluSwbjGHqImbUjxdEA0N9xGJUTCcFBV0= github.com/darkweak/storages/badger v0.0.8 h1:rKVXrasVA74xgiqGRgW0kH11NUIlWwn9HiFyHUok85k= github.com/darkweak/storages/badger v0.0.8/go.mod h1:ZmrNmKkFzyu/B3+1nsvVeTvyg2I2mOV5yTpT46mZ06o= -github.com/darkweak/storages/core v0.0.8 h1:9e7rOxHiJwnvADDVCZ7LFRnUnOHGT+UMpNOFlR8BOiw= -github.com/darkweak/storages/core v0.0.8/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= +github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew= +github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= github.com/darkweak/storages/etcd v0.0.8 h1:Guzv6zgxkQJLjak36KsbtQqkmwMRJoZZI0B7ztZKIik= github.com/darkweak/storages/etcd v0.0.8/go.mod h1:Yw9xJramKAzIRoC7tizVMYPSwUBHqxY5BPTh8OgyISY= github.com/darkweak/storages/nats v0.0.8 h1:HRS3i2zzzIq1Qb3yoOUWD6MoRQgGV1NbECF1ex4wZjg= diff --git a/plugins/skipper/go.mod b/plugins/skipper/go.mod index 4642b9cdc..af9e33926 100644 --- a/plugins/skipper/go.mod +++ b/plugins/skipper/go.mod @@ -4,7 +4,7 @@ go 1.22.1 require ( github.com/darkweak/souin v1.7.2 - github.com/darkweak/souin/plugins/souin/storages v0.0.0-00010101000000-000000000000 + github.com/darkweak/souin/plugins/souin/storages v1.7.2 github.com/zalando/skipper v0.16.145 ) @@ -48,7 +48,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/darkweak/go-esi v0.0.6 // indirect github.com/darkweak/storages/badger v0.0.8 // indirect - github.com/darkweak/storages/core v0.0.8 // indirect + github.com/darkweak/storages/core v0.0.11 // indirect github.com/darkweak/storages/etcd v0.0.8 // indirect github.com/darkweak/storages/nats v0.0.8 // indirect github.com/darkweak/storages/nuts v0.0.8 // indirect @@ -218,7 +218,4 @@ require ( layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf // indirect ) -replace ( - github.com/darkweak/souin v1.7.2 => ../.. - github.com/darkweak/souin/plugins/souin/storages => ../souin/storages -) +replace github.com/darkweak/souin v1.7.2 => ../.. diff --git a/plugins/skipper/go.sum b/plugins/skipper/go.sum index 9ad252c3c..65cb6bae8 100644 --- a/plugins/skipper/go.sum +++ b/plugins/skipper/go.sum @@ -173,10 +173,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/darkweak/go-esi v0.0.6 h1:eVHCJfqrZwOHPfRK7JTlSYG9F8lfpX/d4lz/41RQkd8= github.com/darkweak/go-esi v0.0.6/go.mod h1:IJSayeQZDUh5R5ayyDC3wUEBykti12aUa0eUxZZeodk= +github.com/darkweak/souin/plugins/souin/storages v1.7.2 h1:vA1oFap6sbWO+Ebbq6NGtjmCFuCRJOZeG+XXPhhSIWA= +github.com/darkweak/souin/plugins/souin/storages v1.7.2/go.mod h1:VfkwGN+ubAuluSwbjGHqImbUjxdEA0N9xGJUTCcFBV0= github.com/darkweak/storages/badger v0.0.8 h1:rKVXrasVA74xgiqGRgW0kH11NUIlWwn9HiFyHUok85k= github.com/darkweak/storages/badger v0.0.8/go.mod h1:ZmrNmKkFzyu/B3+1nsvVeTvyg2I2mOV5yTpT46mZ06o= -github.com/darkweak/storages/core v0.0.8 h1:9e7rOxHiJwnvADDVCZ7LFRnUnOHGT+UMpNOFlR8BOiw= -github.com/darkweak/storages/core v0.0.8/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= +github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew= +github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= github.com/darkweak/storages/etcd v0.0.8 h1:Guzv6zgxkQJLjak36KsbtQqkmwMRJoZZI0B7ztZKIik= github.com/darkweak/storages/etcd v0.0.8/go.mod h1:Yw9xJramKAzIRoC7tizVMYPSwUBHqxY5BPTh8OgyISY= github.com/darkweak/storages/nats v0.0.8 h1:HRS3i2zzzIq1Qb3yoOUWD6MoRQgGV1NbECF1ex4wZjg= diff --git a/plugins/souin/agnostic/configuration_parser.go b/plugins/souin/agnostic/configuration_parser.go index 604518212..33e32db1f 100644 --- a/plugins/souin/agnostic/configuration_parser.go +++ b/plugins/souin/agnostic/configuration_parser.go @@ -142,14 +142,20 @@ func parseDefaultCache(dcConfiguration map[string]interface{}) *configurationtyp cdn.APIKey, _ = cdnConfigurationV.(string) case "dynamic": cdn.Dynamic = cdnConfigurationV.(bool) + case "email": + cdn.Email, _ = cdnConfigurationV.(string) case "hostname": cdn.Hostname, _ = cdnConfigurationV.(string) case "network": cdn.Network, _ = cdnConfigurationV.(string) case "provider": cdn.Provider, _ = cdnConfigurationV.(string) + case "service_id": + cdn.ServiceID, _ = cdnConfigurationV.(string) case "strategy": cdn.Strategy, _ = cdnConfigurationV.(string) + case "zone_id": + cdn.ZoneID, _ = cdnConfigurationV.(string) } } dc.CDN = cdn @@ -255,6 +261,17 @@ func parseDefaultCache(dcConfiguration map[string]interface{}) *configurationtyp } } } + case "simplefs": + provider := configurationtypes.CacheProvider{} + for simplefsConfigurationK, simplefsConfigurationV := range defaultCacheV.(map[string]interface{}) { + switch simplefsConfigurationK { + case path: + provider.Path, _ = simplefsConfigurationV.(string) + case configurationPK: + provider.Configuration = simplefsConfigurationV.(map[string]interface{}) + } + } + dc.SimpleFS = provider case "timeout": timeout := configurationtypes.Timeout{} for timeoutK, timeoutV := range defaultCacheV.(map[string]interface{}) { diff --git a/plugins/souin/go.mod b/plugins/souin/go.mod index df223926a..1ba7363a1 100644 --- a/plugins/souin/go.mod +++ b/plugins/souin/go.mod @@ -9,8 +9,8 @@ replace ( require ( github.com/darkweak/souin v1.7.2 - github.com/darkweak/souin/plugins/souin/storages v0.0.0-00010101000000-000000000000 - github.com/darkweak/storages/core v0.0.8 + github.com/darkweak/souin/plugins/souin/storages v1.7.2 + github.com/darkweak/storages/core v0.0.11 github.com/fsnotify/fsnotify v1.7.0 go.uber.org/zap v1.27.0 gopkg.in/yaml.v3 v3.0.1 diff --git a/plugins/souin/go.sum b/plugins/souin/go.sum index 410a8a0c1..79ae19825 100644 --- a/plugins/souin/go.sum +++ b/plugins/souin/go.sum @@ -141,8 +141,8 @@ 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/storages/badger v0.0.8 h1:rKVXrasVA74xgiqGRgW0kH11NUIlWwn9HiFyHUok85k= github.com/darkweak/storages/badger v0.0.8/go.mod h1:ZmrNmKkFzyu/B3+1nsvVeTvyg2I2mOV5yTpT46mZ06o= -github.com/darkweak/storages/core v0.0.8 h1:9e7rOxHiJwnvADDVCZ7LFRnUnOHGT+UMpNOFlR8BOiw= -github.com/darkweak/storages/core v0.0.8/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= +github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew= +github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= github.com/darkweak/storages/etcd v0.0.8 h1:Guzv6zgxkQJLjak36KsbtQqkmwMRJoZZI0B7ztZKIik= github.com/darkweak/storages/etcd v0.0.8/go.mod h1:Yw9xJramKAzIRoC7tizVMYPSwUBHqxY5BPTh8OgyISY= github.com/darkweak/storages/nats v0.0.8 h1:HRS3i2zzzIq1Qb3yoOUWD6MoRQgGV1NbECF1ex4wZjg= diff --git a/plugins/souin/storages/go.mod b/plugins/souin/storages/go.mod index a840100da..b43c421b9 100644 --- a/plugins/souin/storages/go.mod +++ b/plugins/souin/storages/go.mod @@ -7,7 +7,7 @@ replace github.com/darkweak/souin => ../../.. require ( github.com/darkweak/souin v1.7.2 github.com/darkweak/storages/badger v0.0.8 - github.com/darkweak/storages/core v0.0.8 + github.com/darkweak/storages/core v0.0.11 github.com/darkweak/storages/etcd v0.0.8 github.com/darkweak/storages/nats v0.0.8 github.com/darkweak/storages/nuts v0.0.8 diff --git a/plugins/souin/storages/go.sum b/plugins/souin/storages/go.sum index 996a59857..3e05fd418 100644 --- a/plugins/souin/storages/go.sum +++ b/plugins/souin/storages/go.sum @@ -58,8 +58,8 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/darkweak/storages/badger v0.0.8 h1:rKVXrasVA74xgiqGRgW0kH11NUIlWwn9HiFyHUok85k= github.com/darkweak/storages/badger v0.0.8/go.mod h1:ZmrNmKkFzyu/B3+1nsvVeTvyg2I2mOV5yTpT46mZ06o= -github.com/darkweak/storages/core v0.0.8 h1:9e7rOxHiJwnvADDVCZ7LFRnUnOHGT+UMpNOFlR8BOiw= -github.com/darkweak/storages/core v0.0.8/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= +github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew= +github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= github.com/darkweak/storages/etcd v0.0.8 h1:Guzv6zgxkQJLjak36KsbtQqkmwMRJoZZI0B7ztZKIik= github.com/darkweak/storages/etcd v0.0.8/go.mod h1:Yw9xJramKAzIRoC7tizVMYPSwUBHqxY5BPTh8OgyISY= github.com/darkweak/storages/nats v0.0.8 h1:HRS3i2zzzIq1Qb3yoOUWD6MoRQgGV1NbECF1ex4wZjg= diff --git a/plugins/traefik/go.mod b/plugins/traefik/go.mod index bcd076e87..abec048ec 100644 --- a/plugins/traefik/go.mod +++ b/plugins/traefik/go.mod @@ -22,7 +22,7 @@ require ( github.com/caddyserver/zerossl v0.1.3 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect - github.com/darkweak/storages/core v0.0.8 // indirect + github.com/darkweak/storages/core v0.0.11 // indirect github.com/dgraph-io/badger v1.6.2 // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect @@ -96,13 +96,13 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/darkweak/go-esi v0.0.5 + github.com/darkweak/go-esi v0.0.5 // indirect github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/golang/glog v1.2.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/uuid v1.6.0 // indirect + github.com/google/uuid v1.6.0 github.com/imdario/mergo v0.3.13 // indirect github.com/klauspost/compress v1.17.8 // indirect github.com/miekg/dns v1.1.59 // indirect @@ -115,7 +115,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.25.0 // indirect - golang.org/x/sync v0.7.0 // indirect + golang.org/x/sync v0.7.0 golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect golang.org/x/tools v0.21.0 // indirect diff --git a/plugins/traefik/go.sum b/plugins/traefik/go.sum index 4b0caf734..064420ae2 100644 --- a/plugins/traefik/go.sum +++ b/plugins/traefik/go.sum @@ -100,8 +100,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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/storages/core v0.0.8 h1:9e7rOxHiJwnvADDVCZ7LFRnUnOHGT+UMpNOFlR8BOiw= -github.com/darkweak/storages/core v0.0.8/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= +github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew= +github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= 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= diff --git a/plugins/traefik/override/configurationtypes/types.go b/plugins/traefik/override/configurationtypes/types.go index 684b56241..9af88bd8d 100644 --- a/plugins/traefik/override/configurationtypes/types.go +++ b/plugins/traefik/override/configurationtypes/types.go @@ -183,6 +183,10 @@ type URL struct { // CacheProvider config type CacheProvider struct { + // Uuid to identify a unique instance. + Uuid string + // Found to determine if we can use that storage. + Found bool `json:"found" yaml:"found"` // URL to connect to the storage system. URL string `json:"url" yaml:"url"` // Path to the configuration file. @@ -241,12 +245,13 @@ type DefaultCache struct { Redis CacheProvider `json:"redis" yaml:"redis"` Port Port `json:"port" yaml:"port"` Regex Regex `json:"regex" yaml:"regex"` + SimpleFS CacheProvider `json:"simplefs" yaml:"simplefs"` Stale Duration `json:"stale" yaml:"stale"` Storers []string `json:"storers" yaml:"storers"` Timeout Timeout `json:"timeout" yaml:"timeout"` TTL Duration `json:"ttl" yaml:"ttl"` DefaultCacheControl string `json:"default_cache_control" yaml:"default_cache_control"` - MaxBodyBytes uint64 `json:"max_cachable_body_bytes" yaml:"max_cachable_body_bytes"` + MaxBodyBytes uint64 `json:"max_cacheable_body_bytes" yaml:"max_cacheable_body_bytes"` DisableCoalescing bool `json:"disable_coalescing" yaml:"disable_coalescing"` } @@ -295,7 +300,7 @@ func (d *DefaultCache) GetMode() string { return d.Mode } -// GetNats returns nuts configuration +// GetNats returns nats configuration func (d *DefaultCache) GetNats() CacheProvider { return d.Nats } @@ -335,6 +340,11 @@ func (d *DefaultCache) GetTTL() time.Duration { return d.TTL.Duration } +// GetSimpleFS returns simplefs configuration +func (d *DefaultCache) GetSimpleFS() CacheProvider { + return d.SimpleFS +} + // GetStale returns the stale duration func (d *DefaultCache) GetStale() time.Duration { return d.Stale.Duration @@ -370,18 +380,21 @@ type DefaultCacheInterface interface { GetEtcd() CacheProvider GetMode() string GetOtter() CacheProvider + GetNats() CacheProvider GetNuts() CacheProvider GetOlric() CacheProvider GetRedis() CacheProvider GetHeaders() []string GetKey() Key GetRegex() Regex + GetSimpleFS() CacheProvider GetStale() time.Duration GetStorers() []string GetTimeout() Timeout GetTTL() time.Duration GetDefaultCacheControl() string GetMaxBodyBytes() uint64 + IsCoalescingDisable() bool } // APIEndpoint is the minimal structure to define an endpoint diff --git a/plugins/traefik/override/context/cache.go b/plugins/traefik/override/context/cache.go index fbdd89cdc..c4051f0c4 100644 --- a/plugins/traefik/override/context/cache.go +++ b/plugins/traefik/override/context/cache.go @@ -19,6 +19,10 @@ type cacheContext struct { cacheName string } +func (*cacheContext) SetContextWithBaseRequest(req *http.Request, _ *http.Request) *http.Request { + return req +} + func (cc *cacheContext) SetupContext(c configurationtypes.AbstractConfigurationInterface) { cc.cacheName = defaultCacheName if c.GetDefaultCache().GetCacheName() != "" { diff --git a/plugins/traefik/override/context/graphql.go b/plugins/traefik/override/context/graphql.go index 8557de703..dad0e70b8 100644 --- a/plugins/traefik/override/context/graphql.go +++ b/plugins/traefik/override/context/graphql.go @@ -21,6 +21,32 @@ type graphQLContext struct { custom bool } +func (g *graphQLContext) SetContextWithBaseRequest(req *http.Request, baseRq *http.Request) *http.Request { + ctx := req.Context() + ctx = context.WithValue(ctx, GraphQL, g.custom) + ctx = context.WithValue(ctx, HashBody, "") + ctx = context.WithValue(ctx, IsMutationRequest, false) + + if g.custom && req.Body != nil { + b := bytes.NewBuffer([]byte{}) + _, _ = io.Copy(b, req.Body) + req.Body = io.NopCloser(b) + baseRq.Body = io.NopCloser(b) + + if b.Len() > 0 { + if isMutation(b.Bytes()) { + ctx = context.WithValue(ctx, IsMutationRequest, true) + } else { + h := sha256.New() + h.Write(b.Bytes()) + ctx = context.WithValue(ctx, HashBody, fmt.Sprintf("-%x", h.Sum(nil))) + } + } + } + + return req.WithContext(ctx) +} + func (g *graphQLContext) SetupContext(c configurationtypes.AbstractConfigurationInterface) { if len(c.GetDefaultCache().GetAllowedHTTPVerbs()) != 0 { g.custom = true diff --git a/plugins/traefik/override/context/key.go b/plugins/traefik/override/context/key.go index 352fd05b6..9a468cf2e 100644 --- a/plugins/traefik/override/context/key.go +++ b/plugins/traefik/override/context/key.go @@ -12,6 +12,7 @@ const ( Key ctxKey = "souin_ctx.CACHE_KEY" DisplayableKey ctxKey = "souin_ctx.DISPLAYABLE_KEY" IgnoredHeaders ctxKey = "souin_ctx.IGNORE_HEADERS" + Hashed ctxKey = "souin_ctx.HASHED" ) type keyContext struct { @@ -20,10 +21,17 @@ type keyContext struct { disable_method bool disable_query bool disable_scheme bool - hash bool displayable bool + hash bool headers []string + template string overrides []map[*regexp.Regexp]keyContext + + initializer func(r *http.Request) *http.Request +} + +func (*keyContext) SetContextWithBaseRequest(req *http.Request, _ *http.Request) *http.Request { + return req } func (g *keyContext) SetupContext(c configurationtypes.AbstractConfigurationInterface) { @@ -35,89 +43,76 @@ func (g *keyContext) SetupContext(c configurationtypes.AbstractConfigurationInte g.disable_scheme = k.DisableScheme g.hash = k.Hash g.displayable = !k.Hide + g.template = k.Template g.headers = k.Headers g.overrides = make([]map[*regexp.Regexp]keyContext, 0) - for _, cacheKey := range c.GetCacheKeys() { - for r, v := range cacheKey { - g.overrides = append(g.overrides, map[*regexp.Regexp]keyContext{r.Regexp: { - disable_body: v.DisableBody, - disable_host: v.DisableHost, - disable_method: v.DisableMethod, - disable_query: v.DisableQuery, - disable_scheme: v.DisableScheme, - hash: v.Hash, - displayable: !v.Hide, - headers: v.Headers, - }}) - } + // for _, cacheKey := range c.GetCacheKeys() { + // for r, v := range cacheKey { + // g.overrides = append(g.overrides, map[*regexp.Regexp]keyContext{r.Regexp: { + // disable_body: v.DisableBody, + // disable_host: v.DisableHost, + // disable_method: v.DisableMethod, + // disable_query: v.DisableQuery, + // disable_scheme: v.DisableScheme, + // hash: v.Hash, + // displayable: !v.Hide, + // template: v.Template, + // headers: v.Headers, + // }}) + // } + // } + + g.initializer = func(r *http.Request) *http.Request { + return r } } -func (g *keyContext) SetContext(req *http.Request) *http.Request { - key := req.URL.Path - var headers []string +func parseKeyInformations(req *http.Request, kCtx keyContext) (query, body, host, scheme, method, headerValues string, headers []string, displayable, hash bool) { + displayable = kCtx.displayable + hash = kCtx.hash - scheme := "http-" - if req.TLS != nil { - scheme = "https-" - } - query := "" - body := "" - host := "" - method := "" - headerValues := "" - displayable := g.displayable - - if !g.disable_query && len(req.URL.RawQuery) > 0 { + if !kCtx.disable_query && len(req.URL.RawQuery) > 0 { query += "?" + req.URL.RawQuery } - if !g.disable_body { + if !kCtx.disable_body { body = req.Context().Value(HashBody).(string) } - if !g.disable_host { + if !kCtx.disable_host { host = req.Host + "-" } - if !g.disable_method { + if !kCtx.disable_scheme { + scheme = "http-" + if req.TLS != nil { + scheme = "https-" + } + } + + if !kCtx.disable_method { method = req.Method + "-" } - headers = g.headers - for _, hn := range g.headers { + headers = kCtx.headers + for _, hn := range kCtx.headers { headerValues += "-" + req.Header.Get(hn) } + return +} + +func (g *keyContext) computeKey(req *http.Request) (key string, headers []string, hash, displayable bool) { + key = req.URL.Path + query, body, host, scheme, method, headerValues, headers, displayable, hash := parseKeyInformations(req, *g) + hasOverride := false for _, current := range g.overrides { for k, v := range current { if k.MatchString(req.RequestURI) { - displayable = v.displayable - host = "" - method = "" - query = "" - if !v.disable_query && len(req.URL.RawQuery) > 0 { - query = "?" + req.URL.RawQuery - } - if !v.disable_body { - body = req.Context().Value(HashBody).(string) - } - if !v.disable_method { - method = req.Method + "-" - } - if !v.disable_host { - host = req.Host + "-" - } - if len(v.headers) > 0 { - headerValues = "" - for _, hn := range v.headers { - headers = v.headers - headerValues += "-" + req.Header.Get(hn) - } - } + query, body, host, scheme, method, headerValues, headers, displayable, hash = parseKeyInformations(req, v) hasOverride = true break } @@ -128,13 +123,26 @@ func (g *keyContext) SetContext(req *http.Request) *http.Request { } } + key = method + scheme + host + key + query + body + headerValues + + return +} + +func (g *keyContext) SetContext(req *http.Request) *http.Request { + rq := g.initializer(req) + key, headers, hash, displayable := g.computeKey(rq) + return req.WithContext( context.WithValue( context.WithValue( context.WithValue( - req.Context(), - Key, - method+scheme+host+key+query+body+headerValues, + context.WithValue( + req.Context(), + Key, + key, + ), + Hashed, + hash, ), IgnoredHeaders, headers, diff --git a/plugins/traefik/override/context/method.go b/plugins/traefik/override/context/method.go index 1e6417cbb..ee772a3de 100644 --- a/plugins/traefik/override/context/method.go +++ b/plugins/traefik/override/context/method.go @@ -16,6 +16,10 @@ type methodContext struct { custom bool } +func (*methodContext) SetContextWithBaseRequest(req *http.Request, _ *http.Request) *http.Request { + return req +} + func (m *methodContext) SetupContext(c configurationtypes.AbstractConfigurationInterface) { m.allowedVerbs = defaultVerbs if len(c.GetDefaultCache().GetAllowedHTTPVerbs()) != 0 { diff --git a/plugins/traefik/override/context/mode.go b/plugins/traefik/override/context/mode.go index b041abb15..ec2d5221d 100644 --- a/plugins/traefik/override/context/mode.go +++ b/plugins/traefik/override/context/mode.go @@ -13,6 +13,10 @@ type ModeContext struct { Strict, Bypass_request, Bypass_response bool } +func (*ModeContext) SetContextWithBaseRequest(req *http.Request, _ *http.Request) *http.Request { + return req +} + func (mc *ModeContext) SetupContext(c configurationtypes.AbstractConfigurationInterface) { mode := c.GetDefaultCache().GetMode() mc.Bypass_request = mode == "bypass" || mode == "bypass_request" @@ -24,4 +28,4 @@ func (mc *ModeContext) SetContext(req *http.Request) *http.Request { return req.WithContext(context.WithValue(req.Context(), Mode, mc)) } -var _ ctx = (*cacheContext)(nil) +var _ ctx = (*ModeContext)(nil) diff --git a/plugins/traefik/override/context/now.go b/plugins/traefik/override/context/now.go index 898cc18fe..d0d4e0f3b 100644 --- a/plugins/traefik/override/context/now.go +++ b/plugins/traefik/override/context/now.go @@ -12,6 +12,10 @@ const Now ctxKey = "souin_ctx.NOW" type nowContext struct{} +func (*nowContext) SetContextWithBaseRequest(req *http.Request, _ *http.Request) *http.Request { + return req +} + func (cc *nowContext) SetupContext(_ configurationtypes.AbstractConfigurationInterface) {} func (cc *nowContext) SetContext(req *http.Request) *http.Request { diff --git a/plugins/traefik/override/context/timeout.go b/plugins/traefik/override/context/timeout.go index 6c737d24c..4da27d984 100644 --- a/plugins/traefik/override/context/timeout.go +++ b/plugins/traefik/override/context/timeout.go @@ -22,6 +22,10 @@ type timeoutContext struct { timeoutCache, timeoutBackend time.Duration } +func (*timeoutContext) SetContextWithBaseRequest(req *http.Request, _ *http.Request) *http.Request { + return req +} + func (t *timeoutContext) SetupContext(c configurationtypes.AbstractConfigurationInterface) { t.timeoutBackend = defaultTimeoutBackend t.timeoutCache = defaultTimeoutCache @@ -38,4 +42,4 @@ func (t *timeoutContext) SetContext(req *http.Request) *http.Request { return req.WithContext(context.WithValue(context.WithValue(ctx, TimeoutCancel, cancel), TimeoutCache, t.timeoutCache)) } -var _ ctx = (*cacheContext)(nil) +var _ ctx = (*timeoutContext)(nil) diff --git a/plugins/traefik/override/context/types.go b/plugins/traefik/override/context/types.go index 38bf5ed19..34e56363f 100644 --- a/plugins/traefik/override/context/types.go +++ b/plugins/traefik/override/context/types.go @@ -12,6 +12,7 @@ type ( ctx interface { SetupContext(c configurationtypes.AbstractConfigurationInterface) SetContext(req *http.Request) *http.Request + SetContextWithBaseRequest(req *http.Request, baseRq *http.Request) *http.Request } Context struct { @@ -53,6 +54,6 @@ func (c *Context) SetBaseContext(req *http.Request) *http.Request { return c.Mode.SetContext(c.Timeout.SetContext(c.Method.SetContext(c.CacheName.SetContext(c.Now.SetContext(req))))) } -func (c *Context) SetContext(req *http.Request) *http.Request { - return c.Key.SetContext(c.GraphQL.SetContext(req)) +func (c *Context) SetContext(req *http.Request, baseRq *http.Request) *http.Request { + return c.Key.SetContext(c.GraphQL.SetContextWithBaseRequest(req, baseRq)) } diff --git a/plugins/traefik/override/middleware/middleware.go b/plugins/traefik/override/middleware/middleware.go index 7b1dc525d..53f899690 100644 --- a/plugins/traefik/override/middleware/middleware.go +++ b/plugins/traefik/override/middleware/middleware.go @@ -22,7 +22,9 @@ import ( "github.com/darkweak/souin/pkg/storage/types" "github.com/darkweak/souin/pkg/surrogate" "github.com/darkweak/souin/pkg/surrogate/providers" + "github.com/google/uuid" "github.com/pquerna/cachecontrol/cacheobject" + "golang.org/x/sync/singleflight" ) func NewHTTPCacheHandler(c configurationtypes.AbstractConfigurationInterface) *SouinBaseHandler { @@ -65,6 +67,7 @@ func NewHTTPCacheHandler(c configurationtypes.AbstractConfigurationInterface) *S context: ctx, bufPool: bufPool, storersLen: len(storers), + singleflightPool: singleflight.Group{}, } } @@ -78,14 +81,17 @@ type SouinBaseHandler struct { SurrogateKeyStorer providers.SurrogateInterface DefaultMatchedUrl configurationtypes.URL context *context.Context + singleflightPool singleflight.Group bufPool *sync.Pool storersLen int } -type upsreamError struct{} +var Upstream50xError = upstream50xError{} -func (upsreamError) Error() string { - return "Upstream error" +type upstream50xError struct{} + +func (upstream50xError) Error() string { + return "Upstream 50x error" } func isCacheableCode(code int) bool { @@ -97,6 +103,15 @@ func isCacheableCode(code int) bool { return false } +func canStatusCodeEmptyContent(code int) bool { + switch code { + case 204, 301, 405: + return true + } + + return false +} + func canBypassAuthorizationRestriction(headers http.Header, bypassed []string) bool { for _, header := range bypassed { if strings.ToLower(header) == "authorization" { @@ -113,35 +128,37 @@ func (s *SouinBaseHandler) Store( requestCc *cacheobject.RequestCacheDirectives, cachedKey string, ) error { - if !isCacheableCode(customWriter.statusCode) { - customWriter.Headers.Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=UNCACHEABLE-STATUS-CODE", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) + statusCode := customWriter.GetStatusCode() + if !isCacheableCode(statusCode) { + customWriter.Header().Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=UNCACHEABLE-STATUS-CODE", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) - switch customWriter.statusCode { + switch statusCode { case 500, 502, 503, 504: - return new(upsreamError) + return Upstream50xError } return nil } - if customWriter.Header().Get("Cache-Control") == "" { + headerName, cacheControl := s.SurrogateKeyStorer.GetSurrogateControl(customWriter.Header()) + if cacheControl == "" { // TODO see with @mnot if mandatory to not store the response when no Cache-Control given. // if s.DefaultMatchedUrl.DefaultCacheControl == "" { - // customWriter.Headers.Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=EMPTY-RESPONSE-CACHE-CONTROL", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) + // customWriter.Header().Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=EMPTY-RESPONSE-CACHE-CONTROL", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) // return nil // } - customWriter.Header().Set("Cache-Control", s.DefaultMatchedUrl.DefaultCacheControl) + customWriter.Header().Set(headerName, s.DefaultMatchedUrl.DefaultCacheControl) } - responseCc, _ := cacheobject.ParseResponseCacheControl(customWriter.Header().Get("Cache-Control")) + responseCc, _ := cacheobject.ParseResponseCacheControl(rfc.HeaderAllCommaSepValuesString(customWriter.Header(), headerName)) if responseCc == nil { - customWriter.Headers.Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=INVALID-RESPONSE-CACHE-CONTROL", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) + customWriter.Header().Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=INVALID-RESPONSE-CACHE-CONTROL", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) return nil } modeContext := rq.Context().Value(context.Mode).(*context.ModeContext) if !modeContext.Bypass_request && (responseCc.PrivatePresent || rq.Header.Get("Authorization") != "") && !canBypassAuthorizationRestriction(customWriter.Header(), rq.Context().Value(context.IgnoredHeaders).([]string)) { - customWriter.Headers.Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=PRIVATE-OR-AUTHENTICATED-RESPONSE", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) + customWriter.Header().Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=PRIVATE-OR-AUTHENTICATED-RESPONSE", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) return nil } @@ -156,40 +173,55 @@ func (s *SouinBaseHandler) Store( } } + hasFreshness := false ma := currentMatchedURL.TTL.Duration if responseCc.SMaxAge >= 0 { ma = time.Duration(responseCc.SMaxAge) * time.Second } else if responseCc.MaxAge >= 0 { ma = time.Duration(responseCc.MaxAge) * time.Second - } - if ma > currentMatchedURL.TTL.Duration { - ma = currentMatchedURL.TTL.Duration + } else if customWriter.Header().Get("Expires") != "" { + exp, err := time.Parse(time.RFC1123, customWriter.Header().Get("Expires")) + if err != nil { + return nil + } + + duration := time.Until(exp) + if duration <= 0 || duration > 10*types.OneYearDuration { + return nil + } + + date, _ := time.Parse(time.RFC1123, customWriter.Header().Get("Date")) + if date.Sub(exp) > 0 { + return nil + } + + ma = duration + hasFreshness = true } now := rq.Context().Value(context.Now).(time.Time) date, _ := http.ParseTime(now.Format(http.TimeFormat)) - customWriter.Headers.Set(rfc.StoredTTLHeader, ma.String()) + customWriter.Header().Set(rfc.StoredTTLHeader, ma.String()) ma = ma - time.Since(date) - if exp := customWriter.Header().Get("Expires"); exp != "" { - delta, _ := time.Parse(exp, time.RFC1123) - if sub := delta.Sub(now); sub > 0 { - ma = sub - } - } - status := fmt.Sprintf("%s; fwd=uri-miss", rq.Context().Value(context.CacheName)) if (modeContext.Bypass_request || !requestCc.NoStore) && - (modeContext.Bypass_response || !responseCc.NoStore) { - headers := customWriter.Headers.Clone() + (modeContext.Bypass_response || !responseCc.NoStore || hasFreshness) { + headers := customWriter.Header().Clone() for hname, shouldDelete := range responseCc.NoCache { if shouldDelete { headers.Del(hname) } } + + customWriter.mutex.Lock() + b := customWriter.Buf.Bytes() + bLen := customWriter.Buf.Len() + customWriter.mutex.Unlock() + res := http.Response{ - StatusCode: customWriter.statusCode, - Body: io.NopCloser(bytes.NewBuffer(customWriter.Buf.Bytes())), + StatusCode: statusCode, + Body: io.NopCloser(bytes.NewBuffer(b)), Header: headers, } @@ -197,17 +229,23 @@ func (s *SouinBaseHandler) Store( res.Header.Set("Date", now.Format(http.TimeFormat)) } if res.Header.Get("Content-Length") == "" { - res.Header.Set("Content-Length", fmt.Sprint(customWriter.Buf.Len())) + res.Header.Set("Content-Length", fmt.Sprint(bLen)) + } + respBodyMaxSize := int(s.Configuration.GetDefaultCache().GetMaxBodyBytes()) + if respBodyMaxSize > 0 && bLen > respBodyMaxSize { + customWriter.Header().Set("Cache-Status", status+"; detail=UPSTREAM-RESPONSE-TOO-LARGE; key="+rfc.GetCacheKeyFromCtx(rq.Context())) + + return nil } res.Header.Set(rfc.StoredLengthHeader, res.Header.Get("Content-Length")) response, err := httputil.DumpResponse(&res, true) - if err == nil { + if err == nil && (bLen > 0 || canStatusCodeEmptyContent(statusCode)) { variedHeaders, isVaryStar := rfc.VariedHeaderAllCommaSepValues(res.Header) if isVaryStar { // "Implies that the response is uncacheable" status += "; detail=UPSTREAM-VARY-STAR" } else { - cachedKey += rfc.GetVariedCacheKey(rq, variedHeaders) + variedKey := cachedKey + rfc.GetVariedCacheKey(rq, variedHeaders) var wg sync.WaitGroup mu := sync.Mutex{} @@ -216,11 +254,25 @@ func (s *SouinBaseHandler) Store( case <-rq.Context().Done(): status += "; detail=REQUEST-CANCELED-OR-UPSTREAM-BROKEN-PIPE" default: + vhs := http.Header{} + for _, hname := range variedHeaders { + hn := strings.Split(hname, ":") + vhs.Set(hn[0], rq.Header.Get(hn[0])) + } for _, storer := range s.Storers { wg.Add(1) go func(currentStorer types.Storer) { defer wg.Done() - if currentStorer.Set(cachedKey, response, ma) != nil { + if currentStorer.SetMultiLevel( + cachedKey, + variedKey, + response, + vhs, + res.Header.Get("Etag"), ma, + variedKey, + ) == nil { + res.Request = rq + } else { mu.Lock() fails = append(fails, fmt.Sprintf("; detail=%s-INSERTION-ERROR", currentStorer.Name())) mu.Unlock() @@ -232,7 +284,7 @@ func (s *SouinBaseHandler) Store( if len(fails) < s.storersLen { go func(rs http.Response, key string) { _ = s.SurrogateKeyStorer.Store(&rs, key, "") - }(res, cachedKey) + }(res, variedKey) status += "; stored" } @@ -241,15 +293,25 @@ func (s *SouinBaseHandler) Store( } } } + + } else { + status += "; detail=UPSTREAM-ERROR-OR-EMPTY-RESPONSE" } } else { status += "; detail=NO-STORE-DIRECTIVE" } - customWriter.Headers.Set("Cache-Status", status+"; key="+rfc.GetCacheKeyFromCtx(rq.Context())) + customWriter.Header().Set("Cache-Status", status+"; key="+rfc.GetCacheKeyFromCtx(rq.Context())) return nil } +type singleflightValue struct { + body []byte + headers http.Header + requestHeaders http.Header + code int +} + func (s *SouinBaseHandler) Upstream( customWriter *CustomWriter, rq *http.Request, @@ -257,71 +319,138 @@ func (s *SouinBaseHandler) Upstream( requestCc *cacheobject.RequestCacheDirectives, cachedKey string, ) error { - if err := next(customWriter, rq); err != nil { - customWriter.Header().Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=SERVE-HTTP-ERROR", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) - return err + var recoveredFromErr error = nil + defer func() { + // In case of "http.ErrAbortHandler" panic, + // prevent singleflight from wrapping it into "singleflight.panicError". + if r := recover(); r != nil { + err, ok := r.(error) + // Sometimes, the error is a string. + if !ok || errors.Is(err, http.ErrAbortHandler) { + recoveredFromErr = http.ErrAbortHandler + } else { + panic(err) + } + } + }() + + singleflightCacheKey := cachedKey + if s.Configuration.GetDefaultCache().IsCoalescingDisable() { + singleflightCacheKey += uuid.NewString() } + sfValue, err, _ := s.singleflightPool.Do(singleflightCacheKey, func() (interface{}, error) { + if e := next(customWriter, rq); e != nil { + customWriter.Header().Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=SERVE-HTTP-ERROR", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) + return nil, e + } - s.SurrogateKeyStorer.Invalidate(rq.Method, customWriter.Header()) - if !isCacheableCode(customWriter.statusCode) { - customWriter.Headers.Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=UNCACHEABLE-STATUS-CODE", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) + s.SurrogateKeyStorer.Invalidate(rq.Method, customWriter.Header()) - switch customWriter.statusCode { - case 500, 502, 503, 504: - return new(upsreamError) + statusCode := customWriter.GetStatusCode() + if !isCacheableCode(statusCode) { + customWriter.Header().Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=UNCACHEABLE-STATUS-CODE", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) + + switch statusCode { + case 500, 502, 503, 504: + return nil, Upstream50xError + } } - return nil - } + headerName, cacheControl := s.SurrogateKeyStorer.GetSurrogateControl(customWriter.Header()) + if cacheControl == "" { + customWriter.Header().Set(headerName, s.DefaultMatchedUrl.DefaultCacheControl) + } - if customWriter.Header().Get("Cache-Control") == "" { - // TODO see with @mnot if mandatory to not store the response when no Cache-Control given. - // if s.DefaultMatchedUrl.DefaultCacheControl == "" { - // customWriter.Headers.Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=EMPTY-RESPONSE-CACHE-CONTROL", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) - // return nil - // } - customWriter.Header().Set("Cache-Control", s.DefaultMatchedUrl.DefaultCacheControl) + err := s.Store(customWriter, rq, requestCc, cachedKey) + defer customWriter.Buf.Reset() + + return singleflightValue{ + body: customWriter.Buf.Bytes(), + headers: customWriter.Header().Clone(), + requestHeaders: rq.Header, + code: statusCode, + }, err + }) + if recoveredFromErr != nil { + panic(recoveredFromErr) + } + if err != nil { + return err } - select { - case <-rq.Context().Done(): - return baseCtx.Canceled - default: - return s.Store(customWriter, rq, requestCc, cachedKey) + if sfWriter, ok := sfValue.(singleflightValue); ok { + if vary := sfWriter.headers.Get("Vary"); vary != "" { + variedHeaders, isVaryStar := rfc.VariedHeaderAllCommaSepValues(sfWriter.headers) + if !isVaryStar { + for _, vh := range variedHeaders { + if rq.Header.Get(vh) != sfWriter.requestHeaders.Get(vh) { + // cachedKey += rfc.GetVariedCacheKey(rq, variedHeaders) + return s.Upstream(customWriter, rq, next, requestCc, cachedKey) + } + } + } + } + _, _ = customWriter.Write(sfWriter.body) + // Yaegi sucks, we can't use maps. + for k := range sfWriter.headers { + customWriter.Header().Set(k, sfWriter.headers.Get(k)) + } + customWriter.WriteHeader(sfWriter.code) } + + return nil } -func (s *SouinBaseHandler) Revalidate(validator *rfc.Revalidator, next handlerFunc, customWriter *CustomWriter, rq *http.Request, requestCc *cacheobject.RequestCacheDirectives, cachedKey string) error { - err := next(customWriter, rq) - s.SurrogateKeyStorer.Invalidate(rq.Method, customWriter.Header()) +func (s *SouinBaseHandler) Revalidate(validator *types.Revalidator, next handlerFunc, customWriter *CustomWriter, rq *http.Request, requestCc *cacheobject.RequestCacheDirectives, cachedKey string, uri string) error { + singleflightCacheKey := cachedKey + if s.Configuration.GetDefaultCache().IsCoalescingDisable() { + singleflightCacheKey += uuid.NewString() + } + sfValue, err, _ := s.singleflightPool.Do(singleflightCacheKey, func() (interface{}, error) { + err := next(customWriter, rq) + s.SurrogateKeyStorer.Invalidate(rq.Method, customWriter.Header()) - if err == nil { - if validator.IfUnmodifiedSincePresent && customWriter.statusCode != http.StatusNotModified { - customWriter.Buf.Reset() - for h, v := range customWriter.Headers { - if len(v) > 0 { - customWriter.Rw.Header().Set(h, strings.Join(v, ", ")) - } + statusCode := customWriter.GetStatusCode() + if err == nil { + if validator.IfUnmodifiedSincePresent && statusCode != http.StatusNotModified { + customWriter.Buf.Reset() + customWriter.Rw.WriteHeader(http.StatusPreconditionFailed) + + return nil, errors.New("") } - customWriter.Rw.WriteHeader(http.StatusPreconditionFailed) - return errors.New("") + if statusCode != http.StatusNotModified { + err = s.Store(customWriter, rq, requestCc, cachedKey) + } } - if customWriter.statusCode != http.StatusNotModified { - err = s.Store(customWriter, rq, requestCc, cachedKey) + customWriter.Header().Set( + "Cache-Status", + fmt.Sprintf( + "%s; fwd=request; fwd-status=%d; key=%s; detail=REQUEST-REVALIDATION", + rq.Context().Value(context.CacheName), + statusCode, + rfc.GetCacheKeyFromCtx(rq.Context()), + ), + ) + + defer customWriter.Buf.Reset() + return singleflightValue{ + body: customWriter.Buf.Bytes(), + headers: customWriter.Header().Clone(), + code: statusCode, + }, err + }) + + if sfWriter, ok := sfValue.(singleflightValue); ok { + _, _ = customWriter.Write(sfWriter.body) + // Yaegi sucks, we can't use maps. + for k := range sfWriter.headers { + customWriter.Header().Set(k, sfWriter.headers.Get(k)) } + customWriter.WriteHeader(sfWriter.code) } - customWriter.Header().Set( - "Cache-Status", - fmt.Sprintf( - "%s; fwd=request; fwd-status=%d; key=%s; detail=REQUEST-REVALIDATION", - rq.Context().Value(context.CacheName), - customWriter.statusCode, - rfc.GetCacheKeyFromCtx(rq.Context()), - ), - ) return err } @@ -339,89 +468,128 @@ func (s *SouinBaseHandler) HandleInternally(r *http.Request) (bool, http.Handler } type handlerFunc = func(http.ResponseWriter, *http.Request) error +type statusCodeLogger struct { + http.ResponseWriter + statusCode int +} + +func (s *statusCodeLogger) WriteHeader(code int) { + s.statusCode = code + s.ResponseWriter.WriteHeader(code) +} func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, next handlerFunc) error { - b, handler := s.HandleInternally(rq) - if b { + if b, handler := s.HandleInternally(rq); b { handler(rw, rq) return nil } + req := s.context.SetBaseContext(rq) + cacheName := req.Context().Value(context.CacheName).(string) - rq = s.context.SetBaseContext(rq) - cacheName := rq.Context().Value(context.CacheName).(string) - if rq.Header.Get("Upgrade") == "websocket" || (s.ExcludeRegex != nil && s.ExcludeRegex.MatchString(rq.RequestURI)) { + if rq.Header.Get("Upgrade") == "websocket" || rq.Header.Get("Accept") == "text/event-stream" || (s.ExcludeRegex != nil && s.ExcludeRegex.MatchString(rq.RequestURI)) { rw.Header().Set("Cache-Status", cacheName+"; fwd=bypass; detail=EXCLUDED-REQUEST-URI") - return next(rw, rq) + return next(rw, req) } - if !rq.Context().Value(context.SupportedMethod).(bool) { + if !req.Context().Value(context.SupportedMethod).(bool) { rw.Header().Set("Cache-Status", cacheName+"; fwd=bypass; detail=UNSUPPORTED-METHOD") + nrw := &statusCodeLogger{ + ResponseWriter: rw, + statusCode: 0, + } - err := next(rw, rq) - s.SurrogateKeyStorer.Invalidate(rq.Method, rw.Header()) + err := next(nrw, req) + s.SurrogateKeyStorer.Invalidate(req.Method, rw.Header()) + + if err == nil && req.Method != http.MethodGet && nrw.statusCode < http.StatusBadRequest { + // Invalidate related GET keys when the method is not allowed and the response is valid + req.Method = http.MethodGet + keyname := s.context.SetContext(req, rq).Context().Value(context.Key).(string) + for _, storer := range s.Storers { + storer.Delete("IDX_" + keyname) + } + } return err } - requestCc, coErr := cacheobject.ParseRequestCacheControl(rq.Header.Get("Cache-Control")) + requestCc, coErr := cacheobject.ParseRequestCacheControl(rfc.HeaderAllCommaSepValuesString(req.Header, "Cache-Control")) + + modeContext := req.Context().Value(context.Mode).(*context.ModeContext) - modeContext := rq.Context().Value(context.Mode).(*context.ModeContext) if !modeContext.Bypass_request && (coErr != nil || requestCc == nil) { rw.Header().Set("Cache-Status", cacheName+"; fwd=bypass; detail=CACHE-CONTROL-EXTRACTION-ERROR") - err := next(rw, rq) - s.SurrogateKeyStorer.Invalidate(rq.Method, rw.Header()) + err := next(rw, req) + s.SurrogateKeyStorer.Invalidate(req.Method, rw.Header()) return err } - rq = s.context.SetContext(rq) + req = s.context.SetContext(req, rq) + + isMutationRequest := false + // Yaegi sucks AGAIN, it considers the value as nil if we directly try to cast as bool + mutationRequestValue := req.Context().Value(context.IsMutationRequest) + if mutationRequestValue != nil { + isMutationRequest = mutationRequestValue.(bool) + } - // Yaegi sucks again, it considers false as true - isMutationRequest := rq.Context().Value(context.IsMutationRequest).(bool) if isMutationRequest { rw.Header().Set("Cache-Status", cacheName+"; fwd=bypass; detail=IS-MUTATION-REQUEST") - err := next(rw, rq) - s.SurrogateKeyStorer.Invalidate(rq.Method, rw.Header()) + err := next(rw, req) + s.SurrogateKeyStorer.Invalidate(req.Method, rw.Header()) return err } - cachedKey := rq.Context().Value(context.Key).(string) + cachedKey := req.Context().Value(context.Key).(string) + + // Need to copy URL path before calling next because it can alter the URI + uri := req.URL.Path bufPool := s.bufPool.Get().(*bytes.Buffer) bufPool.Reset() defer s.bufPool.Put(bufPool) - customWriter := NewCustomWriter(rq, rw, bufPool) + customWriter := NewCustomWriter(req, rw, bufPool) + go func(req *http.Request, crw *CustomWriter) { <-req.Context().Done() crw.mutex.Lock() crw.headersSent = true crw.mutex.Unlock() - }(rq, customWriter) + }(req, customWriter) + if modeContext.Bypass_request || !requestCc.NoCache { - validator := rfc.ParseRequest(rq) - var response *http.Response + validator := rfc.ParseRequest(req) + var fresh, stale *http.Response + var storerName string for _, currentStorer := range s.Storers { - response = currentStorer.Prefix(cachedKey, rq, validator) - if response != nil { + fresh, stale = currentStorer.GetMultiLevel(cachedKey, req, validator) + + if fresh != nil || stale != nil { + storerName = currentStorer.Name() break } } - if response != nil && (!modeContext.Strict || rfc.ValidateCacheControl(response, requestCc)) { + headerName, _ := s.SurrogateKeyStorer.GetSurrogateControl(customWriter.Header()) + if fresh != nil && (!modeContext.Strict || rfc.ValidateCacheControl(fresh, requestCc)) { + response := fresh if validator.ResponseETag != "" && validator.Matched { - rfc.SetCacheStatusHeader(response, "DEFAULT") - customWriter.Headers = response.Header + rfc.SetCacheStatusHeader(response, storerName) + for h, v := range response.Header { + customWriter.Header()[h] = v + } if validator.NotModified { - customWriter.statusCode = http.StatusNotModified + customWriter.WriteHeader(http.StatusNotModified) customWriter.Buf.Reset() _, _ = customWriter.Send() return nil } - customWriter.statusCode = response.StatusCode + customWriter.WriteHeader(response.StatusCode) _, _ = io.Copy(customWriter.Buf, response.Body) _, _ = customWriter.Send() @@ -429,48 +597,48 @@ func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, n } if validator.NeedRevalidation { - err := s.Revalidate(validator, next, customWriter, rq, requestCc, cachedKey) + err := s.Revalidate(validator, next, customWriter, req, requestCc, cachedKey, uri) _, _ = customWriter.Send() return err } - if resCc, _ := cacheobject.ParseResponseCacheControl(response.Header.Get("Cache-Control")); resCc.NoCachePresent { - err := s.Revalidate(validator, next, customWriter, rq, requestCc, cachedKey) + if resCc, _ := cacheobject.ParseResponseCacheControl(rfc.HeaderAllCommaSepValuesString(response.Header, headerName)); resCc.NoCachePresent { + err := s.Revalidate(validator, next, customWriter, req, requestCc, cachedKey, uri) _, _ = customWriter.Send() return err } - rfc.SetCacheStatusHeader(response, "DEFAULT") + rfc.SetCacheStatusHeader(response, storerName) if !modeContext.Strict || rfc.ValidateMaxAgeCachedResponse(requestCc, response) != nil { - customWriter.Headers = response.Header - customWriter.statusCode = response.StatusCode + for h, v := range response.Header { + customWriter.Header()[h] = v + } + customWriter.WriteHeader(response.StatusCode) _, _ = io.Copy(customWriter.Buf, response.Body) _, err := customWriter.Send() return err } - } else if response == nil && !requestCc.OnlyIfCached && (requestCc.MaxStaleSet || requestCc.MaxStale > -1) { - for _, currentStorer := range s.Storers { - response = currentStorer.Prefix(storage.StalePrefix+cachedKey, rq, validator) - if response != nil { - break - } - } + } else if !requestCc.OnlyIfCached && (requestCc.MaxStaleSet || requestCc.MaxStale > -1) { + response := stale + if nil != response && (!modeContext.Strict || rfc.ValidateCacheControl(response, requestCc)) { addTime, _ := time.ParseDuration(response.Header.Get(rfc.StoredTTLHeader)) - rfc.SetCacheStatusHeader(response, "DEFAULT") + rfc.SetCacheStatusHeader(response, storerName) - responseCc, _ := cacheobject.ParseResponseCacheControl(response.Header.Get("Cache-Control")) + responseCc, _ := cacheobject.ParseResponseCacheControl(rfc.HeaderAllCommaSepValuesString(response.Header, "Cache-Control")) if responseCc.StaleWhileRevalidate > 0 { - customWriter.Headers = response.Header - customWriter.statusCode = response.StatusCode + for h, v := range response.Header { + customWriter.Header()[h] = v + } + customWriter.WriteHeader(response.StatusCode) rfc.HitStaleCache(&response.Header) _, _ = io.Copy(customWriter.Buf, response.Body) _, err := customWriter.Send() - customWriter = NewCustomWriter(rq, rw, bufPool) - go func(v *rfc.Revalidator, goCw *CustomWriter, goRq *http.Request, goNext func(http.ResponseWriter, *http.Request) error, goCc *cacheobject.RequestCacheDirectives, goCk string) { - _ = s.Revalidate(v, goNext, goCw, goRq, goCc, goCk) - }(validator, customWriter, rq, next, requestCc, cachedKey) + customWriter = NewCustomWriter(req, rw, bufPool) + go func(v *types.Revalidator, goCw *CustomWriter, goRq *http.Request, goNext func(http.ResponseWriter, *http.Request) error, goCc *cacheobject.RequestCacheDirectives, goCk string, goUri string) { + _ = s.Revalidate(v, goNext, goCw, goRq, goCc, goCk, goUri) + }(validator, customWriter, req, next, requestCc, cachedKey, uri) buf := s.bufPool.Get().(*bytes.Buffer) buf.Reset() defer s.bufPool.Put(buf) @@ -479,15 +647,20 @@ func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, n } if responseCc.MustRevalidate || responseCc.NoCachePresent || validator.NeedRevalidation { - rq.Header["If-None-Match"] = append(rq.Header["If-None-Match"], validator.ResponseETag) - err := s.Revalidate(validator, next, customWriter, rq, requestCc, cachedKey) + req.Header["If-None-Match"] = append(req.Header["If-None-Match"], validator.ResponseETag) + err := s.Revalidate(validator, next, customWriter, req, requestCc, cachedKey, uri) + statusCode := customWriter.GetStatusCode() if err != nil { if responseCc.StaleIfError > -1 || requestCc.StaleIfError > 0 { - code := fmt.Sprintf("; fwd-status=%d", customWriter.statusCode) - customWriter.Headers = response.Header - customWriter.statusCode = response.StatusCode + code := fmt.Sprintf("; fwd-status=%d", statusCode) rfc.HitStaleCache(&response.Header) response.Header.Set("Cache-Status", response.Header.Get("Cache-Status")+code) + // Yaegi sucks, we can't use maps. + for k := range response.Header { + customWriter.Header().Set(k, response.Header.Get(k)) + } + customWriter.WriteHeader(response.StatusCode) + customWriter.Buf.Reset() _, _ = io.Copy(customWriter.Buf, response.Body) _, err := customWriter.Send() @@ -500,11 +673,14 @@ func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, n return err } - if customWriter.statusCode == http.StatusNotModified { + if statusCode == http.StatusNotModified { if !validator.Matched { - rfc.SetCacheStatusHeader(response, "DEFAULT") - customWriter.statusCode = response.StatusCode - customWriter.Headers = response.Header + rfc.SetCacheStatusHeader(response, storerName) + customWriter.WriteHeader(response.StatusCode) + // Yaegi sucks, we can't use maps. + for k := range response.Header { + customWriter.Header().Set(k, response.Header.Get(k)) + } _, _ = io.Copy(customWriter.Buf, response.Body) _, _ = customWriter.Send() @@ -512,8 +688,8 @@ func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, n } } - if customWriter.statusCode != http.StatusNotModified && validator.Matched { - customWriter.statusCode = http.StatusNotModified + if statusCode != http.StatusNotModified && validator.Matched { + customWriter.WriteHeader(http.StatusNotModified) customWriter.Buf.Reset() _, _ = customWriter.Send() @@ -525,27 +701,64 @@ func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, n return err } - if rfc.ValidateMaxAgeCachedStaleResponse(requestCc, response, int(addTime.Seconds())) != nil { - customWriter.Headers = response.Header - customWriter.statusCode = response.StatusCode + if !modeContext.Strict || rfc.ValidateMaxAgeCachedStaleResponse(requestCc, responseCc, response, int(addTime.Seconds())) != nil { + customWriter.WriteHeader(response.StatusCode) rfc.HitStaleCache(&response.Header) + // Yaegi sucks, we can't use maps. + for k := range response.Header { + customWriter.Header().Set(k, response.Header.Get(k)) + } _, _ = io.Copy(customWriter.Buf, response.Body) _, err := customWriter.Send() return err } } + } else if stale != nil { + response := stale + addTime, _ := time.ParseDuration(response.Header.Get(rfc.StoredTTLHeader)) + responseCc, _ := cacheobject.ParseResponseCacheControl(rfc.HeaderAllCommaSepValuesString(response.Header, "Cache-Control")) + + if !modeContext.Strict || rfc.ValidateMaxAgeCachedStaleResponse(requestCc, responseCc, response, int(addTime.Seconds())) != nil { + _, _ = time.ParseDuration(response.Header.Get(rfc.StoredTTLHeader)) + rfc.SetCacheStatusHeader(response, storerName) + + responseCc, _ := cacheobject.ParseResponseCacheControl(rfc.HeaderAllCommaSepValuesString(response.Header, "Cache-Control")) + + if responseCc.StaleIfError > -1 || requestCc.StaleIfError > 0 { + err := s.Revalidate(validator, next, customWriter, req, requestCc, cachedKey, uri) + statusCode := customWriter.GetStatusCode() + if err != nil { + code := fmt.Sprintf("; fwd-status=%d", statusCode) + rfc.HitStaleCache(&response.Header) + response.Header.Set("Cache-Status", response.Header.Get("Cache-Status")+code) + // Yaegi sucks, we can't use maps. + for k := range response.Header { + customWriter.Header().Set(k, response.Header.Get(k)) + } + customWriter.WriteHeader(response.StatusCode) + customWriter.Buf.Reset() + _, _ = io.Copy(customWriter.Buf, response.Body) + _, err := customWriter.Send() + + return err + } + } + + } } } errorCacheCh := make(chan error) - go func() { - errorCacheCh <- s.Upstream(customWriter, rq, next, requestCc, cachedKey) - }() + + go func(vr *http.Request, cw *CustomWriter) { + errorCacheCh <- s.Upstream(cw, vr, next, requestCc, cachedKey) + }(req, customWriter) select { - case <-rq.Context().Done(): - switch rq.Context().Err() { + case <-req.Context().Done(): + + switch req.Context().Err() { case baseCtx.DeadlineExceeded: customWriter.WriteHeader(http.StatusGatewayTimeout) rw.Header().Set("Cache-Status", cacheName+"; fwd=bypass; detail=DEADLINE-EXCEEDED") @@ -556,9 +769,15 @@ func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, n default: return nil } + case v := <-errorCacheCh: - if v == nil { + + switch v { + case nil: + _, _ = customWriter.Send() + case Upstream50xError: _, _ = customWriter.Send() + return nil } return v } diff --git a/plugins/traefik/override/middleware/writer.go b/plugins/traefik/override/middleware/writer.go index ddbccfdbd..97b479bd7 100644 --- a/plugins/traefik/override/middleware/writer.go +++ b/plugins/traefik/override/middleware/writer.go @@ -3,10 +3,9 @@ package middleware import ( "bytes" "net/http" - "strings" + "strconv" "sync" - "github.com/darkweak/go-esi/esi" "github.com/darkweak/souin/pkg/rfc" ) @@ -37,19 +36,28 @@ type CustomWriter struct { headersSent bool mutex *sync.Mutex statusCode int - // size int } // Header will write the response headers func (r *CustomWriter) Header() http.Header { r.mutex.Lock() defer r.mutex.Unlock() - if r.headersSent { + + if r.headersSent || r.Req.Context().Err() != nil { return http.Header{} } + return r.Rw.Header() } +// GetStatusCode returns the response status code +func (r *CustomWriter) GetStatusCode() int { + r.mutex.Lock() + defer r.mutex.Unlock() + + return r.statusCode +} + // WriteHeader will write the response headers func (r *CustomWriter) WriteHeader(code int) { r.mutex.Lock() @@ -57,14 +65,13 @@ func (r *CustomWriter) WriteHeader(code int) { if r.headersSent { return } - r.Headers = r.Rw.Header() r.statusCode = code - // r.headersSent = true - // r.Rw.WriteHeader(code) } // Write will write the response body func (r *CustomWriter) Write(b []byte) (int, error) { + r.mutex.Lock() + defer r.mutex.Unlock() r.Buf.Grow(len(b)) _, _ = r.Buf.Write(b) @@ -73,24 +80,20 @@ func (r *CustomWriter) Write(b []byte) (int, error) { // Send delays the response to handle Cache-Status func (r *CustomWriter) Send() (int, error) { - contentLength := r.Headers.Get(rfc.StoredLengthHeader) - if contentLength != "" { - r.Header().Set("Content-Length", contentLength) - } defer r.Buf.Reset() - b := esi.Parse(r.Buf.Bytes(), r.Req) - for h, v := range r.Headers { - if len(v) > 0 { - r.Rw.Header().Set(h, strings.Join(v, ", ")) - } + storedLength := r.Header().Get(rfc.StoredLengthHeader) + if storedLength != "" { + r.Header().Set("Content-Length", storedLength) + } + b := r.Buf.Bytes() + if len(b) != 0 { + r.Header().Set("Content-Length", strconv.Itoa(len(b))) } r.Header().Del(rfc.StoredLengthHeader) r.Header().Del(rfc.StoredTTLHeader) if !r.headersSent { - - // r.Rw.Header().Set("Content-Length", fmt.Sprintf("%d", len(b))) - r.Rw.WriteHeader(r.statusCode) + r.Rw.WriteHeader(r.GetStatusCode()) r.headersSent = true } diff --git a/plugins/traefik/override/rfc/revalidation.go b/plugins/traefik/override/rfc/revalidation.go index c0eb363b3..ccb6e73be 100644 --- a/plugins/traefik/override/rfc/revalidation.go +++ b/plugins/traefik/override/rfc/revalidation.go @@ -1,29 +1,17 @@ package rfc import ( + "bufio" + "bytes" + "encoding/json" "net/http" "strings" "time" -) -type Revalidator struct { - Matched bool - IfNoneMatchPresent bool - IfMatchPresent bool - IfModifiedSincePresent bool - IfUnmodifiedSincePresent bool - IfUnmotModifiedSincePresent bool - NeedRevalidation bool - NotModified bool - IfModifiedSince time.Time - IfUnmodifiedSince time.Time - IfNoneMatch []string - IfMatch []string - RequestETags []string - ResponseETag string -} + "github.com/darkweak/souin/pkg/storage/types" +) -func ValidateETagFromHeader(etag string, validator *Revalidator) { +func ValidateETagFromHeader(etag string, validator *types.Revalidator) { validator.ResponseETag = etag validator.NeedRevalidation = validator.NeedRevalidation || validator.ResponseETag != "" validator.Matched = validator.ResponseETag == "" || (validator.ResponseETag != "" && len(validator.RequestETags) == 0) @@ -72,7 +60,7 @@ func ValidateETagFromHeader(etag string, validator *Revalidator) { } } -func ParseRequest(req *http.Request) *Revalidator { +func ParseRequest(req *http.Request) *types.Revalidator { var rqEtags []string if len(req.Header.Get("If-None-Match")) > 0 { rqEtags = strings.Split(req.Header.Get("If-None-Match"), ",") @@ -80,7 +68,7 @@ func ParseRequest(req *http.Request) *Revalidator { for i, tag := range rqEtags { rqEtags[i] = strings.Trim(tag, " ") } - validator := Revalidator{ + validator := types.Revalidator{ NotModified: len(rqEtags) > 0, RequestETags: rqEtags, } @@ -106,3 +94,116 @@ func ParseRequest(req *http.Request) *Revalidator { return &validator } + +func DecodeMapping(item []byte) (*StorageMapper, error) { + mapping := &StorageMapper{} + e := json.Unmarshal(item, mapping) + + return mapping, e +} + +func MappingElection(provider types.Storer, item []byte, req *http.Request, validator *types.Revalidator) (resultFresh *http.Response, resultStale *http.Response, e error) { + mapping := &StorageMapper{} + + if len(item) != 0 { + mapping, e = DecodeMapping(item) + if e != nil { + return resultFresh, resultStale, e + } + } + + for keyName, keyItem := range mapping.Mapping { + valid := true + + for hname, hval := range keyItem.VariedHeaders { + if req.Header.Get(hname) != strings.Join(hval, ", ") { + valid = false + + break + } + } + + if !valid { + continue + } + + ValidateETagFromHeader(keyItem.Etag, validator) + + if validator.Matched { + // If the key is fresh enough. + if time.Since(keyItem.FreshTime) < 0 { + response := provider.Get(keyName) + if response != nil { + if resultFresh, e = http.ReadResponse(bufio.NewReader(bytes.NewBuffer(response)), req); e != nil { + return resultFresh, resultStale, e + } + + return resultFresh, resultStale, e + } + } + + // If the key is still stale. + if time.Since(keyItem.StaleTime) < 0 { + response := provider.Get(keyName) + if response != nil { + if resultStale, e = http.ReadResponse(bufio.NewReader(bytes.NewBuffer(response)), req); e != nil { + return resultFresh, resultStale, e + } + } + } + } + } + + return resultFresh, resultStale, e +} + +type KeyIndex struct { + StoredAt time.Time `json:"stored_at,omitempty"` + FreshTime time.Time `json:"fresh_time,omitempty"` + StaleTime time.Time `json:"stale_time,omitempty"` + VariedHeaders map[string][]string `json:"varied_headers,omitempty"` + Etag string `json:"etag,omitempty"` + RealKey string `json:"real_key,omitempty"` +} +type StorageMapper struct { + Mapping map[string]*KeyIndex `json:"mapping,omitempty"` +} + +func MappingUpdater(key string, item []byte, now, freshTime, staleTime time.Time, variedHeaders http.Header, etag, realKey string) (val []byte, e error) { + mapping := &StorageMapper{} + if len(item) != 0 { + e = json.Unmarshal(item, mapping) + if e != nil { + return nil, e + } + } + + if mapping.Mapping == nil { + mapping.Mapping = make(map[string]*KeyIndex) + } + + var pbvariedeheader map[string][]string + if variedHeaders != nil { + pbvariedeheader = make(map[string][]string) + } + + for k, v := range variedHeaders { + pbvariedeheader[k] = append(pbvariedeheader[k], v...) + } + + mapping.Mapping[key] = &KeyIndex{ + StoredAt: now, + FreshTime: freshTime, + StaleTime: staleTime, + VariedHeaders: pbvariedeheader, + Etag: etag, + RealKey: realKey, + } + + val, e = json.Marshal(mapping) + if e != nil { + return nil, e + } + + return val, e +} diff --git a/plugins/traefik/override/storage/abstractProvider.go b/plugins/traefik/override/storage/abstractProvider.go index 65b1c81f3..577d5fade 100644 --- a/plugins/traefik/override/storage/abstractProvider.go +++ b/plugins/traefik/override/storage/abstractProvider.go @@ -20,7 +20,7 @@ const ( type StorerInstanciator func(configurationtypes.AbstractConfigurationInterface) (types.Storer, error) func NewStorages(configuration configurationtypes.AbstractConfigurationInterface) ([]types.Storer, error) { - s, err := CacheConnectionFactory(configuration) + s, err := Factory(configuration) return []types.Storer{s}, err } diff --git a/plugins/traefik/override/storage/cacheProvider.go b/plugins/traefik/override/storage/cacheProvider.go index 8331486c6..dff56aa09 100644 --- a/plugins/traefik/override/storage/cacheProvider.go +++ b/plugins/traefik/override/storage/cacheProvider.go @@ -22,8 +22,8 @@ type Cache struct { var sharedCache *Cache -// CacheConnectionFactory function create new Cache instance -func CacheConnectionFactory(c t.AbstractConfigurationInterface) (types.Storer, error) { +// Factory function create new Cache instance +func Factory(c t.AbstractConfigurationInterface) (types.Storer, error) { provider := cache.New(1 * time.Second) if sharedCache == nil { @@ -38,6 +38,11 @@ func (provider *Cache) Name() string { return "CACHE" } +// Uuid returns an unique identifier +func (provider *Cache) Uuid() string { + return "" +} + // ListKeys method returns the list of existing keys func (provider *Cache) ListKeys() []string { var keys []string @@ -74,8 +79,44 @@ func (provider *Cache) Get(key string) []byte { return result.([]byte) } +// GetMultiLevel tries to load the key and check if one of linked keys is a fresh/stale candidate. +func (provider *Cache) GetMultiLevel(key string, req *http.Request, validator *types.Revalidator) (fresh *http.Response, stale *http.Response) { + result, found := provider.Cache.Get("IDX_" + key) + if !found { + return + } + + fresh, stale, _ = rfc.MappingElection(provider, result.([]byte), req, validator) + + return +} + +// SetMultiLevel tries to store the key with the given value and update the mapping key to store metadata. +func (provider *Cache) SetMultiLevel(baseKey, variedKey string, value []byte, variedHeaders http.Header, etag string, duration time.Duration, realKey string) error { + now := time.Now() + + var e error + + provider.Cache.Set(variedKey, value, duration) + + mappingKey := "IDX_" + baseKey + item, ok := provider.Cache.Get(mappingKey) + var val []byte + if ok { + val = item.([]byte) + } + + val, e = rfc.MappingUpdater(variedKey, val, now, now.Add(duration), now.Add(duration+provider.stale), variedHeaders, etag, realKey) + if e != nil { + return e + } + + provider.Cache.Set(mappingKey, val, 0) + return nil +} + // Prefix method returns the populated response if exists, empty response then -func (provider *Cache) Prefix(key string, req *http.Request, validator *rfc.Revalidator) *http.Response { +func (provider *Cache) Prefix(key string, req *http.Request, validator *types.Revalidator) *http.Response { var result *http.Response provider.Cache.Range(func(k, v interface{}) bool { @@ -103,7 +144,6 @@ func (provider *Cache) Prefix(key string, req *http.Request, validator *rfc.Reva // Set method will store the response in Cache provider func (provider *Cache) Set(key string, value []byte, duration time.Duration) error { provider.Cache.Set(key, value, duration) - provider.Cache.Set(StalePrefix+key, value, provider.stale+duration) return nil } diff --git a/plugins/traefik/override/storage/types/types.go b/plugins/traefik/override/storage/types/types.go index 67031c15e..e41007745 100644 --- a/plugins/traefik/override/storage/types/types.go +++ b/plugins/traefik/override/storage/types/types.go @@ -3,19 +3,41 @@ package types import ( "net/http" "time" - - "github.com/darkweak/souin/pkg/rfc" ) +type Revalidator struct { + Matched bool + IfNoneMatchPresent bool + IfMatchPresent bool + IfModifiedSincePresent bool + IfUnmodifiedSincePresent bool + IfUnmotModifiedSincePresent bool + NeedRevalidation bool + NotModified bool + IfModifiedSince time.Time + IfUnmodifiedSince time.Time + IfNoneMatch []string + IfMatch []string + RequestETags []string + ResponseETag string +} + +const DefaultStorageName = "CACHE" +const OneYearDuration = 365 * 24 * time.Hour + type Storer interface { MapKeys(prefix string) map[string]string ListKeys() []string - Prefix(key string, req *http.Request, validator *rfc.Revalidator) *http.Response Get(key string) []byte Set(key string, value []byte, duration time.Duration) error Delete(key string) DeleteMany(key string) Init() error Name() string + Uuid() string Reset() error + + // Multi level storer to handle fresh/stale at once + GetMultiLevel(key string, req *http.Request, validator *Revalidator) (fresh *http.Response, stale *http.Response) + SetMultiLevel(baseKey, variedKey string, value []byte, variedHeaders http.Header, etag string, duration time.Duration, realKey string) error } diff --git a/plugins/traefik/vendor/github.com/darkweak/souin/configurationtypes/types.go b/plugins/traefik/vendor/github.com/darkweak/souin/configurationtypes/types.go index 684b56241..9af88bd8d 100644 --- a/plugins/traefik/vendor/github.com/darkweak/souin/configurationtypes/types.go +++ b/plugins/traefik/vendor/github.com/darkweak/souin/configurationtypes/types.go @@ -183,6 +183,10 @@ type URL struct { // CacheProvider config type CacheProvider struct { + // Uuid to identify a unique instance. + Uuid string + // Found to determine if we can use that storage. + Found bool `json:"found" yaml:"found"` // URL to connect to the storage system. URL string `json:"url" yaml:"url"` // Path to the configuration file. @@ -241,12 +245,13 @@ type DefaultCache struct { Redis CacheProvider `json:"redis" yaml:"redis"` Port Port `json:"port" yaml:"port"` Regex Regex `json:"regex" yaml:"regex"` + SimpleFS CacheProvider `json:"simplefs" yaml:"simplefs"` Stale Duration `json:"stale" yaml:"stale"` Storers []string `json:"storers" yaml:"storers"` Timeout Timeout `json:"timeout" yaml:"timeout"` TTL Duration `json:"ttl" yaml:"ttl"` DefaultCacheControl string `json:"default_cache_control" yaml:"default_cache_control"` - MaxBodyBytes uint64 `json:"max_cachable_body_bytes" yaml:"max_cachable_body_bytes"` + MaxBodyBytes uint64 `json:"max_cacheable_body_bytes" yaml:"max_cacheable_body_bytes"` DisableCoalescing bool `json:"disable_coalescing" yaml:"disable_coalescing"` } @@ -295,7 +300,7 @@ func (d *DefaultCache) GetMode() string { return d.Mode } -// GetNats returns nuts configuration +// GetNats returns nats configuration func (d *DefaultCache) GetNats() CacheProvider { return d.Nats } @@ -335,6 +340,11 @@ func (d *DefaultCache) GetTTL() time.Duration { return d.TTL.Duration } +// GetSimpleFS returns simplefs configuration +func (d *DefaultCache) GetSimpleFS() CacheProvider { + return d.SimpleFS +} + // GetStale returns the stale duration func (d *DefaultCache) GetStale() time.Duration { return d.Stale.Duration @@ -370,18 +380,21 @@ type DefaultCacheInterface interface { GetEtcd() CacheProvider GetMode() string GetOtter() CacheProvider + GetNats() CacheProvider GetNuts() CacheProvider GetOlric() CacheProvider GetRedis() CacheProvider GetHeaders() []string GetKey() Key GetRegex() Regex + GetSimpleFS() CacheProvider GetStale() time.Duration GetStorers() []string GetTimeout() Timeout GetTTL() time.Duration GetDefaultCacheControl() string GetMaxBodyBytes() uint64 + IsCoalescingDisable() bool } // APIEndpoint is the minimal structure to define an endpoint diff --git a/plugins/traefik/vendor/github.com/darkweak/souin/context/cache.go b/plugins/traefik/vendor/github.com/darkweak/souin/context/cache.go index fbdd89cdc..c4051f0c4 100644 --- a/plugins/traefik/vendor/github.com/darkweak/souin/context/cache.go +++ b/plugins/traefik/vendor/github.com/darkweak/souin/context/cache.go @@ -19,6 +19,10 @@ type cacheContext struct { cacheName string } +func (*cacheContext) SetContextWithBaseRequest(req *http.Request, _ *http.Request) *http.Request { + return req +} + func (cc *cacheContext) SetupContext(c configurationtypes.AbstractConfigurationInterface) { cc.cacheName = defaultCacheName if c.GetDefaultCache().GetCacheName() != "" { diff --git a/plugins/traefik/vendor/github.com/darkweak/souin/context/graphql.go b/plugins/traefik/vendor/github.com/darkweak/souin/context/graphql.go index 8557de703..dad0e70b8 100644 --- a/plugins/traefik/vendor/github.com/darkweak/souin/context/graphql.go +++ b/plugins/traefik/vendor/github.com/darkweak/souin/context/graphql.go @@ -21,6 +21,32 @@ type graphQLContext struct { custom bool } +func (g *graphQLContext) SetContextWithBaseRequest(req *http.Request, baseRq *http.Request) *http.Request { + ctx := req.Context() + ctx = context.WithValue(ctx, GraphQL, g.custom) + ctx = context.WithValue(ctx, HashBody, "") + ctx = context.WithValue(ctx, IsMutationRequest, false) + + if g.custom && req.Body != nil { + b := bytes.NewBuffer([]byte{}) + _, _ = io.Copy(b, req.Body) + req.Body = io.NopCloser(b) + baseRq.Body = io.NopCloser(b) + + if b.Len() > 0 { + if isMutation(b.Bytes()) { + ctx = context.WithValue(ctx, IsMutationRequest, true) + } else { + h := sha256.New() + h.Write(b.Bytes()) + ctx = context.WithValue(ctx, HashBody, fmt.Sprintf("-%x", h.Sum(nil))) + } + } + } + + return req.WithContext(ctx) +} + func (g *graphQLContext) SetupContext(c configurationtypes.AbstractConfigurationInterface) { if len(c.GetDefaultCache().GetAllowedHTTPVerbs()) != 0 { g.custom = true diff --git a/plugins/traefik/vendor/github.com/darkweak/souin/context/key.go b/plugins/traefik/vendor/github.com/darkweak/souin/context/key.go index 352fd05b6..9a468cf2e 100644 --- a/plugins/traefik/vendor/github.com/darkweak/souin/context/key.go +++ b/plugins/traefik/vendor/github.com/darkweak/souin/context/key.go @@ -12,6 +12,7 @@ const ( Key ctxKey = "souin_ctx.CACHE_KEY" DisplayableKey ctxKey = "souin_ctx.DISPLAYABLE_KEY" IgnoredHeaders ctxKey = "souin_ctx.IGNORE_HEADERS" + Hashed ctxKey = "souin_ctx.HASHED" ) type keyContext struct { @@ -20,10 +21,17 @@ type keyContext struct { disable_method bool disable_query bool disable_scheme bool - hash bool displayable bool + hash bool headers []string + template string overrides []map[*regexp.Regexp]keyContext + + initializer func(r *http.Request) *http.Request +} + +func (*keyContext) SetContextWithBaseRequest(req *http.Request, _ *http.Request) *http.Request { + return req } func (g *keyContext) SetupContext(c configurationtypes.AbstractConfigurationInterface) { @@ -35,89 +43,76 @@ func (g *keyContext) SetupContext(c configurationtypes.AbstractConfigurationInte g.disable_scheme = k.DisableScheme g.hash = k.Hash g.displayable = !k.Hide + g.template = k.Template g.headers = k.Headers g.overrides = make([]map[*regexp.Regexp]keyContext, 0) - for _, cacheKey := range c.GetCacheKeys() { - for r, v := range cacheKey { - g.overrides = append(g.overrides, map[*regexp.Regexp]keyContext{r.Regexp: { - disable_body: v.DisableBody, - disable_host: v.DisableHost, - disable_method: v.DisableMethod, - disable_query: v.DisableQuery, - disable_scheme: v.DisableScheme, - hash: v.Hash, - displayable: !v.Hide, - headers: v.Headers, - }}) - } + // for _, cacheKey := range c.GetCacheKeys() { + // for r, v := range cacheKey { + // g.overrides = append(g.overrides, map[*regexp.Regexp]keyContext{r.Regexp: { + // disable_body: v.DisableBody, + // disable_host: v.DisableHost, + // disable_method: v.DisableMethod, + // disable_query: v.DisableQuery, + // disable_scheme: v.DisableScheme, + // hash: v.Hash, + // displayable: !v.Hide, + // template: v.Template, + // headers: v.Headers, + // }}) + // } + // } + + g.initializer = func(r *http.Request) *http.Request { + return r } } -func (g *keyContext) SetContext(req *http.Request) *http.Request { - key := req.URL.Path - var headers []string +func parseKeyInformations(req *http.Request, kCtx keyContext) (query, body, host, scheme, method, headerValues string, headers []string, displayable, hash bool) { + displayable = kCtx.displayable + hash = kCtx.hash - scheme := "http-" - if req.TLS != nil { - scheme = "https-" - } - query := "" - body := "" - host := "" - method := "" - headerValues := "" - displayable := g.displayable - - if !g.disable_query && len(req.URL.RawQuery) > 0 { + if !kCtx.disable_query && len(req.URL.RawQuery) > 0 { query += "?" + req.URL.RawQuery } - if !g.disable_body { + if !kCtx.disable_body { body = req.Context().Value(HashBody).(string) } - if !g.disable_host { + if !kCtx.disable_host { host = req.Host + "-" } - if !g.disable_method { + if !kCtx.disable_scheme { + scheme = "http-" + if req.TLS != nil { + scheme = "https-" + } + } + + if !kCtx.disable_method { method = req.Method + "-" } - headers = g.headers - for _, hn := range g.headers { + headers = kCtx.headers + for _, hn := range kCtx.headers { headerValues += "-" + req.Header.Get(hn) } + return +} + +func (g *keyContext) computeKey(req *http.Request) (key string, headers []string, hash, displayable bool) { + key = req.URL.Path + query, body, host, scheme, method, headerValues, headers, displayable, hash := parseKeyInformations(req, *g) + hasOverride := false for _, current := range g.overrides { for k, v := range current { if k.MatchString(req.RequestURI) { - displayable = v.displayable - host = "" - method = "" - query = "" - if !v.disable_query && len(req.URL.RawQuery) > 0 { - query = "?" + req.URL.RawQuery - } - if !v.disable_body { - body = req.Context().Value(HashBody).(string) - } - if !v.disable_method { - method = req.Method + "-" - } - if !v.disable_host { - host = req.Host + "-" - } - if len(v.headers) > 0 { - headerValues = "" - for _, hn := range v.headers { - headers = v.headers - headerValues += "-" + req.Header.Get(hn) - } - } + query, body, host, scheme, method, headerValues, headers, displayable, hash = parseKeyInformations(req, v) hasOverride = true break } @@ -128,13 +123,26 @@ func (g *keyContext) SetContext(req *http.Request) *http.Request { } } + key = method + scheme + host + key + query + body + headerValues + + return +} + +func (g *keyContext) SetContext(req *http.Request) *http.Request { + rq := g.initializer(req) + key, headers, hash, displayable := g.computeKey(rq) + return req.WithContext( context.WithValue( context.WithValue( context.WithValue( - req.Context(), - Key, - method+scheme+host+key+query+body+headerValues, + context.WithValue( + req.Context(), + Key, + key, + ), + Hashed, + hash, ), IgnoredHeaders, headers, diff --git a/plugins/traefik/vendor/github.com/darkweak/souin/context/method.go b/plugins/traefik/vendor/github.com/darkweak/souin/context/method.go index 1e6417cbb..ee772a3de 100644 --- a/plugins/traefik/vendor/github.com/darkweak/souin/context/method.go +++ b/plugins/traefik/vendor/github.com/darkweak/souin/context/method.go @@ -16,6 +16,10 @@ type methodContext struct { custom bool } +func (*methodContext) SetContextWithBaseRequest(req *http.Request, _ *http.Request) *http.Request { + return req +} + func (m *methodContext) SetupContext(c configurationtypes.AbstractConfigurationInterface) { m.allowedVerbs = defaultVerbs if len(c.GetDefaultCache().GetAllowedHTTPVerbs()) != 0 { diff --git a/plugins/traefik/vendor/github.com/darkweak/souin/context/mode.go b/plugins/traefik/vendor/github.com/darkweak/souin/context/mode.go index b041abb15..ec2d5221d 100644 --- a/plugins/traefik/vendor/github.com/darkweak/souin/context/mode.go +++ b/plugins/traefik/vendor/github.com/darkweak/souin/context/mode.go @@ -13,6 +13,10 @@ type ModeContext struct { Strict, Bypass_request, Bypass_response bool } +func (*ModeContext) SetContextWithBaseRequest(req *http.Request, _ *http.Request) *http.Request { + return req +} + func (mc *ModeContext) SetupContext(c configurationtypes.AbstractConfigurationInterface) { mode := c.GetDefaultCache().GetMode() mc.Bypass_request = mode == "bypass" || mode == "bypass_request" @@ -24,4 +28,4 @@ func (mc *ModeContext) SetContext(req *http.Request) *http.Request { return req.WithContext(context.WithValue(req.Context(), Mode, mc)) } -var _ ctx = (*cacheContext)(nil) +var _ ctx = (*ModeContext)(nil) diff --git a/plugins/traefik/vendor/github.com/darkweak/souin/context/now.go b/plugins/traefik/vendor/github.com/darkweak/souin/context/now.go index 898cc18fe..d0d4e0f3b 100644 --- a/plugins/traefik/vendor/github.com/darkweak/souin/context/now.go +++ b/plugins/traefik/vendor/github.com/darkweak/souin/context/now.go @@ -12,6 +12,10 @@ const Now ctxKey = "souin_ctx.NOW" type nowContext struct{} +func (*nowContext) SetContextWithBaseRequest(req *http.Request, _ *http.Request) *http.Request { + return req +} + func (cc *nowContext) SetupContext(_ configurationtypes.AbstractConfigurationInterface) {} func (cc *nowContext) SetContext(req *http.Request) *http.Request { diff --git a/plugins/traefik/vendor/github.com/darkweak/souin/context/timeout.go b/plugins/traefik/vendor/github.com/darkweak/souin/context/timeout.go index 6c737d24c..4da27d984 100644 --- a/plugins/traefik/vendor/github.com/darkweak/souin/context/timeout.go +++ b/plugins/traefik/vendor/github.com/darkweak/souin/context/timeout.go @@ -22,6 +22,10 @@ type timeoutContext struct { timeoutCache, timeoutBackend time.Duration } +func (*timeoutContext) SetContextWithBaseRequest(req *http.Request, _ *http.Request) *http.Request { + return req +} + func (t *timeoutContext) SetupContext(c configurationtypes.AbstractConfigurationInterface) { t.timeoutBackend = defaultTimeoutBackend t.timeoutCache = defaultTimeoutCache @@ -38,4 +42,4 @@ func (t *timeoutContext) SetContext(req *http.Request) *http.Request { return req.WithContext(context.WithValue(context.WithValue(ctx, TimeoutCancel, cancel), TimeoutCache, t.timeoutCache)) } -var _ ctx = (*cacheContext)(nil) +var _ ctx = (*timeoutContext)(nil) diff --git a/plugins/traefik/vendor/github.com/darkweak/souin/context/types.go b/plugins/traefik/vendor/github.com/darkweak/souin/context/types.go index 38bf5ed19..34e56363f 100644 --- a/plugins/traefik/vendor/github.com/darkweak/souin/context/types.go +++ b/plugins/traefik/vendor/github.com/darkweak/souin/context/types.go @@ -12,6 +12,7 @@ type ( ctx interface { SetupContext(c configurationtypes.AbstractConfigurationInterface) SetContext(req *http.Request) *http.Request + SetContextWithBaseRequest(req *http.Request, baseRq *http.Request) *http.Request } Context struct { @@ -53,6 +54,6 @@ func (c *Context) SetBaseContext(req *http.Request) *http.Request { return c.Mode.SetContext(c.Timeout.SetContext(c.Method.SetContext(c.CacheName.SetContext(c.Now.SetContext(req))))) } -func (c *Context) SetContext(req *http.Request) *http.Request { - return c.Key.SetContext(c.GraphQL.SetContext(req)) +func (c *Context) SetContext(req *http.Request, baseRq *http.Request) *http.Request { + return c.Key.SetContext(c.GraphQL.SetContextWithBaseRequest(req, baseRq)) } diff --git a/plugins/traefik/vendor/github.com/darkweak/souin/pkg/middleware/middleware.go b/plugins/traefik/vendor/github.com/darkweak/souin/pkg/middleware/middleware.go index 7b1dc525d..53f899690 100644 --- a/plugins/traefik/vendor/github.com/darkweak/souin/pkg/middleware/middleware.go +++ b/plugins/traefik/vendor/github.com/darkweak/souin/pkg/middleware/middleware.go @@ -22,7 +22,9 @@ import ( "github.com/darkweak/souin/pkg/storage/types" "github.com/darkweak/souin/pkg/surrogate" "github.com/darkweak/souin/pkg/surrogate/providers" + "github.com/google/uuid" "github.com/pquerna/cachecontrol/cacheobject" + "golang.org/x/sync/singleflight" ) func NewHTTPCacheHandler(c configurationtypes.AbstractConfigurationInterface) *SouinBaseHandler { @@ -65,6 +67,7 @@ func NewHTTPCacheHandler(c configurationtypes.AbstractConfigurationInterface) *S context: ctx, bufPool: bufPool, storersLen: len(storers), + singleflightPool: singleflight.Group{}, } } @@ -78,14 +81,17 @@ type SouinBaseHandler struct { SurrogateKeyStorer providers.SurrogateInterface DefaultMatchedUrl configurationtypes.URL context *context.Context + singleflightPool singleflight.Group bufPool *sync.Pool storersLen int } -type upsreamError struct{} +var Upstream50xError = upstream50xError{} -func (upsreamError) Error() string { - return "Upstream error" +type upstream50xError struct{} + +func (upstream50xError) Error() string { + return "Upstream 50x error" } func isCacheableCode(code int) bool { @@ -97,6 +103,15 @@ func isCacheableCode(code int) bool { return false } +func canStatusCodeEmptyContent(code int) bool { + switch code { + case 204, 301, 405: + return true + } + + return false +} + func canBypassAuthorizationRestriction(headers http.Header, bypassed []string) bool { for _, header := range bypassed { if strings.ToLower(header) == "authorization" { @@ -113,35 +128,37 @@ func (s *SouinBaseHandler) Store( requestCc *cacheobject.RequestCacheDirectives, cachedKey string, ) error { - if !isCacheableCode(customWriter.statusCode) { - customWriter.Headers.Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=UNCACHEABLE-STATUS-CODE", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) + statusCode := customWriter.GetStatusCode() + if !isCacheableCode(statusCode) { + customWriter.Header().Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=UNCACHEABLE-STATUS-CODE", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) - switch customWriter.statusCode { + switch statusCode { case 500, 502, 503, 504: - return new(upsreamError) + return Upstream50xError } return nil } - if customWriter.Header().Get("Cache-Control") == "" { + headerName, cacheControl := s.SurrogateKeyStorer.GetSurrogateControl(customWriter.Header()) + if cacheControl == "" { // TODO see with @mnot if mandatory to not store the response when no Cache-Control given. // if s.DefaultMatchedUrl.DefaultCacheControl == "" { - // customWriter.Headers.Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=EMPTY-RESPONSE-CACHE-CONTROL", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) + // customWriter.Header().Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=EMPTY-RESPONSE-CACHE-CONTROL", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) // return nil // } - customWriter.Header().Set("Cache-Control", s.DefaultMatchedUrl.DefaultCacheControl) + customWriter.Header().Set(headerName, s.DefaultMatchedUrl.DefaultCacheControl) } - responseCc, _ := cacheobject.ParseResponseCacheControl(customWriter.Header().Get("Cache-Control")) + responseCc, _ := cacheobject.ParseResponseCacheControl(rfc.HeaderAllCommaSepValuesString(customWriter.Header(), headerName)) if responseCc == nil { - customWriter.Headers.Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=INVALID-RESPONSE-CACHE-CONTROL", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) + customWriter.Header().Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=INVALID-RESPONSE-CACHE-CONTROL", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) return nil } modeContext := rq.Context().Value(context.Mode).(*context.ModeContext) if !modeContext.Bypass_request && (responseCc.PrivatePresent || rq.Header.Get("Authorization") != "") && !canBypassAuthorizationRestriction(customWriter.Header(), rq.Context().Value(context.IgnoredHeaders).([]string)) { - customWriter.Headers.Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=PRIVATE-OR-AUTHENTICATED-RESPONSE", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) + customWriter.Header().Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=PRIVATE-OR-AUTHENTICATED-RESPONSE", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) return nil } @@ -156,40 +173,55 @@ func (s *SouinBaseHandler) Store( } } + hasFreshness := false ma := currentMatchedURL.TTL.Duration if responseCc.SMaxAge >= 0 { ma = time.Duration(responseCc.SMaxAge) * time.Second } else if responseCc.MaxAge >= 0 { ma = time.Duration(responseCc.MaxAge) * time.Second - } - if ma > currentMatchedURL.TTL.Duration { - ma = currentMatchedURL.TTL.Duration + } else if customWriter.Header().Get("Expires") != "" { + exp, err := time.Parse(time.RFC1123, customWriter.Header().Get("Expires")) + if err != nil { + return nil + } + + duration := time.Until(exp) + if duration <= 0 || duration > 10*types.OneYearDuration { + return nil + } + + date, _ := time.Parse(time.RFC1123, customWriter.Header().Get("Date")) + if date.Sub(exp) > 0 { + return nil + } + + ma = duration + hasFreshness = true } now := rq.Context().Value(context.Now).(time.Time) date, _ := http.ParseTime(now.Format(http.TimeFormat)) - customWriter.Headers.Set(rfc.StoredTTLHeader, ma.String()) + customWriter.Header().Set(rfc.StoredTTLHeader, ma.String()) ma = ma - time.Since(date) - if exp := customWriter.Header().Get("Expires"); exp != "" { - delta, _ := time.Parse(exp, time.RFC1123) - if sub := delta.Sub(now); sub > 0 { - ma = sub - } - } - status := fmt.Sprintf("%s; fwd=uri-miss", rq.Context().Value(context.CacheName)) if (modeContext.Bypass_request || !requestCc.NoStore) && - (modeContext.Bypass_response || !responseCc.NoStore) { - headers := customWriter.Headers.Clone() + (modeContext.Bypass_response || !responseCc.NoStore || hasFreshness) { + headers := customWriter.Header().Clone() for hname, shouldDelete := range responseCc.NoCache { if shouldDelete { headers.Del(hname) } } + + customWriter.mutex.Lock() + b := customWriter.Buf.Bytes() + bLen := customWriter.Buf.Len() + customWriter.mutex.Unlock() + res := http.Response{ - StatusCode: customWriter.statusCode, - Body: io.NopCloser(bytes.NewBuffer(customWriter.Buf.Bytes())), + StatusCode: statusCode, + Body: io.NopCloser(bytes.NewBuffer(b)), Header: headers, } @@ -197,17 +229,23 @@ func (s *SouinBaseHandler) Store( res.Header.Set("Date", now.Format(http.TimeFormat)) } if res.Header.Get("Content-Length") == "" { - res.Header.Set("Content-Length", fmt.Sprint(customWriter.Buf.Len())) + res.Header.Set("Content-Length", fmt.Sprint(bLen)) + } + respBodyMaxSize := int(s.Configuration.GetDefaultCache().GetMaxBodyBytes()) + if respBodyMaxSize > 0 && bLen > respBodyMaxSize { + customWriter.Header().Set("Cache-Status", status+"; detail=UPSTREAM-RESPONSE-TOO-LARGE; key="+rfc.GetCacheKeyFromCtx(rq.Context())) + + return nil } res.Header.Set(rfc.StoredLengthHeader, res.Header.Get("Content-Length")) response, err := httputil.DumpResponse(&res, true) - if err == nil { + if err == nil && (bLen > 0 || canStatusCodeEmptyContent(statusCode)) { variedHeaders, isVaryStar := rfc.VariedHeaderAllCommaSepValues(res.Header) if isVaryStar { // "Implies that the response is uncacheable" status += "; detail=UPSTREAM-VARY-STAR" } else { - cachedKey += rfc.GetVariedCacheKey(rq, variedHeaders) + variedKey := cachedKey + rfc.GetVariedCacheKey(rq, variedHeaders) var wg sync.WaitGroup mu := sync.Mutex{} @@ -216,11 +254,25 @@ func (s *SouinBaseHandler) Store( case <-rq.Context().Done(): status += "; detail=REQUEST-CANCELED-OR-UPSTREAM-BROKEN-PIPE" default: + vhs := http.Header{} + for _, hname := range variedHeaders { + hn := strings.Split(hname, ":") + vhs.Set(hn[0], rq.Header.Get(hn[0])) + } for _, storer := range s.Storers { wg.Add(1) go func(currentStorer types.Storer) { defer wg.Done() - if currentStorer.Set(cachedKey, response, ma) != nil { + if currentStorer.SetMultiLevel( + cachedKey, + variedKey, + response, + vhs, + res.Header.Get("Etag"), ma, + variedKey, + ) == nil { + res.Request = rq + } else { mu.Lock() fails = append(fails, fmt.Sprintf("; detail=%s-INSERTION-ERROR", currentStorer.Name())) mu.Unlock() @@ -232,7 +284,7 @@ func (s *SouinBaseHandler) Store( if len(fails) < s.storersLen { go func(rs http.Response, key string) { _ = s.SurrogateKeyStorer.Store(&rs, key, "") - }(res, cachedKey) + }(res, variedKey) status += "; stored" } @@ -241,15 +293,25 @@ func (s *SouinBaseHandler) Store( } } } + + } else { + status += "; detail=UPSTREAM-ERROR-OR-EMPTY-RESPONSE" } } else { status += "; detail=NO-STORE-DIRECTIVE" } - customWriter.Headers.Set("Cache-Status", status+"; key="+rfc.GetCacheKeyFromCtx(rq.Context())) + customWriter.Header().Set("Cache-Status", status+"; key="+rfc.GetCacheKeyFromCtx(rq.Context())) return nil } +type singleflightValue struct { + body []byte + headers http.Header + requestHeaders http.Header + code int +} + func (s *SouinBaseHandler) Upstream( customWriter *CustomWriter, rq *http.Request, @@ -257,71 +319,138 @@ func (s *SouinBaseHandler) Upstream( requestCc *cacheobject.RequestCacheDirectives, cachedKey string, ) error { - if err := next(customWriter, rq); err != nil { - customWriter.Header().Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=SERVE-HTTP-ERROR", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) - return err + var recoveredFromErr error = nil + defer func() { + // In case of "http.ErrAbortHandler" panic, + // prevent singleflight from wrapping it into "singleflight.panicError". + if r := recover(); r != nil { + err, ok := r.(error) + // Sometimes, the error is a string. + if !ok || errors.Is(err, http.ErrAbortHandler) { + recoveredFromErr = http.ErrAbortHandler + } else { + panic(err) + } + } + }() + + singleflightCacheKey := cachedKey + if s.Configuration.GetDefaultCache().IsCoalescingDisable() { + singleflightCacheKey += uuid.NewString() } + sfValue, err, _ := s.singleflightPool.Do(singleflightCacheKey, func() (interface{}, error) { + if e := next(customWriter, rq); e != nil { + customWriter.Header().Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=SERVE-HTTP-ERROR", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) + return nil, e + } - s.SurrogateKeyStorer.Invalidate(rq.Method, customWriter.Header()) - if !isCacheableCode(customWriter.statusCode) { - customWriter.Headers.Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=UNCACHEABLE-STATUS-CODE", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) + s.SurrogateKeyStorer.Invalidate(rq.Method, customWriter.Header()) - switch customWriter.statusCode { - case 500, 502, 503, 504: - return new(upsreamError) + statusCode := customWriter.GetStatusCode() + if !isCacheableCode(statusCode) { + customWriter.Header().Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=UNCACHEABLE-STATUS-CODE", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) + + switch statusCode { + case 500, 502, 503, 504: + return nil, Upstream50xError + } } - return nil - } + headerName, cacheControl := s.SurrogateKeyStorer.GetSurrogateControl(customWriter.Header()) + if cacheControl == "" { + customWriter.Header().Set(headerName, s.DefaultMatchedUrl.DefaultCacheControl) + } - if customWriter.Header().Get("Cache-Control") == "" { - // TODO see with @mnot if mandatory to not store the response when no Cache-Control given. - // if s.DefaultMatchedUrl.DefaultCacheControl == "" { - // customWriter.Headers.Set("Cache-Status", fmt.Sprintf("%s; fwd=uri-miss; key=%s; detail=EMPTY-RESPONSE-CACHE-CONTROL", rq.Context().Value(context.CacheName), rfc.GetCacheKeyFromCtx(rq.Context()))) - // return nil - // } - customWriter.Header().Set("Cache-Control", s.DefaultMatchedUrl.DefaultCacheControl) + err := s.Store(customWriter, rq, requestCc, cachedKey) + defer customWriter.Buf.Reset() + + return singleflightValue{ + body: customWriter.Buf.Bytes(), + headers: customWriter.Header().Clone(), + requestHeaders: rq.Header, + code: statusCode, + }, err + }) + if recoveredFromErr != nil { + panic(recoveredFromErr) + } + if err != nil { + return err } - select { - case <-rq.Context().Done(): - return baseCtx.Canceled - default: - return s.Store(customWriter, rq, requestCc, cachedKey) + if sfWriter, ok := sfValue.(singleflightValue); ok { + if vary := sfWriter.headers.Get("Vary"); vary != "" { + variedHeaders, isVaryStar := rfc.VariedHeaderAllCommaSepValues(sfWriter.headers) + if !isVaryStar { + for _, vh := range variedHeaders { + if rq.Header.Get(vh) != sfWriter.requestHeaders.Get(vh) { + // cachedKey += rfc.GetVariedCacheKey(rq, variedHeaders) + return s.Upstream(customWriter, rq, next, requestCc, cachedKey) + } + } + } + } + _, _ = customWriter.Write(sfWriter.body) + // Yaegi sucks, we can't use maps. + for k := range sfWriter.headers { + customWriter.Header().Set(k, sfWriter.headers.Get(k)) + } + customWriter.WriteHeader(sfWriter.code) } + + return nil } -func (s *SouinBaseHandler) Revalidate(validator *rfc.Revalidator, next handlerFunc, customWriter *CustomWriter, rq *http.Request, requestCc *cacheobject.RequestCacheDirectives, cachedKey string) error { - err := next(customWriter, rq) - s.SurrogateKeyStorer.Invalidate(rq.Method, customWriter.Header()) +func (s *SouinBaseHandler) Revalidate(validator *types.Revalidator, next handlerFunc, customWriter *CustomWriter, rq *http.Request, requestCc *cacheobject.RequestCacheDirectives, cachedKey string, uri string) error { + singleflightCacheKey := cachedKey + if s.Configuration.GetDefaultCache().IsCoalescingDisable() { + singleflightCacheKey += uuid.NewString() + } + sfValue, err, _ := s.singleflightPool.Do(singleflightCacheKey, func() (interface{}, error) { + err := next(customWriter, rq) + s.SurrogateKeyStorer.Invalidate(rq.Method, customWriter.Header()) - if err == nil { - if validator.IfUnmodifiedSincePresent && customWriter.statusCode != http.StatusNotModified { - customWriter.Buf.Reset() - for h, v := range customWriter.Headers { - if len(v) > 0 { - customWriter.Rw.Header().Set(h, strings.Join(v, ", ")) - } + statusCode := customWriter.GetStatusCode() + if err == nil { + if validator.IfUnmodifiedSincePresent && statusCode != http.StatusNotModified { + customWriter.Buf.Reset() + customWriter.Rw.WriteHeader(http.StatusPreconditionFailed) + + return nil, errors.New("") } - customWriter.Rw.WriteHeader(http.StatusPreconditionFailed) - return errors.New("") + if statusCode != http.StatusNotModified { + err = s.Store(customWriter, rq, requestCc, cachedKey) + } } - if customWriter.statusCode != http.StatusNotModified { - err = s.Store(customWriter, rq, requestCc, cachedKey) + customWriter.Header().Set( + "Cache-Status", + fmt.Sprintf( + "%s; fwd=request; fwd-status=%d; key=%s; detail=REQUEST-REVALIDATION", + rq.Context().Value(context.CacheName), + statusCode, + rfc.GetCacheKeyFromCtx(rq.Context()), + ), + ) + + defer customWriter.Buf.Reset() + return singleflightValue{ + body: customWriter.Buf.Bytes(), + headers: customWriter.Header().Clone(), + code: statusCode, + }, err + }) + + if sfWriter, ok := sfValue.(singleflightValue); ok { + _, _ = customWriter.Write(sfWriter.body) + // Yaegi sucks, we can't use maps. + for k := range sfWriter.headers { + customWriter.Header().Set(k, sfWriter.headers.Get(k)) } + customWriter.WriteHeader(sfWriter.code) } - customWriter.Header().Set( - "Cache-Status", - fmt.Sprintf( - "%s; fwd=request; fwd-status=%d; key=%s; detail=REQUEST-REVALIDATION", - rq.Context().Value(context.CacheName), - customWriter.statusCode, - rfc.GetCacheKeyFromCtx(rq.Context()), - ), - ) return err } @@ -339,89 +468,128 @@ func (s *SouinBaseHandler) HandleInternally(r *http.Request) (bool, http.Handler } type handlerFunc = func(http.ResponseWriter, *http.Request) error +type statusCodeLogger struct { + http.ResponseWriter + statusCode int +} + +func (s *statusCodeLogger) WriteHeader(code int) { + s.statusCode = code + s.ResponseWriter.WriteHeader(code) +} func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, next handlerFunc) error { - b, handler := s.HandleInternally(rq) - if b { + if b, handler := s.HandleInternally(rq); b { handler(rw, rq) return nil } + req := s.context.SetBaseContext(rq) + cacheName := req.Context().Value(context.CacheName).(string) - rq = s.context.SetBaseContext(rq) - cacheName := rq.Context().Value(context.CacheName).(string) - if rq.Header.Get("Upgrade") == "websocket" || (s.ExcludeRegex != nil && s.ExcludeRegex.MatchString(rq.RequestURI)) { + if rq.Header.Get("Upgrade") == "websocket" || rq.Header.Get("Accept") == "text/event-stream" || (s.ExcludeRegex != nil && s.ExcludeRegex.MatchString(rq.RequestURI)) { rw.Header().Set("Cache-Status", cacheName+"; fwd=bypass; detail=EXCLUDED-REQUEST-URI") - return next(rw, rq) + return next(rw, req) } - if !rq.Context().Value(context.SupportedMethod).(bool) { + if !req.Context().Value(context.SupportedMethod).(bool) { rw.Header().Set("Cache-Status", cacheName+"; fwd=bypass; detail=UNSUPPORTED-METHOD") + nrw := &statusCodeLogger{ + ResponseWriter: rw, + statusCode: 0, + } - err := next(rw, rq) - s.SurrogateKeyStorer.Invalidate(rq.Method, rw.Header()) + err := next(nrw, req) + s.SurrogateKeyStorer.Invalidate(req.Method, rw.Header()) + + if err == nil && req.Method != http.MethodGet && nrw.statusCode < http.StatusBadRequest { + // Invalidate related GET keys when the method is not allowed and the response is valid + req.Method = http.MethodGet + keyname := s.context.SetContext(req, rq).Context().Value(context.Key).(string) + for _, storer := range s.Storers { + storer.Delete("IDX_" + keyname) + } + } return err } - requestCc, coErr := cacheobject.ParseRequestCacheControl(rq.Header.Get("Cache-Control")) + requestCc, coErr := cacheobject.ParseRequestCacheControl(rfc.HeaderAllCommaSepValuesString(req.Header, "Cache-Control")) + + modeContext := req.Context().Value(context.Mode).(*context.ModeContext) - modeContext := rq.Context().Value(context.Mode).(*context.ModeContext) if !modeContext.Bypass_request && (coErr != nil || requestCc == nil) { rw.Header().Set("Cache-Status", cacheName+"; fwd=bypass; detail=CACHE-CONTROL-EXTRACTION-ERROR") - err := next(rw, rq) - s.SurrogateKeyStorer.Invalidate(rq.Method, rw.Header()) + err := next(rw, req) + s.SurrogateKeyStorer.Invalidate(req.Method, rw.Header()) return err } - rq = s.context.SetContext(rq) + req = s.context.SetContext(req, rq) + + isMutationRequest := false + // Yaegi sucks AGAIN, it considers the value as nil if we directly try to cast as bool + mutationRequestValue := req.Context().Value(context.IsMutationRequest) + if mutationRequestValue != nil { + isMutationRequest = mutationRequestValue.(bool) + } - // Yaegi sucks again, it considers false as true - isMutationRequest := rq.Context().Value(context.IsMutationRequest).(bool) if isMutationRequest { rw.Header().Set("Cache-Status", cacheName+"; fwd=bypass; detail=IS-MUTATION-REQUEST") - err := next(rw, rq) - s.SurrogateKeyStorer.Invalidate(rq.Method, rw.Header()) + err := next(rw, req) + s.SurrogateKeyStorer.Invalidate(req.Method, rw.Header()) return err } - cachedKey := rq.Context().Value(context.Key).(string) + cachedKey := req.Context().Value(context.Key).(string) + + // Need to copy URL path before calling next because it can alter the URI + uri := req.URL.Path bufPool := s.bufPool.Get().(*bytes.Buffer) bufPool.Reset() defer s.bufPool.Put(bufPool) - customWriter := NewCustomWriter(rq, rw, bufPool) + customWriter := NewCustomWriter(req, rw, bufPool) + go func(req *http.Request, crw *CustomWriter) { <-req.Context().Done() crw.mutex.Lock() crw.headersSent = true crw.mutex.Unlock() - }(rq, customWriter) + }(req, customWriter) + if modeContext.Bypass_request || !requestCc.NoCache { - validator := rfc.ParseRequest(rq) - var response *http.Response + validator := rfc.ParseRequest(req) + var fresh, stale *http.Response + var storerName string for _, currentStorer := range s.Storers { - response = currentStorer.Prefix(cachedKey, rq, validator) - if response != nil { + fresh, stale = currentStorer.GetMultiLevel(cachedKey, req, validator) + + if fresh != nil || stale != nil { + storerName = currentStorer.Name() break } } - if response != nil && (!modeContext.Strict || rfc.ValidateCacheControl(response, requestCc)) { + headerName, _ := s.SurrogateKeyStorer.GetSurrogateControl(customWriter.Header()) + if fresh != nil && (!modeContext.Strict || rfc.ValidateCacheControl(fresh, requestCc)) { + response := fresh if validator.ResponseETag != "" && validator.Matched { - rfc.SetCacheStatusHeader(response, "DEFAULT") - customWriter.Headers = response.Header + rfc.SetCacheStatusHeader(response, storerName) + for h, v := range response.Header { + customWriter.Header()[h] = v + } if validator.NotModified { - customWriter.statusCode = http.StatusNotModified + customWriter.WriteHeader(http.StatusNotModified) customWriter.Buf.Reset() _, _ = customWriter.Send() return nil } - customWriter.statusCode = response.StatusCode + customWriter.WriteHeader(response.StatusCode) _, _ = io.Copy(customWriter.Buf, response.Body) _, _ = customWriter.Send() @@ -429,48 +597,48 @@ func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, n } if validator.NeedRevalidation { - err := s.Revalidate(validator, next, customWriter, rq, requestCc, cachedKey) + err := s.Revalidate(validator, next, customWriter, req, requestCc, cachedKey, uri) _, _ = customWriter.Send() return err } - if resCc, _ := cacheobject.ParseResponseCacheControl(response.Header.Get("Cache-Control")); resCc.NoCachePresent { - err := s.Revalidate(validator, next, customWriter, rq, requestCc, cachedKey) + if resCc, _ := cacheobject.ParseResponseCacheControl(rfc.HeaderAllCommaSepValuesString(response.Header, headerName)); resCc.NoCachePresent { + err := s.Revalidate(validator, next, customWriter, req, requestCc, cachedKey, uri) _, _ = customWriter.Send() return err } - rfc.SetCacheStatusHeader(response, "DEFAULT") + rfc.SetCacheStatusHeader(response, storerName) if !modeContext.Strict || rfc.ValidateMaxAgeCachedResponse(requestCc, response) != nil { - customWriter.Headers = response.Header - customWriter.statusCode = response.StatusCode + for h, v := range response.Header { + customWriter.Header()[h] = v + } + customWriter.WriteHeader(response.StatusCode) _, _ = io.Copy(customWriter.Buf, response.Body) _, err := customWriter.Send() return err } - } else if response == nil && !requestCc.OnlyIfCached && (requestCc.MaxStaleSet || requestCc.MaxStale > -1) { - for _, currentStorer := range s.Storers { - response = currentStorer.Prefix(storage.StalePrefix+cachedKey, rq, validator) - if response != nil { - break - } - } + } else if !requestCc.OnlyIfCached && (requestCc.MaxStaleSet || requestCc.MaxStale > -1) { + response := stale + if nil != response && (!modeContext.Strict || rfc.ValidateCacheControl(response, requestCc)) { addTime, _ := time.ParseDuration(response.Header.Get(rfc.StoredTTLHeader)) - rfc.SetCacheStatusHeader(response, "DEFAULT") + rfc.SetCacheStatusHeader(response, storerName) - responseCc, _ := cacheobject.ParseResponseCacheControl(response.Header.Get("Cache-Control")) + responseCc, _ := cacheobject.ParseResponseCacheControl(rfc.HeaderAllCommaSepValuesString(response.Header, "Cache-Control")) if responseCc.StaleWhileRevalidate > 0 { - customWriter.Headers = response.Header - customWriter.statusCode = response.StatusCode + for h, v := range response.Header { + customWriter.Header()[h] = v + } + customWriter.WriteHeader(response.StatusCode) rfc.HitStaleCache(&response.Header) _, _ = io.Copy(customWriter.Buf, response.Body) _, err := customWriter.Send() - customWriter = NewCustomWriter(rq, rw, bufPool) - go func(v *rfc.Revalidator, goCw *CustomWriter, goRq *http.Request, goNext func(http.ResponseWriter, *http.Request) error, goCc *cacheobject.RequestCacheDirectives, goCk string) { - _ = s.Revalidate(v, goNext, goCw, goRq, goCc, goCk) - }(validator, customWriter, rq, next, requestCc, cachedKey) + customWriter = NewCustomWriter(req, rw, bufPool) + go func(v *types.Revalidator, goCw *CustomWriter, goRq *http.Request, goNext func(http.ResponseWriter, *http.Request) error, goCc *cacheobject.RequestCacheDirectives, goCk string, goUri string) { + _ = s.Revalidate(v, goNext, goCw, goRq, goCc, goCk, goUri) + }(validator, customWriter, req, next, requestCc, cachedKey, uri) buf := s.bufPool.Get().(*bytes.Buffer) buf.Reset() defer s.bufPool.Put(buf) @@ -479,15 +647,20 @@ func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, n } if responseCc.MustRevalidate || responseCc.NoCachePresent || validator.NeedRevalidation { - rq.Header["If-None-Match"] = append(rq.Header["If-None-Match"], validator.ResponseETag) - err := s.Revalidate(validator, next, customWriter, rq, requestCc, cachedKey) + req.Header["If-None-Match"] = append(req.Header["If-None-Match"], validator.ResponseETag) + err := s.Revalidate(validator, next, customWriter, req, requestCc, cachedKey, uri) + statusCode := customWriter.GetStatusCode() if err != nil { if responseCc.StaleIfError > -1 || requestCc.StaleIfError > 0 { - code := fmt.Sprintf("; fwd-status=%d", customWriter.statusCode) - customWriter.Headers = response.Header - customWriter.statusCode = response.StatusCode + code := fmt.Sprintf("; fwd-status=%d", statusCode) rfc.HitStaleCache(&response.Header) response.Header.Set("Cache-Status", response.Header.Get("Cache-Status")+code) + // Yaegi sucks, we can't use maps. + for k := range response.Header { + customWriter.Header().Set(k, response.Header.Get(k)) + } + customWriter.WriteHeader(response.StatusCode) + customWriter.Buf.Reset() _, _ = io.Copy(customWriter.Buf, response.Body) _, err := customWriter.Send() @@ -500,11 +673,14 @@ func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, n return err } - if customWriter.statusCode == http.StatusNotModified { + if statusCode == http.StatusNotModified { if !validator.Matched { - rfc.SetCacheStatusHeader(response, "DEFAULT") - customWriter.statusCode = response.StatusCode - customWriter.Headers = response.Header + rfc.SetCacheStatusHeader(response, storerName) + customWriter.WriteHeader(response.StatusCode) + // Yaegi sucks, we can't use maps. + for k := range response.Header { + customWriter.Header().Set(k, response.Header.Get(k)) + } _, _ = io.Copy(customWriter.Buf, response.Body) _, _ = customWriter.Send() @@ -512,8 +688,8 @@ func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, n } } - if customWriter.statusCode != http.StatusNotModified && validator.Matched { - customWriter.statusCode = http.StatusNotModified + if statusCode != http.StatusNotModified && validator.Matched { + customWriter.WriteHeader(http.StatusNotModified) customWriter.Buf.Reset() _, _ = customWriter.Send() @@ -525,27 +701,64 @@ func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, n return err } - if rfc.ValidateMaxAgeCachedStaleResponse(requestCc, response, int(addTime.Seconds())) != nil { - customWriter.Headers = response.Header - customWriter.statusCode = response.StatusCode + if !modeContext.Strict || rfc.ValidateMaxAgeCachedStaleResponse(requestCc, responseCc, response, int(addTime.Seconds())) != nil { + customWriter.WriteHeader(response.StatusCode) rfc.HitStaleCache(&response.Header) + // Yaegi sucks, we can't use maps. + for k := range response.Header { + customWriter.Header().Set(k, response.Header.Get(k)) + } _, _ = io.Copy(customWriter.Buf, response.Body) _, err := customWriter.Send() return err } } + } else if stale != nil { + response := stale + addTime, _ := time.ParseDuration(response.Header.Get(rfc.StoredTTLHeader)) + responseCc, _ := cacheobject.ParseResponseCacheControl(rfc.HeaderAllCommaSepValuesString(response.Header, "Cache-Control")) + + if !modeContext.Strict || rfc.ValidateMaxAgeCachedStaleResponse(requestCc, responseCc, response, int(addTime.Seconds())) != nil { + _, _ = time.ParseDuration(response.Header.Get(rfc.StoredTTLHeader)) + rfc.SetCacheStatusHeader(response, storerName) + + responseCc, _ := cacheobject.ParseResponseCacheControl(rfc.HeaderAllCommaSepValuesString(response.Header, "Cache-Control")) + + if responseCc.StaleIfError > -1 || requestCc.StaleIfError > 0 { + err := s.Revalidate(validator, next, customWriter, req, requestCc, cachedKey, uri) + statusCode := customWriter.GetStatusCode() + if err != nil { + code := fmt.Sprintf("; fwd-status=%d", statusCode) + rfc.HitStaleCache(&response.Header) + response.Header.Set("Cache-Status", response.Header.Get("Cache-Status")+code) + // Yaegi sucks, we can't use maps. + for k := range response.Header { + customWriter.Header().Set(k, response.Header.Get(k)) + } + customWriter.WriteHeader(response.StatusCode) + customWriter.Buf.Reset() + _, _ = io.Copy(customWriter.Buf, response.Body) + _, err := customWriter.Send() + + return err + } + } + + } } } errorCacheCh := make(chan error) - go func() { - errorCacheCh <- s.Upstream(customWriter, rq, next, requestCc, cachedKey) - }() + + go func(vr *http.Request, cw *CustomWriter) { + errorCacheCh <- s.Upstream(cw, vr, next, requestCc, cachedKey) + }(req, customWriter) select { - case <-rq.Context().Done(): - switch rq.Context().Err() { + case <-req.Context().Done(): + + switch req.Context().Err() { case baseCtx.DeadlineExceeded: customWriter.WriteHeader(http.StatusGatewayTimeout) rw.Header().Set("Cache-Status", cacheName+"; fwd=bypass; detail=DEADLINE-EXCEEDED") @@ -556,9 +769,15 @@ func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, n default: return nil } + case v := <-errorCacheCh: - if v == nil { + + switch v { + case nil: + _, _ = customWriter.Send() + case Upstream50xError: _, _ = customWriter.Send() + return nil } return v } diff --git a/plugins/traefik/vendor/github.com/darkweak/souin/pkg/middleware/writer.go b/plugins/traefik/vendor/github.com/darkweak/souin/pkg/middleware/writer.go index ddbccfdbd..97b479bd7 100644 --- a/plugins/traefik/vendor/github.com/darkweak/souin/pkg/middleware/writer.go +++ b/plugins/traefik/vendor/github.com/darkweak/souin/pkg/middleware/writer.go @@ -3,10 +3,9 @@ package middleware import ( "bytes" "net/http" - "strings" + "strconv" "sync" - "github.com/darkweak/go-esi/esi" "github.com/darkweak/souin/pkg/rfc" ) @@ -37,19 +36,28 @@ type CustomWriter struct { headersSent bool mutex *sync.Mutex statusCode int - // size int } // Header will write the response headers func (r *CustomWriter) Header() http.Header { r.mutex.Lock() defer r.mutex.Unlock() - if r.headersSent { + + if r.headersSent || r.Req.Context().Err() != nil { return http.Header{} } + return r.Rw.Header() } +// GetStatusCode returns the response status code +func (r *CustomWriter) GetStatusCode() int { + r.mutex.Lock() + defer r.mutex.Unlock() + + return r.statusCode +} + // WriteHeader will write the response headers func (r *CustomWriter) WriteHeader(code int) { r.mutex.Lock() @@ -57,14 +65,13 @@ func (r *CustomWriter) WriteHeader(code int) { if r.headersSent { return } - r.Headers = r.Rw.Header() r.statusCode = code - // r.headersSent = true - // r.Rw.WriteHeader(code) } // Write will write the response body func (r *CustomWriter) Write(b []byte) (int, error) { + r.mutex.Lock() + defer r.mutex.Unlock() r.Buf.Grow(len(b)) _, _ = r.Buf.Write(b) @@ -73,24 +80,20 @@ func (r *CustomWriter) Write(b []byte) (int, error) { // Send delays the response to handle Cache-Status func (r *CustomWriter) Send() (int, error) { - contentLength := r.Headers.Get(rfc.StoredLengthHeader) - if contentLength != "" { - r.Header().Set("Content-Length", contentLength) - } defer r.Buf.Reset() - b := esi.Parse(r.Buf.Bytes(), r.Req) - for h, v := range r.Headers { - if len(v) > 0 { - r.Rw.Header().Set(h, strings.Join(v, ", ")) - } + storedLength := r.Header().Get(rfc.StoredLengthHeader) + if storedLength != "" { + r.Header().Set("Content-Length", storedLength) + } + b := r.Buf.Bytes() + if len(b) != 0 { + r.Header().Set("Content-Length", strconv.Itoa(len(b))) } r.Header().Del(rfc.StoredLengthHeader) r.Header().Del(rfc.StoredTTLHeader) if !r.headersSent { - - // r.Rw.Header().Set("Content-Length", fmt.Sprintf("%d", len(b))) - r.Rw.WriteHeader(r.statusCode) + r.Rw.WriteHeader(r.GetStatusCode()) r.headersSent = true } diff --git a/plugins/traefik/vendor/github.com/darkweak/souin/pkg/rfc/age.go b/plugins/traefik/vendor/github.com/darkweak/souin/pkg/rfc/age.go index 03b8593a9..03f12acbc 100644 --- a/plugins/traefik/vendor/github.com/darkweak/souin/pkg/rfc/age.go +++ b/plugins/traefik/vendor/github.com/darkweak/souin/pkg/rfc/age.go @@ -31,11 +31,25 @@ func ValidateMaxAgeCachedResponse(co *cacheobject.RequestCacheDirectives, res *h return validateMaxAgeCachedResponse(res, int(ma), 0) } -func ValidateMaxAgeCachedStaleResponse(co *cacheobject.RequestCacheDirectives, res *http.Response, addTime int) *http.Response { +func ValidateMaxAgeCachedStaleResponse(co *cacheobject.RequestCacheDirectives, resCo *cacheobject.ResponseCacheDirectives, res *http.Response, addTime int) *http.Response { if co.MaxStaleSet { return res } + if resCo != nil && (resCo.StaleIfError > -1 || co.StaleIfError > 0) { + if resCo.StaleIfError > -1 { + if response := validateMaxAgeCachedResponse(res, int(resCo.StaleIfError), addTime); response != nil { + return response + } + } + + if co.StaleIfError > 0 { + if response := validateMaxAgeCachedResponse(res, int(co.StaleIfError), addTime); response != nil { + return response + } + } + } + if co.MaxStale < 0 { return nil } diff --git a/plugins/traefik/vendor/github.com/darkweak/souin/pkg/rfc/revalidation.go b/plugins/traefik/vendor/github.com/darkweak/souin/pkg/rfc/revalidation.go index c0eb363b3..ccb6e73be 100644 --- a/plugins/traefik/vendor/github.com/darkweak/souin/pkg/rfc/revalidation.go +++ b/plugins/traefik/vendor/github.com/darkweak/souin/pkg/rfc/revalidation.go @@ -1,29 +1,17 @@ package rfc import ( + "bufio" + "bytes" + "encoding/json" "net/http" "strings" "time" -) -type Revalidator struct { - Matched bool - IfNoneMatchPresent bool - IfMatchPresent bool - IfModifiedSincePresent bool - IfUnmodifiedSincePresent bool - IfUnmotModifiedSincePresent bool - NeedRevalidation bool - NotModified bool - IfModifiedSince time.Time - IfUnmodifiedSince time.Time - IfNoneMatch []string - IfMatch []string - RequestETags []string - ResponseETag string -} + "github.com/darkweak/souin/pkg/storage/types" +) -func ValidateETagFromHeader(etag string, validator *Revalidator) { +func ValidateETagFromHeader(etag string, validator *types.Revalidator) { validator.ResponseETag = etag validator.NeedRevalidation = validator.NeedRevalidation || validator.ResponseETag != "" validator.Matched = validator.ResponseETag == "" || (validator.ResponseETag != "" && len(validator.RequestETags) == 0) @@ -72,7 +60,7 @@ func ValidateETagFromHeader(etag string, validator *Revalidator) { } } -func ParseRequest(req *http.Request) *Revalidator { +func ParseRequest(req *http.Request) *types.Revalidator { var rqEtags []string if len(req.Header.Get("If-None-Match")) > 0 { rqEtags = strings.Split(req.Header.Get("If-None-Match"), ",") @@ -80,7 +68,7 @@ func ParseRequest(req *http.Request) *Revalidator { for i, tag := range rqEtags { rqEtags[i] = strings.Trim(tag, " ") } - validator := Revalidator{ + validator := types.Revalidator{ NotModified: len(rqEtags) > 0, RequestETags: rqEtags, } @@ -106,3 +94,116 @@ func ParseRequest(req *http.Request) *Revalidator { return &validator } + +func DecodeMapping(item []byte) (*StorageMapper, error) { + mapping := &StorageMapper{} + e := json.Unmarshal(item, mapping) + + return mapping, e +} + +func MappingElection(provider types.Storer, item []byte, req *http.Request, validator *types.Revalidator) (resultFresh *http.Response, resultStale *http.Response, e error) { + mapping := &StorageMapper{} + + if len(item) != 0 { + mapping, e = DecodeMapping(item) + if e != nil { + return resultFresh, resultStale, e + } + } + + for keyName, keyItem := range mapping.Mapping { + valid := true + + for hname, hval := range keyItem.VariedHeaders { + if req.Header.Get(hname) != strings.Join(hval, ", ") { + valid = false + + break + } + } + + if !valid { + continue + } + + ValidateETagFromHeader(keyItem.Etag, validator) + + if validator.Matched { + // If the key is fresh enough. + if time.Since(keyItem.FreshTime) < 0 { + response := provider.Get(keyName) + if response != nil { + if resultFresh, e = http.ReadResponse(bufio.NewReader(bytes.NewBuffer(response)), req); e != nil { + return resultFresh, resultStale, e + } + + return resultFresh, resultStale, e + } + } + + // If the key is still stale. + if time.Since(keyItem.StaleTime) < 0 { + response := provider.Get(keyName) + if response != nil { + if resultStale, e = http.ReadResponse(bufio.NewReader(bytes.NewBuffer(response)), req); e != nil { + return resultFresh, resultStale, e + } + } + } + } + } + + return resultFresh, resultStale, e +} + +type KeyIndex struct { + StoredAt time.Time `json:"stored_at,omitempty"` + FreshTime time.Time `json:"fresh_time,omitempty"` + StaleTime time.Time `json:"stale_time,omitempty"` + VariedHeaders map[string][]string `json:"varied_headers,omitempty"` + Etag string `json:"etag,omitempty"` + RealKey string `json:"real_key,omitempty"` +} +type StorageMapper struct { + Mapping map[string]*KeyIndex `json:"mapping,omitempty"` +} + +func MappingUpdater(key string, item []byte, now, freshTime, staleTime time.Time, variedHeaders http.Header, etag, realKey string) (val []byte, e error) { + mapping := &StorageMapper{} + if len(item) != 0 { + e = json.Unmarshal(item, mapping) + if e != nil { + return nil, e + } + } + + if mapping.Mapping == nil { + mapping.Mapping = make(map[string]*KeyIndex) + } + + var pbvariedeheader map[string][]string + if variedHeaders != nil { + pbvariedeheader = make(map[string][]string) + } + + for k, v := range variedHeaders { + pbvariedeheader[k] = append(pbvariedeheader[k], v...) + } + + mapping.Mapping[key] = &KeyIndex{ + StoredAt: now, + FreshTime: freshTime, + StaleTime: staleTime, + VariedHeaders: pbvariedeheader, + Etag: etag, + RealKey: realKey, + } + + val, e = json.Marshal(mapping) + if e != nil { + return nil, e + } + + return val, e +} diff --git a/plugins/traefik/vendor/github.com/darkweak/souin/pkg/storage/abstractProvider.go b/plugins/traefik/vendor/github.com/darkweak/souin/pkg/storage/abstractProvider.go index 65b1c81f3..577d5fade 100644 --- a/plugins/traefik/vendor/github.com/darkweak/souin/pkg/storage/abstractProvider.go +++ b/plugins/traefik/vendor/github.com/darkweak/souin/pkg/storage/abstractProvider.go @@ -20,7 +20,7 @@ const ( type StorerInstanciator func(configurationtypes.AbstractConfigurationInterface) (types.Storer, error) func NewStorages(configuration configurationtypes.AbstractConfigurationInterface) ([]types.Storer, error) { - s, err := CacheConnectionFactory(configuration) + s, err := Factory(configuration) return []types.Storer{s}, err } diff --git a/plugins/traefik/vendor/github.com/darkweak/souin/pkg/storage/cacheProvider.go b/plugins/traefik/vendor/github.com/darkweak/souin/pkg/storage/cacheProvider.go index 8331486c6..dff56aa09 100644 --- a/plugins/traefik/vendor/github.com/darkweak/souin/pkg/storage/cacheProvider.go +++ b/plugins/traefik/vendor/github.com/darkweak/souin/pkg/storage/cacheProvider.go @@ -22,8 +22,8 @@ type Cache struct { var sharedCache *Cache -// CacheConnectionFactory function create new Cache instance -func CacheConnectionFactory(c t.AbstractConfigurationInterface) (types.Storer, error) { +// Factory function create new Cache instance +func Factory(c t.AbstractConfigurationInterface) (types.Storer, error) { provider := cache.New(1 * time.Second) if sharedCache == nil { @@ -38,6 +38,11 @@ func (provider *Cache) Name() string { return "CACHE" } +// Uuid returns an unique identifier +func (provider *Cache) Uuid() string { + return "" +} + // ListKeys method returns the list of existing keys func (provider *Cache) ListKeys() []string { var keys []string @@ -74,8 +79,44 @@ func (provider *Cache) Get(key string) []byte { return result.([]byte) } +// GetMultiLevel tries to load the key and check if one of linked keys is a fresh/stale candidate. +func (provider *Cache) GetMultiLevel(key string, req *http.Request, validator *types.Revalidator) (fresh *http.Response, stale *http.Response) { + result, found := provider.Cache.Get("IDX_" + key) + if !found { + return + } + + fresh, stale, _ = rfc.MappingElection(provider, result.([]byte), req, validator) + + return +} + +// SetMultiLevel tries to store the key with the given value and update the mapping key to store metadata. +func (provider *Cache) SetMultiLevel(baseKey, variedKey string, value []byte, variedHeaders http.Header, etag string, duration time.Duration, realKey string) error { + now := time.Now() + + var e error + + provider.Cache.Set(variedKey, value, duration) + + mappingKey := "IDX_" + baseKey + item, ok := provider.Cache.Get(mappingKey) + var val []byte + if ok { + val = item.([]byte) + } + + val, e = rfc.MappingUpdater(variedKey, val, now, now.Add(duration), now.Add(duration+provider.stale), variedHeaders, etag, realKey) + if e != nil { + return e + } + + provider.Cache.Set(mappingKey, val, 0) + return nil +} + // Prefix method returns the populated response if exists, empty response then -func (provider *Cache) Prefix(key string, req *http.Request, validator *rfc.Revalidator) *http.Response { +func (provider *Cache) Prefix(key string, req *http.Request, validator *types.Revalidator) *http.Response { var result *http.Response provider.Cache.Range(func(k, v interface{}) bool { @@ -103,7 +144,6 @@ func (provider *Cache) Prefix(key string, req *http.Request, validator *rfc.Reva // Set method will store the response in Cache provider func (provider *Cache) Set(key string, value []byte, duration time.Duration) error { provider.Cache.Set(key, value, duration) - provider.Cache.Set(StalePrefix+key, value, provider.stale+duration) return nil } diff --git a/plugins/traefik/vendor/github.com/darkweak/souin/pkg/storage/types/types.go b/plugins/traefik/vendor/github.com/darkweak/souin/pkg/storage/types/types.go index 67031c15e..e41007745 100644 --- a/plugins/traefik/vendor/github.com/darkweak/souin/pkg/storage/types/types.go +++ b/plugins/traefik/vendor/github.com/darkweak/souin/pkg/storage/types/types.go @@ -3,19 +3,41 @@ package types import ( "net/http" "time" - - "github.com/darkweak/souin/pkg/rfc" ) +type Revalidator struct { + Matched bool + IfNoneMatchPresent bool + IfMatchPresent bool + IfModifiedSincePresent bool + IfUnmodifiedSincePresent bool + IfUnmotModifiedSincePresent bool + NeedRevalidation bool + NotModified bool + IfModifiedSince time.Time + IfUnmodifiedSince time.Time + IfNoneMatch []string + IfMatch []string + RequestETags []string + ResponseETag string +} + +const DefaultStorageName = "CACHE" +const OneYearDuration = 365 * 24 * time.Hour + type Storer interface { MapKeys(prefix string) map[string]string ListKeys() []string - Prefix(key string, req *http.Request, validator *rfc.Revalidator) *http.Response Get(key string) []byte Set(key string, value []byte, duration time.Duration) error Delete(key string) DeleteMany(key string) Init() error Name() string + Uuid() string Reset() error + + // Multi level storer to handle fresh/stale at once + GetMultiLevel(key string, req *http.Request, validator *Revalidator) (fresh *http.Response, stale *http.Response) + SetMultiLevel(baseKey, variedKey string, value []byte, variedHeaders http.Header, etag string, duration time.Duration, realKey string) error } diff --git a/plugins/traefik/vendor/modules.txt b/plugins/traefik/vendor/modules.txt index 2e2975c6f..a706e2349 100644 --- a/plugins/traefik/vendor/modules.txt +++ b/plugins/traefik/vendor/modules.txt @@ -80,7 +80,7 @@ github.com/darkweak/souin/pkg/storage github.com/darkweak/souin/pkg/storage/types github.com/darkweak/souin/pkg/surrogate github.com/darkweak/souin/pkg/surrogate/providers -# github.com/darkweak/storages/core v0.0.8 +# github.com/darkweak/storages/core v0.0.11 ## explicit; go 1.22.1 github.com/darkweak/storages/core # github.com/dgraph-io/badger v1.6.2 diff --git a/plugins/tyk/go.mod b/plugins/tyk/go.mod index 2148daeb3..98bc3e91d 100644 --- a/plugins/tyk/go.mod +++ b/plugins/tyk/go.mod @@ -6,7 +6,7 @@ require ( github.com/TykTechnologies/tyk v1.9.2-0.20230330071232-370295d796b5 github.com/cespare/xxhash/v2 v2.2.0 github.com/darkweak/souin v1.7.2 - github.com/darkweak/souin/plugins/souin v0.0.0-00010101000000-000000000000 + github.com/darkweak/souin/plugins/souin v1.7.2 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pquerna/cachecontrol v0.2.0 go.uber.org/zap v1.27.0 @@ -37,7 +37,7 @@ require ( github.com/clbanning/mxj v1.8.4 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/darkweak/go-esi v0.0.5 // indirect - github.com/darkweak/storages/core v0.0.8 // indirect + github.com/darkweak/storages/core v0.0.11 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgraph-io/badger v1.6.2 // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect diff --git a/plugins/tyk/go.sum b/plugins/tyk/go.sum index 142448bad..412584a79 100644 --- a/plugins/tyk/go.sum +++ b/plugins/tyk/go.sum @@ -243,8 +243,8 @@ github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= 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/storages/core v0.0.8 h1:9e7rOxHiJwnvADDVCZ7LFRnUnOHGT+UMpNOFlR8BOiw= -github.com/darkweak/storages/core v0.0.8/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= +github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew= +github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/dave/jennifer v1.4.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/plugins/tyk/main.go b/plugins/tyk/main.go index acc06f6df..5b634733c 100644 --- a/plugins/tyk/main.go +++ b/plugins/tyk/main.go @@ -236,7 +236,7 @@ func SouinRequestHandler(rw http.ResponseWriter, baseRq *http.Request) { return } - if rfc.ValidateMaxAgeCachedStaleResponse(requestCc, response, int(addTime.Seconds())) != nil { + if !modeContext.Strict || rfc.ValidateMaxAgeCachedStaleResponse(requestCc, response, int(addTime.Seconds())) != nil { h := response.Header rfc.HitStaleCache(&h) for hn, hv := range h { diff --git a/plugins/webgo/go.mod b/plugins/webgo/go.mod index 0f04f9a24..dd6c7b906 100644 --- a/plugins/webgo/go.mod +++ b/plugins/webgo/go.mod @@ -5,7 +5,7 @@ go 1.22.1 require ( github.com/bnkamalesh/webgo/v6 v6.7.0 github.com/darkweak/souin v1.7.2 - github.com/darkweak/souin/plugins/souin/storages v0.0.0-00010101000000-000000000000 + github.com/darkweak/souin/plugins/souin/storages v1.7.2 ) require ( @@ -38,7 +38,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/darkweak/go-esi v0.0.6 // indirect github.com/darkweak/storages/badger v0.0.8 // indirect - github.com/darkweak/storages/core v0.0.8 // indirect + github.com/darkweak/storages/core v0.0.11 // indirect github.com/darkweak/storages/etcd v0.0.8 // indirect github.com/darkweak/storages/nats v0.0.8 // indirect github.com/darkweak/storages/nuts v0.0.8 // indirect @@ -178,7 +178,4 @@ require ( howett.net/plist v1.0.0 // indirect ) -replace ( - github.com/darkweak/souin v1.7.2 => ../.. - github.com/darkweak/souin/plugins/souin/storages => ../souin/storages -) +replace github.com/darkweak/souin v1.7.2 => ../.. diff --git a/plugins/webgo/go.sum b/plugins/webgo/go.sum index dbf25b342..e4a1b95b2 100644 --- a/plugins/webgo/go.sum +++ b/plugins/webgo/go.sum @@ -141,10 +141,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/darkweak/go-esi v0.0.6 h1:eVHCJfqrZwOHPfRK7JTlSYG9F8lfpX/d4lz/41RQkd8= github.com/darkweak/go-esi v0.0.6/go.mod h1:IJSayeQZDUh5R5ayyDC3wUEBykti12aUa0eUxZZeodk= +github.com/darkweak/souin/plugins/souin/storages v1.7.2 h1:vA1oFap6sbWO+Ebbq6NGtjmCFuCRJOZeG+XXPhhSIWA= +github.com/darkweak/souin/plugins/souin/storages v1.7.2/go.mod h1:VfkwGN+ubAuluSwbjGHqImbUjxdEA0N9xGJUTCcFBV0= github.com/darkweak/storages/badger v0.0.8 h1:rKVXrasVA74xgiqGRgW0kH11NUIlWwn9HiFyHUok85k= github.com/darkweak/storages/badger v0.0.8/go.mod h1:ZmrNmKkFzyu/B3+1nsvVeTvyg2I2mOV5yTpT46mZ06o= -github.com/darkweak/storages/core v0.0.8 h1:9e7rOxHiJwnvADDVCZ7LFRnUnOHGT+UMpNOFlR8BOiw= -github.com/darkweak/storages/core v0.0.8/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= +github.com/darkweak/storages/core v0.0.11 h1:IwvpAtkhOmxC5pIffJ8opW6erpTnIi5zqPveiAQs8ew= +github.com/darkweak/storages/core v0.0.11/go.mod h1:ajTpB9IFLRIRY0EEFLjM5vtsrcNTh+TJK9yRxgG5/wY= github.com/darkweak/storages/etcd v0.0.8 h1:Guzv6zgxkQJLjak36KsbtQqkmwMRJoZZI0B7ztZKIik= github.com/darkweak/storages/etcd v0.0.8/go.mod h1:Yw9xJramKAzIRoC7tizVMYPSwUBHqxY5BPTh8OgyISY= github.com/darkweak/storages/nats v0.0.8 h1:HRS3i2zzzIq1Qb3yoOUWD6MoRQgGV1NbECF1ex4wZjg= diff --git a/tests/mock.go b/tests/mock.go index d88e6f532..97606b2b2 100644 --- a/tests/mock.go +++ b/tests/mock.go @@ -278,7 +278,7 @@ urls: ` } -// EtcdConfiguration simulate the configuration for the Nuts storage +// EtcdConfiguration simulate the configuration for the Etcd storage func EtcdConfiguration() string { return ` api: @@ -321,7 +321,7 @@ urls: ` } -// RedisConfiguration simulate the configuration for the Nuts storage +// RedisConfiguration simulate the configuration for the Redis storage func RedisConfiguration() string { return ` api: @@ -362,6 +362,46 @@ urls: ` } +// SimpleFSConfiguration simulate the configuration for the SimpleFS storage +func SimpleFSConfiguration() string { + return ` +api: + basepath: /souin-api + security: + secret: your_secret_key + enable: true + users: + - username: user1 + password: test + souin: + enable: true +default_cache: + simplefs: + path: "./simplefs" + headers: + - Authorization + port: + web: 80 + tls: 443 + regex: + exclude: 'ARegexHere' + ttl: 1000s +reverse_proxy_url: 'http://domain.com:81' +ssl_providers: + - traefik +urls: + 'domain.com/': + ttl: 1000s + headers: + - Authorization + 'mysubdomain.domain.com': + ttl: 50s + headers: + - Authorization + - 'Content-Type' +` +} + func baseEmbeddedOlricConfiguration(path string) string { return fmt.Sprintf(` api: