cache/httpcache: Add respectCacheControlNoStoreInResponse and respectCacheControlNoStoreInRequest options

This cache is used by `resources.GetRemote`.

Default values are:

* respectCacheControlNoStoreInResponse: false
* respectCacheControlNoStoreInRequest: true

This is a slightly breaking change, but the current behaviour is confusing, as:

* Many servers set the `no-store` header without much consideration, see https://developer.chrome.com/docs/web-platform/bfcache-ccns for more context
* We almost always want to cache the `resources.GetRemote` to disk.

Fixes #13990
This commit is contained in:
Bjørn Erik Pedersen
2025-09-29 13:05:24 +02:00
parent 4d1303512b
commit 3e46ba5ce2
4 changed files with 29 additions and 3 deletions
+13 -2
View File
@@ -25,6 +25,8 @@ import (
// DefaultConfig holds the default configuration for the HTTP cache.
var DefaultConfig = Config{
RespectCacheControlNoStoreInRequest: true,
RespectCacheControlNoStoreInResponse: false,
Cache: Cache{
For: GlobMatcher{
Excludes: []string{"**"},
@@ -42,7 +44,13 @@ var DefaultConfig = Config{
// Config holds the configuration for the HTTP cache.
type Config struct {
// Configures the HTTP cache behavior (RFC 9111).
// When enabled and there's a Cache-Control: no-store directive in the request, response will never be stored in disk cache.
RespectCacheControlNoStoreInRequest bool
// When enabled and there's a Cache-Control: no-store directive in the response, response will never be stored in disk cache.
RespectCacheControlNoStoreInResponse bool
// Enables HTTP cache behavior (RFC 9111) for these resources.
// When this is not enabled for a resource, Hugo will go straight to the file cache.
Cache Cache
@@ -57,7 +65,9 @@ type Cache struct {
}
func (c *Config) Compile() (ConfigCompiled, error) {
var cc ConfigCompiled
cc := ConfigCompiled{
Base: *c,
}
p, err := c.Cache.For.CompilePredicate()
if err != nil {
@@ -127,6 +137,7 @@ func (gm GlobMatcher) IsZero() bool {
}
type ConfigCompiled struct {
Base Config
For predicate.P[string]
PollConfigs []PollConfigCompiled
}
+1 -1
View File
@@ -37,7 +37,7 @@ require (
github.com/gobwas/glob v0.2.3
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e
github.com/gohugoio/hashstructure v0.5.0
github.com/gohugoio/httpcache v0.7.0
github.com/gohugoio/httpcache v0.8.0
github.com/gohugoio/hugo-goldmark-extensions/extras v0.5.0
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.3.1
github.com/gohugoio/locales v0.14.0
+2
View File
@@ -268,6 +268,8 @@ github.com/gohugoio/hashstructure v0.5.0 h1:G2fjSBU36RdwEJBWJ+919ERvOVqAg9tfcYp4
github.com/gohugoio/hashstructure v0.5.0/go.mod h1:Ser0TniXuu/eauYmrwM4o64EBvySxNzITEOLlm4igec=
github.com/gohugoio/httpcache v0.7.0 h1:ukPnn04Rgvx48JIinZvZetBfHaWE7I01JR2Q2RrQ3Vs=
github.com/gohugoio/httpcache v0.7.0/go.mod h1:fMlPrdY/vVJhAriLZnrF5QpN3BNAcoBClgAyQd+lGFI=
github.com/gohugoio/httpcache v0.8.0 h1:hNdsmGSELztetYCsPVgjA960zSa4dfEqqF/SficorCU=
github.com/gohugoio/httpcache v0.8.0/go.mod h1:fMlPrdY/vVJhAriLZnrF5QpN3BNAcoBClgAyQd+lGFI=
github.com/gohugoio/hugo-goldmark-extensions/extras v0.5.0 h1:dco+7YiOryRoPOMXwwaf+kktZSCtlFtreNdiJbETvYE=
github.com/gohugoio/hugo-goldmark-extensions/extras v0.5.0/go.mod h1:CRrxQTKeM3imw+UoS4EHKyrqB7Zp6sAJiqHit+aMGTE=
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.3.1 h1:nUzXfRTszLliZuN0JTKeunXTRaiFX6ksaWP0puLLYAY=
@@ -111,6 +111,19 @@ func New(rs *resources.Spec) *Client {
ShouldCache: func(req *http.Request, resp *http.Response, key string) bool {
return shouldCache(resp.StatusCode)
},
CanStore: func(reqCacheControl, respCacheControl httpcache.CacheControl) (canStore bool) {
if httpCacheConfig.Base.RespectCacheControlNoStoreInResponse {
if _, ok := respCacheControl["no-store"]; ok {
return false
}
}
if httpCacheConfig.Base.RespectCacheControlNoStoreInRequest {
if _, ok := reqCacheControl["no-store"]; ok {
return false
}
}
return true
},
MarkCachedResponses: true,
EnableETagPair: true,
Transport: &transport{