middlewares.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. package server
  2. import (
  3. "crypto/subtle"
  4. "fmt"
  5. "net/http"
  6. "github.com/imgproxy/imgproxy/v3/errorreport"
  7. "github.com/imgproxy/imgproxy/v3/httpheaders"
  8. "github.com/imgproxy/imgproxy/v3/ierrors"
  9. "github.com/imgproxy/imgproxy/v3/metrics"
  10. )
  11. // WithMetrics wraps RouteHandler with metrics handling.
  12. func (r *Router) WithMetrics(h RouteHandler) RouteHandler {
  13. if !metrics.Enabled() {
  14. return h
  15. }
  16. return func(reqID string, rw http.ResponseWriter, req *http.Request) error {
  17. ctx, metricsCancel, rw := metrics.StartRequest(req.Context(), rw, req)
  18. defer metricsCancel()
  19. return h(reqID, rw, req.WithContext(ctx))
  20. }
  21. }
  22. // WithCORS wraps RouteHandler with CORS handling
  23. func (r *Router) WithCORS(h RouteHandler) RouteHandler {
  24. if len(r.config.CORSAllowOrigin) == 0 {
  25. return h
  26. }
  27. return func(reqID string, rw http.ResponseWriter, req *http.Request) error {
  28. rw.Header().Set(httpheaders.AccessControlAllowOrigin, r.config.CORSAllowOrigin)
  29. rw.Header().Set(httpheaders.AccessControlAllowMethods, "GET, OPTIONS")
  30. return h(reqID, rw, req)
  31. }
  32. }
  33. // WithSecret wraps RouteHandler with secret handling
  34. func (r *Router) WithSecret(h RouteHandler) RouteHandler {
  35. if len(r.config.Secret) == 0 {
  36. return h
  37. }
  38. authHeader := fmt.Appendf(nil, "Bearer %s", r.config.Secret)
  39. return func(reqID string, rw http.ResponseWriter, req *http.Request) error {
  40. if subtle.ConstantTimeCompare([]byte(req.Header.Get(httpheaders.Authorization)), authHeader) == 1 {
  41. return h(reqID, rw, req)
  42. } else {
  43. return newInvalidSecretError()
  44. }
  45. }
  46. }
  47. // WithReportError handles error reporting.
  48. // It should be placed after `WithMetrics`, but before `WithPanic`.
  49. func (r *Router) WithReportError(h RouteHandler) RouteHandler {
  50. return func(reqID string, rw http.ResponseWriter, req *http.Request) error {
  51. // Open the error context
  52. ctx := errorreport.StartRequest(req)
  53. req = req.WithContext(ctx)
  54. errorreport.SetMetadata(req, "Request ID", reqID)
  55. // Call the underlying handler passing the context downwards
  56. err := h(reqID, rw, req)
  57. if err == nil {
  58. return nil
  59. }
  60. // Wrap a resulting error into ierrors.Error
  61. ierr := ierrors.Wrap(err, 0)
  62. // Get the error category
  63. errCat := ierr.Category()
  64. // Report error to metrics (if metrics are disabled, it will be a no-op)
  65. // Skip context closed error
  66. if ierr.StatusCode() != 499 {
  67. metrics.SendError(ctx, errCat, err)
  68. }
  69. // Report error to error collectors
  70. if ierr.ShouldReport() {
  71. errorreport.Report(ierr, req)
  72. }
  73. // Log response and format the error output
  74. LogResponse(reqID, req, ierr.StatusCode(), ierr)
  75. // Error message: either is public message or full development error
  76. rw.Header().Set(httpheaders.ContentType, "text/plain")
  77. rw.WriteHeader(ierr.StatusCode())
  78. if r.config.DevelopmentErrorsMode {
  79. rw.Write([]byte(ierr.Error()))
  80. } else {
  81. rw.Write([]byte(ierr.PublicMessage()))
  82. }
  83. return nil
  84. }
  85. }
  86. // WithPanic recovers panic and converts it to normal error
  87. func (r *Router) WithPanic(h RouteHandler) RouteHandler {
  88. return func(reqID string, rw http.ResponseWriter, r *http.Request) (retErr error) {
  89. defer func() {
  90. // try to recover from panic
  91. rerr := recover()
  92. if rerr == nil {
  93. return
  94. }
  95. // abort handler is an exception of net/http, we should simply repanic it.
  96. // it will supress the stack trace
  97. if rerr == http.ErrAbortHandler {
  98. panic(rerr)
  99. }
  100. // let's recover error value from panic if it has panicked with error
  101. err, ok := rerr.(error)
  102. if !ok {
  103. err = fmt.Errorf("panic: %v", err)
  104. }
  105. retErr = err
  106. }()
  107. return h(reqID, rw, r)
  108. }
  109. }