diff --git a/README.md b/README.md index 588e2db..fece930 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,12 @@ path under `/v1/` answers 200, in maintenance mode too. authentication with `metrics.username` and `metrics.password`. Answers: 200; 401 without them; 404 when they are not set, as the route then does not exist. +Every response carries an `X-Request-ID` header holding the request's ID, which +a client can quote when reporting a problem: the request's own `X-Request-ID` +when it sent one, as a reverse proxy in front of pixa may, otherwise one pixa +makes up from its host name, a random string chosen at startup and a counter. +pixa's log line for the request carries the same ID as `request_id`. + Both `POST` routes accept only a form that pixa's own page served: the page puts a token in the form and sets a cookie to match, and a request without both is refused with 403, so another site cannot submit the form from a visitor's diff --git a/internal/middleware/middleware.go b/internal/middleware/middleware.go index 492ba87..758f774 100644 --- a/internal/middleware/middleware.go +++ b/internal/middleware/middleware.go @@ -115,6 +115,20 @@ func (s *Middleware) RateLimit( }) } +// RequestIDResponseHeader returns a middleware that sends the request's ID as +// the X-Request-Id response header, so a client can quote it when reporting a +// problem. The ID is the one chi's RequestID middleware stored in the request +// context, so RequestID must run first. +func (s *Middleware) RequestIDResponseHeader() func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set(middleware.RequestIDHeader, + middleware.GetReqID(r.Context())) + next.ServeHTTP(w, r) + }) + } +} + type loggingResponseWriter struct { http.ResponseWriter diff --git a/internal/server/routes.go b/internal/server/routes.go index f341e8a..f92b472 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -29,6 +29,7 @@ func (s *Server) SetupRoutes() { s.router.Use(middleware.Recoverer) s.router.Use(middleware.RequestID) + s.router.Use(s.mw.RequestIDResponseHeader()) s.router.Use(s.mw.ClientIP()) s.router.Use(s.mw.SecurityHeaders()) s.router.Use(s.mw.Logging())