Skip to content

Commit

Permalink
feat(configuration): extends with allowed_addional_status_code (#577)
Browse files Browse the repository at this point in the history
* feat(configuration): extends with allowed_addional_status_code

* fix(status-code-extension): allow empty responses to be cached

* fix(typo): allowed_additional_status_codes
  • Loading branch information
darkweak authored Nov 21, 2024
1 parent f75ca71 commit e03395b
Show file tree
Hide file tree
Showing 53 changed files with 406 additions and 140 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ default_cache:
- GET
- POST
- HEAD
allowed_additional_status_codes: # Allowed additional HTTP status code to cache.
- 202
- 400
cache_name: Souin # Override the cache name to use in the Cache-Status header
distributed: true # Use Olric or Etcd distributed storage
key:
Expand Down Expand Up @@ -196,6 +199,7 @@ surrogate_keys:
| `cdn.service_id` | The service id if required, depending the provider | `123456_id` |
| `cdn.zone_id` | The zone id if required, depending the provider | `anywhere_zone` |
| `default_cache.allowed_http_verbs` | The HTTP verbs to support cache | `- GET`<br/><br/>`- POST`<br/><br/>`(default: GET, HEAD)` |
| `default_cache.allowed_additional_status_codes` | The additional HTTP status code to support cache | `- 200`<br/><br/>`- 404` |
| `default_cache.badger` | Configure the Badger cache storage | |
| `default_cache.badger.path` | Configure Badger with a file | `/anywhere/badger_configuration.json` |
| `default_cache.badger.configuration` | Configure Badger directly in the Caddyfile or your JSON caddy configuration | [See the Badger configuration for the options](https://dgraph.io/docs/badger/get-started/) |
Expand Down Expand Up @@ -425,6 +429,7 @@ There is the fully configuration below
}
cache {
allowed_http_verbs GET POST PATCH
allowed_additional_status_codes 202
api {
basepath /some-basepath
prometheus {
Expand Down Expand Up @@ -882,6 +887,9 @@ http:
- GET
- POST
- HEAD
allowed_additional_status_codes:
- 202
- 400
cdn:
api_key: XXXX
dynamic: true
Expand Down Expand Up @@ -979,6 +987,9 @@ http:
- GET
- HEAD
- POST
allowed_additional_status_codes:
- 202
- 400
default_cache_control: no-store
log_level: debug
urls:
Expand Down
3 changes: 3 additions & 0 deletions configuration/configuration.sample.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ default_cache: # Required part
- GET
- POST
- HEAD
allowed_additional_status_codes: # Allowed additional HTTP status code to cache.
- 202
- 400
cache_name: Souin # Override the Cache-Status name
distributed: true # Use Olric distributed storage
headers: # Default headers concatenated in stored keys
Expand Down
55 changes: 31 additions & 24 deletions configurationtypes/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,37 +230,43 @@ type Key struct {

// DefaultCache configuration
type DefaultCache struct {
AllowedHTTPVerbs []string `json:"allowed_http_verbs" yaml:"allowed_http_verbs"`
Badger CacheProvider `json:"badger" yaml:"badger"`
CDN CDN `json:"cdn" yaml:"cdn"`
CacheName string `json:"cache_name" yaml:"cache_name"`
Distributed bool `json:"distributed" yaml:"distributed"`
Headers []string `json:"headers" yaml:"headers"`
Key Key `json:"key" yaml:"key"`
Etcd CacheProvider `json:"etcd" yaml:"etcd"`
Mode string `json:"mode" yaml:"mode"`
Nats CacheProvider `json:"nats" yaml:"nats"`
Nuts CacheProvider `json:"nuts" yaml:"nuts"`
Olric CacheProvider `json:"olric" yaml:"olric"`
Otter CacheProvider `json:"otter" yaml:"otter"`
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_cacheable_body_bytes" yaml:"max_cacheable_body_bytes"`
DisableCoalescing bool `json:"disable_coalescing" yaml:"disable_coalescing"`
AllowedHTTPVerbs []string `json:"allowed_http_verbs" yaml:"allowed_http_verbs"`
AllowedAdditionalStatusCodes []int `json:"allowed_additional_status_codes" yaml:"allowed_additional_status_codes"`
Badger CacheProvider `json:"badger" yaml:"badger"`
CDN CDN `json:"cdn" yaml:"cdn"`
CacheName string `json:"cache_name" yaml:"cache_name"`
Distributed bool `json:"distributed" yaml:"distributed"`
Headers []string `json:"headers" yaml:"headers"`
Key Key `json:"key" yaml:"key"`
Etcd CacheProvider `json:"etcd" yaml:"etcd"`
Mode string `json:"mode" yaml:"mode"`
Nats CacheProvider `json:"nats" yaml:"nats"`
Nuts CacheProvider `json:"nuts" yaml:"nuts"`
Olric CacheProvider `json:"olric" yaml:"olric"`
Otter CacheProvider `json:"otter" yaml:"otter"`
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_cacheable_body_bytes" yaml:"max_cacheable_body_bytes"`
DisableCoalescing bool `json:"disable_coalescing" yaml:"disable_coalescing"`
}

// GetAllowedHTTPVerbs returns the allowed verbs to cache
func (d *DefaultCache) GetAllowedHTTPVerbs() []string {
return d.AllowedHTTPVerbs
}

// GetAllowedAdditionalStatusCodes returns the allowed verbs to cache
func (d *DefaultCache) GetAllowedAdditionalStatusCodes() []int {
return d.AllowedAdditionalStatusCodes
}

// GetBadger returns the Badger configuration
func (d *DefaultCache) GetBadger() CacheProvider {
return d.Badger
Expand Down Expand Up @@ -374,6 +380,7 @@ func (d *DefaultCache) IsCoalescingDisable() bool {
// DefaultCacheInterface interface
type DefaultCacheInterface interface {
GetAllowedHTTPVerbs() []string
GetAllowedAdditionalStatusCodes() []int
GetBadger() CacheProvider
GetCacheName() string
GetCDN() CDN
Expand Down
4 changes: 4 additions & 0 deletions docs/website/content/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ The default_cache prefix configure the default cache behavior. (e.g. `default_ca
The allowed_http_verbs prefix configure the HTTP verbs allowed to get cached. (e.g. `default_cache.allowed_http_verbs`).
default: `[GET, HEAD]`

#### Allowed additional status code
The allowed_additional_status_codes prefix configure the additional HTTP status codes allowed to get cached. (e.g. `default_cache.allowed_additional_status_codes`).
default: `[]`

#### Badger
The badger prefix configure the badger storage. (e.g. `default_cache.badger`).

Expand Down
16 changes: 13 additions & 3 deletions pkg/middleware/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,16 @@ func canBypassAuthorizationRestriction(headers http.Header, bypassed []string) b
return strings.Contains(strings.ToLower(headers.Get("Vary")), "authorization")
}

func (s *SouinBaseHandler) hasAllowedAdditionalStatusCodesToCache(code int) bool {
for _, sc := range s.Configuration.GetDefaultCache().GetAllowedAdditionalStatusCodes() {
if sc == code {
return true
}
}

return false
}

func (s *SouinBaseHandler) Store(
customWriter *CustomWriter,
rq *http.Request,
Expand All @@ -212,7 +222,7 @@ func (s *SouinBaseHandler) Store(
uri string,
) error {
statusCode := customWriter.GetStatusCode()
if !isCacheableCode(statusCode) {
if !isCacheableCode(statusCode) && !s.hasAllowedAdditionalStatusCodesToCache(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 {
Expand Down Expand Up @@ -323,7 +333,7 @@ func (s *SouinBaseHandler) Store(
}
res.Header.Set(rfc.StoredLengthHeader, res.Header.Get("Content-Length"))
response, err := httputil.DumpResponse(&res, true)
if err == nil && (bLen > 0 || canStatusCodeEmptyContent(statusCode)) {
if err == nil && (bLen > 0 || canStatusCodeEmptyContent(statusCode) || s.hasAllowedAdditionalStatusCodesToCache(statusCode)) {
variedHeaders, isVaryStar := rfc.VariedHeaderAllCommaSepValues(res.Header)
if isVaryStar {
// "Implies that the response is uncacheable"
Expand Down Expand Up @@ -442,7 +452,7 @@ func (s *SouinBaseHandler) Upstream(
s.SurrogateKeyStorer.Invalidate(rq.Method, customWriter.Header())

statusCode := customWriter.GetStatusCode()
if !isCacheableCode(statusCode) {
if !isCacheableCode(statusCode) && !s.hasAllowedAdditionalStatusCodesToCache(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 {
Expand Down
4 changes: 3 additions & 1 deletion plugins/beego/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ require (
github.com/darkweak/storages/olric v0.0.8 // indirect
github.com/darkweak/storages/otter v0.0.8 // indirect
github.com/darkweak/storages/redis v0.0.8 // indirect
github.com/darkweak/storages/simplefs 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/dgraph-io/badger/v3 v3.2103.5 // indirect
Expand Down Expand Up @@ -94,6 +95,7 @@ require (
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgtype v1.14.0 // indirect
github.com/jackc/pgx/v4 v4.18.3 // indirect
github.com/jellydator/ttlcache/v3 v3.3.0 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/libdns/libdns v0.2.2 // indirect
Expand Down Expand Up @@ -168,7 +170,7 @@ require (
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // 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.8.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
Expand Down
8 changes: 6 additions & 2 deletions plugins/beego/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ github.com/darkweak/storages/otter v0.0.8 h1:Tsv4NiLAiZyvovs4oCWT+WQUYe5YvbFvEx2
github.com/darkweak/storages/otter v0.0.8/go.mod h1:BBYxyWClX3PFdbvns6W95kLNNihq1FxljBYGIzP4snI=
github.com/darkweak/storages/redis v0.0.8 h1:0CHLkImyaI/sYs+IOurYLAxFkrmz5dFblhfpF7oGhQc=
github.com/darkweak/storages/redis v0.0.8/go.mod h1:pypJ5T3hweQWfHzFUjmZWeb1KaNK3ikNg1+rn0G+rD0=
github.com/darkweak/storages/simplefs v0.0.11 h1:BagJxTJVtWCnCsQi4kMuJdHGN8LHzARAEEiQ1WJYJl4=
github.com/darkweak/storages/simplefs v0.0.11/go.mod h1:FegezzdPj5m3ExDea64WxcDxPz7dFdu8G5WoIaYfbLM=
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=
Expand Down Expand Up @@ -384,6 +386,8 @@ github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc=
github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
Expand Down Expand Up @@ -770,8 +774,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand Down
1 change: 1 addition & 0 deletions plugins/caddy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ What does these directives mean?
| Key | Description | Value example |
|:------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------|
| `allowed_http_verbs` | The HTTP verbs allowed to be cached | `GET POST PATCH`<br/><br/>`(default: GET HEAD)` |
| `allowed_additional_status_codes` | The additional HTTP status codes allowed to be cached | `202 400` |
| `api` | The cache-handler API cache management | |
| `api.basepath` | BasePath for all APIs to avoid conflicts | `/your-non-conflict-route`<br/><br/>`(default: /souin-api)` |
| `api.prometheus` | Enable the Prometheus metrics | |
Expand Down
18 changes: 18 additions & 0 deletions plugins/caddy/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
type DefaultCache struct {
// Allowed HTTP verbs to be cached by the system.
AllowedHTTPVerbs []string `json:"allowed_http_verbs"`
// Allowed additional status code to be cached by the system.
AllowedAdditionalStatusCodes []int `json:"allowed_additional_status_codes"`
// Badger provider configuration.
Badger configurationtypes.CacheProvider `json:"badger"`
// The cache name to use in the Cache-Status response header.
Expand Down Expand Up @@ -65,6 +67,11 @@ func (d *DefaultCache) GetAllowedHTTPVerbs() []string {
return d.AllowedHTTPVerbs
}

// GetAllowedAdditionalStatusCodes returns the allowed verbs to cache
func (d *DefaultCache) GetAllowedAdditionalStatusCodes() []int {
return d.AllowedAdditionalStatusCodes
}

// GetBadger returns the Badger configuration
func (d *DefaultCache) GetBadger() configurationtypes.CacheProvider {
return d.Badger
Expand Down Expand Up @@ -364,6 +371,17 @@ func parseConfiguration(cfg *Configuration, h *caddyfile.Dispenser, isGlobal boo
allowed := cfg.DefaultCache.AllowedHTTPVerbs
allowed = append(allowed, h.RemainingArgs()...)
cfg.DefaultCache.AllowedHTTPVerbs = allowed
case "allowed_additional_status_codes":
allowed := cfg.DefaultCache.AllowedAdditionalStatusCodes
additional := h.RemainingArgs()
codes := make([]int, 0)
for _, code := range additional {
if c, err := strconv.Atoi(code); err == nil {
codes = append(codes, c)
}
}
allowed = append(allowed, codes...)
cfg.DefaultCache.AllowedAdditionalStatusCodes = allowed
case "api":
if !isGlobal {
return h.Err("'api' block must be global")
Expand Down
Loading

0 comments on commit e03395b

Please sign in to comment.