plugin: cache http responses from devices that omit the Date header (#31675)

This commit is contained in:
andig 2026-07-10 23:04:14 +02:00 • committed by GitHub
parent c136038dfb
commit df437d536f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 45 additions and 0 deletions

View file

@ -115,6 +115,11 @@ func NewHTTP(log *util.Logger, method, uri string, insecure bool, cache time.Dur
Modifier: func(resp *http.Response) error {
dropCacheBusting(resp, "Cache-Control")
dropCacheBusting(resp, "Pragma")
// httpcache derives freshness from the response Date; stamp one
// for devices that omit it, else every read is treated as stale
if resp.Header.Get("Date") == "" {
resp.Header.Set("Date", time.Now().UTC().Format(http.TimeFormat))
}
return nil
},
Base: base,

View file

@ -1,6 +1,7 @@
package plugin
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
@ -16,11 +17,26 @@ type httpHandler struct {
req *http.Request
cnt int
cacheBusting bool
noDate bool
}
func (h *httpHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
h.req = req
h.val = lo.RandomString(16, lo.LettersCharset)
// emulate a device that omits the Date header (e.g. Zendure Solarflow)
if h.noDate {
conn, buf, err := w.(http.Hijacker).Hijack()
if err != nil {
panic(err)
}
defer conn.Close()
fmt.Fprintf(buf, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n%s", len(h.val), h.val)
buf.Flush()
h.cnt++
return
}
if h.cacheBusting {
w.Header().Set("Cache-Control", "no-store, no-cache, max-age=0, must-revalidate")
w.Header().Set("Pragma", "no-cache")
@ -101,6 +117,30 @@ func (suite *httpTestSuite) TestCacheGetNoStore() {
}
}
func (suite *httpTestSuite) TestCacheGetNoDate() {
// upstream omits the Date header, cache must still take effect via injected Date
suite.h.noDate = true
defer func() { suite.h.noDate = false }()
uri := suite.srv.URL + "/foo/bar?baz=3"
p := NewHTTP(util.NewLogger("foo"), http.MethodGet, uri, false, time.Minute)
g, err := p.StringGetter()
suite.Require().NoError(err)
suite.h.cnt = 0
res, err := g()
suite.Require().NoError(err)
suite.Require().Equal(1, suite.h.cnt)
for range 3 {
val, err := g()
suite.Require().NoError(err)
suite.Require().Equal(res, val)
suite.Require().Equal(1, suite.h.cnt)
}
}
func (suite *httpTestSuite) TestSetQuery() {
uri := suite.srv.URL + "/foo/bar?baz={{.baz}}"
p := NewHTTP(util.NewLogger("foo"), http.MethodGet, uri, false, 0)