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:
parent
26bd9e4ac3
commit
f7ca800b36
108
download.go
108
download.go
@ -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)
|
|
||||||
}
|
}
|
||||||
|
2
etag.go
2
etag.go
@ -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()
|
||||||
|
25
process.go
25
process.go
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
vips.go
2
vips.go
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user