Skip to content

Commit

Permalink
fix(plugins): traefik and caddy to use Storers
Browse files Browse the repository at this point in the history
  • Loading branch information
darkweak committed Aug 29, 2023
1 parent e5fdbcf commit d3c6183
Show file tree
Hide file tree
Showing 24 changed files with 1,241 additions and 205 deletions.
11 changes: 0 additions & 11 deletions pkg/middleware/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ func NewHTTPCacheHandler(c configurationtypes.AbstractConfigurationInterface) *S

type SouinBaseHandler struct {
Configuration configurationtypes.AbstractConfigurationInterface
Storer storage.Storer
Storers []storage.Storer
InternalEndpointHandlers *api.MapHandler
ExcludeRegex *regexp.Regexp
Expand Down Expand Up @@ -267,16 +266,6 @@ func (s *SouinBaseHandler) Store(
if len(fails) > 0 {
status += strings.Join(fails, "")
}

// if s.Storer.Set(cachedKey, response, currentMatchedURL, ma) == nil {
// s.Configuration.GetLogger().Sugar().Debugf("Store the cache key %s into the surrogate keys from the following headers %v", cachedKey, res)
// go func(rs http.Response, key string) {
// _ = s.SurrogateKeyStorer.Store(&rs, key)
// }(res, cachedKey)
// status += "; stored"
// } else {
// status += "; detail=STORAGE-INSERTION-ERROR"
// }
}
} else {
status += "; detail=NO-STORE-DIRECTIVE"
Expand Down
2 changes: 1 addition & 1 deletion plugins/caddy/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
type SouinApp struct {
*DefaultCache
// The provider to use.
Storer storage.Storer
Storers []storage.Storer
// Surrogate storage to support th econfiguration reload without surrogate-key data loss.
SurrogateStorage providers.SurrogateInterface
// Cache-key tweaking.
Expand Down
2 changes: 1 addition & 1 deletion plugins/caddy/httpcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ func (s *SouinCaddyMiddleware) Provision(ctx caddy.Context) error {
})

if l && e == nil {
s.SouinBaseHandler.Storer = v.(storage.Storer)
s.SouinBaseHandler.Storers = append(s.SouinBaseHandler.Storers, v.(storage.Storer))
}
} else {
s.logger.Sugar().Debug("Store the olric instance.")
Expand Down
8 changes: 4 additions & 4 deletions plugins/traefik/override/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type MapHandler struct {
// GenerateHandlerMap generate the MapHandler
func GenerateHandlerMap(
configuration configurationtypes.AbstractConfigurationInterface,
storer storage.Storer,
storers []storage.Storer,
surrogateStorage providers.SurrogateInterface,
) *MapHandler {
hm := make(map[string]http.HandlerFunc)
Expand All @@ -28,7 +28,7 @@ func GenerateHandlerMap(
basePathAPIS = "/souin-api"
}

for _, endpoint := range Initialize(configuration, storer, surrogateStorage) {
for _, endpoint := range Initialize(configuration, storers, surrogateStorage) {
if endpoint.IsEnabled() {
shouldEnable = true
if e, ok := endpoint.(*SouinAPI); ok {
Expand All @@ -45,6 +45,6 @@ func GenerateHandlerMap(
}

// Initialize contains all apis that should be enabled
func Initialize(c configurationtypes.AbstractConfigurationInterface, storer storage.Storer, surrogateStorage providers.SurrogateInterface) []EndpointInterface {
return []EndpointInterface{initializeSouin(c, storer, surrogateStorage)}
func Initialize(c configurationtypes.AbstractConfigurationInterface, storers []storage.Storer, surrogateStorage providers.SurrogateInterface) []EndpointInterface {
return []EndpointInterface{initializeSouin(c, storers, surrogateStorage)}
}
126 changes: 118 additions & 8 deletions plugins/traefik/override/api/souin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"regexp"
"strings"

"github.com/darkweak/souin/configurationtypes"
"github.com/darkweak/souin/pkg/storage"
Expand All @@ -15,40 +16,73 @@ import (
type SouinAPI struct {
basePath string
enabled bool
storer storage.Storer
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(
configuration configurationtypes.AbstractConfigurationInterface,
storer storage.Storer,
storers []storage.Storer,
surrogateStorage providers.SurrogateInterface,
) *SouinAPI {
basePath := configuration.GetAPI().Souin.BasePath
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,
storer,
storers,
surrogateStorage,
allowedMethods,
}
}

// BulkDelete allow user to delete multiple items with regexp
func (s *SouinAPI) BulkDelete(key string) {
s.storer.DeleteMany(key)
for _, current := range s.storers {
current.DeleteMany(key)
}
}

// Delete will delete a record into the provider cache system and will update the Souin API if enabled
func (s *SouinAPI) Delete(key string) {
s.storer.Delete(key)
for _, current := range s.storers {
current.Delete(key)
}
}

// GetAll will retrieve all stored keys in the provider
func (s *SouinAPI) GetAll() []string {
return s.storer.ListKeys()
keys := []string{}
for _, current := range s.storers {
keys = append(keys, current.ListKeys()...)
}

return keys
}

// GetBasePath will return the basepath for this resource
Expand Down Expand Up @@ -94,13 +128,87 @@ 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() + "/(.+)")
flushRg := regexp.MustCompile(s.GetBasePath() + "/flush$")

if flushRg.FindString(r.RequestURI) != "" {
s.storer.DeleteMany(".+")
for _, current := range s.storers {
current.DeleteMany(".+")
}
e := s.surrogateStorage.Destruct()
if e != nil {
fmt.Printf("Error while purging the surrogate keys: %+v.", e)
Expand All @@ -113,7 +221,9 @@ func (s *SouinAPI) HandleRequest(w http.ResponseWriter, r *http.Request) {
} else {
ck, _ := s.surrogateStorage.Purge(r.Header)
for _, k := range ck {
s.storer.Delete(k)
for _, current := range s.storers {
current.Delete(k)
}
}
}
w.WriteHeader(http.StatusNoContent)
Expand Down
27 changes: 27 additions & 0 deletions plugins/traefik/override/context/mode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package context

import (
"context"
"net/http"

"github.com/darkweak/souin/configurationtypes"
)

const Mode ctxKey = "souin_ctx.MODE"

type ModeContext struct {
Strict, Bypass_request, Bypass_response bool
}

func (mc *ModeContext) SetupContext(c configurationtypes.AbstractConfigurationInterface) {
mode := c.GetDefaultCache().GetMode()
mc.Bypass_request = mode == "bypass" || mode == "bypass_request"
mc.Bypass_response = mode == "bypass" || mode == "bypass_response"
mc.Strict = !mc.Bypass_request && !mc.Bypass_response
}

func (mc *ModeContext) SetContext(req *http.Request) *http.Request {
return req.WithContext(context.WithValue(req.Context(), Mode, mc))
}

var _ ctx = (*cacheContext)(nil)
23 changes: 23 additions & 0 deletions plugins/traefik/override/context/now.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package context

import (
"context"
"net/http"
"time"

"github.com/darkweak/souin/configurationtypes"
)

const Now ctxKey = "souin_ctx.NOW"

type nowContext struct{}

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))
return req.WithContext(context.WithValue(req.Context(), Now, now))
}

var _ ctx = (*nowContext)(nil)
8 changes: 7 additions & 1 deletion plugins/traefik/override/context/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ type (
GraphQL ctx
Key ctx
Method ctx
Mode ctx
Now ctx
Timeout ctx
}
)
Expand All @@ -31,6 +33,8 @@ func GetContext() *Context {
GraphQL: &graphQLContext{},
Key: &keyContext{},
Method: &methodContext{},
Mode: &ModeContext{},
Now: &nowContext{},
Timeout: &timeoutContext{},
}
}
Expand All @@ -40,11 +44,13 @@ func (c *Context) Init(co configurationtypes.AbstractConfigurationInterface) {
c.GraphQL.SetupContext(co)
c.Key.SetupContext(co)
c.Method.SetupContext(co)
c.Mode.SetupContext(co)
c.Now.SetupContext(co)
c.Timeout.SetupContext(co)
}

func (c *Context) SetBaseContext(req *http.Request) *http.Request {
return c.Timeout.SetContext(c.Method.SetContext(c.CacheName.SetContext(req)))
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 {
Expand Down
Loading

0 comments on commit d3c6183

Please sign in to comment.