Skip to content

Commit

Permalink
Sort rate limits (issue #10)
Browse files Browse the repository at this point in the history
  • Loading branch information
mholt committed Aug 2, 2022
1 parent 9c011f6 commit 163fde7
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 7 deletions.
25 changes: 18 additions & 7 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
weakrand "math/rand"
"net/http"
"sort"
"strconv"
"sync"
"time"
Expand Down Expand Up @@ -50,9 +51,10 @@ type Handler struct {
// the global or default storage configuration will be used.
StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"`

storage certmagic.Storage
random *weakrand.Rand
logger *zap.Logger
rateLimits []*RateLimit
storage certmagic.Storage
random *weakrand.Rand
logger *zap.Logger
}

// CaddyModule returns the Caddy module information.
Expand Down Expand Up @@ -107,13 +109,21 @@ func (h *Handler) Provision(ctx caddy.Context) error {
go h.syncDistributed(ctx)
}

// provision each rate limit and put them in a slice so we can sort them
for name, rl := range h.RateLimits {
rl.zoneName = name
err := rl.provision(ctx, name)
if err != nil {
return fmt.Errorf("setting up rate limit %s: %v", name, err)
}
h.rateLimits = append(h.rateLimits, rl)
}

// sort by tightest rate limit to most permissive (issue #10)
sort.Slice(h.rateLimits, func(i, j int) bool {
return h.rateLimits[i].permissiveness() > h.rateLimits[j].permissiveness()
})

if h.Jitter < 0 {
return fmt.Errorf("jitter must be at least zero")
} else if h.Jitter > 0 {
Expand All @@ -132,7 +142,8 @@ func (h *Handler) Provision(ctx caddy.Context) error {
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)

for zoneName, rl := range h.RateLimits {
// iterate the slice, not the map, so the order is deterministic
for _, rl := range h.rateLimits {
// ignore rate limit if request doesn't qualify
if !rl.matcherSets.AnyMatch(r) {
continue
Expand All @@ -159,16 +170,16 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhtt
if h.Distributed == nil {
// internal rate limiter only
if dur := limiter.When(); dur > 0 {
return h.rateLimitExceeded(w, repl, zoneName, dur)
return h.rateLimitExceeded(w, repl, rl.zoneName, dur)
}
} else {
// distributed rate limiting; add last known state of other instances
if err := h.distributedRateLimiting(w, repl, limiter, key, zoneName); err != nil {
if err := h.distributedRateLimiting(w, repl, limiter, key, rl.zoneName); err != nil {
return err
}
}

}

return next.ServeHTTP(w, r)
}

Expand Down
6 changes: 6 additions & 0 deletions ratelimit.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ type RateLimit struct {

matcherSets caddyhttp.MatcherSets

zoneName string

limiters *sync.Map
}

Expand Down Expand Up @@ -68,3 +70,7 @@ func (rl *RateLimit) provision(ctx caddy.Context, name string) error {

return nil
}

func (rl *RateLimit) permissiveness() float64 {
return float64(rl.MaxEvents) / float64(rl.Window)
}

0 comments on commit 163fde7

Please sign in to comment.