From 8a74d255eb142ffa7dc6c0a71c39756037daee3e Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Fri, 29 Apr 2022 12:44:23 +0100 Subject: [PATCH] Add WithIfModifiedSince to enable use of caching The HTTP Getter can leverage the use of RFC7234 4.3.2 by setting the HTTP Header 'If-Modified-Since'. This will enable callers to avoid downloading index files when they haven't been modified since the last get operation. Signed-off-by: Paulo Gomes --- pkg/getter/getter.go | 15 +++++++++ pkg/getter/httpgetter.go | 5 +++ pkg/getter/httpgetter_test.go | 59 +++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+) diff --git a/pkg/getter/getter.go b/pkg/getter/getter.go index 653b032fe..20e214b38 100644 --- a/pkg/getter/getter.go +++ b/pkg/getter/getter.go @@ -45,6 +45,7 @@ type options struct { registryClient *registry.Client timeout time.Duration transport *http.Transport + ifModifiedSince *time.Time } // Option allows specifying various settings configurable by the user for overriding the defaults @@ -121,6 +122,20 @@ func WithUntar() Option { } } +// WithIfModifiedSince leverages the HTTP caching mechanism +// defined on RFC7234 4.3.2. It prevents the HTTP getter from +// downloading index that haven't been modified based on `since`. +// If the file was not modified, getter will return a +// `304 Not Modified`, as per RFC. +// +// The caller is responsible for caching previously downloaded +// index files and handling the returning error. +func WithIfModifiedSince(since time.Time) Option { + return func(opts *options) { + opts.ifModifiedSince = &since + } +} + // WithTransport sets the http.Transport to allow overwriting the HTTPGetter default. func WithTransport(transport *http.Transport) Option { return func(opts *options) { diff --git a/pkg/getter/httpgetter.go b/pkg/getter/httpgetter.go index 6fe1aa71f..cf7aae3dd 100644 --- a/pkg/getter/httpgetter.go +++ b/pkg/getter/httpgetter.go @@ -22,6 +22,7 @@ import ( "net/http" "net/url" "sync" + "time" "github.com/pkg/errors" @@ -58,6 +59,10 @@ func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) { req.Header.Set("User-Agent", g.opts.userAgent) } + if g.opts.ifModifiedSince != nil { + req.Header.Set("If-Modified-Since", g.opts.ifModifiedSince.Format(time.RFC1123)) + } + // Before setting the basic auth credentials, make sure the URL associated // with the basic auth is the one being fetched. u1, err := url.Parse(g.opts.url) diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index 140b2c714..c44a8bef9 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -530,3 +530,62 @@ func TestHTTPTransportOption(t *testing.T) { t.Fatal("transport.TLSClientConfig should not be set") } } + +func TestHTTPIfModifiedSinceOption(t *testing.T) { + sinceNow := time.Now() + sinceNowExpected := sinceNow.Format(time.RFC1123) + + tests := []struct { + name string + since *time.Time + sinceExpected string + }{ + { + name: "If-Modified-Since option set", + since: &sinceNow, + sinceExpected: sinceNowExpected, + }, + { + name: "If-Modified-Since option not set", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var g Getter + var err error + + if tt.since == nil { + g, err = NewHTTPGetter() + } else { + g, err = NewHTTPGetter( + WithIfModifiedSince(*tt.since), + ) + } + if err != nil { + t.Fatal(err) + } + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotSince, found := r.Header["If-Modified-Since"] + if tt.since != nil && !found { + t.Errorf("Header If-Modified-Since was expected but was not set") + } + + if tt.since != nil && gotSince[0] != tt.sinceExpected { + t.Errorf("Expected '%s', got '%s'", tt.sinceExpected, gotSince) + } + + if tt.since == nil && found { + t.Errorf("Header If-Modified-Since was not expected but was set") + } + })) + defer srv.Close() + + _, err = g.Get(srv.URL, WithURL(srv.URL)) + if err != nil { + t.Fatal(err) + } + }) + } +}