Skip to content

Commit

Permalink
feat(pgs): forward etag and last-modified headers from imgproxy
Browse files Browse the repository at this point in the history
  • Loading branch information
mac-chaffee committed Dec 21, 2024
1 parent 5e966b8 commit d23fe53
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 42 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ IMGPROXY_URL=http://imgproxy:8080
IMGPROXY_ALLOWED_SOURCES=s3://,local://
IMGPROXY_LOCAL_FILESYSTEM_ROOT=/storage
IMGPROXY_USE_S3=true
IMGPROXY_USE_LAST_MODIFIED=true
IMGPROXY_USE_ETAG=true
IMGPROXY_S3_ENDPOINT=$MINIO_URL
IMGPROXY_KEY=6465616462656566 # deadbeef
IMGPROXY_SALT=6465616462656566 # deadbeef
Expand Down
19 changes: 6 additions & 13 deletions pgs/web_asset_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,19 +131,14 @@ func (h *ApiAssetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}

attempts = append(attempts, fp.Filepath)
mimeType := storage.GetMimeType(fp.Filepath)
logger = logger.With("filename", fp.Filepath)
var c io.ReadCloser
var err error
if strings.HasPrefix(mimeType, "image/") {
c, contentType, err = h.Storage.ServeObject(
h.Bucket,
fp.Filepath,
h.ImgProcessOpts,
)
} else {
c, info, err = h.Storage.GetObject(h.Bucket, fp.Filepath)
}
c, info, err = h.Storage.ServeObject(
h.Bucket,
fp.Filepath,
h.ImgProcessOpts,
)
if err == nil {
contents = c
assetFilepath = fp.Filepath
Expand All @@ -163,9 +158,7 @@ func (h *ApiAssetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
defer contents.Close()

if contentType == "" {
contentType = storage.GetMimeType(assetFilepath)
}
contentType = info.Metadata.Get("content-type")

var headers []*HeaderRule
headersFp, headersInfo, err := h.Storage.GetObject(h.Bucket, filepath.Join(h.ProjectDir, "_headers"))
Expand Down
27 changes: 18 additions & 9 deletions shared/storage/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"os"
"path/filepath"
"strings"

sst "github.com/picosh/pobj/storage"
)
Expand All @@ -21,14 +22,22 @@ func NewStorageFS(dir string) (*StorageFS, error) {
return &StorageFS{st}, nil
}

func (s *StorageFS) ServeObject(bucket sst.Bucket, fpath string, opts *ImgProcessOpts) (io.ReadCloser, string, error) {
if opts == nil || os.Getenv("IMGPROXY_URL") == "" {
contentType := GetMimeType(fpath)
rc, _, err := s.GetObject(bucket, fpath)
return rc, contentType, err
func (s *StorageFS) ServeObject(bucket sst.Bucket, fpath string, opts *ImgProcessOpts) (io.ReadCloser, *sst.ObjectInfo, error) {
var rc io.ReadCloser
var info *sst.ObjectInfo
var err error
mimeType := GetMimeType(fpath)
if !strings.HasPrefix(mimeType, "image/") || opts == nil || os.Getenv("IMGPROXY_URL") == "" {
rc, info, err = s.GetObject(bucket, fpath)
// StorageFS never returns a content-type.
info.Metadata.Set("content-type", mimeType)
} else {
filePath := filepath.Join(bucket.Name, fpath)
dataURL := fmt.Sprintf("s3://%s", filePath)
rc, info, err = HandleProxy(dataURL, opts)
}

filePath := filepath.Join(bucket.Path, fpath)
dataURL := fmt.Sprintf("local://%s", filePath)
return HandleProxy(dataURL, opts)
if err != nil {
return nil, nil, err
}
return rc, info, err
}
8 changes: 5 additions & 3 deletions shared/storage/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ func NewStorageMemory(sto map[string]map[string]string) (*StorageMemory, error)
return &StorageMemory{st}, nil
}

func (s *StorageMemory) ServeObject(bucket sst.Bucket, fpath string, opts *ImgProcessOpts) (io.ReadCloser, string, error) {
obj, _, err := s.GetObject(bucket, fpath)
return obj, GetMimeType(fpath), err
func (s *StorageMemory) ServeObject(bucket sst.Bucket, fpath string, opts *ImgProcessOpts) (io.ReadCloser, *sst.ObjectInfo, error) {
obj, info, err := s.GetObject(bucket, fpath)
mimeType := GetMimeType(fpath)
info.Metadata.Set("content-type", mimeType)
return obj, info, err
}
27 changes: 18 additions & 9 deletions shared/storage/minio.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"io"
"os"
"path/filepath"
"strings"

sst "github.com/picosh/pobj/storage"
)
Expand All @@ -21,14 +22,22 @@ func NewStorageMinio(address, user, pass string) (*StorageMinio, error) {
return &StorageMinio{st}, nil
}

func (s *StorageMinio) ServeObject(bucket sst.Bucket, fpath string, opts *ImgProcessOpts) (io.ReadCloser, string, error) {
if opts == nil || os.Getenv("IMGPROXY_URL") == "" {
contentType := GetMimeType(fpath)
rc, _, err := s.GetObject(bucket, fpath)
return rc, contentType, err
func (s *StorageMinio) ServeObject(bucket sst.Bucket, fpath string, opts *ImgProcessOpts) (io.ReadCloser, *sst.ObjectInfo, error) {
var rc io.ReadCloser
var info *sst.ObjectInfo
var err error
mimeType := GetMimeType(fpath)
if !strings.HasPrefix(mimeType, "image/") || opts == nil || os.Getenv("IMGPROXY_URL") == "" {
rc, info, err = s.GetObject(bucket, fpath)
// Minio always returns application/octet-stream which needs to be overridden.
info.Metadata.Set("content-type", mimeType)
} else {
filePath := filepath.Join(bucket.Name, fpath)
dataURL := fmt.Sprintf("s3://%s", filePath)
rc, info, err = HandleProxy(dataURL, opts)
}

filePath := filepath.Join(bucket.Name, fpath)
dataURL := fmt.Sprintf("s3://%s", filePath)
return HandleProxy(dataURL, opts)
if err != nil {
return nil, nil, err
}
return rc, info, err
}
33 changes: 26 additions & 7 deletions shared/storage/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import (
"path/filepath"
"strconv"
"strings"
"time"

"github.com/picosh/pobj/storage"
)

func GetMimeType(fpath string) string {
Expand Down Expand Up @@ -199,7 +202,7 @@ func (img *ImgProcessOpts) String() string {
return processOpts
}

func HandleProxy(dataURL string, opts *ImgProcessOpts) (io.ReadCloser, string, error) {
func HandleProxy(dataURL string, opts *ImgProcessOpts) (io.ReadCloser, *storage.ObjectInfo, error) {
imgProxyURL := os.Getenv("IMGPROXY_URL")
imgProxySalt := os.Getenv("IMGPROXY_SALT")
imgProxyKey := os.Getenv("IMGPROXY_KEY")
Expand All @@ -213,30 +216,46 @@ func HandleProxy(dataURL string, opts *ImgProcessOpts) (io.ReadCloser, string, e
if imgProxySalt != "" && imgProxyKey != "" {
keyBin, err := hex.DecodeString(imgProxyKey)
if err != nil {
return nil, "", err
return nil, nil, err
}

saltBin, err := hex.DecodeString(imgProxySalt)
if err != nil {
return nil, "", err
return nil, nil, err
}

mac := hmac.New(sha256.New, keyBin)
mac.Write(saltBin)
mac.Write([]byte(processPath))
signature = base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
}

proxyAddress := fmt.Sprintf("%s/%s%s", imgProxyURL, signature, processPath)

res, err := http.Get(proxyAddress)
if err != nil {
return nil, "", err
return nil, nil, err
}

if res.StatusCode < 200 || res.StatusCode >= 300 {
return nil, "", fmt.Errorf("%s", res.Status)
return nil, nil, fmt.Errorf("imgproxy returned %s", res.Status)
}
lastModified := res.Header.Get("Last-Modified")
parsedTime, err := time.Parse(time.RFC1123, lastModified)
if err != nil {
return nil, nil, fmt.Errorf("decoding last-modified: %w", err)
}
info := &storage.ObjectInfo{
Size: res.ContentLength,
LastModified: parsedTime,
ETag: trimEtag(res.Header.Get("etag")),
Metadata: res.Header,
}

return res.Body, info, nil
}

return res.Body, res.Header.Get("Content-Type"), nil
// trimEtag removes quotes from the etag header, which matches the behavior of the minio-go SDK
func trimEtag(etag string) string {
etag = strings.TrimPrefix(etag, "\"")
return strings.TrimSuffix(etag, "\"")
}
2 changes: 1 addition & 1 deletion shared/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ import (

type StorageServe interface {
sst.ObjectStorage
ServeObject(bucket sst.Bucket, fpath string, opts *ImgProcessOpts) (io.ReadCloser, string, error)
ServeObject(bucket sst.Bucket, fpath string, opts *ImgProcessOpts) (io.ReadCloser, *sst.ObjectInfo, error)
}

0 comments on commit d23fe53

Please sign in to comment.