Skip to content

Commit

Permalink
feat(chore): support placeholders to generate the key (#514)
Browse files Browse the repository at this point in the history
* feat(chore): support placeholders to generate the key

* fix(tests): unit tests, handle crc nutsdb error

* fix(plugins): traefik issue with yaegi

* fix(context): solve caddy issue on value map placeholder

* feat(tests): add more caddy test cases

* feat(release): prepare v1.6.48
  • Loading branch information
darkweak committed May 28, 2024
1 parent d399288 commit 308e8d5
Show file tree
Hide file tree
Showing 2,235 changed files with 548,198 additions and 1,432 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ default_cache:
headers: # Add headers to the key
- Authorization # Add the header value in the key
- Content-Type # Add the header value in the key
hide: true # Prevent the key from being exposed in the `Cache-Status` HTTP response header
template: "{http.request.method}-{http.request.host}-{http.request.path}" # Use caddy placeholders to create the key (when this option is enabled, disable_* directives are skipped)
etcd: # If distributed is set to true, you'll have to define either the etcd or olric section
configuration: # Configure directly the Etcd client
endpoints: # Define multiple endpoints
Expand Down Expand Up @@ -209,6 +209,7 @@ surrogate_keys:
| `default_cache.key.hash` | Hash the key name in the storage | `true`<br/><br/>`(default: false)` |
| `default_cache.key.headers` | Add headers to the key matching the regexp | `- Authorization`<br/><br/>`- Content-Type`<br/><br/>`- X-Additional-Header` |
| `default_cache.key.hide` | Prevent the key from being exposed in the `Cache-Status` HTTP response header | `true`<br/><br/>`(default: false)` |
| `default_cache.key.template` | Use caddy placeholders to create the key (when this option is enabled, disable_* directives are skipped) | [Placeholders documentation](https://caddyserver.com/docs/caddyfile/concepts#placeholders) |
| `default_cache.mode` | RFC respect tweaking | One of `bypass` `bypass_request` `bypass_response` `strict` (default `strict`) |
| `default_cache.nuts` | Configure the Nuts cache storage | |
| `default_cache.nuts.path` | Set the Nuts file path storage | `/anywhere/nuts/storage` |
Expand Down Expand Up @@ -986,7 +987,7 @@ experimental:
plugins:
souin:
moduleName: github.com/darkweak/souin
version: v1.6.47
version: v1.6.48
```
After that you can declare either the whole configuration at once in the middleware block or by service. See the examples below.
```yaml
Expand Down
Binary file added caddy
Binary file not shown.
5 changes: 5 additions & 0 deletions configurationtypes/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ func (c *CacheKeys) parseJSON(rootDecoder *json.Decoder) {
case "hide":
val, _ := rootDecoder.Token()
key.Hide, _ = strconv.ParseBool(fmt.Sprint(val))
case "template":
val, _ := rootDecoder.Token()
key.Template = fmt.Sprint(val)
case "headers":
val, _ := rootDecoder.Token()
key.Headers = []string{}
Expand Down Expand Up @@ -217,6 +220,7 @@ type Key struct {
DisableScheme bool `json:"disable_scheme,omitempty" yaml:"disable_scheme,omitempty"`
Hash bool `json:"hash,omitempty" yaml:"hash,omitempty"`
Hide bool `json:"hide,omitempty" yaml:"hide,omitempty"`
Template string `json:"template,omitempty" yaml:"template,omitempty"`
Headers []string `json:"headers,omitempty" yaml:"headers,omitempty"`
}

Expand Down Expand Up @@ -413,6 +417,7 @@ type SurrogateKeys struct {
// AbstractConfigurationInterface interface
type AbstractConfigurationInterface interface {
GetUrls() map[string]URL
GetPluginName() string
GetDefaultCache() DefaultCacheInterface
GetAPI() API
GetLogLevel() string
Expand Down
103 changes: 51 additions & 52 deletions context/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"net/http"
"regexp"

"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"github.com/darkweak/souin/configurationtypes"
)

Expand All @@ -24,7 +26,10 @@ type keyContext struct {
displayable bool
hash bool
headers []string
template string
overrides []map[*regexp.Regexp]keyContext

initializer func(r *http.Request) *http.Request
}

func (*keyContext) SetContextWithBaseRequest(req *http.Request, _ *http.Request) *http.Request {
Expand All @@ -40,6 +45,7 @@ func (g *keyContext) SetupContext(c configurationtypes.AbstractConfigurationInte
g.disable_scheme = k.DisableScheme
g.hash = k.Hash
g.displayable = !k.Hide
g.template = k.Template
g.headers = k.Headers

g.overrides = make([]map[*regexp.Regexp]keyContext, 0)
Expand All @@ -54,90 +60,76 @@ func (g *keyContext) SetupContext(c configurationtypes.AbstractConfigurationInte
disable_scheme: v.DisableScheme,
hash: v.Hash,
displayable: !v.Hide,
template: v.Template,
headers: v.Headers,
}})
}
}

switch c.GetPluginName() {
case "caddy":
g.initializer = func(r *http.Request) *http.Request {
return r
}
default:
g.initializer = func(r *http.Request) *http.Request {
repl := caddy.NewReplacer()

return caddyhttp.PrepareRequest(r, repl, nil, nil)
}
}
}

func (g *keyContext) SetContext(req *http.Request) *http.Request {
key := req.URL.Path
var headers []string

hash := g.hash
query := ""
scheme := ""
body := ""
host := ""
method := ""
headerValues := ""
displayable := g.displayable

if !g.disable_query && len(req.URL.RawQuery) > 0 {
func parseKeyInformations(req *http.Request, kCtx keyContext) (query, body, host, scheme, method, headerValues string, headers []string, displayable, hash bool) {
displayable = kCtx.displayable
hash = kCtx.hash

if !kCtx.disable_query && len(req.URL.RawQuery) > 0 {
query += "?" + req.URL.RawQuery
}

if !g.disable_body {
if !kCtx.disable_body {
body = req.Context().Value(HashBody).(string)
}

if !g.disable_host {
if !kCtx.disable_host {
host = req.Host + "-"
}

if !g.disable_scheme {
if !kCtx.disable_scheme {
scheme = "http-"
if req.TLS != nil {
scheme = "https-"
}
}

if !g.disable_method {
if !kCtx.disable_method {
method = req.Method + "-"
}

headers = g.headers
for _, hn := range g.headers {
headers = kCtx.headers
for _, hn := range kCtx.headers {
headerValues += "-" + req.Header.Get(hn)
}

return
}

func (g *keyContext) computeKey(req *http.Request) (key string, headers []string, hash, displayable bool) {
if g.template != "" {
return req.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer).ReplaceAll(g.template, ""), g.headers, g.hash, g.displayable
}
key = req.URL.Path
query, body, host, scheme, method, headerValues, headers, displayable, hash := parseKeyInformations(req, *g)

hasOverride := false
for _, current := range g.overrides {
for k, v := range current {
if k.MatchString(req.RequestURI) {
displayable = v.displayable
host = ""
method = ""
query = ""
scheme = ""
if !v.disable_query && len(req.URL.RawQuery) > 0 {
query = "?" + req.URL.RawQuery
}
if !v.disable_body {
body = req.Context().Value(HashBody).(string)
}
if !v.disable_method {
method = req.Method + "-"
}
if !v.disable_host {
host = req.Host + "-"
}
if !v.disable_scheme {
scheme = "http-"
if req.TLS != nil {
scheme = "https-"
}
}
if len(v.headers) > 0 {
headerValues = ""
for _, hn := range v.headers {
headers = v.headers
headerValues += "-" + req.Header.Get(hn)
}
}
if v.hash {
hash = true
if v.template != "" {
return req.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer).ReplaceAll(v.template, ""), v.headers, v.hash, v.displayable
}
query, body, host, scheme, method, headerValues, headers, displayable, hash = parseKeyInformations(req, v)
hasOverride = true
break
}
Expand All @@ -150,6 +142,13 @@ func (g *keyContext) SetContext(req *http.Request) *http.Request {

key = method + scheme + host + key + query + body + headerValues

return
}

func (g *keyContext) SetContext(req *http.Request) *http.Request {
rq := g.initializer(req)
key, headers, hash, displayable := g.computeKey(rq)

return req.WithContext(
context.WithValue(
context.WithValue(
Expand Down
19 changes: 18 additions & 1 deletion context/key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"regexp"
"testing"

"github.com/caddyserver/caddy/v2"
"github.com/darkweak/souin/configurationtypes"
"github.com/darkweak/souin/plugins/souin/configuration"
)
Expand Down Expand Up @@ -61,8 +62,12 @@ func Test_KeyContext_SetupContext(t *testing.T) {
}

func Test_KeyContext_SetContext(t *testing.T) {
ctx := keyContext{}
req := httptest.NewRequest(http.MethodGet, "http://domain.com", nil)
ctx := keyContext{
initializer: func(r *http.Request) *http.Request {
return r.WithContext(context.WithValue(r.Context(), caddy.ReplacerCtxKey, caddy.NewReplacer()))
},
}
req = ctx.SetContext(req.WithContext(context.WithValue(req.Context(), HashBody, "-with_the_hash")))
if req.Context().Value(Key).(string) != "GET-http-domain.com--with_the_hash" {
t.Errorf("The Key context must be equal to GET-http-domain.com--with_the_hash, %s given.", req.Context().Value(Key).(string))
Expand All @@ -78,6 +83,9 @@ func Test_KeyContext_SetContext(t *testing.T) {
disable_host: true,
disable_method: true,
overrides: []map[*regexp.Regexp]keyContext{m},
initializer: func(r *http.Request) *http.Request {
return r.WithContext(context.WithValue(r.Context(), caddy.ReplacerCtxKey, caddy.NewReplacer()))
},
}
req2 := httptest.NewRequest(http.MethodGet, "http://domain.com/matched", nil)
req2 = ctx2.SetContext(req2.WithContext(context.WithValue(req2.Context(), HashBody, "")))
Expand All @@ -94,6 +102,9 @@ func Test_KeyContext_SetContext(t *testing.T) {
ctx3 := keyContext{
disable_method: true,
overrides: []map[*regexp.Regexp]keyContext{m},
initializer: func(r *http.Request) *http.Request {
return r.WithContext(context.WithValue(r.Context(), caddy.ReplacerCtxKey, caddy.NewReplacer()))
},
}
req3 := httptest.NewRequest(http.MethodGet, "http://domain.com/matched", nil)
req3 = ctx3.SetContext(req3.WithContext(context.WithValue(req3.Context(), HashBody, "")))
Expand All @@ -112,6 +123,9 @@ func Test_KeyContext_SetContext(t *testing.T) {
disable_query: true,
disable_method: false,
disable_host: false,
initializer: func(r *http.Request) *http.Request {
return r.WithContext(context.WithValue(r.Context(), caddy.ReplacerCtxKey, caddy.NewReplacer()))
},
}
req5 := httptest.NewRequest(http.MethodGet, "http://domain.com/matched?query=string", nil)
req5 = ctx4.SetContext(req5.WithContext(context.WithValue(req5.Context(), HashBody, "")))
Expand All @@ -123,6 +137,9 @@ func Test_KeyContext_SetContext(t *testing.T) {
disable_query: false,
disable_method: false,
disable_host: false,
initializer: func(r *http.Request) *http.Request {
return r.WithContext(context.WithValue(r.Context(), caddy.ReplacerCtxKey, caddy.NewReplacer()))
},
}
req6 := httptest.NewRequest(http.MethodGet, "http://domain.com/matched?query=string", nil)
req6 = ctx5.SetContext(req6.WithContext(context.WithValue(req6.Context(), HashBody, "")))
Expand Down
Loading

0 comments on commit 308e8d5

Please sign in to comment.