mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-10-03 18:05:50 +02:00
Provide shared resources to handlers via HandlerContext interface
This commit is contained in:
committed by
Sergei Aleksandrovich
parent
adb4341713
commit
53645688fb
@@ -19,25 +19,27 @@ import (
|
||||
"github.com/imgproxy/imgproxy/v3/semaphores"
|
||||
)
|
||||
|
||||
// HandlerContext provides access to shared handler dependencies
|
||||
type HandlerContext interface {
|
||||
HeaderWriter() *headerwriter.Writer
|
||||
Semaphores() *semaphores.Semaphores
|
||||
FallbackImage() auximageprovider.Provider
|
||||
WatermarkImage() auximageprovider.Provider
|
||||
ImageDataFactory() *imagedata.Factory
|
||||
}
|
||||
|
||||
// Handler handles image processing requests
|
||||
type Handler struct {
|
||||
hw *headerwriter.Writer // Configured HeaderWriter instance
|
||||
stream *stream.Handler // Stream handler for raw image streaming
|
||||
config *Config // Handler configuration
|
||||
semaphores *semaphores.Semaphores
|
||||
fallbackImage auximageprovider.Provider
|
||||
watermarkImage auximageprovider.Provider
|
||||
idf *imagedata.Factory
|
||||
HandlerContext
|
||||
|
||||
stream *stream.Handler // Stream handler for raw image streaming
|
||||
config *Config // Handler configuration
|
||||
}
|
||||
|
||||
// New creates new handler object
|
||||
func New(
|
||||
hCtx HandlerContext,
|
||||
stream *stream.Handler,
|
||||
hw *headerwriter.Writer,
|
||||
semaphores *semaphores.Semaphores,
|
||||
fi auximageprovider.Provider,
|
||||
wi auximageprovider.Provider,
|
||||
idf *imagedata.Factory,
|
||||
config *Config,
|
||||
) (*Handler, error) {
|
||||
if err := config.Validate(); err != nil {
|
||||
@@ -45,13 +47,9 @@ func New(
|
||||
}
|
||||
|
||||
return &Handler{
|
||||
hw: hw,
|
||||
HandlerContext: hCtx,
|
||||
config: config,
|
||||
stream: stream,
|
||||
semaphores: semaphores,
|
||||
fallbackImage: fi,
|
||||
watermarkImage: wi,
|
||||
idf: idf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -59,49 +57,48 @@ func New(
|
||||
func (h *Handler) Execute(
|
||||
reqID string,
|
||||
rw http.ResponseWriter,
|
||||
imageRequest *http.Request,
|
||||
req *http.Request,
|
||||
) error {
|
||||
// Increment the number of requests in progress
|
||||
stats.IncRequestsInProgress()
|
||||
defer stats.DecRequestsInProgress()
|
||||
|
||||
ctx := imageRequest.Context()
|
||||
ctx := req.Context()
|
||||
|
||||
// Verify URL signature and extract image url and processing options
|
||||
imageURL, po, mm, err := h.newRequest(ctx, imageRequest)
|
||||
imageURL, po, mm, err := h.newRequest(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if processing options indicate raw image streaming, stream it and return
|
||||
if po.Raw {
|
||||
return h.stream.Execute(ctx, imageRequest, imageURL, reqID, po, rw)
|
||||
return h.stream.Execute(ctx, req, imageURL, reqID, po, rw)
|
||||
}
|
||||
|
||||
req := &request{
|
||||
handler: h,
|
||||
imageRequest: imageRequest,
|
||||
hReq := &request{
|
||||
HandlerContext: h,
|
||||
|
||||
reqID: reqID,
|
||||
req: req,
|
||||
rw: rw,
|
||||
config: h.config,
|
||||
po: po,
|
||||
imageURL: imageURL,
|
||||
monitoringMeta: mm,
|
||||
semaphores: h.semaphores,
|
||||
hwr: h.hw.NewRequest(),
|
||||
idf: h.idf,
|
||||
hwr: h.HeaderWriter().NewRequest(),
|
||||
}
|
||||
|
||||
return req.execute(ctx)
|
||||
return hReq.execute(ctx)
|
||||
}
|
||||
|
||||
// newRequest extracts image url and processing options from request URL and verifies them
|
||||
func (h *Handler) newRequest(
|
||||
ctx context.Context,
|
||||
imageRequest *http.Request,
|
||||
req *http.Request,
|
||||
) (string, *options.ProcessingOptions, monitoring.Meta, error) {
|
||||
// let's extract signature and valid request path from a request
|
||||
path, signature, err := handlers.SplitPathSignature(imageRequest)
|
||||
path, signature, err := handlers.SplitPathSignature(req)
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
@@ -112,7 +109,7 @@ func (h *Handler) newRequest(
|
||||
}
|
||||
|
||||
// parse image url and processing options
|
||||
po, imageURL, err := options.ParsePath(path, imageRequest.Header)
|
||||
po, imageURL, err := options.ParsePath(path, req.Header)
|
||||
if err != nil {
|
||||
return "", nil, nil, ierrors.Wrap(err, 0, ierrors.WithCategory(handlers.CategoryPathParsing))
|
||||
}
|
||||
@@ -127,9 +124,9 @@ func (h *Handler) newRequest(
|
||||
}
|
||||
|
||||
// set error reporting and monitoring context
|
||||
errorreport.SetMetadata(imageRequest, "Source Image URL", imageURL)
|
||||
errorreport.SetMetadata(imageRequest, "Source Image Origin", imageOrigin)
|
||||
errorreport.SetMetadata(imageRequest, "Processing Options", po)
|
||||
errorreport.SetMetadata(req, "Source Image URL", imageURL)
|
||||
errorreport.SetMetadata(req, "Source Image Origin", imageOrigin)
|
||||
errorreport.SetMetadata(req, "Processing Options", po)
|
||||
|
||||
monitoring.SetMetadata(ctx, mm)
|
||||
|
||||
|
@@ -9,29 +9,26 @@ import (
|
||||
"github.com/imgproxy/imgproxy/v3/handlers"
|
||||
"github.com/imgproxy/imgproxy/v3/headerwriter"
|
||||
"github.com/imgproxy/imgproxy/v3/ierrors"
|
||||
"github.com/imgproxy/imgproxy/v3/imagedata"
|
||||
"github.com/imgproxy/imgproxy/v3/imagetype"
|
||||
"github.com/imgproxy/imgproxy/v3/monitoring"
|
||||
"github.com/imgproxy/imgproxy/v3/monitoring/stats"
|
||||
"github.com/imgproxy/imgproxy/v3/options"
|
||||
"github.com/imgproxy/imgproxy/v3/semaphores"
|
||||
"github.com/imgproxy/imgproxy/v3/server"
|
||||
"github.com/imgproxy/imgproxy/v3/vips"
|
||||
)
|
||||
|
||||
// request holds the parameters and state for a single request request
|
||||
type request struct {
|
||||
handler *Handler
|
||||
imageRequest *http.Request
|
||||
HandlerContext
|
||||
|
||||
reqID string
|
||||
req *http.Request
|
||||
rw http.ResponseWriter
|
||||
config *Config
|
||||
po *options.ProcessingOptions
|
||||
imageURL string
|
||||
monitoringMeta monitoring.Meta
|
||||
semaphores *semaphores.Semaphores
|
||||
hwr *headerwriter.Request
|
||||
idf *imagedata.Factory
|
||||
}
|
||||
|
||||
// execute handles the actual processing logic
|
||||
@@ -46,7 +43,7 @@ func (r *request) execute(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// Acquire queue semaphore (if enabled)
|
||||
releaseQueueSem, err := r.semaphores.AcquireQueue()
|
||||
releaseQueueSem, err := r.Semaphores().AcquireQueue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -25,12 +25,12 @@ func (r *request) makeImageRequestHeaders() http.Header {
|
||||
|
||||
// If ETag is enabled, we forward If-None-Match header
|
||||
if r.config.ETagEnabled {
|
||||
h.Set(httpheaders.IfNoneMatch, r.imageRequest.Header.Get(httpheaders.IfNoneMatch))
|
||||
h.Set(httpheaders.IfNoneMatch, r.req.Header.Get(httpheaders.IfNoneMatch))
|
||||
}
|
||||
|
||||
// If LastModified is enabled, we forward If-Modified-Since header
|
||||
if r.config.LastModifiedEnabled {
|
||||
h.Set(httpheaders.IfModifiedSince, r.imageRequest.Header.Get(httpheaders.IfModifiedSince))
|
||||
h.Set(httpheaders.IfModifiedSince, r.req.Header.Get(httpheaders.IfModifiedSince))
|
||||
}
|
||||
|
||||
return h
|
||||
@@ -40,7 +40,7 @@ func (r *request) makeImageRequestHeaders() http.Header {
|
||||
func (r *request) acquireProcessingSem(ctx context.Context) (context.CancelFunc, error) {
|
||||
defer monitoring.StartQueueSegment(ctx)()
|
||||
|
||||
fn, err := r.semaphores.AcquireProcessing(ctx)
|
||||
fn, err := r.Semaphores().AcquireProcessing(ctx)
|
||||
if err != nil {
|
||||
// We don't actually need to check timeout here,
|
||||
// but it's an easy way to check if this is an actual timeout
|
||||
@@ -76,13 +76,13 @@ func (r *request) fetchImage(ctx context.Context, do imagedata.DownloadOptions)
|
||||
var err error
|
||||
|
||||
if r.config.CookiePassthrough {
|
||||
do.CookieJar, err = cookies.JarFromRequest(r.imageRequest)
|
||||
do.CookieJar, err = cookies.JarFromRequest(r.req)
|
||||
if err != nil {
|
||||
return nil, nil, ierrors.Wrap(err, 0, ierrors.WithCategory(handlers.CategoryDownload))
|
||||
}
|
||||
}
|
||||
|
||||
return r.idf.DownloadAsync(ctx, r.imageURL, "source image", do)
|
||||
return r.ImageDataFactory().DownloadAsync(ctx, r.imageURL, "source image", do)
|
||||
}
|
||||
|
||||
// handleDownloadError replaces the image data with fallback image if needed
|
||||
@@ -103,7 +103,7 @@ func (r *request) handleDownloadError(
|
||||
|
||||
// We didn't return, so we have to report error
|
||||
if err.ShouldReport() {
|
||||
errorreport.Report(err, r.imageRequest)
|
||||
errorreport.Report(err, r.req)
|
||||
}
|
||||
|
||||
log.
|
||||
@@ -134,16 +134,18 @@ func (r *request) getFallbackImage(
|
||||
ctx context.Context,
|
||||
po *options.ProcessingOptions,
|
||||
) (imagedata.ImageData, http.Header) {
|
||||
if r.handler.fallbackImage == nil {
|
||||
fbi := r.FallbackImage()
|
||||
|
||||
if fbi == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
data, h, err := r.handler.fallbackImage.Get(ctx, po)
|
||||
data, h, err := fbi.Get(ctx, po)
|
||||
if err != nil {
|
||||
log.Warning(err.Error())
|
||||
|
||||
if ierr := r.wrapDownloadingErr(err); ierr.ShouldReport() {
|
||||
errorreport.Report(ierr, r.imageRequest)
|
||||
errorreport.Report(ierr, r.req)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
@@ -155,7 +157,7 @@ func (r *request) getFallbackImage(
|
||||
// processImage calls actual image processing
|
||||
func (r *request) processImage(ctx context.Context, originData imagedata.ImageData) (*processing.Result, error) {
|
||||
defer monitoring.StartProcessingSegment(ctx, r.monitoringMeta.Filter(monitoring.MetaProcessingOptions))()
|
||||
return processing.ProcessImage(ctx, originData, r.po, r.handler.watermarkImage)
|
||||
return processing.ProcessImage(ctx, originData, r.po, r.WatermarkImage())
|
||||
}
|
||||
|
||||
// writeDebugHeaders writes debug headers (X-Origin-*, X-Result-*) to the response
|
||||
@@ -200,7 +202,7 @@ func (r *request) respondWithNotModified() error {
|
||||
r.rw.WriteHeader(http.StatusNotModified)
|
||||
|
||||
server.LogResponse(
|
||||
r.reqID, r.imageRequest, http.StatusNotModified, nil,
|
||||
r.reqID, r.req, http.StatusNotModified, nil,
|
||||
log.Fields{
|
||||
"image_url": r.imageURL,
|
||||
"processing_options": r.po,
|
||||
@@ -256,7 +258,7 @@ func (r *request) respondWithImage(statusCode int, resultData imagedata.ImageDat
|
||||
}
|
||||
|
||||
server.LogResponse(
|
||||
r.reqID, r.imageRequest, statusCode, ierr,
|
||||
r.reqID, r.req, statusCode, ierr,
|
||||
log.Fields{
|
||||
"image_url": r.imageURL,
|
||||
"processing_options": r.po,
|
||||
|
78
imgproxy.go
78
imgproxy.go
@@ -34,14 +34,14 @@ type ImgproxyHandlers struct {
|
||||
|
||||
// Imgproxy holds all the components needed for imgproxy to function
|
||||
type Imgproxy struct {
|
||||
HeaderWriter *headerwriter.Writer
|
||||
Semaphores *semaphores.Semaphores
|
||||
FallbackImage auximageprovider.Provider
|
||||
WatermarkImage auximageprovider.Provider
|
||||
Fetcher *fetcher.Fetcher
|
||||
ImageDataFactory *imagedata.Factory
|
||||
Handlers ImgproxyHandlers
|
||||
Config *Config
|
||||
headerWriter *headerwriter.Writer
|
||||
semaphores *semaphores.Semaphores
|
||||
fallbackImage auximageprovider.Provider
|
||||
watermarkImage auximageprovider.Provider
|
||||
fetcher *fetcher.Fetcher
|
||||
imageDataFactory *imagedata.Factory
|
||||
handlers ImgproxyHandlers
|
||||
config *Config
|
||||
}
|
||||
|
||||
// New creates a new imgproxy instance
|
||||
@@ -74,25 +74,25 @@ func New(ctx context.Context, config *Config) (*Imgproxy, error) {
|
||||
}
|
||||
|
||||
imgproxy := &Imgproxy{
|
||||
HeaderWriter: headerWriter,
|
||||
Semaphores: semaphores,
|
||||
FallbackImage: fallbackImage,
|
||||
WatermarkImage: watermarkImage,
|
||||
Fetcher: fetcher,
|
||||
ImageDataFactory: idf,
|
||||
Config: config,
|
||||
headerWriter: headerWriter,
|
||||
semaphores: semaphores,
|
||||
fallbackImage: fallbackImage,
|
||||
watermarkImage: watermarkImage,
|
||||
fetcher: fetcher,
|
||||
imageDataFactory: idf,
|
||||
config: config,
|
||||
}
|
||||
|
||||
imgproxy.Handlers.Health = healthhandler.New()
|
||||
imgproxy.Handlers.Landing = landinghandler.New()
|
||||
imgproxy.handlers.Health = healthhandler.New()
|
||||
imgproxy.handlers.Landing = landinghandler.New()
|
||||
|
||||
imgproxy.Handlers.Stream, err = streamhandler.New(&config.Handlers.Stream, headerWriter, fetcher)
|
||||
imgproxy.handlers.Stream, err = streamhandler.New(&config.Handlers.Stream, headerWriter, fetcher)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
imgproxy.Handlers.Processing, err = processinghandler.New(
|
||||
imgproxy.Handlers.Stream, headerWriter, semaphores, fallbackImage, watermarkImage, idf, &config.Handlers.Processing,
|
||||
imgproxy.handlers.Processing, err = processinghandler.New(
|
||||
imgproxy, imgproxy.handlers.Stream, &config.Handlers.Processing,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -103,22 +103,22 @@ func New(ctx context.Context, config *Config) (*Imgproxy, error) {
|
||||
|
||||
// BuildRouter sets up the HTTP routes and middleware
|
||||
func (i *Imgproxy) BuildRouter() (*server.Router, error) {
|
||||
r, err := server.NewRouter(&i.Config.Server)
|
||||
r, err := server.NewRouter(&i.config.Server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.GET("/", i.Handlers.Landing.Execute)
|
||||
r.GET("", i.Handlers.Landing.Execute)
|
||||
r.GET("/", i.handlers.Landing.Execute)
|
||||
r.GET("", i.handlers.Landing.Execute)
|
||||
|
||||
r.GET(faviconPath, r.NotFoundHandler).Silent()
|
||||
r.GET(healthPath, i.Handlers.Health.Execute).Silent()
|
||||
if i.Config.Server.HealthCheckPath != "" {
|
||||
r.GET(i.Config.Server.HealthCheckPath, i.Handlers.Health.Execute).Silent()
|
||||
r.GET(healthPath, i.handlers.Health.Execute).Silent()
|
||||
if i.config.Server.HealthCheckPath != "" {
|
||||
r.GET(i.config.Server.HealthCheckPath, i.handlers.Health.Execute).Silent()
|
||||
}
|
||||
|
||||
r.GET(
|
||||
"/*", i.Handlers.Processing.Execute,
|
||||
"/*", i.handlers.Processing.Execute,
|
||||
r.WithSecret, r.WithCORS, r.WithPanic, r.WithReportError, r.WithMonitoring,
|
||||
)
|
||||
|
||||
@@ -163,7 +163,7 @@ func (i *Imgproxy) StartServer(ctx context.Context, hasStarted chan net.Addr) er
|
||||
|
||||
// startMemoryTicker starts a ticker that periodically frees memory and optionally logs memory stats
|
||||
func (i *Imgproxy) startMemoryTicker(ctx context.Context) {
|
||||
ticker := time.NewTicker(i.Config.Server.FreeMemoryInterval)
|
||||
ticker := time.NewTicker(i.config.Server.FreeMemoryInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
@@ -173,9 +173,29 @@ func (i *Imgproxy) startMemoryTicker(ctx context.Context) {
|
||||
case <-ticker.C:
|
||||
memory.Free()
|
||||
|
||||
if i.Config.Server.LogMemStats {
|
||||
if i.config.Server.LogMemStats {
|
||||
memory.LogStats()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Imgproxy) HeaderWriter() *headerwriter.Writer {
|
||||
return i.headerWriter
|
||||
}
|
||||
|
||||
func (i *Imgproxy) Semaphores() *semaphores.Semaphores {
|
||||
return i.semaphores
|
||||
}
|
||||
|
||||
func (i *Imgproxy) FallbackImage() auximageprovider.Provider {
|
||||
return i.fallbackImage
|
||||
}
|
||||
|
||||
func (i *Imgproxy) WatermarkImage() auximageprovider.Provider {
|
||||
return i.watermarkImage
|
||||
}
|
||||
|
||||
func (i *Imgproxy) ImageDataFactory() *imagedata.Factory {
|
||||
return i.imageDataFactory
|
||||
}
|
||||
|
Reference in New Issue
Block a user