mirror of
https://github.com/imgproxy/imgproxy.git
synced 2026-06-18 17:43:40 +02:00
139 lines
3.8 KiB
Go
139 lines
3.8 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"crypto/subtle"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/imgproxy/imgproxy/v4/errctx"
|
|
"github.com/imgproxy/imgproxy/v4/errorreport"
|
|
"github.com/imgproxy/imgproxy/v4/httpheaders"
|
|
)
|
|
|
|
// WithMonitoring wraps RouteHandler with monitoring handling.
|
|
func (r *Router) WithMonitoring(h RouteHandler) RouteHandler {
|
|
if !r.monitoring.Enabled() {
|
|
return h
|
|
}
|
|
|
|
return func(reqID string, rw ResponseWriter, req *http.Request) *Error {
|
|
ctx, cancel, newRw := r.monitoring.StartRequest(req.Context(), rw.HTTPResponseWriter(), req)
|
|
defer cancel()
|
|
|
|
// Replace rw.ResponseWriter with new one returned from monitoring
|
|
rw.SetHTTPResponseWriter(newRw)
|
|
|
|
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 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 ResponseWriter, req *http.Request) *Error {
|
|
if subtle.ConstantTimeCompare([]byte(req.Header.Get(httpheaders.Authorization)), authHeader) == 1 {
|
|
return h(reqID, rw, req)
|
|
} else {
|
|
return NewError(newInvalidSecretError(), errCategorySecurity)
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithPanic recovers panic and converts it to normal error
|
|
func (r *Router) WithPanic(h RouteHandler) RouteHandler {
|
|
return func(reqID string, rw 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 suppress the stack trace
|
|
if e, ok := rerr.(error); ok && errors.Is(e, 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: %w", err)
|
|
}
|
|
|
|
retErr = NewError(errctx.WrapWithStackSkip(err, 1), errCategoryUnexpected)
|
|
}()
|
|
|
|
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 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
|
|
}
|
|
|
|
// We do not need to send any canceled context
|
|
if !errors.Is(err.Err, context.Canceled) {
|
|
r.monitoring.SendError(ctx, err.Category, err.Err)
|
|
}
|
|
|
|
// Report error to error collectors
|
|
if err.Err.ShouldReport() {
|
|
r.errorReporter.Report(err.Err, req)
|
|
}
|
|
|
|
// Log response and format the error output
|
|
LogResponse(reqID, req, err.Err.StatusCode(), err.Err)
|
|
|
|
// Error message: either is public message or full development error
|
|
if r.config.DevelopmentErrorsMode {
|
|
// Generate and serve HTML error page
|
|
html, contentType := generateErrorHTML(err.Err, reqID, req.Header)
|
|
|
|
rw.Header().Set(httpheaders.ContentType, contentType)
|
|
rw.WriteHeader(err.Err.StatusCode())
|
|
rw.Write(html)
|
|
|
|
return nil
|
|
}
|
|
|
|
rw.Header().Set(httpheaders.ContentType, "text/plain")
|
|
rw.WriteHeader(err.Err.StatusCode())
|
|
rw.Write([]byte(err.Err.PublicMessage()))
|
|
|
|
return nil
|
|
}
|
|
}
|