mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-09-29 13:03:50 +02:00
146 lines
3.7 KiB
Go
146 lines
3.7 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"crypto/subtle"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/imgproxy/imgproxy/v3/errorreport"
|
|
"github.com/imgproxy/imgproxy/v3/httpheaders"
|
|
"github.com/imgproxy/imgproxy/v3/ierrors"
|
|
"github.com/imgproxy/imgproxy/v3/monitoring"
|
|
)
|
|
|
|
const (
|
|
categoryTimeout = "timeout"
|
|
)
|
|
|
|
// WithMonitoring wraps RouteHandler with monitoring handling.
|
|
func (r *Router) WithMonitoring(h RouteHandler) RouteHandler {
|
|
if !monitoring.Enabled() {
|
|
return h
|
|
}
|
|
|
|
return func(reqID string, rw http.ResponseWriter, req *http.Request) error {
|
|
ctx, cancel, rw := monitoring.StartRequest(req.Context(), rw, req)
|
|
defer cancel()
|
|
|
|
return h(reqID, rw, req.WithContext(ctx))
|
|
}
|
|
}
|
|
|
|
// WithCORS wraps RouteHandler with CORS handling
|
|
func (r *Router) WithCORS(h RouteHandler) RouteHandler {
|
|
if len(r.config.CORSAllowOrigin) == 0 {
|
|
return h
|
|
}
|
|
|
|
return func(reqID string, rw http.ResponseWriter, req *http.Request) error {
|
|
rw.Header().Set(httpheaders.AccessControlAllowOrigin, r.config.CORSAllowOrigin)
|
|
rw.Header().Set(httpheaders.AccessControlAllowMethods, "GET, OPTIONS")
|
|
|
|
return h(reqID, rw, req)
|
|
}
|
|
}
|
|
|
|
// WithSecret wraps RouteHandler with secret handling
|
|
func (r *Router) WithSecret(h RouteHandler) RouteHandler {
|
|
if len(r.config.Secret) == 0 {
|
|
return h
|
|
}
|
|
|
|
authHeader := fmt.Appendf(nil, "Bearer %s", r.config.Secret)
|
|
|
|
return func(reqID string, rw http.ResponseWriter, req *http.Request) error {
|
|
if subtle.ConstantTimeCompare([]byte(req.Header.Get(httpheaders.Authorization)), authHeader) == 1 {
|
|
return h(reqID, rw, req)
|
|
} else {
|
|
return newInvalidSecretError()
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithPanic recovers panic and converts it to normal error
|
|
func (r *Router) WithPanic(h RouteHandler) RouteHandler {
|
|
return func(reqID string, rw http.ResponseWriter, r *http.Request) (retErr error) {
|
|
defer func() {
|
|
// try to recover from panic
|
|
rerr := recover()
|
|
if rerr == nil {
|
|
return
|
|
}
|
|
|
|
// abort handler is an exception of net/http, we should simply repanic it.
|
|
// it will supress the stack trace
|
|
if rerr == http.ErrAbortHandler {
|
|
panic(rerr)
|
|
}
|
|
|
|
// let's recover error value from panic if it has panicked with error
|
|
err, ok := rerr.(error)
|
|
if !ok {
|
|
err = fmt.Errorf("panic: %v", err)
|
|
}
|
|
|
|
retErr = ierrors.Wrap(err, 1)
|
|
}()
|
|
|
|
return h(reqID, rw, r)
|
|
}
|
|
}
|
|
|
|
// WithReportError handles error reporting.
|
|
// It should be placed after `WithMonitoring`, but before `WithPanic`.
|
|
func (r *Router) WithReportError(h RouteHandler) RouteHandler {
|
|
return func(reqID string, rw http.ResponseWriter, req *http.Request) error {
|
|
// Open the error context
|
|
ctx := errorreport.StartRequest(req)
|
|
req = req.WithContext(ctx)
|
|
errorreport.SetMetadata(req, "Request ID", reqID)
|
|
|
|
// Call the underlying handler passing the context downwards
|
|
err := h(reqID, rw, req)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
// Wrap a resulting error into ierrors.Error
|
|
ierr := ierrors.Wrap(err, 0)
|
|
|
|
// Get the error category
|
|
errCat := ierr.Category()
|
|
|
|
// Exception: any context.DeadlineExceeded error is timeout
|
|
if errors.Is(ierr, context.DeadlineExceeded) {
|
|
errCat = categoryTimeout
|
|
}
|
|
|
|
// We do not need to send any canceled context
|
|
if !errors.Is(ierr, context.Canceled) {
|
|
monitoring.SendError(ctx, errCat, err)
|
|
}
|
|
|
|
// Report error to error collectors
|
|
if ierr.ShouldReport() {
|
|
errorreport.Report(ierr, req)
|
|
}
|
|
|
|
// Log response and format the error output
|
|
LogResponse(reqID, req, ierr.StatusCode(), ierr)
|
|
|
|
// Error message: either is public message or full development error
|
|
rw.Header().Set(httpheaders.ContentType, "text/plain")
|
|
rw.WriteHeader(ierr.StatusCode())
|
|
|
|
if r.config.DevelopmentErrorsMode {
|
|
rw.Write([]byte(ierr.Error()))
|
|
} else {
|
|
rw.Write([]byte(ierr.PublicMessage()))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|