From 00c4b70a9122d5ebb167493937e89641993d4707 Mon Sep 17 00:00:00 2001 From: darkweak Date: Sun, 22 Dec 2024 18:11:18 +0100 Subject: [PATCH 1/6] fix(multiple): december 2024 wave #584 --- pkg/middleware/middleware.go | 4 +-- plugins/caddy/httpcache_test.go | 59 +++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go index 18302bfaa..c46c9e9bd 100644 --- a/pkg/middleware/middleware.go +++ b/pkg/middleware/middleware.go @@ -922,10 +922,10 @@ func (s *SouinBaseHandler) ServeHTTP(rw http.ResponseWriter, rq *http.Request, n case <-req.Context().Done(): switch req.Context().Err() { case baseCtx.DeadlineExceeded: - customWriter.WriteHeader(http.StatusGatewayTimeout) - s.Configuration.GetLogger().Infof("Internal server error on endpoint %s: %v", req.URL, s.Storers) rw.Header().Set("Cache-Status", cacheName+"; fwd=bypass; detail=DEADLINE-EXCEEDED") + customWriter.Rw.WriteHeader(http.StatusGatewayTimeout) _, _ = customWriter.Rw.Write([]byte("Internal server error")) + s.Configuration.GetLogger().Infof("Internal server error on endpoint %s: %v", req.URL, s.Storers) return baseCtx.DeadlineExceeded case baseCtx.Canceled: return baseCtx.Canceled diff --git a/plugins/caddy/httpcache_test.go b/plugins/caddy/httpcache_test.go index 2b4270997..419f43652 100644 --- a/plugins/caddy/httpcache_test.go +++ b/plugins/caddy/httpcache_test.go @@ -1281,3 +1281,62 @@ func TestAllowedAdditionalStatusCode(t *testing.T) { t.Error("Age header should be present") } } + +type testTimeoutHandler struct { + iterator int +} + +func (t *testTimeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + t.iterator++ + if t.iterator%2 == 0 { + time.Sleep(5 * time.Second) + + return + } + + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("Hello timeout!")) +} + +func TestTimeout(t *testing.T) { + tester := caddytest.NewTester(t) + tester.InitServer(` + { + admin localhost:2999 + http_port 9080 + cache { + ttl 1ns + stale 1ns + timeout { + backend 1s + } + } + } + localhost:9080 { + route /cache-timeout { + cache + reverse_proxy localhost:9086 + } + }`, "caddyfile") + + go func() { + errorHandler := testTimeoutHandler{} + _ = http.ListenAndServe(":9086", &errorHandler) + }() + time.Sleep(time.Second) + resp1, _ := tester.AssertGetResponse(`http://localhost:9080/cache-timeout`, http.StatusOK, "Hello timeout!") + time.Sleep(time.Millisecond) + resp2, _ := tester.AssertGetResponse(`http://localhost:9080/cache-timeout`, http.StatusGatewayTimeout, "Internal server error") + + if resp1.Header.Get("Cache-Status") != "Souin; fwd=uri-miss; stored; key=GET-http-localhost:9080-/cache-timeout" { + 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-Status") != "Souin; fwd=bypass; detail=DEADLINE-EXCEEDED" { + t.Errorf("unexpected resp2 Cache-Status header %v", resp2.Header.Get("Cache-Status")) + } +} From ed12ace71505cd585c11d00117927771efd5dc8e Mon Sep 17 00:00:00 2001 From: darkweak Date: Sun, 22 Dec 2024 21:04:35 +0100 Subject: [PATCH 2/6] fix(now context): https://github.com/caddyserver/cache-handler/issues/116 --- context/now.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/context/now.go b/context/now.go index d0d4e0f3b..d4e7ccd62 100644 --- a/context/now.go +++ b/context/now.go @@ -19,8 +19,16 @@ func (*nowContext) SetContextWithBaseRequest(req *http.Request, _ *http.Request) func (cc *nowContext) SetupContext(_ configurationtypes.AbstractConfigurationInterface) {} func (cc *nowContext) SetContext(req *http.Request) *http.Request { - now := time.Now().UTC() - req.Header.Set("Date", now.Format(time.RFC1123)) + var now time.Time + var e error + + now, e = time.Parse(time.RFC1123, req.Header.Get("Date")) + + if e != nil { + now := time.Now() + req.Header.Set("Date", now.Format(time.RFC1123)) + } + return req.WithContext(context.WithValue(req.Context(), Now, now)) } From cae85eea6558e65bf2eb049a4c885a648fd781d7 Mon Sep 17 00:00:00 2001 From: darkweak Date: Mon, 23 Dec 2024 22:08:33 +0100 Subject: [PATCH 3/6] fix(surrogate-key): unescape if the key contains % #583 --- context/now.go | 2 +- pkg/surrogate/providers/common.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/context/now.go b/context/now.go index d4e7ccd62..da52beb39 100644 --- a/context/now.go +++ b/context/now.go @@ -25,7 +25,7 @@ func (cc *nowContext) SetContext(req *http.Request) *http.Request { now, e = time.Parse(time.RFC1123, req.Header.Get("Date")) if e != nil { - now := time.Now() + now = time.Now().UTC() req.Header.Set("Date", now.Format(time.RFC1123)) } diff --git a/pkg/surrogate/providers/common.go b/pkg/surrogate/providers/common.go index 60b9315b7..2f79a2ebf 100644 --- a/pkg/surrogate/providers/common.go +++ b/pkg/surrogate/providers/common.go @@ -81,7 +81,7 @@ func uniqueTag(values []string) []string { if _, found := tmp[item]; !found { tmp[item] = true - if strings.Contains(item, "%3B") || strings.Contains(item, "%3A") { + if strings.Contains(item, "%") { item, _ = url.QueryUnescape(item) } list = append(list, item) From bcca5508fef84f2c163952731a9138752039ad7a Mon Sep 17 00:00:00 2001 From: darkweak Date: Thu, 26 Dec 2024 22:25:53 +0100 Subject: [PATCH 4/6] fix(rfc): Last-Modified handling #588 --- pkg/middleware/middleware.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go index c46c9e9bd..7dd00948b 100644 --- a/pkg/middleware/middleware.go +++ b/pkg/middleware/middleware.go @@ -556,6 +556,17 @@ func (s *SouinBaseHandler) Revalidate(validator *core.Revalidator, next handlerF return nil, errors.New("") } + if validator.IfModifiedSincePresent { + if lastModified, err := time.Parse(time.RFC1123, customWriter.Header().Get("Last-Modified")); err == nil && validator.IfModifiedSince.Sub(lastModified) > 0 { + customWriter.handleBuffer(func(b *bytes.Buffer) { + b.Reset() + }) + customWriter.Rw.WriteHeader(http.StatusNotModified) + + return nil, errors.New("") + } + } + if statusCode != http.StatusNotModified { err = s.Store(customWriter, rq, requestCc, cachedKey, uri) } From bf9d7e9e49ac09e22becdbf0f3e9e88f2c691370 Mon Sep 17 00:00:00 2001 From: darkweak Date: Mon, 20 Jan 2025 18:01:51 +0100 Subject: [PATCH 5/6] feat(caddy): handle API directly using the caddy admin api, solves #585 --- plugins/caddy/admin.go | 82 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 plugins/caddy/admin.go diff --git a/plugins/caddy/admin.go b/plugins/caddy/admin.go new file mode 100644 index 000000000..4fd9b844a --- /dev/null +++ b/plugins/caddy/admin.go @@ -0,0 +1,82 @@ +package httpcache + +import ( + "fmt" + "github.com/caddyserver/caddy/v2" + "github.com/darkweak/souin/pkg/api" + "net/http" + "strings" + + "github.com/darkweak/storages/core" +) + +func init() { + caddy.RegisterModule(new(adminAPI)) +} + +// adminAPI is a module that serves PKI endpoints to retrieve +// information about the CAs being managed by Caddy. +type adminAPI struct { + ctx caddy.Context + logger core.Logger + app *SouinApp + InternalEndpointHandlers *api.MapHandler +} + +// CaddyModule returns the Caddy module information. +func (adminAPI) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "admin.api.souin", + New: func() caddy.Module { return new(adminAPI) }, + } +} + +func (a *adminAPI) handleAPIEndpoints(writer http.ResponseWriter, request *http.Request) error { + if a.InternalEndpointHandlers != nil { + for k, handler := range *a.InternalEndpointHandlers.Handlers { + if strings.Contains(request.RequestURI, k) { + handler(writer, request) + return nil + } + } + } + + return caddy.APIError{ + HTTPStatus: http.StatusNotFound, + Err: fmt.Errorf("resource not found: %v", request.URL.Path), + } +} + +// Provision sets up the adminAPI module. +func (a *adminAPI) Provision(ctx caddy.Context) error { + a.ctx = ctx + a.logger = ctx.Logger(a).Sugar() + + app, err := ctx.App(moduleName) + if err != nil { + return err + } + + a.app = app.(*SouinApp) + config := Configuration{ + API: a.app.API, + } + a.InternalEndpointHandlers = api.GenerateHandlerMap(&config, a.app.Storers, a.app.SurrogateStorage) + + return nil +} + +// Routes returns the admin routes for the PKI app. +func (a *adminAPI) Routes() []caddy.AdminRoute { + basepath := "/souin-api" + if a.app != nil && a.app.API.BasePath != "" { + basepath = a.app.API.BasePath + } + + return []caddy.AdminRoute{ + { + Pattern: basepath + "/{params...}", + Handler: caddy.AdminHandlerFunc(a.handleAPIEndpoints), + }, + } +} From fad3f2f3162e1f9f588fa7a4bb70cdaa41403374 Mon Sep 17 00:00:00 2001 From: darkweak Date: Mon, 20 Jan 2025 18:44:49 +0100 Subject: [PATCH 6/6] fix(ci): remove useless ttl check --- plugins/caddy/admin.go | 7 +++++++ plugins/caddy/app.go | 6 +----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/plugins/caddy/admin.go b/plugins/caddy/admin.go index 4fd9b844a..4cc02aee1 100644 --- a/plugins/caddy/admin.go +++ b/plugins/caddy/admin.go @@ -3,9 +3,11 @@ package httpcache import ( "fmt" "github.com/caddyserver/caddy/v2" + "github.com/darkweak/souin/configurationtypes" "github.com/darkweak/souin/pkg/api" "net/http" "strings" + "time" "github.com/darkweak/storages/core" ) @@ -60,6 +62,11 @@ func (a *adminAPI) Provision(ctx caddy.Context) error { a.app = app.(*SouinApp) config := Configuration{ API: a.app.API, + DefaultCache: DefaultCache{ + TTL: configurationtypes.Duration{ + Duration: 120 * time.Second, + }, + }, } a.InternalEndpointHandlers = api.GenerateHandlerMap(&config, a.app.Storers, a.app.SurrogateStorage) diff --git a/plugins/caddy/app.go b/plugins/caddy/app.go index 3bcc65031..1e1f09d01 100644 --- a/plugins/caddy/app.go +++ b/plugins/caddy/app.go @@ -1,8 +1,6 @@ package httpcache import ( - "errors" - "github.com/caddyserver/caddy/v2" "github.com/darkweak/souin/configurationtypes" "github.com/darkweak/souin/pkg/storage/types" @@ -39,9 +37,7 @@ func (s SouinApp) Start() error { core.ResetRegisteredStorages() _, _ = up.Delete(stored_providers_key) _, _ = up.LoadOrStore(stored_providers_key, newStorageProvider()) - if s.DefaultCache.GetTTL() == 0 { - return errors.New("Invalid/Incomplete default cache declaration") - } + return nil }