瀏覽代碼

ImageData.Headers()

Viktor Sokolov 2 月之前
父節點
當前提交
c601fd6390
共有 12 個文件被更改,包括 54 次插入99 次删除
  1. 3 2
      etag/etag.go
  2. 3 2
      etag/etag_test.go
  3. 1 16
      imagedata/download.go
  4. 4 11
      imagedata/factory.go
  5. 7 5
      imagedata/image_data.go
  6. 4 7
      processing/processing.go
  7. 21 25
      processing_handler.go
  8. 6 5
      processing_handler_test.go
  9. 1 4
      stream.go
  10. 2 15
      svg/svg.go
  11. 1 1
      svg/svg_test.go
  12. 1 6
      vips/vips.go

+ 3 - 2
etag/etag.go

@@ -14,6 +14,7 @@ import (
 	"github.com/imgproxy/imgproxy/v3/config"
 	"github.com/imgproxy/imgproxy/v3/imagedata"
 	"github.com/imgproxy/imgproxy/v3/options"
+	"go.withmatt.com/httpheaders"
 )
 
 type eTagCalc struct {
@@ -108,8 +109,8 @@ func (h *Handler) ImageEtagExpected() string {
 
 func (h *Handler) SetActualImageData(imgdata *imagedata.ImageData) (bool, error) {
 	var haveActualImgETag bool
-	h.imgEtagActual, haveActualImgETag = imgdata.Headers["ETag"]
-	haveActualImgETag = haveActualImgETag && len(h.imgEtagActual) > 0
+	h.imgEtagActual = imgdata.Headers().Get(httpheaders.Etag)
+	haveActualImgETag = len(h.imgEtagActual) > 0
 
 	// Just in case server didn't check ETag properly and returned the same one
 	// as we expected

+ 3 - 2
etag/etag_test.go

@@ -9,6 +9,7 @@ import (
 
 	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/suite"
+	"go.withmatt.com/httpheaders"
 
 	"github.com/imgproxy/imgproxy/v3/config"
 	"github.com/imgproxy/imgproxy/v3/imagedata"
@@ -37,7 +38,7 @@ func (s *EtagTestSuite) SetupSuite() {
 	d, err := os.ReadFile("../testdata/test1.jpg")
 	s.Require().NoError(err)
 
-	imgWithETag, err := imagedata.NewFromBytes(d, http.Header{"ETag": []string{`"loremipsumdolor"`}})
+	imgWithETag, err := imagedata.NewFromBytes(d, http.Header{httpheaders.Etag: []string{`"loremipsumdolor"`}})
 	s.Require().NoError(err)
 
 	imgWithoutETag, err := imagedata.NewFromBytes(d, make(http.Header))
@@ -101,7 +102,7 @@ func (s *EtagTestSuite) TestImageETagExpectedPresent() {
 	s.h.ParseExpectedETag(etagReq)
 
 	//nolint:testifylint // False-positive expected-actual
-	s.Require().Equal(s.imgWithETag.Headers["ETag"], s.h.ImageEtagExpected())
+	s.Require().Equal(s.imgWithETag.Headers().Get(httpheaders.Etag), s.h.ImageEtagExpected())
 }
 
 func (s *EtagTestSuite) TestImageETagExpectedBlank() {

+ 1 - 16
imagedata/download.go

@@ -70,22 +70,7 @@ func download(ctx context.Context, imageURL string, opts DownloadOptions, secopt
 		return nil, ierrors.Wrap(err, 0)
 	}
 
-	h := make(map[string]string)
-	for k := range res.Header {
-		value := res.Header.Get(k)
-		h[k] = value
-
-		// This is temporary workaround, will be addressed in the subsequent PR
-		if k == "Etag" {
-			h["ETag"] = value
-		}
-
-		if k == "ETag" {
-			h["Etag"] = value
-		}
-	}
-
-	imgdata.Headers = h
+	imgdata.headers = res.Header.Clone()
 
 	return imgdata, nil
 }

+ 4 - 11
imagedata/factory.go

@@ -3,7 +3,6 @@ package imagedata
 import (
 	"bytes"
 	"net/http"
-	"strings"
 
 	"github.com/imgproxy/imgproxy/v3/imagemeta"
 	"github.com/imgproxy/imgproxy/v3/imagetype"
@@ -18,20 +17,14 @@ func NewFromBytes(b []byte, headers http.Header) (*ImageData, error) {
 		return nil, err
 	}
 
-	return NewFromBytesWithFormat(meta.Format(), b, headers)
+	return NewFromBytesWithFormat(meta.Format(), b, headers), nil
 }
 
 // NewFromBytesWithFormat creates a new ImageData instance from the provided format and byte slice.
-func NewFromBytesWithFormat(format imagetype.Type, b []byte, headers http.Header) (*ImageData, error) {
-	// Temporary workaround for the old ImageData interface
-	h := make(map[string]string, len(headers))
-	for k, v := range headers {
-		h[k] = strings.Join(v, ", ")
-	}
-
+func NewFromBytesWithFormat(format imagetype.Type, b []byte, headers http.Header) *ImageData {
 	return &ImageData{
 		data:    b,
 		format:  format,
-		Headers: h,
-	}, nil
+		headers: headers,
+	}
 }

+ 7 - 5
imagedata/image_data.go

@@ -6,6 +6,7 @@ import (
 	"encoding/base64"
 	"fmt"
 	"io"
+	"net/http"
 	"os"
 	"strings"
 	"sync"
@@ -24,7 +25,7 @@ var (
 type ImageData struct {
 	format  imagetype.Type
 	data    []byte
-	Headers map[string]string
+	headers http.Header
 
 	cancel     context.CancelFunc
 	cancelOnce sync.Once
@@ -56,6 +57,10 @@ func (d *ImageData) Size() (int, error) {
 	return len(d.data), nil
 }
 
+func (d *ImageData) Headers() http.Header {
+	return d.headers
+}
+
 func (d *ImageData) SetCancel(cancel context.CancelFunc) {
 	d.cancel = cancel
 }
@@ -110,10 +115,7 @@ func loadFallbackImage() (err error) {
 	}
 
 	if FallbackImage != nil && err == nil && config.FallbackImageTTL > 0 {
-		if FallbackImage.Headers == nil {
-			FallbackImage.Headers = make(map[string]string)
-		}
-		FallbackImage.Headers["Fallback-Image"] = "1"
+		FallbackImage.headers.Set("Fallback-Image", "1")
 	}
 
 	return err

+ 4 - 7
processing/processing.go

@@ -359,13 +359,10 @@ func ProcessImage(ctx context.Context, imgdata *imagedata.ImageData, po *options
 	}
 
 	if err == nil {
-		if outData.Headers == nil {
-			outData.Headers = make(map[string]string)
-		}
-		outData.Headers["X-Origin-Width"] = strconv.Itoa(originWidth)
-		outData.Headers["X-Origin-Height"] = strconv.Itoa(originHeight)
-		outData.Headers["X-Result-Width"] = strconv.Itoa(img.Width())
-		outData.Headers["X-Result-Height"] = strconv.Itoa(img.Height())
+		outData.Headers().Set("X-Origin-Width", strconv.Itoa(originWidth))
+		outData.Headers().Set("X-Origin-Height", strconv.Itoa(originHeight))
+		outData.Headers().Set("X-Result-Width", strconv.Itoa(img.Width()))
+		outData.Headers().Set("X-Result-Height", strconv.Itoa(img.Height()))
 	}
 
 	return outData, err

+ 21 - 25
processing_handler.go

@@ -13,6 +13,7 @@ import (
 	"time"
 
 	log "github.com/sirupsen/logrus"
+	"go.withmatt.com/httpheaders"
 	"golang.org/x/sync/semaphore"
 
 	"github.com/imgproxy/imgproxy/v3/config"
@@ -61,7 +62,7 @@ func initProcessingHandler() {
 	headerVaryValue = strings.Join(vary, ", ")
 }
 
-func setCacheControl(rw http.ResponseWriter, force *time.Time, originHeaders map[string]string) {
+func setCacheControl(rw http.ResponseWriter, force *time.Time, originHeaders http.Header) {
 	ttl := -1
 
 	if _, ok := originHeaders["Fallback-Image"]; ok && config.FallbackImageTTL > 0 {
@@ -73,12 +74,12 @@ func setCacheControl(rw http.ResponseWriter, force *time.Time, originHeaders map
 	}
 
 	if config.CacheControlPassthrough && ttl < 0 && originHeaders != nil {
-		if val, ok := originHeaders["Cache-Control"]; ok && len(val) > 0 {
-			rw.Header().Set("Cache-Control", val)
+		if val := originHeaders.Get(httpheaders.CacheControl); len(val) > 0 {
+			rw.Header().Set(httpheaders.CacheControl, val)
 			return
 		}
 
-		if val, ok := originHeaders["Expires"]; ok && len(val) > 0 {
+		if val := originHeaders.Get(httpheaders.Expires); len(val) > 0 {
 			if t, err := time.Parse(http.TimeFormat, val); err == nil {
 				ttl = imath.Max(0, int(time.Until(t).Seconds()))
 			}
@@ -90,23 +91,23 @@ func setCacheControl(rw http.ResponseWriter, force *time.Time, originHeaders map
 	}
 
 	if ttl > 0 {
-		rw.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d, public", ttl))
+		rw.Header().Set(httpheaders.CacheControl, fmt.Sprintf("max-age=%d, public", ttl))
 	} else {
-		rw.Header().Set("Cache-Control", "no-cache")
+		rw.Header().Set(httpheaders.CacheControl, "no-cache")
 	}
 }
 
-func setLastModified(rw http.ResponseWriter, originHeaders map[string]string) {
+func setLastModified(rw http.ResponseWriter, originHeaders http.Header) {
 	if config.LastModifiedEnabled {
-		if val, ok := originHeaders["Last-Modified"]; ok && len(val) != 0 {
-			rw.Header().Set("Last-Modified", val)
+		if val := originHeaders.Get(httpheaders.LastModified); len(val) != 0 {
+			rw.Header().Set(httpheaders.LastModified, val)
 		}
 	}
 }
 
 func setVary(rw http.ResponseWriter) {
 	if len(headerVaryValue) > 0 {
-		rw.Header().Set("Vary", headerVaryValue)
+		rw.Header().Set(httpheaders.Vary, headerVaryValue)
 	}
 }
 
@@ -130,8 +131,8 @@ func respondWithImage(reqID string, r *http.Request, rw http.ResponseWriter, sta
 	rw.Header().Set("Content-Type", resultData.Format().Mime())
 	rw.Header().Set("Content-Disposition", contentDisposition)
 
-	setCacheControl(rw, po.Expires, originData.Headers)
-	setLastModified(rw, originData.Headers)
+	setCacheControl(rw, po.Expires, originData.Headers())
+	setLastModified(rw, originData.Headers())
 	setVary(rw)
 	setCanonical(rw, originURL)
 
@@ -142,10 +143,10 @@ func respondWithImage(reqID string, r *http.Request, rw http.ResponseWriter, sta
 		}
 
 		rw.Header().Set("X-Origin-Content-Length", strconv.Itoa(originSize))
-		rw.Header().Set("X-Origin-Width", resultData.Headers["X-Origin-Width"])
-		rw.Header().Set("X-Origin-Height", resultData.Headers["X-Origin-Height"])
-		rw.Header().Set("X-Result-Width", resultData.Headers["X-Result-Width"])
-		rw.Header().Set("X-Result-Height", resultData.Headers["X-Result-Height"])
+		rw.Header().Set("X-Origin-Width", resultData.Headers().Get("X-Origin-Width"))
+		rw.Header().Set("X-Origin-Height", resultData.Headers().Get("X-Origin-Height"))
+		rw.Header().Set("X-Result-Width", resultData.Headers().Get("X-Result-Width"))
+		rw.Header().Set("X-Result-Height", resultData.Headers().Get("X-Result-Height"))
 	}
 
 	rw.Header().Set("Content-Security-Policy", "script-src 'none'")
@@ -179,7 +180,7 @@ func respondWithImage(reqID string, r *http.Request, rw http.ResponseWriter, sta
 	)
 }
 
-func respondWithNotModified(reqID string, r *http.Request, rw http.ResponseWriter, po *options.ProcessingOptions, originURL string, originHeaders map[string]string) {
+func respondWithNotModified(reqID string, r *http.Request, rw http.ResponseWriter, po *options.ProcessingOptions, originURL string, originHeaders http.Header) {
 	setCacheControl(rw, po.Expires, originHeaders)
 	setVary(rw)
 
@@ -369,15 +370,10 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
 
 	case errors.As(err, &nmErr):
 		if config.ETagEnabled && len(etagHandler.ImageEtagExpected()) != 0 {
-			rw.Header().Set("ETag", etagHandler.GenerateExpectedETag())
+			rw.Header().Set(httpheaders.Etag, etagHandler.GenerateExpectedETag())
 		}
 
-		h := make(map[string]string)
-		for k := range nmErr.Headers() {
-			h[k] = nmErr.Headers().Get(k)
-		}
-
-		respondWithNotModified(reqID, r, rw, po, imageURL, h)
+		respondWithNotModified(reqID, r, rw, po, imageURL, nmErr.Headers())
 		return
 
 	default:
@@ -421,7 +417,7 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
 			rw.Header().Set("ETag", etagHandler.GenerateActualETag())
 
 			if imgDataMatch && etagHandler.ProcessingOptionsMatch() {
-				respondWithNotModified(reqID, r, rw, po, imageURL, originData.Headers)
+				respondWithNotModified(reqID, r, rw, po, imageURL, originData.Headers())
 				return
 			}
 		}

+ 6 - 5
processing_handler_test.go

@@ -14,6 +14,7 @@ import (
 
 	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/suite"
+	"go.withmatt.com/httpheaders"
 
 	"github.com/imgproxy/imgproxy/v3/config"
 	"github.com/imgproxy/imgproxy/v3/config/configurators"
@@ -116,7 +117,7 @@ func (s *ProcessingHandlerTestSuite) sampleETagData(imgETag string) (string, *im
 	imgdata := s.readTestImageData("test1.png")
 
 	if len(imgETag) != 0 {
-		imgdata.Headers = map[string]string{"ETag": imgETag}
+		imgdata.Headers().Set(httpheaders.Etag, imgETag)
 	}
 
 	var h etag.Handler
@@ -416,7 +417,7 @@ func (s *ProcessingHandlerTestSuite) TestETagReqNoIfNotModified() {
 	ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
 		s.Empty(r.Header.Get("If-None-Match"))
 
-		rw.Header().Set("ETag", imgdata.Headers["ETag"])
+		rw.Header().Set("ETag", imgdata.Headers().Get(httpheaders.Etag))
 		rw.WriteHeader(200)
 		rw.Write(s.readTestFile("test1.png"))
 	}))
@@ -455,7 +456,7 @@ func (s *ProcessingHandlerTestSuite) TestETagReqMatch() {
 	poStr, imgdata, etag := s.sampleETagData(`"loremipsumdolor"`)
 
 	ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
-		s.Equal(imgdata.Headers["ETag"], r.Header.Get("If-None-Match"))
+		s.Equal(imgdata.Headers().Get(httpheaders.Etag), r.Header.Get(httpheaders.IfNoneMatch))
 
 		rw.WriteHeader(304)
 	}))
@@ -503,7 +504,7 @@ func (s *ProcessingHandlerTestSuite) TestETagReqNotMatch() {
 	ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
 		s.Equal(`"loremipsum"`, r.Header.Get("If-None-Match"))
 
-		rw.Header().Set("ETag", imgdata.Headers["ETag"])
+		rw.Header().Set("ETag", imgdata.Headers().Get(httpheaders.Etag))
 		rw.WriteHeader(200)
 		rw.Write(s.readImageData(imgdata))
 	}))
@@ -554,7 +555,7 @@ func (s *ProcessingHandlerTestSuite) TestETagProcessingOptionsNotMatch() {
 	ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
 		s.Empty(r.Header.Get("If-None-Match"))
 
-		rw.Header().Set("ETag", imgdata.Headers["ETag"])
+		rw.Header().Set("ETag", imgdata.Headers().Get(httpheaders.Etag))
 		rw.WriteHeader(200)
 		rw.Write(s.readImageData(imgdata))
 	}))

+ 1 - 4
stream.go

@@ -113,10 +113,7 @@ func streamOriginImage(ctx context.Context, reqID string, r *http.Request, rw ht
 		rw.Header().Set("Content-Disposition", imagetype.ContentDisposition(filename, ext, po.ReturnAttachment))
 	}
 
-	setCacheControl(rw, po.Expires, map[string]string{
-		"Cache-Control": res.Header.Get("Cache-Control"),
-		"Expires":       res.Header.Get("Expires"),
-	})
+	setCacheControl(rw, po.Expires, res.Header)
 	setCanonical(rw, imageURL)
 	rw.Header().Set("Content-Security-Policy", "script-src 'none'")
 

+ 2 - 15
svg/svg.go

@@ -2,7 +2,6 @@ package svg
 
 import (
 	"io"
-	"net/http"
 	"strings"
 
 	"github.com/tdewolff/parse/v2"
@@ -12,15 +11,6 @@ import (
 	"github.com/imgproxy/imgproxy/v3/imagetype"
 )
 
-func cloneHeaders(src map[string]string) http.Header {
-	h := make(http.Header, len(src))
-	for k, v := range src {
-		h.Set(k, v)
-	}
-
-	return h
-}
-
 func Sanitize(data *imagedata.ImageData) (*imagedata.ImageData, error) {
 	r := data.Reader()
 	l := xml.NewLexer(parse.NewInput(r))
@@ -55,14 +45,11 @@ func Sanitize(data *imagedata.ImageData) (*imagedata.ImageData, error) {
 				return nil, l.Err()
 			}
 
-			newData, err := imagedata.NewFromBytesWithFormat(
+			newData := imagedata.NewFromBytesWithFormat(
 				imagetype.SVG,
 				buf.Bytes(),
-				cloneHeaders(data.Headers),
+				data.Headers().Clone(),
 			)
-			if err != nil {
-				return nil, err
-			}
 			newData.SetCancel(cancel)
 
 			return newData, nil

+ 1 - 1
svg/svg_test.go

@@ -49,7 +49,7 @@ func (s *SvgTestSuite) TestSanitize() {
 
 	s.Require().NoError(err)
 	s.Require().True(testutil.ReadersEqual(s.T(), expected.Reader(), actual.Reader()))
-	s.Require().Equal(origin.Headers, actual.Headers)
+	s.Require().Equal(origin.Headers(), actual.Headers())
 }
 
 func TestSvg(t *testing.T) {

+ 1 - 6
vips/vips.go

@@ -470,12 +470,7 @@ func (img *Image) Save(imgtype imagetype.Type, quality int) (*imagedata.ImageDat
 
 	b := ptrToBytes(ptr, int(imgsize))
 
-	imgdata, ierr := imagedata.NewFromBytesWithFormat(imgtype, b, make(http.Header))
-	if ierr != nil {
-		cancel()
-		return nil, ierr
-	}
-
+	imgdata := imagedata.NewFromBytesWithFormat(imgtype, b, make(http.Header))
 	imgdata.SetCancel(cancel)
 
 	return imgdata, nil