From fe73f4180aaf556110a734f98b6e95d394a5c3d1 Mon Sep 17 00:00:00 2001 From: darkweak Date: Sun, 27 Aug 2023 22:14:36 +0200 Subject: [PATCH] feat(chore): support http-invalidation RFC as early stage (https://datatracker.ietf.org/doc/draft-nottingham-http-invalidation) --- pkg/api/souin.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/pkg/api/souin.go b/pkg/api/souin.go index da73bcbd4..8e8b847ce 100644 --- a/pkg/api/souin.go +++ b/pkg/api/souin.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "regexp" + "strings" "github.com/darkweak/souin/configurationtypes" "github.com/darkweak/souin/pkg/storage" @@ -17,6 +18,23 @@ type SouinAPI struct { enabled bool storers []storage.Storer surrogateStorage providers.SurrogateInterface + allowedMethods []string +} + +type invalidationType string + +const ( + uriInvalidationType invalidationType = "uri" + uriPrefixInvalidationType invalidationType = "uri-prefix" + originInvalidationType invalidationType = "origin" + groupInvalidationType invalidationType = "group" +) + +type invalidation struct { + Type invalidationType `json:"type"` + Selectors []string `json:"selectors"` + Groups []string `json:"groups"` + Purge bool `json:"purge"` } func initializeSouin( @@ -28,11 +46,18 @@ func initializeSouin( if basePath == "" { basePath = "/souin" } + + allowedMethods := configuration.GetDefaultCache().GetAllowedHTTPVerbs() + if len(allowedMethods) == 0 { + allowedMethods = []string{http.MethodGet, http.MethodHead} + } + return &SouinAPI{ basePath, configuration.GetAPI().Souin.Enable, storers, surrogateStorage, + allowedMethods, } } @@ -103,6 +128,78 @@ func (s *SouinAPI) HandleRequest(w http.ResponseWriter, r *http.Request) { res, _ = json.Marshal(s.GetAll()) } w.Header().Set("Content-Type", "application/json") + case http.MethodPost: + var invalidator invalidation + err := json.NewDecoder(r.Body).Decode(invalidator) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + keysToInvalidate := []string{} + switch invalidator.Type { + case groupInvalidationType: + keysToInvalidate, _ = s.surrogateStorage.Purge(http.Header{"Surrogate-Key": invalidator.Groups}) + case uriPrefixInvalidationType, uriInvalidationType: + bodyKeys := []string{} + listedKeys := s.GetAll() + for _, k := range invalidator.Selectors { + if !strings.Contains(k, "//") { + rq, err := http.NewRequest(http.MethodGet, "//"+k, nil) + if err != nil { + continue + } + + bodyKeys = append(bodyKeys, rq.Host+"-"+rq.URL.Path) + } + } + + for _, allKey := range listedKeys { + for _, bk := range bodyKeys { + if invalidator.Type == uriInvalidationType { + if strings.Contains(allKey, bk) && strings.Contains(allKey, bk+"-") && strings.HasSuffix(allKey, bk) { + keysToInvalidate = append(keysToInvalidate, allKey) + break + } + } else { + if strings.Contains(allKey, bk) && + (strings.Contains(allKey, bk+"-") || strings.Contains(allKey, bk+"?") || strings.Contains(allKey, bk+"/") || strings.HasSuffix(allKey, bk)) { + keysToInvalidate = append(keysToInvalidate, allKey) + break + } + } + } + } + case originInvalidationType: + bodyKeys := []string{} + listedKeys := s.GetAll() + for _, k := range invalidator.Selectors { + if !strings.Contains(k, "//") { + rq, err := http.NewRequest(http.MethodGet, "//"+k, nil) + if err != nil { + continue + } + + bodyKeys = append(bodyKeys, rq.Host) + } + } + + for _, allKey := range listedKeys { + for _, bk := range bodyKeys { + if strings.Contains(allKey, bk) { + keysToInvalidate = append(keysToInvalidate, allKey) + break + } + } + } + } + + for _, k := range keysToInvalidate { + for _, current := range s.storers { + current.Delete(k) + } + } + w.WriteHeader(http.StatusOK) case "PURGE": if compile { keysRg := regexp.MustCompile(s.GetBasePath() + "/(.+)")