rateLimit is the Huma middleware that enforces the per-user poll limiter on reads and the per-IP registration limiter on agent create, and stamps the IETF RateLimit-Limit/Remaining/Reset headers (plus Retry-After on a 429) on the response. The per-agent SEND limiter is enforced inside the outbound h
(ctx huma.Context, next func(huma.Context))
| 44 | // resolveOwnedAgent ownership check), which this middleware doesn't perform — |
| 45 | // so the send limit is applied in deliver()/the outbound handlers, not here. |
| 46 | func (s *Server) rateLimit(ctx huma.Context, next func(huma.Context)) { |
| 47 | op := ctx.Operation() |
| 48 | if op == nil { |
| 49 | next(ctx) |
| 50 | return |
| 51 | } |
| 52 | |
| 53 | var snap RateSnapshot |
| 54 | var key string |
| 55 | switch { |
| 56 | case pollLimitedOps[op.OperationID] && s.deps.PollLimit != nil: |
| 57 | r := RequestFromContext(ctx.Context()) |
| 58 | if r == nil || s.deps.Authenticator == nil { |
| 59 | next(ctx) |
| 60 | return |
| 61 | } |
| 62 | p, err := s.resolvePrincipal(r) |
| 63 | if err != nil { |
| 64 | // Unauthenticated: let the handler emit the canonical 401 rather |
| 65 | // than masking a missing credential as a rate-limit decision. |
| 66 | next(ctx) |
| 67 | return |
| 68 | } |
| 69 | snap, key = s.deps.PollLimit, p.User.ID |
| 70 | // Reuse the principal so the handler does not authenticate a second |
| 71 | // time on the hot read path. |
| 72 | ctx = huma.WithContext(ctx, withPrincipal(ctx.Context(), p)) |
| 73 | case op.OperationID == "createAgent" && s.deps.RegLimit != nil: |
| 74 | r := RequestFromContext(ctx.Context()) |
| 75 | if r == nil { |
| 76 | next(ctx) |
| 77 | return |
| 78 | } |
| 79 | snap, key = s.deps.RegLimit, clientIP(r) |
| 80 | default: |
| 81 | next(ctx) |
| 82 | return |
| 83 | } |
| 84 | |
| 85 | ok, retryAfter, limit, remaining, reset := snap(key) |
| 86 | ctx.SetHeader("RateLimit-Limit", strconv.Itoa(limit)) |
| 87 | ctx.SetHeader("RateLimit-Remaining", strconv.Itoa(remaining)) |
| 88 | ctx.SetHeader("RateLimit-Reset", strconv.Itoa(reset)) |
| 89 | if ok { |
| 90 | next(ctx) |
| 91 | return |
| 92 | } |
| 93 | secs := int(retryAfter.Round(time.Second).Seconds()) |
| 94 | if secs < 1 { |
| 95 | secs = 1 |
| 96 | } |
| 97 | ctx.SetHeader("Retry-After", strconv.Itoa(secs)) |
| 98 | writeEnvelope(ctx, NewError(http.StatusTooManyRequests, "rate_limited", |
| 99 | "rate limit exceeded").WithDetails(map[string]any{"retry_after_seconds": secs})) |
| 100 | } |
| 101 | |
| 102 | // writeEnvelope serializes an error envelope directly to the response from a |
| 103 | // middleware, where there is no handler return value for Huma to render. It |
nothing calls this directly
no test coverage detected