8878711cf3
- GATEWAY_MAX_BODY_BYTES (1 MiB): connect WithReadMaxBytes + http.MaxBytesReader
on the public mux; explicit http2.Server MaxConcurrentStreams/IdleTimeout and
an http.Server ReadHeaderTimeout (R2 report follow-up).
- gateway_rate_limited_total{class} counter, Debug per rejection, a rejection
tracker drained every 30 s into a Warn summary per key and a report POST to
/api/v1/internal/ratelimit/report (feeds the admin view + auto-flag).
- The dead AdminPerMinute/AdminBurst policy now guards the /_gm mount (429),
ahead of its Basic-Auth.
- resolve() logs the cause of infra session-resolve failures at Warn (the
transient unauthenticated dips from the R2 run); unknown tokens stay silent.
53 lines
1.5 KiB
Go
53 lines
1.5 KiB
Go
package ratelimit
|
|
|
|
import "sync"
|
|
|
|
// Rejection aggregates the limiter rejections of one key within one report
|
|
// window. Class is the limiter class (user, public, email or admin); Key is the
|
|
// class-specific subject — an account id for the user class, a client IP for the
|
|
// others. The JSON shape is the gateway→backend rate-limit report wire contract.
|
|
type Rejection struct {
|
|
Class string `json:"class"`
|
|
Key string `json:"key"`
|
|
Rejected int `json:"rejected"`
|
|
}
|
|
|
|
// Tracker accumulates limiter rejections between drains. The gateway's periodic
|
|
// reporter drains it to emit the per-key log summary and the backend report; the
|
|
// per-rejection cost is one map increment under a mutex, safe on the hot path.
|
|
type Tracker struct {
|
|
mu sync.Mutex
|
|
m map[trackerKey]int
|
|
}
|
|
|
|
type trackerKey struct{ class, key string }
|
|
|
|
// NewTracker constructs an empty Tracker.
|
|
func NewTracker() *Tracker {
|
|
return &Tracker{m: make(map[trackerKey]int)}
|
|
}
|
|
|
|
// Add counts one rejection of key under class.
|
|
func (t *Tracker) Add(class, key string) {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
t.m[trackerKey{class: class, key: key}]++
|
|
}
|
|
|
|
// Drain returns the rejections accumulated since the previous drain, in
|
|
// unspecified order, and resets the tracker. It returns nil when nothing was
|
|
// rejected.
|
|
func (t *Tracker) Drain() []Rejection {
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
if len(t.m) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]Rejection, 0, len(t.m))
|
|
for k, n := range t.m {
|
|
out = append(out, Rejection{Class: k.class, Key: k.key, Rejected: n})
|
|
}
|
|
clear(t.m)
|
|
return out
|
|
}
|