Browse Source

Fallback image http code config (#589)

Svyatoslav Kryukov 4 years ago
parent
commit
b93aa2f144
3 changed files with 32 additions and 6 deletions
  1. 10 3
      config.go
  2. 1 0
      docs/configuration.md
  3. 21 3
      processing_handler.go

+ 10 - 3
config.go

@@ -279,9 +279,10 @@ type config struct {
 	WatermarkURL     string
 	WatermarkURL     string
 	WatermarkOpacity float64
 	WatermarkOpacity float64
 
 
-	FallbackImageData string
-	FallbackImagePath string
-	FallbackImageURL  string
+	FallbackImageData     string
+	FallbackImagePath     string
+	FallbackImageURL      string
+	FallbackImageHTTPCode int
 
 
 	NewRelicAppName string
 	NewRelicAppName string
 	NewRelicKey     string
 	NewRelicKey     string
@@ -328,6 +329,7 @@ var conf = config{
 	UserAgent:                      fmt.Sprintf("imgproxy/%s", version),
 	UserAgent:                      fmt.Sprintf("imgproxy/%s", version),
 	Presets:                        make(presets),
 	Presets:                        make(presets),
 	WatermarkOpacity:               1,
 	WatermarkOpacity:               1,
+	FallbackImageHTTPCode:          200,
 	BugsnagStage:                   "production",
 	BugsnagStage:                   "production",
 	HoneybadgerEnv:                 "production",
 	HoneybadgerEnv:                 "production",
 	SentryEnvironment:              "production",
 	SentryEnvironment:              "production",
@@ -451,6 +453,7 @@ func configure() error {
 	strEnvConfig(&conf.FallbackImageData, "IMGPROXY_FALLBACK_IMAGE_DATA")
 	strEnvConfig(&conf.FallbackImageData, "IMGPROXY_FALLBACK_IMAGE_DATA")
 	strEnvConfig(&conf.FallbackImagePath, "IMGPROXY_FALLBACK_IMAGE_PATH")
 	strEnvConfig(&conf.FallbackImagePath, "IMGPROXY_FALLBACK_IMAGE_PATH")
 	strEnvConfig(&conf.FallbackImageURL, "IMGPROXY_FALLBACK_IMAGE_URL")
 	strEnvConfig(&conf.FallbackImageURL, "IMGPROXY_FALLBACK_IMAGE_URL")
+	intEnvConfig(&conf.FallbackImageHTTPCode, "IMGPROXY_FALLBACK_IMAGE_HTTP_CODE")
 
 
 	strEnvConfig(&conf.NewRelicAppName, "IMGPROXY_NEW_RELIC_APP_NAME")
 	strEnvConfig(&conf.NewRelicAppName, "IMGPROXY_NEW_RELIC_APP_NAME")
 	strEnvConfig(&conf.NewRelicKey, "IMGPROXY_NEW_RELIC_KEY")
 	strEnvConfig(&conf.NewRelicKey, "IMGPROXY_NEW_RELIC_KEY")
@@ -574,6 +577,10 @@ func configure() error {
 		return fmt.Errorf("Watermark opacity should be less than or equal to 1")
 		return fmt.Errorf("Watermark opacity should be less than or equal to 1")
 	}
 	}
 
 
+	if conf.FallbackImageHTTPCode < 100 || conf.FallbackImageHTTPCode > 599 {
+		return fmt.Errorf("Fallback image HTTP code should be between 100 and 599")
+	}
+
 	if len(conf.PrometheusBind) > 0 && conf.PrometheusBind == conf.Bind {
 	if len(conf.PrometheusBind) > 0 && conf.PrometheusBind == conf.Bind {
 		return fmt.Errorf("Can't use the same binding for the main server and Prometheus")
 		return fmt.Errorf("Can't use the same binding for the main server and Prometheus")
 	}
 	}

+ 1 - 0
docs/configuration.md

@@ -185,6 +185,7 @@ You can set up a fallback image that will be used in case imgproxy can't fetch t
 * `IMGPROXY_FALLBACK_IMAGE_DATA`: Base64-encoded image data. You can easily calculate it with `base64 tmp/fallback.png | tr -d '\n'`;
 * `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_PATH`: path to the locally stored image;
 * `IMGPROXY_FALLBACK_IMAGE_URL`: fallback image URL.
 * `IMGPROXY_FALLBACK_IMAGE_URL`: fallback image URL.
+* `IMGPROXY_FALLBACK_IMAGE_HTTP_CODE`: HTTP code for the fallback image response. Default: `200`.
 
 
 ## Skip processing
 ## Skip processing
 
 

+ 21 - 3
processing_handler.go

@@ -16,6 +16,10 @@ var (
 	fallbackImage   *imageData
 	fallbackImage   *imageData
 )
 )
 
 
+const (
+	fallbackImageUsedCtxKey = ctxKey("fallbackImageUsed")
+)
+
 func initProcessingHandler() error {
 func initProcessingHandler() error {
 	var err error
 	var err error
 
 
@@ -94,12 +98,16 @@ func respondWithImage(ctx context.Context, reqID string, r *http.Request, rw htt
 	}
 	}
 
 
 	rw.Header().Set("Content-Length", strconv.Itoa(len(data)))
 	rw.Header().Set("Content-Length", strconv.Itoa(len(data)))
-	rw.WriteHeader(200)
+	statusCode := 200
+	if getFallbackImageUsed(ctx) {
+		statusCode = conf.FallbackImageHTTPCode
+	}
+	rw.WriteHeader(statusCode)
 	rw.Write(data)
 	rw.Write(data)
 
 
 	imageURL := getImageURL(ctx)
 	imageURL := getImageURL(ctx)
 
 
-	logResponse(reqID, r, 200, nil, &imageURL, po)
+	logResponse(reqID, r, statusCode, nil, &imageURL, po)
 }
 }
 
 
 func respondWithNotModified(ctx context.Context, reqID string, r *http.Request, rw http.ResponseWriter) {
 func respondWithNotModified(ctx context.Context, reqID string, r *http.Request, rw http.ResponseWriter) {
@@ -158,12 +166,13 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
 		}
 		}
 
 
 		logWarning("Could not load image. Using fallback image: %s", err.Error())
 		logWarning("Could not load image. Using fallback image: %s", err.Error())
+		ctx = setFallbackImageUsedCtx(ctx)
 		ctx = context.WithValue(ctx, imageDataCtxKey, fallbackImage)
 		ctx = context.WithValue(ctx, imageDataCtxKey, fallbackImage)
 	}
 	}
 
 
 	checkTimeout(ctx)
 	checkTimeout(ctx)
 
 
-	if conf.ETagEnabled {
+	if conf.ETagEnabled && !getFallbackImageUsed(ctx) {
 		eTag := calcETag(ctx)
 		eTag := calcETag(ctx)
 		rw.Header().Set("ETag", eTag)
 		rw.Header().Set("ETag", eTag)
 
 
@@ -206,3 +215,12 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
 
 
 	respondWithImage(ctx, reqID, r, rw, imageData)
 	respondWithImage(ctx, reqID, r, rw, imageData)
 }
 }
+
+func setFallbackImageUsedCtx(ctx context.Context) context.Context {
+	return context.WithValue(ctx, fallbackImageUsedCtxKey, true)
+}
+
+func getFallbackImageUsed(ctx context.Context) bool {
+	result, _ := ctx.Value(fallbackImageUsedCtxKey).(bool)
+	return result
+}