1
0
mirror of https://github.com/imgproxy/imgproxy.git synced 2025-02-02 11:34:20 +02:00

Support Last-Modified response header and support If-Modified-Since request header. (#1147)

* Always return Last-Modified and support If-Modified-Since.

* IMGPROXY_USE_LAST_MODIFIED config setting.

IMGPROXY_USE_LAST_MODIFIED  (default false) when enabled will return the
Last-Modified time of the upstream image and also allow the support of
the If-Modified-Since request header (returning a 304 if the image
hasn't been modified).

If-Modified-Since allows If-None-Match to take precedence.

* Fixes based on DarthSim's feedback.

1. Don't worry about nil maps.
2. Fix a test now that we use the config.LastModifiedEnabled (and move
   it's location it he test file to a more sane place).
3. Update GCS transport code based on the refactoring of DarthSim.

In this iteration, we pull the Updated time from the GCS object attributes
and format them as a string. We then parse it in the notmodified module.
Seems a bit silly to do it this way. If we agree on the approach here,
then AWS and Azure can follow.

* Support azure, fs, s3, and swift.

* Grab the headers for If-Modified-Since and Last-Modified before parsing them.

* Add tests for last-modified for fs.

* Support Last-Modified being passed when streaming an upstream file.

* Tests for Last-Modified for GCS and Azure

* Support s3 and swift tests. Sadly fakes3 doesn't support Last-Modified

* Test against forked gofakes3
This commit is contained in:
Ewan Higgs 2023-05-03 17:21:46 +02:00 committed by GitHub
parent 87faa5a07d
commit 4944dfab30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 472 additions and 25 deletions

View File

@ -123,6 +123,8 @@ var (
ETagEnabled bool
ETagBuster string
LastModifiedEnabled bool
BaseURL string
Presets []string
@ -310,6 +312,8 @@ func Reset() {
ETagEnabled = false
ETagBuster = ""
LastModifiedEnabled = false
BaseURL = ""
Presets = make([]string, 0)
@ -508,6 +512,8 @@ func Configure() error {
configurators.Bool(&ETagEnabled, "IMGPROXY_USE_ETAG")
configurators.String(&ETagBuster, "IMGPROXY_ETAG_BUSTER")
configurators.Bool(&LastModifiedEnabled, "IMGPROXY_USE_LAST_MODIFIED")
configurators.String(&BaseURL, "IMGPROXY_BASE_URL")
configurators.StringSlice(&Presets, "IMGPROXY_PRESETS")

View File

@ -34,6 +34,7 @@ var (
"Cache-Control",
"Expires",
"ETag",
"Last-Modified",
}
// For tests

View File

@ -91,6 +91,14 @@ func setCacheControl(rw http.ResponseWriter, force *time.Time, originHeaders map
}
}
func setLastModified(rw http.ResponseWriter, originHeaders map[string]string) {
if config.LastModifiedEnabled {
if val, ok := originHeaders["Last-Modified"]; ok && len(val) != 0 {
rw.Header().Set("Last-Modified", val)
}
}
}
func setVary(rw http.ResponseWriter) {
if len(headerVaryValue) > 0 {
rw.Header().Set("Vary", headerVaryValue)
@ -118,6 +126,7 @@ func respondWithImage(reqID string, r *http.Request, rw http.ResponseWriter, sta
rw.Header().Set("Content-Disposition", contentDisposition)
setCacheControl(rw, po.Expires, originData.Headers)
setLastModified(rw, originData.Headers)
setVary(rw)
setCanonical(rw, originURL)
@ -260,6 +269,12 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
}
}
if config.LastModifiedEnabled {
if modifiedSince := r.Header.Get("If-Modified-Since"); len(modifiedSince) != 0 {
imgRequestHeader.Set("If-Modified-Since", modifiedSince)
}
}
// The heavy part start here, so we need to restrict concurrency
var processingSemToken *semaphore.Token
func() {
@ -299,8 +314,10 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
if err == nil {
defer originData.Close()
} else if nmErr, ok := err.(*imagedata.ErrorNotModified); ok && config.ETagEnabled {
rw.Header().Set("ETag", etagHandler.GenerateExpectedETag())
} else if nmErr, ok := err.(*imagedata.ErrorNotModified); ok {
if config.ETagEnabled && len(etagHandler.ImageEtagExpected()) != 0 {
rw.Header().Set("ETag", etagHandler.GenerateExpectedETag())
}
respondWithNotModified(reqID, r, rw, po, imageURL, nmErr.Headers)
return
} else {

View File

@ -11,6 +11,7 @@ import (
"regexp"
"strings"
"testing"
"time"
"github.com/imgproxy/imgproxy/v3/config"
"github.com/imgproxy/imgproxy/v3/config/configurators"
@ -550,6 +551,158 @@ func (s *ProcessingHandlerTestSuite) TestETagProcessingOptionsNotMatch() {
require.Equal(s.T(), actualETag, res.Header.Get("ETag"))
}
func (s *ProcessingHandlerTestSuite) TestLastModifiedEnabled() {
config.LastModifiedEnabled = true
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Last-Modified", "Wed, 21 Oct 2015 07:28:00 GMT")
rw.WriteHeader(200)
rw.Write(s.readTestFile("test1.png"))
}))
defer ts.Close()
rw := s.send("/unsafe/rs:fill:4:4/plain/" + ts.URL)
res := rw.Result()
require.Equal(s.T(), "Wed, 21 Oct 2015 07:28:00 GMT", res.Header.Get("Last-Modified"))
}
func (s *ProcessingHandlerTestSuite) TestLastModifiedDisabled() {
config.LastModifiedEnabled = false
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Last-Modified", "Wed, 21 Oct 2015 07:28:00 GMT")
rw.WriteHeader(200)
rw.Write(s.readTestFile("test1.png"))
}))
defer ts.Close()
rw := s.send("/unsafe/rs:fill:4:4/plain/" + ts.URL)
res := rw.Result()
require.Equal(s.T(), "", res.Header.Get("Last-Modified"))
}
func (s *ProcessingHandlerTestSuite) TestModifiedSinceReqExactMatchLastModifiedDisabled() {
config.LastModifiedEnabled = false
data := s.readTestFile("test1.png")
lastModified := "Wed, 21 Oct 2015 07:28:00 GMT"
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
modifiedSince := r.Header.Get("If-Modified-Since")
require.Equal(s.T(), "", modifiedSince)
rw.WriteHeader(200)
rw.Write(data)
}))
defer ts.Close()
header := make(http.Header)
header.Set("If-Modified-Since", lastModified)
rw := s.send(fmt.Sprintf("/unsafe/plain/%s", ts.URL), header)
res := rw.Result()
require.Equal(s.T(), 200, res.StatusCode)
}
func (s *ProcessingHandlerTestSuite) TestModifiedSinceReqExactMatchLastModifiedEnabled() {
config.LastModifiedEnabled = true
lastModified := "Wed, 21 Oct 2015 07:28:00 GMT"
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
modifiedSince := r.Header.Get("If-Modified-Since")
require.Equal(s.T(), lastModified, modifiedSince)
rw.WriteHeader(304)
}))
defer ts.Close()
header := make(http.Header)
header.Set("If-Modified-Since", lastModified)
rw := s.send(fmt.Sprintf("/unsafe/plain/%s", ts.URL), header)
res := rw.Result()
require.Equal(s.T(), 304, res.StatusCode)
}
func (s *ProcessingHandlerTestSuite) TestModifiedSinceReqCompareMoreRecentLastModifiedDisabled() {
data := s.readTestFile("test1.png")
config.LastModifiedEnabled = false
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
modifiedSince := r.Header.Get("If-Modified-Since")
require.Equal(s.T(), modifiedSince, "")
rw.WriteHeader(200)
rw.Write(data)
}))
defer ts.Close()
recentTimestamp := "Thu, 25 Feb 2021 01:45:00 GMT"
header := make(http.Header)
header.Set("If-Modified-Since", recentTimestamp)
rw := s.send(fmt.Sprintf("/unsafe/plain/%s", ts.URL), header)
res := rw.Result()
require.Equal(s.T(), 200, res.StatusCode)
}
func (s *ProcessingHandlerTestSuite) TestModifiedSinceReqCompareMoreRecentLastModifiedEnabled() {
config.LastModifiedEnabled = true
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
fileLastModified, _ := time.Parse(http.TimeFormat, "Wed, 21 Oct 2015 07:28:00 GMT")
modifiedSince := r.Header.Get("If-Modified-Since")
parsedModifiedSince, err := time.Parse(http.TimeFormat, modifiedSince)
require.Nil(s.T(), err)
require.True(s.T(), fileLastModified.Before(parsedModifiedSince))
rw.WriteHeader(304)
}))
defer ts.Close()
recentTimestamp := "Thu, 25 Feb 2021 01:45:00 GMT"
header := make(http.Header)
header.Set("If-Modified-Since", recentTimestamp)
rw := s.send(fmt.Sprintf("/unsafe/plain/%s", ts.URL), header)
res := rw.Result()
require.Equal(s.T(), 304, res.StatusCode)
}
func (s *ProcessingHandlerTestSuite) TestModifiedSinceReqCompareTooOldLastModifiedDisabled() {
config.LastModifiedEnabled = false
data := s.readTestFile("test1.png")
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
modifiedSince := r.Header.Get("If-Modified-Since")
require.Equal(s.T(), modifiedSince, "")
rw.WriteHeader(200)
rw.Write(data)
}))
defer ts.Close()
oldTimestamp := "Tue, 01 Oct 2013 17:31:00 GMT"
header := make(http.Header)
header.Set("If-Modified-Since", oldTimestamp)
rw := s.send(fmt.Sprintf("/unsafe/plain/%s", ts.URL), header)
res := rw.Result()
require.Equal(s.T(), 200, res.StatusCode)
}
func (s *ProcessingHandlerTestSuite) TestModifiedSinceReqCompareTooOldLastModifiedEnabled() {
config.LastModifiedEnabled = true
data := s.readTestFile("test1.png")
ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
fileLastModified, _ := time.Parse(http.TimeFormat, "Wed, 21 Oct 2015 07:28:00 GMT")
modifiedSince := r.Header.Get("If-Modified-Since")
parsedModifiedSince, err := time.Parse(http.TimeFormat, modifiedSince)
require.Nil(s.T(), err)
require.True(s.T(), fileLastModified.After(parsedModifiedSince))
rw.WriteHeader(200)
rw.Write(data)
}))
defer ts.Close()
oldTimestamp := "Tue, 01 Oct 2013 17:31:00 GMT"
header := make(http.Header)
header.Set("If-Modified-Since", oldTimestamp)
rw := s.send(fmt.Sprintf("/unsafe/plain/%s", ts.URL), header)
res := rw.Result()
require.Equal(s.T(), 200, res.StatusCode)
}
func TestProcessingHandler(t *testing.T) {
suite.Run(t, new(ProcessingHandlerTestSuite))
}

View File

@ -37,6 +37,7 @@ var (
"Content-Encoding",
"Content-Range",
"Accept-Ranges",
"Last-Modified",
}
streamBufPool = sync.Pool{

View File

@ -123,6 +123,10 @@ func (t transport) RoundTrip(req *http.Request) (*http.Response, error) {
etag := string(*result.ETag)
header.Set("ETag", etag)
}
if config.LastModifiedEnabled && result.LastModified != nil {
lastModified := result.LastModified.Format(http.TimeFormat)
header.Set("Last-Modified", lastModified)
}
if resp := notmodified.Response(req, header); resp != nil {
if result.Body != nil {

View File

@ -5,6 +5,7 @@ import (
"net/http/httptest"
"os"
"testing"
"time"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
@ -16,9 +17,10 @@ import (
type AzureTestSuite struct {
suite.Suite
server *httptest.Server
transport http.RoundTripper
etag string
server *httptest.Server
transport http.RoundTripper
etag string
lastModified time.Time
}
func (s *AzureTestSuite) SetupSuite() {
@ -27,11 +29,13 @@ func (s *AzureTestSuite) SetupSuite() {
logrus.SetOutput(os.Stdout)
s.etag = "testetag"
s.lastModified, _ = time.Parse(http.TimeFormat, "Wed, 21 Oct 2015 07:28:00 GMT")
s.server = httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
require.Equal(s.T(), "/test/foo/test.png", r.URL.Path)
rw.Header().Set("Etag", s.etag)
rw.Header().Set("Last-Modified", s.lastModified.Format(http.TimeFormat))
rw.WriteHeader(200)
rw.Write(data)
}))
@ -91,6 +95,46 @@ func (s *AzureTestSuite) TestRoundTripWithUpdatedETagReturns200() {
require.Equal(s.T(), http.StatusOK, response.StatusCode)
}
func (s *AzureTestSuite) TestRoundTripWithLastModifiedDisabledReturns200() {
config.LastModifiedEnabled = false
request, _ := http.NewRequest("GET", "abs://test/foo/test.png", nil)
response, err := s.transport.RoundTrip(request)
require.Nil(s.T(), err)
require.Equal(s.T(), 200, response.StatusCode)
}
func (s *AzureTestSuite) TestRoundTripWithLastModifiedEnabled() {
config.LastModifiedEnabled = true
request, _ := http.NewRequest("GET", "abs://test/foo/test.png", nil)
response, err := s.transport.RoundTrip(request)
require.Nil(s.T(), err)
require.Equal(s.T(), 200, response.StatusCode)
require.Equal(s.T(), s.lastModified.Format(http.TimeFormat), response.Header.Get("Last-Modified"))
}
func (s *AzureTestSuite) TestRoundTripWithIfModifiedSinceReturns304() {
config.LastModifiedEnabled = true
request, _ := http.NewRequest("GET", "abs://test/foo/test.png", nil)
request.Header.Set("If-Modified-Since", s.lastModified.Format(http.TimeFormat))
response, err := s.transport.RoundTrip(request)
require.Nil(s.T(), err)
require.Equal(s.T(), http.StatusNotModified, response.StatusCode)
}
func (s *AzureTestSuite) TestRoundTripWithUpdatedLastModifiedReturns200() {
config.LastModifiedEnabled = true
request, _ := http.NewRequest("GET", "abs://test/foo/test.png", nil)
request.Header.Set("If-Modified-Since", s.lastModified.Add(-24*time.Hour).Format(http.TimeFormat))
response, err := s.transport.RoundTrip(request)
require.Nil(s.T(), err)
require.Equal(s.T(), http.StatusOK, response.StatusCode)
}
func TestAzureTransport(t *testing.T) {
suite.Run(t, new(AzureTestSuite))
}

View File

@ -78,6 +78,11 @@ func (t transport) RoundTrip(req *http.Request) (resp *http.Response, err error)
etag := BuildEtag(req.URL.Path, fi)
header.Set("ETag", etag)
}
if config.LastModifiedEnabled {
lastModified := fi.ModTime().Format(http.TimeFormat)
header.Set("Last-Modified", lastModified)
}
}
if resp := notmodified.Response(req, header); resp != nil {

View File

@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
@ -17,6 +18,7 @@ type FsTestSuite struct {
transport http.RoundTripper
etag string
modTime time.Time
}
func (s *FsTestSuite) SetupSuite() {
@ -29,6 +31,7 @@ func (s *FsTestSuite) SetupSuite() {
require.Nil(s.T(), err)
s.etag = BuildEtag("/test1.png", fi)
s.modTime = fi.ModTime()
s.transport = New()
}
@ -50,7 +53,6 @@ func (s *FsTestSuite) TestRoundTripWithETagEnabled() {
require.Equal(s.T(), 200, response.StatusCode)
require.Equal(s.T(), s.etag, response.Header.Get("ETag"))
}
func (s *FsTestSuite) TestRoundTripWithIfNoneMatchReturns304() {
config.ETagEnabled = true
@ -72,7 +74,46 @@ func (s *FsTestSuite) TestRoundTripWithUpdatedETagReturns200() {
require.Nil(s.T(), err)
require.Equal(s.T(), http.StatusOK, response.StatusCode)
}
func (s *FsTestSuite) TestRoundTripWithLastModifiedDisabledReturns200() {
config.LastModifiedEnabled = false
request, _ := http.NewRequest("GET", "local:///test1.png", nil)
response, err := s.transport.RoundTrip(request)
require.Nil(s.T(), err)
require.Equal(s.T(), 200, response.StatusCode)
}
func (s *FsTestSuite) TestRoundTripWithLastModifiedEnabledReturns200() {
config.LastModifiedEnabled = true
request, _ := http.NewRequest("GET", "local:///test1.png", nil)
response, err := s.transport.RoundTrip(request)
require.Nil(s.T(), err)
require.Equal(s.T(), 200, response.StatusCode)
require.Equal(s.T(), s.modTime.Format(http.TimeFormat), response.Header.Get("Last-Modified"))
}
func (s *FsTestSuite) TestRoundTripWithIfModifiedSinceReturns304() {
config.LastModifiedEnabled = true
request, _ := http.NewRequest("GET", "local:///test1.png", nil)
request.Header.Set("If-Modified-Since", s.modTime.Format(http.TimeFormat))
response, err := s.transport.RoundTrip(request)
require.Nil(s.T(), err)
require.Equal(s.T(), http.StatusNotModified, response.StatusCode)
}
func (s *FsTestSuite) TestRoundTripWithUpdatedLastModifiedReturns200() {
config.LastModifiedEnabled = true
request, _ := http.NewRequest("GET", "local:///test1.png", nil)
request.Header.Set("If-Modified-Since", s.modTime.Add(-time.Minute).Format(http.TimeFormat))
response, err := s.transport.RoundTrip(request)
require.Nil(s.T(), err)
require.Equal(s.T(), http.StatusOK, response.StatusCode)
}
func TestS3Transport(t *testing.T) {
suite.Run(t, new(FsTestSuite))
}

View File

@ -122,12 +122,17 @@ func (t transport) RoundTrip(req *http.Request) (*http.Response, error) {
// We haven't initialize reader yet, this means that we need non-ranged reader
if reader == nil {
if config.ETagEnabled {
if config.ETagEnabled || config.LastModifiedEnabled {
attrs, err := obj.Attrs(req.Context())
if err != nil {
return handleError(req, err)
}
header.Set("ETag", attrs.Etag)
if config.ETagEnabled {
header.Set("ETag", attrs.Etag)
}
if config.LastModifiedEnabled {
header.Set("Last-Modified", attrs.Updated.Format(http.TimeFormat))
}
}
if resp := notmodified.Response(req, header); resp != nil {

View File

@ -5,6 +5,7 @@ import (
"net"
"net/http"
"testing"
"time"
"github.com/fsouza/fake-gcs-server/fakestorage"
"github.com/stretchr/testify/require"
@ -30,15 +31,17 @@ func getFreePort() (int, error) {
type GCSTestSuite struct {
suite.Suite
server *fakestorage.Server
transport http.RoundTripper
etag string
server *fakestorage.Server
transport http.RoundTripper
etag string
lastModified time.Time
}
func (s *GCSTestSuite) SetupSuite() {
noAuth = true
// s.etag = "testetag"
s.lastModified, _ = time.Parse(http.TimeFormat, "Wed, 21 Oct 2015 07:28:00 GMT")
port, err := getFreePort()
require.Nil(s.T(), err)
@ -53,6 +56,7 @@ func (s *GCSTestSuite) SetupSuite() {
BucketName: "test",
Name: "foo/test.png",
// Etag: s.etag,
Updated: s.lastModified,
},
Content: make([]byte, 32),
},
@ -116,6 +120,43 @@ func (s *GCSTestSuite) TestRoundTripWithUpdatedETagReturns200() {
require.Equal(s.T(), http.StatusOK, response.StatusCode)
}
func (s *GCSTestSuite) TestRoundTripWithLastModifiedDisabledReturns200() {
config.LastModifiedEnabled = false
request, _ := http.NewRequest("GET", "gcs://test/foo/test.png", nil)
response, err := s.transport.RoundTrip(request)
require.Nil(s.T(), err)
require.Equal(s.T(), 200, response.StatusCode)
}
func (s *GCSTestSuite) TestRoundTripWithLastModifiedEnabled() {
config.LastModifiedEnabled = true
request, _ := http.NewRequest("GET", "gcs://test/foo/test.png", nil)
response, err := s.transport.RoundTrip(request)
require.Nil(s.T(), err)
require.Equal(s.T(), 200, response.StatusCode)
require.Equal(s.T(), s.lastModified.Format(http.TimeFormat), response.Header.Get("Last-Modified"))
}
func (s *GCSTestSuite) TestRoundTripWithIfModifiedSinceReturns304() {
config.LastModifiedEnabled = true
request, _ := http.NewRequest("GET", "gcs://test/foo/test.png", nil)
request.Header.Set("If-Modified-Since", s.lastModified.Format(http.TimeFormat))
response, err := s.transport.RoundTrip(request)
require.Nil(s.T(), err)
require.Equal(s.T(), http.StatusNotModified, response.StatusCode)
}
func (s *GCSTestSuite) TestRoundTripWithUpdatedLastModifiedReturns200() {
config.LastModifiedEnabled = true
request, _ := http.NewRequest("GET", "gcs://test/foo/test.png", nil)
request.Header.Set("If-Modified-Sicne", s.lastModified.Add(-24*time.Hour).Format(http.TimeFormat))
response, err := s.transport.RoundTrip(request)
require.Nil(s.T(), err)
require.Equal(s.T(), http.StatusOK, response.StatusCode)
}
func TestGCSTransport(t *testing.T) {
suite.Run(t, new(GCSTestSuite))
}

View File

@ -2,6 +2,7 @@ package notmodified
import (
"net/http"
"time"
"github.com/imgproxy/imgproxy/v3/config"
)
@ -15,6 +16,27 @@ func Response(req *http.Request, header http.Header) *http.Response {
return response(req, header)
}
}
if config.LastModifiedEnabled {
lastModifiedRaw := header.Get("Last-Modified")
if len(lastModifiedRaw) == 0 {
return nil
}
ifModifiedSinceRaw := req.Header.Get("If-Modified-Since")
if len(ifModifiedSinceRaw) == 0 {
return nil
}
lastModified, err := time.Parse(http.TimeFormat, lastModifiedRaw)
if err != nil {
return nil
}
ifModifiedSince, err := time.Parse(http.TimeFormat, ifModifiedSinceRaw)
if err != nil {
return nil
}
if !ifModifiedSince.Before(lastModified) {
return response(req, header)
}
}
return nil
}

View File

@ -5,6 +5,7 @@ import (
"io"
http "net/http"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
@ -64,9 +65,19 @@ func (t transport) RoundTrip(req *http.Request) (resp *http.Response, err error)
if r := req.Header.Get("Range"); len(r) != 0 {
input.Range = aws.String(r)
} else if config.ETagEnabled {
if ifNoneMatch := req.Header.Get("If-None-Match"); len(ifNoneMatch) > 0 {
input.IfNoneMatch = aws.String(ifNoneMatch)
} else {
if config.ETagEnabled {
if ifNoneMatch := req.Header.Get("If-None-Match"); len(ifNoneMatch) > 0 {
input.IfNoneMatch = aws.String(ifNoneMatch)
}
}
if config.LastModifiedEnabled {
if ifModifiedSince := req.Header.Get("If-Modified-Since"); len(ifModifiedSince) > 0 {
parsedIfModifiedSince, err := time.Parse(http.TimeFormat, ifModifiedSince)
if err == nil {
input.IfModifiedSince = &parsedIfModifiedSince
}
}
}
}

View File

@ -6,6 +6,7 @@ import (
"net/http/httptest"
"os"
"testing"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3"
@ -20,9 +21,10 @@ import (
type S3TestSuite struct {
suite.Suite
server *httptest.Server
transport http.RoundTripper
etag string
server *httptest.Server
transport http.RoundTripper
etag string
lastModified time.Time
}
func (s *S3TestSuite) SetupSuite() {
@ -63,6 +65,7 @@ func (s *S3TestSuite) SetupSuite() {
defer obj.Body.Close()
s.etag = *obj.ETag
s.lastModified = *obj.LastModified
}
func (s *S3TestSuite) TearDownSuite() {
@ -110,6 +113,48 @@ func (s *S3TestSuite) TestRoundTripWithUpdatedETagReturns200() {
require.Equal(s.T(), http.StatusOK, response.StatusCode)
}
func (s *S3TestSuite) TestRoundTripWithLastModifiedDisabledReturns200() {
config.LastModifiedEnabled = false
request, _ := http.NewRequest("GET", "s3://test/foo/test.png", nil)
response, err := s.transport.RoundTrip(request)
require.Nil(s.T(), err)
require.Equal(s.T(), 200, response.StatusCode)
}
func (s *S3TestSuite) TestRoundTripWithLastModifiedEnabled() {
config.ETagEnabled = true
request, _ := http.NewRequest("GET", "s3://test/foo/test.png", nil)
response, err := s.transport.RoundTrip(request)
require.Nil(s.T(), err)
require.Equal(s.T(), 200, response.StatusCode)
require.Equal(s.T(), s.lastModified.Format(http.TimeFormat), response.Header.Get("Last-Modified"))
}
// gofakes3 doesn't support If-Modified-Since (yet?)
func (s *S3TestSuite) TestRoundTripWithIfModifiedSinceReturns304() {
config.LastModifiedEnabled = true
request, _ := http.NewRequest("GET", "s3://test/foo/test.png", nil)
request.Header.Set("If-Modified-Since", s.lastModified.Format(http.TimeFormat))
response, err := s.transport.RoundTrip(request)
require.Nil(s.T(), err)
require.Equal(s.T(), http.StatusNotModified, response.StatusCode)
}
func (s *S3TestSuite) TestRoundTripWithUpdatedLastModifiedReturns200() {
config.LastModifiedEnabled = true
request, _ := http.NewRequest("GET", "s3://test/foo/test.png", nil)
request.Header.Set("If-Modified-Since", s.lastModified.Add(-24*time.Hour).Format(http.TimeFormat))
response, err := s.transport.RoundTrip(request)
require.Nil(s.T(), err)
require.Equal(s.T(), http.StatusOK, response.StatusCode)
}
func TestS3Transport(t *testing.T) {
suite.Run(t, new(S3TestSuite))
}

View File

@ -84,13 +84,19 @@ func (t transport) RoundTrip(req *http.Request) (resp *http.Response, err error)
if etag, ok := objectHeaders["Etag"]; ok {
header.Set("ETag", etag)
}
}
if resp := notmodified.Response(req, header); resp != nil {
object.Close()
return resp, nil
if config.LastModifiedEnabled {
if lastModified, ok := objectHeaders["Last-Modified"]; ok {
header.Set("Last-Modified", lastModified)
}
}
if resp := notmodified.Response(req, header); resp != nil {
object.Close()
return resp, nil
}
for k, v := range objectHeaders {
header.Set(k, v)
}

View File

@ -4,6 +4,7 @@ import (
"context"
"net/http"
"testing"
"time"
"github.com/ncw/swift/v2"
"github.com/ncw/swift/v2/swifttest"
@ -20,9 +21,10 @@ const (
type SwiftTestSuite struct {
suite.Suite
server *swifttest.SwiftServer
transport http.RoundTripper
etag string
server *swifttest.SwiftServer
transport http.RoundTripper
etag string
lastModified time.Time
}
func (s *SwiftTestSuite) SetupSuite() {
@ -71,10 +73,12 @@ func (s *SwiftTestSuite) setupTestFile() {
require.Nil(t, err)
f.Close()
h, err := f.Headers()
// The Etag is written on file close; but Last-Modified is only available when we get the object again.
_, h, err := c.Object(ctx, testContainer, testObject)
require.Nil(t, err)
s.etag = h["Etag"]
s.lastModified, err = time.Parse(http.TimeFormat, h["Date"])
require.Nil(t, err)
}
func (s *SwiftTestSuite) TearDownSuite() {
@ -140,6 +144,47 @@ func (s *SwiftTestSuite) TestRoundTripWithUpdatedETagReturns200() {
require.Equal(s.T(), http.StatusOK, response.StatusCode)
}
func (s *SwiftTestSuite) TestRoundTripWithLastModifiedDisabledReturns200() {
config.LastModifiedEnabled = false
request, _ := http.NewRequest("GET", "swift://test/foo/test.png", nil)
response, err := s.transport.RoundTrip(request)
require.Nil(s.T(), err)
require.Equal(s.T(), 200, response.StatusCode)
}
func (s *SwiftTestSuite) TestRoundTripWithLastModifiedEnabled() {
config.LastModifiedEnabled = true
request, _ := http.NewRequest("GET", "swift://test/foo/test.png", nil)
response, err := s.transport.RoundTrip(request)
require.Nil(s.T(), err)
require.Equal(s.T(), 200, response.StatusCode)
require.Equal(s.T(), s.lastModified.Format(http.TimeFormat), response.Header.Get("Last-Modified"))
}
func (s *SwiftTestSuite) TestRoundTripWithIfModifiedSinceReturns304() {
config.LastModifiedEnabled = true
request, _ := http.NewRequest("GET", "swift://test/foo/test.png", nil)
request.Header.Set("If-Modified-Since", s.lastModified.Format(http.TimeFormat))
response, err := s.transport.RoundTrip(request)
require.Nil(s.T(), err)
require.Equal(s.T(), http.StatusNotModified, response.StatusCode)
}
func (s *SwiftTestSuite) TestRoundTripWithUpdatedLastModifiedReturns200() {
config.LastModifiedEnabled = true
request, _ := http.NewRequest("GET", "swift://test/foo/test.png", nil)
request.Header.Set("If-Modified-Since", s.lastModified.Add(-24*time.Hour).Format(http.TimeFormat))
response, err := s.transport.RoundTrip(request)
require.Nil(s.T(), err)
require.Equal(s.T(), http.StatusOK, response.StatusCode)
}
func TestSwiftTransport(t *testing.T) {
suite.Run(t, new(SwiftTestSuite))
}