Return and pass on request IDs, and give /v1/e/ ETag, 304 and HEAD (closes #84)
check / check (push) Failing after 2s

Every response carries X-Request-Id, the upstream fetch sends it, and
the "upstream fetched", "image converted" and "image served" lines log
it as request_id. pixa's own RequestID middleware keeps a request's own
ID only when it is at most 64 letters, digits, '-', '_' or '.', and
otherwise makes a random one with crypto/rand, so nothing a client
chooses freely and nothing about the host reaches upstream. /v1/e/ now
sets ETag, answers a matching If-None-Match with 304 and is routed for
HEAD, through notModified, which both image handlers call. No Vary is
added: go-chi/cors already sends Vary: Origin.

Model: opus-5-5
This commit was merged in pull request #179.
This commit is contained in:
2026-10-04 12:41:54 +02:00
parent 363774c058
commit c7173c47d8
15 changed files with 619 additions and 58 deletions
+29
View File
@@ -2,9 +2,12 @@
package middleware
import (
"context"
"crypto/rand"
"log/slog"
"net/http"
"net/netip"
"regexp"
"time"
basicauth "github.com/99designs/basicauth-go"
@@ -115,6 +118,32 @@ func (s *Middleware) RateLimit(
})
}
// requestIDPattern is what a request's own X-Request-Id must look like to be
// kept as its ID: 1 to 64 letters, digits, '-', '_' or '.'.
var requestIDPattern = regexp.MustCompile(`^[A-Za-z0-9._-]{1,64}$`)
// RequestID returns a middleware that gives each request an ID and sends it as
// the X-Request-Id response header, so a client can quote it when reporting a
// problem. The ID is the request's own X-Request-Id when that matches
// requestIDPattern, and otherwise a random one, which tells nothing about the
// machine or the traffic. It is stored in the request context under chi's
// RequestIDKey, where the logging middleware, the handlers and the upstream
// fetch read it.
func (s *Middleware) RequestID() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
id := r.Header.Get(middleware.RequestIDHeader)
if !requestIDPattern.MatchString(id) {
id = rand.Text()
}
w.Header().Set(middleware.RequestIDHeader, id)
ctx := context.WithValue(r.Context(), middleware.RequestIDKey, id)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
type loggingResponseWriter struct {
http.ResponseWriter