1
0
mirror of https://github.com/imgproxy/imgproxy.git synced 2024-11-29 08:22:11 +02:00
imgproxy/processing_handler.go

229 lines
5.1 KiB
Go

package main
import (
"context"
"fmt"
"net/http"
"strconv"
"strings"
"time"
)
var (
responseGzipBufPool *bufPool
responseGzipPool *gzipPool
processingSem chan struct{}
headerVaryValue string
fallbackImage *imageData
)
func initProcessingHandler() error {
var err error
processingSem = make(chan struct{}, conf.Concurrency)
if conf.GZipCompression > 0 {
responseGzipBufPool = newBufPool("gzip", conf.Concurrency, conf.GZipBufferSize)
if responseGzipPool, err = newGzipPool(conf.Concurrency); err != nil {
return err
}
}
vary := make([]string, 0)
if conf.EnableWebpDetection || conf.EnforceWebp {
vary = append(vary, "Accept")
}
if conf.GZipCompression > 0 {
vary = append(vary, "Accept-Encoding")
}
if conf.EnableClientHints {
vary = append(vary, "DPR", "Viewport-Width", "Width")
}
headerVaryValue = strings.Join(vary, ", ")
if fallbackImage, err = getFallbackImageData(); err != nil {
return err
}
return nil
}
func respondWithImage(ctx context.Context, reqID string, r *http.Request, rw http.ResponseWriter, data []byte) {
po := getProcessingOptions(ctx)
var contentDisposition string
if len(po.Filename) > 0 {
contentDisposition = po.Format.ContentDisposition(po.Filename)
} else {
contentDisposition = po.Format.ContentDispositionFromURL(getImageURL(ctx))
}
rw.Header().Set("Content-Type", po.Format.Mime())
rw.Header().Set("Content-Disposition", contentDisposition)
var cacheControl, expires string
if conf.CacheControlPassthrough {
cacheControl = getCacheControlHeader(ctx)
expires = getExpiresHeader(ctx)
}
if len(cacheControl) == 0 && len(expires) == 0 {
cacheControl = fmt.Sprintf("max-age=%d, public", conf.TTL)
expires = time.Now().Add(time.Second * time.Duration(conf.TTL)).Format(http.TimeFormat)
}
if len(cacheControl) > 0 {
rw.Header().Set("Cache-Control", cacheControl)
}
if len(expires) > 0 {
rw.Header().Set("Expires", expires)
}
if len(headerVaryValue) > 0 {
rw.Header().Set("Vary", headerVaryValue)
}
if conf.GZipCompression > 0 && strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
buf := responseGzipBufPool.Get(0)
defer responseGzipBufPool.Put(buf)
gz := responseGzipPool.Get(buf)
defer responseGzipPool.Put(gz)
gz.Write(data)
gz.Close()
rw.Header().Set("Content-Encoding", "gzip")
rw.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
rw.WriteHeader(200)
rw.Write(buf.Bytes())
} else {
rw.Header().Set("Content-Length", strconv.Itoa(len(data)))
rw.WriteHeader(200)
rw.Write(data)
}
if conf.EnableDebugHeaders {
imgdata := getImageData(ctx)
rw.Header().Set("X-Origin-Content-Length", strconv.Itoa(len(imgdata.Data)))
}
imageURL := getImageURL(ctx)
logResponse(reqID, r, 200, nil, &imageURL, po)
// logResponse(reqID, r, 200, getTimerSince(ctx), getImageURL(ctx), po))
}
func respondWithNotModified(ctx context.Context, reqID string, r *http.Request, rw http.ResponseWriter) {
rw.WriteHeader(304)
imageURL := getImageURL(ctx)
logResponse(reqID, r, 304, nil, &imageURL, getProcessingOptions(ctx))
}
func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if newRelicEnabled {
var newRelicCancel context.CancelFunc
ctx, newRelicCancel, rw = startNewRelicTransaction(ctx, rw, r)
defer newRelicCancel()
}
if prometheusEnabled {
prometheusRequestsTotal.Inc()
defer startPrometheusDuration(prometheusRequestDuration)()
}
select {
case processingSem <- struct{}{}:
case <-ctx.Done():
panic(newError(499, "Request was cancelled before processing", "Cancelled"))
}
defer func() { <-processingSem }()
ctx, timeoutCancel := context.WithTimeout(ctx, time.Duration(conf.WriteTimeout)*time.Second)
defer timeoutCancel()
ctx, err := parsePath(ctx, r)
if err != nil {
panic(err)
}
ctx, downloadcancel, err := downloadImage(ctx)
defer downloadcancel()
if err != nil {
if newRelicEnabled {
sendErrorToNewRelic(ctx, err)
}
if prometheusEnabled {
incrementPrometheusErrorsTotal("download")
}
if fallbackImage == nil {
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)
if conf.ETagEnabled {
eTag := calcETag(ctx)
rw.Header().Set("ETag", eTag)
if eTag == r.Header.Get("If-None-Match") {
respondWithNotModified(ctx, reqID, r, rw)
return
}
}
checkTimeout(ctx)
if len(conf.SkipProcessingFormats) > 0 {
imgdata := getImageData(ctx)
po := getProcessingOptions(ctx)
if imgdata.Type == po.Format || po.Format == imageTypeUnknown {
for _, f := range conf.SkipProcessingFormats {
if f == imgdata.Type {
po.Format = imgdata.Type
respondWithImage(ctx, reqID, r, rw, imgdata.Data)
return
}
}
}
}
imageData, processcancel, err := processImage(ctx)
defer processcancel()
if err != nil {
if newRelicEnabled {
sendErrorToNewRelic(ctx, err)
}
if prometheusEnabled {
incrementPrometheusErrorsTotal("processing")
}
panic(err)
}
checkTimeout(ctx)
respondWithImage(ctx, reqID, r, rw, imageData)
}