Limit each client's requests to the image routes (closes #236)
check / check (push) Successful in 54m15s

The new setting image_requests_per_minute (PIXA_IMAGE_REQUESTS_PER_MINUTE),
default 600, caps the GET and HEAD requests one client sends to /v1/image/
and /v1/e/ per minute; the next is answered 429 with Retry-After and the
image routes' JSON error body. It uses the login limit's RateLimit
middleware, so clients are told apart the same way, behind the CORS
middleware, so a preflight is not counted. 0 turns it off, as
script/loadtest does; a negative or non-integer value stops startup.

TestEnvironmentSetsEveryKey gains the new variable, under the standing
approval in #64. To stay within the
linters' limits, the sentry_dsn check moves unchanged into
validateSentryDSN, and the cache_max_bytes explicit flag is set in one
line.

Model: opus-5-5
This commit was merged in pull request #237.
This commit is contained in:
2026-10-13 01:36:18 +02:00
parent bdde021b45
commit 40ad39843b
11 changed files with 431 additions and 35 deletions
+5 -3
View File
@@ -98,9 +98,10 @@ func (s *Middleware) ClientIP() func(http.Handler) http.Handler {
// holds whole; an IPv4-mapped address (::ffff:a.b.c.d) is counted as the
// IPv4 address it carries, since every such address falls in the same /64.
// Counts are kept only for the current and the previous window, so memory
// stays bounded.
// stays bounded. limitHandler writes the 429 after Retry-After is set; nil
// keeps the rate limit library's plain-text "Too Many Requests".
func (s *Middleware) RateLimit(
requestLimit int, window time.Duration,
requestLimit int, window time.Duration, limitHandler http.HandlerFunc,
) func(http.Handler) http.Handler {
return httprate.LimitBy(requestLimit, window,
func(r *http.Request) (string, error) {
@@ -112,7 +113,8 @@ func (s *Middleware) RateLimit(
}
return httprate.CanonicalizeIP(ip), nil
})
},
httprate.WithLimitHandler(limitHandler))
}
// requestIDPattern is what a request's own X-Request-Id must look like to be