File tree Expand file tree Collapse file tree 3 files changed +59
-7
lines changed
Expand file tree Collapse file tree 3 files changed +59
-7
lines changed Original file line number Diff line number Diff line change @@ -24,6 +24,7 @@ import (
2424 "iter"
2525 "net/http"
2626 "net/http/httputil"
27+ "os"
2728 "time"
2829)
2930
@@ -43,8 +44,30 @@ func (r *Response) DateHeader() time.Time {
4344 return date
4445}
4546
46- func (r * Response ) ExpiresHeader () RawTime {
47- return RawTime (r .Data .Header .Get ("Expires" ))
47+ // Deprecated: This function is a workaround for Kubernetes' handling of "UTC" Expires headers.
48+ func parseHTTPDateCompat (dateStr string ) (t time.Time , err error ) {
49+ if os .Getenv ("HTTPCACHE_ALLOW_UTC_DATETIMEFORMAT" ) == "1" {
50+ // TODO(bartventer): PR Kubernetes to emit "GMT" per RFC 9110 §5.6.7.
51+ // See k8s.io/kube-openapi/pkg/handler3/handler.go for "UTC" usage.
52+ return time .Parse (time .RFC1123 , dateStr )
53+ }
54+ return
55+ }
56+
57+ func (r * Response ) ExpiresHeader () (t time.Time , found bool , valid bool ) {
58+ expiresStr := r .Data .Header .Get ("Expires" )
59+ if expiresStr == "" {
60+ return
61+ }
62+ found = true
63+ if t , valid = RawTime (expiresStr ).Value (); valid {
64+ return
65+ }
66+ expires , err := parseHTTPDateCompat (expiresStr )
67+ if err != nil || expires .IsZero () {
68+ return time.Time {}, false , false
69+ }
70+ return expires , true , true
4871}
4972
5073func (r * Response ) WriteTo (w io.Writer ) (int64 , error ) {
Original file line number Diff line number Diff line change @@ -162,3 +162,29 @@ func TestParseResponse(t *testing.T) {
162162 })
163163 }
164164}
165+
166+ func TestParseHTTPDateCompat (t * testing.T ) {
167+ r := Response {
168+ Data : & http.Response {
169+ Header : http.Header {
170+ "Expires" : []string {"Mon, 02 Jan 2006 15:04:05 UTC" },
171+ },
172+ },
173+ }
174+ t .Run ("Not Enabled" , func (t * testing.T ) {
175+ _ , err := parseHTTPDateCompat (r .Data .Header .Get ("Expires" ))
176+ testutil .RequireNoError (t , err )
177+
178+ _ , _ , valid := r .ExpiresHeader ()
179+ testutil .AssertTrue (t , ! valid )
180+ })
181+
182+ t .Run ("Enabled" , func (t * testing.T ) {
183+ t .Setenv ("HTTPCACHE_ALLOW_UTC_DATETIMEFORMAT" , "1" )
184+ _ , err := parseHTTPDateCompat (r .Data .Header .Get ("Expires" ))
185+ testutil .RequireNoError (t , err )
186+
187+ _ , found , valid := r .ExpiresHeader ()
188+ testutil .AssertTrue (t , found && valid )
189+ })
190+ }
Original file line number Diff line number Diff line change @@ -119,15 +119,18 @@ func (f *freshnessCalculator) CalculateFreshness(
119119 if maxAge , ok := resCC .MaxAge (); ok && maxAge >= 0 {
120120 usefulLife = maxAge // Response is fresh for max-age seconds
121121 }
122+
122123 if usefulLife == 0 {
123- if expires , ok := entry .ExpiresHeader ().Value (); ok && expires .After (date ) {
124+ expires , found , valid := entry .ExpiresHeader ()
125+ switch {
126+ case valid && expires .After (date ):
127+ // Use Expires header if available
124128 usefulLife = expires .Sub (date )
129+ case ! found && (isHeuristicallyCacheableCode (resp .StatusCode ) || resCC .Public ()):
130+ // Heuristic fallback if allowed by RFC9111 §4.2.2 (only if expires is not set)
131+ usefulLife = heuristicFreshness (resp .Header , date )
125132 }
126133 }
127- // Heuristic fallback if allowed by RFC9111 §4.2.2
128- if usefulLife == 0 && (isHeuristicallyCacheableCode (resp .StatusCode ) || resCC .Public ()) {
129- usefulLife = heuristicFreshness (resp .Header , date )
130- }
131134
132135 if reqMaxAge , ok := reqCC .MaxAge (); ok && reqMaxAge > 0 {
133136 usefulLife = min (usefulLife , reqMaxAge ) // Client prefers a response no older than max-age
You can’t perform that action at this time.
0 commit comments