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

Refactored image downloading and watermark preparation

This commit is contained in:
DarthSim 2019-09-20 17:01:00 +06:00
parent 26bd9e4ac3
commit f7ca800b36
5 changed files with 101 additions and 120 deletions

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"bytes"
"context" "context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
@ -21,7 +20,6 @@ import (
var ( var (
downloadClient *http.Client downloadClient *http.Client
imageTypeCtxKey = ctxKey("imageType")
imageDataCtxKey = ctxKey("imageData") imageDataCtxKey = ctxKey("imageData")
errSourceDimensionsTooBig = newError(422, "Source image dimensions are too big", "Invalid source image") errSourceDimensionsTooBig = newError(422, "Source image dimensions are too big", "Invalid source image")
@ -34,8 +32,21 @@ const msgSourceImageIsUnreachable = "Source image is unreachable"
var downloadBufPool *bufPool var downloadBufPool *bufPool
type imageData struct {
Data []byte
Type imageType
cancel context.CancelFunc
}
func (d *imageData) Close() {
if d.cancel != nil {
d.cancel()
}
}
type limitReader struct { type limitReader struct {
r io.ReadCloser r io.Reader
left int left int
} }
@ -50,10 +61,6 @@ func (lr *limitReader) Read(p []byte) (n int, err error) {
return return
} }
func (lr *limitReader) Close() error {
return lr.r.Close()
}
func initDownloading() { func initDownloading() {
transport := &http.Transport{ transport := &http.Transport{
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
@ -120,45 +127,56 @@ func checkTypeAndDimensions(r io.Reader) (imageType, error) {
return imgtype, nil return imgtype, nil
} }
func readAndCheckImage(ctx context.Context, res *http.Response) (context.Context, context.CancelFunc, error) { func readAndCheckImage(r io.Reader, contentLength int) (*imageData, error) {
var contentLength int if conf.MaxSrcFileSize > 0 && contentLength > conf.MaxSrcFileSize {
return nil, errSourceFileTooBig
if res.ContentLength > 0 {
contentLength = int(res.ContentLength)
if conf.MaxSrcFileSize > 0 && contentLength > conf.MaxSrcFileSize {
return ctx, func() {}, errSourceFileTooBig
}
} }
buf := downloadBufPool.Get(contentLength) buf := downloadBufPool.Get(contentLength)
cancel := func() { cancel := func() { downloadBufPool.Put(buf) }
downloadBufPool.Put(buf)
}
body := res.Body
if conf.MaxSrcFileSize > 0 { if conf.MaxSrcFileSize > 0 {
body = &limitReader{r: body, left: conf.MaxSrcFileSize} r = &limitReader{r: r, left: conf.MaxSrcFileSize}
} }
imgtype, err := checkTypeAndDimensions(io.TeeReader(body, buf)) imgtype, err := checkTypeAndDimensions(io.TeeReader(r, buf))
if err != nil { if err != nil {
return ctx, cancel, err cancel()
return nil, err
} }
if _, err = buf.ReadFrom(body); err != nil { if _, err = buf.ReadFrom(r); err != nil {
return ctx, cancel, newError(404, err.Error(), msgSourceImageIsUnreachable) cancel()
return nil, newError(404, err.Error(), msgSourceImageIsUnreachable)
} }
ctx = context.WithValue(ctx, imageTypeCtxKey, imgtype) return &imageData{buf.Bytes(), imgtype, cancel}, nil
ctx = context.WithValue(ctx, imageDataCtxKey, buf) }
return ctx, cancel, nil func requestImage(imageURL string) (*http.Response, error) {
req, err := http.NewRequest("GET", imageURL, nil)
if err != nil {
return nil, newError(404, err.Error(), msgSourceImageIsUnreachable).MarkAsUnexpected()
}
req.Header.Set("User-Agent", conf.UserAgent)
res, err := downloadClient.Do(req)
if err != nil {
return res, newError(404, err.Error(), msgSourceImageIsUnreachable).MarkAsUnexpected()
}
if res.StatusCode != 200 {
body, _ := ioutil.ReadAll(res.Body)
msg := fmt.Sprintf("Can't download image; Status: %d; %s", res.StatusCode, string(body))
return res, newError(404, msg, msgSourceImageIsUnreachable).MarkAsUnexpected()
}
return res, nil
} }
func downloadImage(ctx context.Context) (context.Context, context.CancelFunc, error) { func downloadImage(ctx context.Context) (context.Context, context.CancelFunc, error) {
url := getImageURL(ctx) imageURL := getImageURL(ctx)
if newRelicEnabled { if newRelicEnabled {
newRelicCancel := startNewRelicSegment(ctx, "Downloading image") newRelicCancel := startNewRelicSegment(ctx, "Downloading image")
@ -169,34 +187,24 @@ func downloadImage(ctx context.Context) (context.Context, context.CancelFunc, er
defer startPrometheusDuration(prometheusDownloadDuration)() defer startPrometheusDuration(prometheusDownloadDuration)()
} }
req, err := http.NewRequest("GET", url, nil) res, err := requestImage(imageURL)
if err != nil {
return ctx, func() {}, newError(404, err.Error(), msgSourceImageIsUnreachable).MarkAsUnexpected()
}
req.Header.Set("User-Agent", conf.UserAgent)
res, err := downloadClient.Do(req)
if res != nil { if res != nil {
defer res.Body.Close() defer res.Body.Close()
} }
if err != nil { if err != nil {
return ctx, func() {}, newError(404, err.Error(), msgSourceImageIsUnreachable).MarkAsUnexpected() return ctx, func() {}, err
} }
if res.StatusCode != 200 { imgdata, err := readAndCheckImage(res.Body, int(res.ContentLength))
body, _ := ioutil.ReadAll(res.Body) if err != nil {
msg := fmt.Sprintf("Can't download image; Status: %d; %s", res.StatusCode, string(body)) return ctx, func() {}, err
return ctx, func() {}, newError(404, msg, msgSourceImageIsUnreachable).MarkAsUnexpected()
} }
return readAndCheckImage(ctx, res) ctx = context.WithValue(ctx, imageDataCtxKey, imgdata)
return ctx, imgdata.Close, err
} }
func getImageType(ctx context.Context) imageType { func getImageData(ctx context.Context) *imageData {
return ctx.Value(imageTypeCtxKey).(imageType) return ctx.Value(imageDataCtxKey).(*imageData)
}
func getImageData(ctx context.Context) *bytes.Buffer {
return ctx.Value(imageDataCtxKey).(*bytes.Buffer)
} }

View File

@ -31,7 +31,7 @@ func calcETag(ctx context.Context) string {
defer eTagCalcPool.Put(c) defer eTagCalcPool.Put(c)
c.hash.Reset() c.hash.Reset()
c.hash.Write(getImageData(ctx).Bytes()) c.hash.Write(getImageData(ctx).Data)
footprint := c.hash.Sum(nil) footprint := c.hash.Sum(nil)
c.hash.Reset() c.hash.Reset()

View File

@ -195,8 +195,8 @@ func cropImage(img *vipsImage, cropWidth, cropHeight int, gravity *gravityOption
return img.Crop(left, top, cropWidth, cropHeight) return img.Crop(left, top, cropWidth, cropHeight)
} }
func prepareWatermark(wm *vipsImage, wmData *watermarkData, opts *watermarkOptions, imgWidth, imgHeight int) error { func prepareWatermark(wm *vipsImage, wmData *imageData, opts *watermarkOptions, imgWidth, imgHeight int) error {
if err := wm.Load(wmData.data, wmData.imgtype, 1, 1.0, 1); err != nil { if err := wm.Load(wmData.Data, wmData.Type, 1, 1.0, 1); err != nil {
return err return err
} }
@ -204,14 +204,14 @@ func prepareWatermark(wm *vipsImage, wmData *watermarkData, opts *watermarkOptio
po.Resize = resizeFit po.Resize = resizeFit
po.Dpr = 1 po.Dpr = 1
po.Enlarge = true po.Enlarge = true
po.Format = wmData.imgtype po.Format = wmData.Type
if opts.Scale > 0 { if opts.Scale > 0 {
po.Width = maxInt(scaleInt(imgWidth, opts.Scale), 1) po.Width = maxInt(scaleInt(imgWidth, opts.Scale), 1)
po.Height = maxInt(scaleInt(imgHeight, opts.Scale), 1) po.Height = maxInt(scaleInt(imgHeight, opts.Scale), 1)
} }
if err := transformImage(context.Background(), wm, wmData.data, po, wmData.imgtype); err != nil { if err := transformImage(context.Background(), wm, wmData.Data, po, wmData.Type); err != nil {
return err return err
} }
@ -226,7 +226,7 @@ func prepareWatermark(wm *vipsImage, wmData *watermarkData, opts *watermarkOptio
return wm.Embed(opts.Gravity, imgWidth, imgHeight, opts.OffsetX, opts.OffsetY, rgbColor{0, 0, 0}) return wm.Embed(opts.Gravity, imgWidth, imgHeight, opts.OffsetX, opts.OffsetY, rgbColor{0, 0, 0})
} }
func applyWatermark(img *vipsImage, wmData *watermarkData, opts *watermarkOptions, framesCount int) error { func applyWatermark(img *vipsImage, wmData *imageData, opts *watermarkOptions, framesCount int) error {
wm := new(vipsImage) wm := new(vipsImage)
defer wm.Clear() defer wm.Clear()
@ -541,15 +541,14 @@ func processImage(ctx context.Context) ([]byte, context.CancelFunc, error) {
defer vipsCleanup() defer vipsCleanup()
po := getProcessingOptions(ctx) po := getProcessingOptions(ctx)
data := getImageData(ctx).Bytes() imgdata := getImageData(ctx)
imgtype := getImageType(ctx)
if po.Format == imageTypeUnknown { if po.Format == imageTypeUnknown {
switch { switch {
case po.PreferWebP && vipsTypeSupportSave[imageTypeWEBP]: case po.PreferWebP && vipsTypeSupportSave[imageTypeWEBP]:
po.Format = imageTypeWEBP po.Format = imageTypeWEBP
case vipsTypeSupportSave[imgtype] && imgtype != imageTypeHEIC: case vipsTypeSupportSave[imgdata.Type] && imgdata.Type != imageTypeHEIC:
po.Format = imgtype po.Format = imgdata.Type
default: default:
po.Format = imageTypeJPEG po.Format = imageTypeJPEG
} }
@ -577,7 +576,7 @@ func processImage(ctx context.Context) ([]byte, context.CancelFunc, error) {
po.Width, po.Height = 0, 0 po.Width, po.Height = 0, 0
} }
animationSupport := conf.MaxAnimationFrames > 1 && vipsSupportAnimation(imgtype) && vipsSupportAnimation(po.Format) animationSupport := conf.MaxAnimationFrames > 1 && vipsSupportAnimation(imgdata.Type) && vipsSupportAnimation(po.Format)
pages := 1 pages := 1
if animationSupport { if animationSupport {
@ -587,16 +586,16 @@ func processImage(ctx context.Context) ([]byte, context.CancelFunc, error) {
img := new(vipsImage) img := new(vipsImage)
defer img.Clear() defer img.Clear()
if err := img.Load(data, imgtype, 1, 1.0, pages); err != nil { if err := img.Load(imgdata.Data, imgdata.Type, 1, 1.0, pages); err != nil {
return nil, func() {}, err return nil, func() {}, err
} }
if animationSupport && img.IsAnimated() { if animationSupport && img.IsAnimated() {
if err := transformAnimated(ctx, img, data, po, imgtype); err != nil { if err := transformAnimated(ctx, img, imgdata.Data, po, imgdata.Type); err != nil {
return nil, func() {}, err return nil, func() {}, err
} }
} else { } else {
if err := transformImage(ctx, img, data, po, imgtype); err != nil { if err := transformImage(ctx, img, imgdata.Data, po, imgdata.Type); err != nil {
return nil, func() {}, err return nil, func() {}, err
} }
} }

View File

@ -25,7 +25,7 @@ var (
vipsTypeSupportLoad = make(map[imageType]bool) vipsTypeSupportLoad = make(map[imageType]bool)
vipsTypeSupportSave = make(map[imageType]bool) vipsTypeSupportSave = make(map[imageType]bool)
watermark *watermarkData watermark *imageData
) )
var vipsConf struct { var vipsConf struct {

View File

@ -2,99 +2,73 @@ package main
import ( import (
"bytes" "bytes"
"context"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io/ioutil"
"os" "os"
) )
type watermarkData struct { func getWatermarkData() (*imageData, error) {
data []byte
imgtype imageType
}
func getWatermarkData() (*watermarkData, error) {
if len(conf.WatermarkData) > 0 { if len(conf.WatermarkData) > 0 {
data, imgtype, err := base64WatermarkData() return base64WatermarkData(conf.WatermarkData)
if err != nil {
return nil, err
}
return &watermarkData{data, imgtype}, err
} }
if len(conf.WatermarkPath) > 0 { if len(conf.WatermarkPath) > 0 {
data, imgtype, err := fileWatermarkData() return fileWatermarkData(conf.WatermarkPath)
if err != nil {
return nil, err
}
return &watermarkData{data, imgtype}, err
} }
if len(conf.WatermarkURL) > 0 { if len(conf.WatermarkURL) > 0 {
b, imgtype, cancel, err := remoteWatermarkData() return remoteWatermarkData(conf.WatermarkURL)
defer cancel()
if err != nil {
return nil, err
}
data := make([]byte, len(b))
copy(data, b)
return &watermarkData{data, imgtype}, err
} }
return nil, nil return nil, nil
} }
func base64WatermarkData() ([]byte, imageType, error) { func base64WatermarkData(encoded string) (*imageData, error) {
data, err := base64.StdEncoding.DecodeString(conf.WatermarkData) data, err := base64.StdEncoding.DecodeString(encoded)
if err != nil { if err != nil {
return nil, imageTypeUnknown, fmt.Errorf("Can't decode watermark data: %s", err) return nil, fmt.Errorf("Can't decode watermark data: %s", err)
} }
imgtype, err := checkTypeAndDimensions(bytes.NewReader(data)) imgtype, err := checkTypeAndDimensions(bytes.NewReader(data))
if err != nil { if err != nil {
return nil, imageTypeUnknown, fmt.Errorf("Can't decode watermark: %s", err) return nil, fmt.Errorf("Can't decode watermark: %s", err)
} }
return data, imgtype, nil return &imageData{Data: data, Type: imgtype}, nil
} }
func fileWatermarkData() ([]byte, imageType, error) { func fileWatermarkData(path string) (*imageData, error) {
f, err := os.Open(conf.WatermarkPath) f, err := os.Open(path)
if err != nil { if err != nil {
return nil, imageTypeUnknown, fmt.Errorf("Can't read watermark: %s", err) return nil, fmt.Errorf("Can't read watermark: %s", err)
} }
imgtype, err := checkTypeAndDimensions(f) fi, err := f.Stat()
if err != nil { if err != nil {
return nil, imageTypeUnknown, fmt.Errorf("Can't decode watermark: %s", err) return nil, fmt.Errorf("Can't read watermark: %s", err)
} }
// Return to the beginning of the file imgdata, err := readAndCheckImage(f, int(fi.Size()))
f.Seek(0, 0)
data, err := ioutil.ReadAll(f)
if err != nil { if err != nil {
return nil, imageTypeUnknown, fmt.Errorf("Can't read watermark: %s", err) return nil, fmt.Errorf("Can't read watermark: %s", err)
} }
return data, imgtype, nil return imgdata, err
} }
func remoteWatermarkData() ([]byte, imageType, context.CancelFunc, error) { func remoteWatermarkData(imageURL string) (*imageData, error) {
ctx := context.WithValue(context.Background(), imageURLCtxKey, conf.WatermarkURL) res, err := requestImage(imageURL)
ctx, cancel, err := downloadImage(ctx) if res != nil {
defer res.Body.Close()
}
if err != nil { if err != nil {
return nil, imageTypeUnknown, cancel, fmt.Errorf("Can't download watermark: %s", err) return nil, fmt.Errorf("Can't download watermark: %s", err)
} }
return getImageData(ctx).Bytes(), getImageType(ctx), cancel, err imgdata, err := readAndCheckImage(res.Body, int(res.ContentLength))
if err != nil {
return nil, fmt.Errorf("Can't download watermark: %s", err)
}
return imgdata, err
} }