Something went wrong. Try again.
forked from darkweak/souin
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354package httpcache
import ( "encoding/json" "net/http" "time"
"github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" "github.com/caddyserver/caddy/v2/modules/caddyhttp" "github.com/darkweak/souin/configurationtypes" "github.com/darkweak/souin/pkg/middleware" surrogates_providers "github.com/darkweak/souin/pkg/surrogate/providers" "github.com/darkweak/storages/core")
const moduleName = "cache"
var up = caddy.NewUsagePool()
func init() { caddy.RegisterModule(SouinCaddyMiddleware{}) httpcaddyfile.RegisterGlobalOption(moduleName, parseCaddyfileGlobalOption) httpcaddyfile.RegisterHandlerDirective(moduleName, parseCaddyfileHandlerDirective) httpcaddyfile.RegisterDirectiveOrder(moduleName, httpcaddyfile.Before, "rewrite")}
// SouinCaddyMiddleware development repository of the cache handler, allows// the user to set up an HTTP cache system, RFC-7234 compliant and// supports the tag based cache purge, distributed and not-distributed// storage, key generation tweaking.type SouinCaddyMiddleware struct { *middleware.SouinBaseHandler logger core.Logger cacheKeys configurationtypes.CacheKeys Configuration Configuration // Logger level, fallback on caddy's one when not redefined. LogLevel string `json:"log_level,omitempty"` // Allowed HTTP verbs to be cached by the system. AllowedHTTPVerbs []string `json:"allowed_http_verbs,omitempty"` // Allowed HTTP verbs to be cached by the system. AllowedAdditionalStatusCodes []string `json:"allowed_additional_status_codes,omitempty"` // Headers to add to the cache key if they are present. Headers []string `json:"headers,omitempty"` // Configure the Badger cache storage. Badger configurationtypes.CacheProvider `json:"badger,omitempty"` // Configure the global key generation. Key configurationtypes.Key `json:"key,omitempty"` // Override the cache key generation matching the pattern. CacheKeys configurationtypes.CacheKeys `json:"cache_keys,omitempty"` // MappingEvictionInterval interval between eviction MappingEvictionInterval configurationtypes.Duration `json:"mapping_eviction_interval"` // 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. Otter configurationtypes.CacheProvider `json:"otter,omitempty"` // Enable the Etcd distributed cache storage. Etcd configurationtypes.CacheProvider `json:"etcd,omitempty"` // Enable the Redis distributed cache storage. Redis configurationtypes.CacheProvider `json:"redis,omitempty"` // Enable the Olric distributed cache storage. Olric configurationtypes.CacheProvider `json:"olric,omitempty"` // Time to live for a key, using time.duration. 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. Storers []string `json:"storers,omitempty"` // The default Cache-Control header value if none set by the upstream server. DefaultCacheControl string `json:"default_cache_control,omitempty"` // The cache name to use in the Cache-Status response header. CacheName string `json:"cache_name,omitempty"`}
// CaddyModule returns the Caddy module information.func (SouinCaddyMiddleware) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ ID: "http.handlers.cache", New: func() caddy.Module { return new(SouinCaddyMiddleware) }, }}
// ServeHTTP implements caddyhttp.MiddlewareHandler.func (s *SouinCaddyMiddleware) ServeHTTP(rw http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { return s.SouinBaseHandler.ServeHTTP(rw, r, func(w http.ResponseWriter, _ *http.Request) error { return next.ServeHTTP(w, r) })}
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, CacheName: s.CacheName, Distributed: s.Olric.URL != "" || s.Olric.Path != "" || s.Olric.Configuration != nil || s.Etcd.Configuration != nil || s.Redis.URL != "" || s.Redis.Configuration != nil, Headers: s.Headers, Olric: s.Olric, Etcd: s.Etcd, Redis: s.Redis, Timeout: s.Timeout, TTL: s.TTL, MappingEvictionInterval: s.MappingEvictionInterval, Stale: s.Stale, Storers: s.Storers, } s.Configuration = Configuration{ CacheKeys: s.cacheKeys, DefaultCache: defaultCache, LogLevel: s.LogLevel, } }
return nil}
func isProviderEmpty(c configurationtypes.CacheProvider) bool { return !c.Found}
// FromApp to initialize configuration from App structure.func (s *SouinCaddyMiddleware) FromApp(app *SouinApp) error { if s.Configuration.GetDefaultCache() == nil { s.Configuration = Configuration{ URLs: make(map[string]configurationtypes.URL), } }
if app.DefaultCache.GetTTL() == 0 { if s.Configuration.DefaultCache.GetTTL() == 0 { app.DefaultCache.TTL = configurationtypes.Duration{Duration: 120 * time.Second} } }
if app.DefaultCache.GetMappingEvictionInterval() == 0 { if s.Configuration.DefaultCache.GetMappingEvictionInterval() == 0 { app.DefaultCache.MappingEvictionInterval = configurationtypes.Duration{Duration: time.Hour} } }
if s.Configuration.GetDefaultCache() == nil { s.Configuration.DefaultCache = DefaultCache{ AllowedHTTPVerbs: app.DefaultCache.AllowedHTTPVerbs, AllowedAdditionalStatusCodes: app.DefaultCache.AllowedAdditionalStatusCodes, Headers: app.Headers, Key: app.Key, TTL: app.TTL, Stale: app.Stale, DefaultCacheControl: app.DefaultCacheControl, MappingEvictionInterval: app.MappingEvictionInterval, CacheName: app.CacheName, Timeout: app.Timeout, } return nil } if s.Configuration.CacheKeys == nil || len(s.Configuration.CacheKeys) == 0 { s.Configuration.CacheKeys = configurationtypes.CacheKeys{} } if s.CacheKeys == nil { s.CacheKeys = app.CacheKeys } for _, cacheKey := range s.Configuration.CacheKeys { for k, v := range cacheKey { s.Configuration.CacheKeys = append( s.Configuration.CacheKeys, map[configurationtypes.RegValue]configurationtypes.Key{k: v}, ) } }
dc := s.Configuration.DefaultCache appDc := app.DefaultCache s.Configuration.DefaultCache.AllowedHTTPVerbs = append(s.Configuration.DefaultCache.AllowedHTTPVerbs, appDc.AllowedHTTPVerbs...) s.Configuration.DefaultCache.AllowedAdditionalStatusCodes = append(s.Configuration.DefaultCache.AllowedAdditionalStatusCodes, appDc.AllowedAdditionalStatusCodes...) s.Configuration.DefaultCache.CDN = app.DefaultCache.CDN if dc.Headers == nil { s.Configuration.DefaultCache.Headers = appDc.Headers }
if s.Configuration.LogLevel == "" { s.Configuration.LogLevel = app.LogLevel } if !s.Configuration.SurrogateKeyDisabled { s.Configuration.SurrogateKeyDisabled = app.SurrogateKeyDisabled } if dc.TTL.Duration == 0 { s.Configuration.DefaultCache.TTL = appDc.TTL } if dc.MappingEvictionInterval.Duration == 0 { s.Configuration.DefaultCache.MappingEvictionInterval = appDc.MappingEvictionInterval } if dc.Stale.Duration == 0 { s.Configuration.DefaultCache.Stale = appDc.Stale } if len(dc.Storers) == 0 { s.Configuration.DefaultCache.Storers = appDc.Storers } if dc.Timeout.Backend.Duration == 0 { s.Configuration.DefaultCache.Timeout.Backend = appDc.Timeout.Backend } if dc.Mode == "" { s.Configuration.DefaultCache.Mode = appDc.Mode } if dc.Timeout.Cache.Duration == 0 { s.Configuration.DefaultCache.Timeout.Cache = appDc.Timeout.Cache } if !dc.Key.DisableBody && !dc.Key.DisableHost && !dc.Key.DisableMethod && !dc.Key.DisableQuery && !dc.Key.DisableScheme && !dc.Key.DisableVary && !dc.Key.Hash && !dc.Key.Hide && len(dc.Key.Headers) == 0 && dc.Key.Template == "" { s.Configuration.DefaultCache.Key = appDc.Key } if dc.DefaultCacheControl == "" { s.Configuration.DefaultCache.DefaultCacheControl = appDc.DefaultCacheControl } if dc.MaxBodyBytes == 0 { s.Configuration.DefaultCache.MaxBodyBytes = appDc.MaxBodyBytes } 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) && 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 }
return nil}
func dispatchStorage(ctx caddy.Context, name string, provider configurationtypes.CacheProvider, stale time.Duration) error { b, _ := json.Marshal(core.Configuration{ Provider: core.CacheProvider{ Path: provider.Path, Configuration: provider.Configuration, URL: provider.URL, }, Stale: stale, }) _, e := ctx.LoadModuleByID("storages.cache."+name, b)
return e}
// Provision to do the provisioning part.func (s *SouinCaddyMiddleware) Provision(ctx caddy.Context) error { s.logger = ctx.Logger(s).Sugar()
if err := s.configurationPropertyMapper(); err != nil { return err }
s.Configuration.SetLogger(s.logger) ctxApp, _ := ctx.App(moduleName) app := ctxApp.(*SouinApp)
if err := s.FromApp(app); err != nil { return err }
s.parseStorages(ctx)
bh := middleware.NewHTTPCacheHandler(&s.Configuration) surrogates, ok := up.LoadOrStore(surrogate_key, bh.SurrogateKeyStorer) if ok { bh.SurrogateKeyStorer = surrogates.(surrogates_providers.SurrogateInterface) }
s.SouinBaseHandler = bh if len(app.Storers) == 0 { app.Storers = s.SouinBaseHandler.Storers }
if app.SurrogateStorage == nil { app.withSurrogateStorer(s.SouinBaseHandler.SurrogateKeyStorer) } else { s.SouinBaseHandler.SurrogateKeyStorer = app.SurrogateStorage }
return nil}
func parseCaddyfileGlobalOption(h *caddyfile.Dispenser, _ interface{}) (interface{}, error) { souinApp := new(SouinApp) cfg := Configuration{ DefaultCache: DefaultCache{ AllowedHTTPVerbs: make([]string, 0), Distributed: false, Headers: []string{}, TTL: configurationtypes.Duration{ Duration: 120 * time.Second, }, MappingEvictionInterval: configurationtypes.Duration{Duration: time.Hour}, DefaultCacheControl: "", CacheName: "", }, URLs: make(map[string]configurationtypes.URL), }
err := parseConfiguration(&cfg, h, true)
souinApp.DefaultCache = cfg.DefaultCache souinApp.API = cfg.API souinApp.CacheKeys = cfg.CacheKeys souinApp.LogLevel = cfg.LogLevel souinApp.SurrogateKeyDisabled = cfg.SurrogateKeyDisabled
return httpcaddyfile.App{ Name: moduleName, Value: caddyconfig.JSON(souinApp, nil), }, err}func parseCaddyfileHandlerDirective(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { var s SouinCaddyMiddleware return &s, s.UnmarshalCaddyfile(h.Dispenser)}func (s *SouinCaddyMiddleware) UnmarshalCaddyfile(h *caddyfile.Dispenser) error { dc := DefaultCache{ AllowedHTTPVerbs: make([]string, 0), } s.Configuration = Configuration{ DefaultCache: dc, }
return parseConfiguration(&s.Configuration, h, false)}
// Interface guardsvar ( _ caddy.CleanerUpper = (*SouinCaddyMiddleware)(nil) _ caddy.Provisioner = (*SouinCaddyMiddleware)(nil) _ caddyhttp.MiddlewareHandler = (*SouinCaddyMiddleware)(nil) _ caddyfile.Unmarshaler = (*SouinCaddyMiddleware)(nil))