mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-07-12 23:00:55 +02:00
Minor changes in fallback images feature; Add docs for fallback images
This commit is contained in:
@ -1,6 +1,8 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- Fallback images.
|
||||||
|
|
||||||
## [2.12.0] - 2020-04-07
|
## [2.12.0] - 2020-04-07
|
||||||
### Addded
|
### Addded
|
||||||
|
@ -155,6 +155,14 @@ imgproxy Pro can extract specific frames of videos to create thumbnails. The fea
|
|||||||
|
|
||||||
Read more about watermarks in the [Watermark](watermark.md) guide.
|
Read more about watermarks in the [Watermark](watermark.md) guide.
|
||||||
|
|
||||||
|
## Fallback image
|
||||||
|
|
||||||
|
You can set up a fallback image that will be used in case imgproxy can't fetch the requested one. Use one of the following variables:
|
||||||
|
|
||||||
|
* `IMGPROXY_FALLBACK_IMAGE_DATA`: Base64-encoded image data. You can easily calculate it with `base64 tmp/fallback.png | tr -d '\n'`;
|
||||||
|
* `IMGPROXY_FALLBACK_IMAGE_PATH`: path to the locally stored image;
|
||||||
|
* `IMGPROXY_FALLBACK_IMAGE_URL`: fallback image URL.
|
||||||
|
|
||||||
## Presets
|
## Presets
|
||||||
|
|
||||||
Read about imgproxy presets in the [Presets](presets.md) guide.
|
Read about imgproxy presets in the [Presets](presets.md) guide.
|
||||||
|
13
download.go
13
download.go
@ -30,19 +30,6 @@ 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.Reader
|
r io.Reader
|
||||||
left int
|
left int
|
||||||
|
@ -2,22 +2,36 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type imageData struct {
|
||||||
|
Data []byte
|
||||||
|
Type imageType
|
||||||
|
|
||||||
|
cancel context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *imageData) Close() {
|
||||||
|
if d.cancel != nil {
|
||||||
|
d.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func getWatermarkData() (*imageData, error) {
|
func getWatermarkData() (*imageData, error) {
|
||||||
if len(conf.WatermarkData) > 0 {
|
if len(conf.WatermarkData) > 0 {
|
||||||
return base64ImageData(conf.WatermarkData)
|
return base64ImageData(conf.WatermarkData, "watermark")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(conf.WatermarkPath) > 0 {
|
if len(conf.WatermarkPath) > 0 {
|
||||||
return fileImageData(conf.WatermarkPath)
|
return fileImageData(conf.WatermarkPath, "watermark")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(conf.WatermarkURL) > 0 {
|
if len(conf.WatermarkURL) > 0 {
|
||||||
return remoteImageData(conf.WatermarkURL)
|
return remoteImageData(conf.WatermarkURL, "watermark")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -25,65 +39,65 @@ func getWatermarkData() (*imageData, error) {
|
|||||||
|
|
||||||
func getFallbackImageData() (*imageData, error) {
|
func getFallbackImageData() (*imageData, error) {
|
||||||
if len(conf.FallbackImageData) > 0 {
|
if len(conf.FallbackImageData) > 0 {
|
||||||
return base64ImageData(conf.FallbackImageData)
|
return base64ImageData(conf.FallbackImageData, "fallback image")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(conf.FallbackImagePath) > 0 {
|
if len(conf.FallbackImagePath) > 0 {
|
||||||
return fileImageData(conf.FallbackImagePath)
|
return fileImageData(conf.FallbackImagePath, "fallback image")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(conf.FallbackImageURL) > 0 {
|
if len(conf.FallbackImageURL) > 0 {
|
||||||
return remoteImageData(conf.FallbackImageURL)
|
return remoteImageData(conf.FallbackImageURL, "fallback image")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func base64ImageData(encoded string) (*imageData, error) {
|
func base64ImageData(encoded, desc string) (*imageData, error) {
|
||||||
data, err := base64.StdEncoding.DecodeString(encoded)
|
data, err := base64.StdEncoding.DecodeString(encoded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Can't decode image data: %s", err)
|
return nil, fmt.Errorf("Can't decode %s data: %s", desc, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
imgtype, err := checkTypeAndDimensions(bytes.NewReader(data))
|
imgtype, err := checkTypeAndDimensions(bytes.NewReader(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Can't decode image: %s", err)
|
return nil, fmt.Errorf("Can't decode %s: %s", desc, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &imageData{Data: data, Type: imgtype}, nil
|
return &imageData{Data: data, Type: imgtype}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fileImageData(path string) (*imageData, error) {
|
func fileImageData(path, desc string) (*imageData, error) {
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Can't read watermark: %s", err)
|
return nil, fmt.Errorf("Can't read %s: %s", desc, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fi, err := f.Stat()
|
fi, err := f.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Can't read watermark: %s", err)
|
return nil, fmt.Errorf("Can't read %s: %s", desc, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
imgdata, err := readAndCheckImage(f, int(fi.Size()))
|
imgdata, err := readAndCheckImage(f, int(fi.Size()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Can't read watermark: %s", err)
|
return nil, fmt.Errorf("Can't read %s: %s", desc, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return imgdata, err
|
return imgdata, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func remoteImageData(imageURL string) (*imageData, error) {
|
func remoteImageData(imageURL, desc string) (*imageData, error) {
|
||||||
res, err := requestImage(imageURL)
|
res, err := requestImage(imageURL)
|
||||||
if res != nil {
|
if res != nil {
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Can't download image: %s", err)
|
return nil, fmt.Errorf("Can't download %s: %s", desc, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
imgdata, err := readAndCheckImage(res.Body, int(res.ContentLength))
|
imgdata, err := readAndCheckImage(res.Body, int(res.ContentLength))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Can't download image: %s", err)
|
return nil, fmt.Errorf("Can't download %s: %s", desc, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return imgdata, err
|
return imgdata, err
|
||||||
|
@ -16,14 +16,15 @@ var (
|
|||||||
processingSem chan struct{}
|
processingSem chan struct{}
|
||||||
|
|
||||||
headerVaryValue string
|
headerVaryValue string
|
||||||
fallback *imageData
|
fallbackImage *imageData
|
||||||
)
|
)
|
||||||
|
|
||||||
func initProcessingHandler() error {
|
func initProcessingHandler() error {
|
||||||
|
var err error
|
||||||
|
|
||||||
processingSem = make(chan struct{}, conf.Concurrency)
|
processingSem = make(chan struct{}, conf.Concurrency)
|
||||||
|
|
||||||
if conf.GZipCompression > 0 {
|
if conf.GZipCompression > 0 {
|
||||||
var err error
|
|
||||||
responseGzipBufPool = newBufPool("gzip", conf.Concurrency, conf.GZipBufferSize)
|
responseGzipBufPool = newBufPool("gzip", conf.Concurrency, conf.GZipBufferSize)
|
||||||
if responseGzipPool, err = newGzipPool(conf.Concurrency); err != nil {
|
if responseGzipPool, err = newGzipPool(conf.Concurrency); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -46,7 +47,7 @@ func initProcessingHandler() error {
|
|||||||
|
|
||||||
headerVaryValue = strings.Join(vary, ", ")
|
headerVaryValue = strings.Join(vary, ", ")
|
||||||
|
|
||||||
if err := loadFallback(); err != nil {
|
if fallbackImage, err = getFallbackImageData(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,12 +159,17 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
|
|||||||
if prometheusEnabled {
|
if prometheusEnabled {
|
||||||
incrementPrometheusErrorsTotal("download")
|
incrementPrometheusErrorsTotal("download")
|
||||||
}
|
}
|
||||||
if fallback != nil {
|
|
||||||
logError("Could not load image. Using fallback image: %s", err.Error())
|
if fallbackImage == nil {
|
||||||
ctx = context.WithValue(ctx, imageDataCtxKey, fallback)
|
|
||||||
} else {
|
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ierr, ok := err.(*imgproxyError); !ok || ierr.Unexpected {
|
||||||
|
reportError(err, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
logWarning("Could not load image. Using fallback image: %s", err.Error())
|
||||||
|
ctx = context.WithValue(ctx, imageDataCtxKey, fallbackImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
checkTimeout(ctx)
|
checkTimeout(ctx)
|
||||||
@ -196,11 +202,3 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
respondWithImage(ctx, reqID, r, rw, imageData)
|
respondWithImage(ctx, reqID, r, rw, imageData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadFallback() (err error) {
|
|
||||||
fallback, err = getFallbackImageData()
|
|
||||||
if err != nil {
|
|
||||||
logError("Could not load fallback data. Fallback images will not be available: %s", err.Error())
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
Reference in New Issue
Block a user