Browse Source

Implement fallback images in a similar fashion to watermarks. (#374)

* 220. Implement fallback images in a similar fashion to watermarks.

* 220. Fix error formatting.

* 220. Fixes based on @darthsim's feedback.
Ewan Higgs 5 years ago
parent
commit
21b990d895
3 changed files with 53 additions and 11 deletions
  1. 8 0
      config.go
  2. 26 10
      image_data.go
  3. 19 1
      processing_handler.go

+ 8 - 0
config.go

@@ -224,6 +224,10 @@ type config struct {
 	WatermarkURL     string
 	WatermarkOpacity float64
 
+	FallbackImageData string
+	FallbackImagePath string
+	FallbackImageURL  string
+
 	NewRelicAppName string
 	NewRelicKey     string
 
@@ -377,6 +381,10 @@ func configure() error {
 	strEnvConfig(&conf.WatermarkURL, "IMGPROXY_WATERMARK_URL")
 	floatEnvConfig(&conf.WatermarkOpacity, "IMGPROXY_WATERMARK_OPACITY")
 
+	strEnvConfig(&conf.FallbackImageData, "IMGPROXY_FALLBACK_IMAGE_DATA")
+	strEnvConfig(&conf.FallbackImagePath, "IMGPROXY_FALLBACK_IMAGE_PATH")
+	strEnvConfig(&conf.FallbackImageURL, "IMGPROXY_FALLBACK_IMAGE_URL")
+
 	strEnvConfig(&conf.NewRelicAppName, "IMGPROXY_NEW_RELIC_APP_NAME")
 	strEnvConfig(&conf.NewRelicKey, "IMGPROXY_NEW_RELIC_KEY")
 

+ 26 - 10
watermark_data.go → image_data.go

@@ -9,35 +9,51 @@ import (
 
 func getWatermarkData() (*imageData, error) {
 	if len(conf.WatermarkData) > 0 {
-		return base64WatermarkData(conf.WatermarkData)
+		return base64ImageData(conf.WatermarkData)
 	}
 
 	if len(conf.WatermarkPath) > 0 {
-		return fileWatermarkData(conf.WatermarkPath)
+		return fileImageData(conf.WatermarkPath)
 	}
 
 	if len(conf.WatermarkURL) > 0 {
-		return remoteWatermarkData(conf.WatermarkURL)
+		return remoteImageData(conf.WatermarkURL)
 	}
 
 	return nil, nil
 }
 
-func base64WatermarkData(encoded string) (*imageData, error) {
+func getFallbackImageData() (*imageData, error) {
+	if len(conf.FallbackImageData) > 0 {
+		return base64ImageData(conf.FallbackImageData)
+	}
+
+	if len(conf.FallbackImagePath) > 0 {
+		return fileImageData(conf.FallbackImagePath)
+	}
+
+	if len(conf.FallbackImageURL) > 0 {
+		return remoteImageData(conf.FallbackImageURL)
+	}
+
+	return nil, nil
+}
+
+func base64ImageData(encoded string) (*imageData, error) {
 	data, err := base64.StdEncoding.DecodeString(encoded)
 	if err != nil {
-		return nil, fmt.Errorf("Can't decode watermark data: %s", err)
+		return nil, fmt.Errorf("Can't decode image data: %s", err)
 	}
 
 	imgtype, err := checkTypeAndDimensions(bytes.NewReader(data))
 	if err != nil {
-		return nil, fmt.Errorf("Can't decode watermark: %s", err)
+		return nil, fmt.Errorf("Can't decode image: %s", err)
 	}
 
 	return &imageData{Data: data, Type: imgtype}, nil
 }
 
-func fileWatermarkData(path string) (*imageData, error) {
+func fileImageData(path string) (*imageData, error) {
 	f, err := os.Open(path)
 	if err != nil {
 		return nil, fmt.Errorf("Can't read watermark: %s", err)
@@ -56,18 +72,18 @@ func fileWatermarkData(path string) (*imageData, error) {
 	return imgdata, err
 }
 
-func remoteWatermarkData(imageURL string) (*imageData, error) {
+func remoteImageData(imageURL string) (*imageData, error) {
 	res, err := requestImage(imageURL)
 	if res != nil {
 		defer res.Body.Close()
 	}
 	if err != nil {
-		return nil, fmt.Errorf("Can't download watermark: %s", err)
+		return nil, fmt.Errorf("Can't download image: %s", err)
 	}
 
 	imgdata, err := readAndCheckImage(res.Body, int(res.ContentLength))
 	if err != nil {
-		return nil, fmt.Errorf("Can't download watermark: %s", err)
+		return nil, fmt.Errorf("Can't download image: %s", err)
 	}
 
 	return imgdata, err

+ 19 - 1
processing_handler.go

@@ -16,6 +16,7 @@ var (
 	processingSem chan struct{}
 
 	headerVaryValue string
+	fallback        *imageData
 )
 
 func initProcessingHandler() error {
@@ -45,6 +46,10 @@ func initProcessingHandler() error {
 
 	headerVaryValue = strings.Join(vary, ", ")
 
+	if err := loadFallback(); err != nil {
+		return err
+	}
+
 	return nil
 }
 
@@ -153,7 +158,12 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
 		if prometheusEnabled {
 			incrementPrometheusErrorsTotal("download")
 		}
-		panic(err)
+		if fallback != nil {
+			logError("Could not load image. Using fallback image: %s", err.Error())
+			ctx = context.WithValue(ctx, imageDataCtxKey, fallback)
+		} else {
+			panic(err)
+		}
 	}
 
 	checkTimeout(ctx)
@@ -186,3 +196,11 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
 
 	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
+}