Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions configurationtypes/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ func (c *CacheKeys) parseJSON(rootDecoder *json.Decoder) {
case "disable_vary":
val, _ := rootDecoder.Token()
key.DisableVary, _ = strconv.ParseBool(fmt.Sprint(val))
case "sort_query":
val, _ := rootDecoder.Token()
key.SortQuery, _ = strconv.ParseBool(fmt.Sprint(val))
case "hash":
val, _ := rootDecoder.Token()
key.Hash, _ = strconv.ParseBool(fmt.Sprint(val))
Expand Down Expand Up @@ -246,6 +249,7 @@ type Key struct {
DisableQuery bool `json:"disable_query,omitempty" yaml:"disable_query,omitempty"`
DisableScheme bool `json:"disable_scheme,omitempty" yaml:"disable_scheme,omitempty"`
DisableVary bool `json:"disable_vary,omitempty" yaml:"disable_vary,omitempty"`
SortQuery bool `json:"sort_query,omitempty" yaml:"sort_query,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"`
Expand Down
17 changes: 16 additions & 1 deletion context/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package context
import (
"context"
"net/http"
"net/url"
"regexp"
"sort"

"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
Expand All @@ -23,6 +25,7 @@ type keyContext struct {
disable_host bool
disable_method bool
disable_query bool
sort_query bool
disable_vary bool
disable_scheme bool
displayable bool
Expand All @@ -44,6 +47,7 @@ func (g *keyContext) SetupContext(c configurationtypes.AbstractConfigurationInte
g.disable_host = k.DisableHost
g.disable_method = k.DisableMethod
g.disable_query = k.DisableQuery
g.sort_query = k.SortQuery
g.disable_scheme = k.DisableScheme
g.disable_vary = k.DisableVary
g.hash = k.Hash
Expand All @@ -60,6 +64,7 @@ func (g *keyContext) SetupContext(c configurationtypes.AbstractConfigurationInte
disable_host: v.DisableHost,
disable_method: v.DisableMethod,
disable_query: v.DisableQuery,
sort_query: v.SortQuery,
disable_scheme: v.DisableScheme,
disable_vary: v.DisableVary,
hash: v.Hash,
Expand Down Expand Up @@ -89,7 +94,17 @@ func parseKeyInformations(req *http.Request, kCtx keyContext) (query, body, host
hash = kCtx.hash

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

if kCtx.sort_query {
v, _ := url.ParseQuery(req.URL.RawQuery)
for _, values := range v {
sort.Strings(values)
}
queryPart = v.Encode()
}
Comment thread
IndraGunawan marked this conversation as resolved.

query += "?" + queryPart
}

if !kCtx.disable_body {
Expand Down
21 changes: 21 additions & 0 deletions context/key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,25 @@ func Test_KeyContext_SetContext(t *testing.T) {
t.Errorf("The Key context must be equal to GET-http-domain.com-/matched?query=string, %s given.", req6.Context().Value(Key).(string))
}

// Added tests for sort_query
ctx6 := keyContext{
sort_query: true,
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()))
},
}
req7 := httptest.NewRequest(http.MethodGet, "http://domain.com/matched?b=2&a=1", nil)
req7 = ctx6.SetContext(req7.WithContext(context.WithValue(req7.Context(), HashBody, "")))
if req7.Context().Value(Key).(string) != "GET-http-domain.com-/matched?a=1&b=2" {
t.Errorf("The Key context must be equal to GET-http-domain.com-/matched?a=1&b=2, %s given.", req7.Context().Value(Key).(string))
}

req8 := httptest.NewRequest(http.MethodGet, "http://domain.com/matched?word=beta&word=alpha", nil)
req8 = ctx6.SetContext(req8.WithContext(context.WithValue(req8.Context(), HashBody, "")))
if req8.Context().Value(Key).(string) != "GET-http-domain.com-/matched?word=alpha&word=beta" {
t.Errorf("The Key context must be equal to GET-http-domain.com-/matched?word=alpha&word=beta, %s given.", req8.Context().Value(Key).(string))
}
}
6 changes: 6 additions & 0 deletions docs/website/content/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ default: `false`
* **disable_query**: Prevent the URL query to be part of the generated key.
default: `false`

* **sort_query**: Sort the query string parameters alphabetically by name, and parameters with the same name will be sorted by their values. This ensures consistent cache keys regardless of query parameter order.
default: `false`

* **disable_scheme**: Prevent the scheme to be part of the generated key.
default: `false`

Expand Down Expand Up @@ -163,6 +166,9 @@ default: `false`
* **disable_query**: Prevent the URL query to be part of the generated key.
default: `false`

* **sort_query**: Sort the query string parameters alphabetically by name, and parameters with the same name will be sorted by their values. This ensures consistent cache keys regardless of query parameter order.
default: `false`

* **disable_scheme**: Prevent the scheme to be part of the generated key.
default: `false`

Expand Down
4 changes: 4 additions & 0 deletions plugins/caddy/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,8 @@ func parseConfiguration(cfg *Configuration, h *caddyfile.Dispenser, isGlobal boo
ck.DisableMethod = true
case "disable_query":
ck.DisableQuery = true
case "sort_query":
ck.SortQuery = true
case "disable_scheme":
ck.DisableScheme = true
case "disable_vary":
Expand Down Expand Up @@ -593,6 +595,8 @@ func parseConfiguration(cfg *Configuration, h *caddyfile.Dispenser, isGlobal boo
config_key.DisableMethod = true
case "disable_query":
config_key.DisableQuery = true
case "sort_query":
config_key.SortQuery = true
case "disable_scheme":
config_key.DisableScheme = true
case "disable_vary":
Expand Down
34 changes: 34 additions & 0 deletions plugins/caddy/httpcache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,40 @@ func TestQueryString(t *testing.T) {
}
}

func TestQueryStringSort(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`
{
admin localhost:2999
http_port 9080
https_port 9443
cache {
key {
sort_query
}
}
}
localhost:9080 {
route /query-string-sort {
cache
respond "Hello, query string sort!"
}
}`, "caddyfile")

resp1, _ := tester.AssertGetResponse(`http://localhost:9080/query-string-sort?b=2&a=1`, 200, "Hello, query string sort!")
if resp1.Header.Get("Cache-Status") != "Souin; fwd=uri-miss; stored; key=GET-http-localhost:9080-/query-string-sort?a=1&b=2" {
t.Errorf("unexpected Cache-Status header %v", resp1.Header.Get("Cache-Status"))
}

resp2, _ := tester.AssertGetResponse(`http://localhost:9080/query-string-sort?a=1&b=2`, 200, "Hello, query string sort!")
compareHit(t, resp2.Header, "GET-http-localhost:9080-/query-string-sort?a=1&b=2", "DEFAULT", 119)

resp3, _ := tester.AssertGetResponse(`http://localhost:9080/query-string-sort?word=beta&word=alpha`, 200, "Hello, query string sort!")
if resp3.Header.Get("Cache-Status") != "Souin; fwd=uri-miss; stored; key=GET-http-localhost:9080-/query-string-sort?word=alpha&word=beta" {
t.Errorf("unexpected Cache-Status header %v", resp3.Header.Get("Cache-Status"))
}
}

func TestMaxAge(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`
Expand Down
2 changes: 2 additions & 0 deletions plugins/kratos/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ func parseCacheKeys(ccConfiguration map[string]config.Value) configurationtypes.
ck.DisableMethod = true
case "disable_query":
ck.DisableQuery = true
case "sort_query":
ck.SortQuery = true
case "disable_scheme":
ck.DisableScheme = true
case "disable_vary":
Expand Down
2 changes: 2 additions & 0 deletions plugins/souin/agnostic/configuration_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ func parseCacheKeys(ccConfiguration map[string]interface{}) configurationtypes.C
ck.DisableMethod = true
case "disable_query":
ck.DisableQuery = true
case "sort_query":
ck.SortQuery = true
case "disable_scheme":
ck.DisableScheme = true
case "disable_vary":
Expand Down
2 changes: 2 additions & 0 deletions plugins/traefik/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ func configCacheKey(keyConfiguration map[string]interface{}) configurationtypes.
key.DisableMethod = cast.ToBool(keyV)
case "disable_query":
key.DisableQuery = cast.ToBool(keyV)
case "sort_query":
key.SortQuery = cast.ToBool(keyV)
case "disable_scheme":
key.DisableScheme = cast.ToBool(keyV)
case "disable_vary":
Expand Down
4 changes: 4 additions & 0 deletions plugins/traefik/override/configurationtypes/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ func (c *CacheKeys) parseJSON(rootDecoder *json.Decoder) {
case "disable_query":
val, _ := rootDecoder.Token()
key.DisableQuery, _ = strconv.ParseBool(fmt.Sprint(val))
case "sort_query":
val, _ := rootDecoder.Token()
key.SortQuery, _ = strconv.ParseBool(fmt.Sprint(val))
case "disable_scheme":
val, _ := rootDecoder.Token()
key.DisableScheme, _ = strconv.ParseBool(fmt.Sprint(val))
Expand Down Expand Up @@ -223,6 +226,7 @@ type Key struct {
DisableHost bool `json:"disable_host,omitempty" yaml:"disable_host,omitempty"`
DisableMethod bool `json:"disable_method,omitempty" yaml:"disable_method,omitempty"`
DisableQuery bool `json:"disable_query,omitempty" yaml:"disable_query,omitempty"`
SortQuery bool `json:"sort_query,omitempty" yaml:"sort_query,omitempty"`
DisableScheme bool `json:"disable_scheme,omitempty" yaml:"disable_scheme,omitempty"`
DisableVary bool `json:"disable_vary,omitempty" yaml:"disable_vary,omitempty"`
Hash bool `json:"hash,omitempty" yaml:"hash,omitempty"`
Expand Down
14 changes: 13 additions & 1 deletion plugins/traefik/override/context/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package context
import (
"context"
"net/http"
"net/url"
"regexp"
"sort"

"github.com/darkweak/souin/configurationtypes"
)
Expand All @@ -20,6 +22,7 @@ type keyContext struct {
disable_host bool
disable_method bool
disable_query bool
sort_query bool
disable_scheme bool
disable_vary bool
displayable bool
Expand All @@ -41,6 +44,7 @@ func (g *keyContext) SetupContext(c configurationtypes.AbstractConfigurationInte
g.disable_host = k.DisableHost
g.disable_method = k.DisableMethod
g.disable_query = k.DisableQuery
g.sort_query = k.SortQuery
g.disable_scheme = k.DisableScheme
g.disable_vary = k.DisableVary
g.hash = k.Hash
Expand Down Expand Up @@ -76,7 +80,15 @@ func parseKeyInformations(req *http.Request, kCtx keyContext) (query, body, host
hash = kCtx.hash

if !kCtx.disable_query && len(req.URL.RawQuery) > 0 {
query += "?" + req.URL.RawQuery
if kCtx.sort_query {
v, _ := url.ParseQuery(req.URL.RawQuery)
for _, values := range v {
sort.Strings(values)
}
query += "?" + v.Encode()
} else {
query += "?" + req.URL.RawQuery
}
}

if !kCtx.disable_body {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 13 additions & 1 deletion plugins/traefik/vendor/github.com/darkweak/souin/context/key.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading