Skip to content

Commit f00ee6d

Browse files
authored
Nobids/keep old ratelimit headers (#2845)
* (NOBIDS) improve local-deployment * (NOBIDS) make ratelimit-headers consistent with previous versions
1 parent 140c11a commit f00ee6d

File tree

3 files changed

+97
-24
lines changed

3 files changed

+97
-24
lines changed

local-deployment/docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ services:
1212
<<: *default-service
1313
profiles:
1414
- build-once
15-
command: /bin/bash -c "git config --global --add safe.directory '*' && make -j -B all"
15+
command: /bin/bash -c "trap stopit SIGINT; stopit() { echo 'trapped SIGINT'; exit; }; git config --global --add safe.directory '*' && make -B all"
1616
indexer:
1717
<<: *default-service
1818
command: go run ./cmd/explorer -config /app/local-deployment/config.yml
@@ -40,7 +40,7 @@ services:
4040
command: go run ./cmd/misc -config /app/local-deployment/config.yml -command=update-ratelimits
4141
misc:
4242
<<: *default-service
43-
command: /bin/bash -c "while true; do date; sleep 1; done"
43+
command: /bin/bash -c "trap stopit SIGINT; stopit() { echo 'trapped SIGINT'; exit; }; while true; do date; sleep 1; done"
4444
redis-sessions:
4545
image: redis:7
4646
volumes:

local-deployment/run.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ fn_redis() {
5454
fn_start() {
5555
fn_stop
5656
# build once before starting all services to prevent multiple parallel builds
57-
docker compose --profile=build-once run build-once &
57+
docker compose --profile=build-once run -T build-once &
5858
kurtosis run --enclave my-testnet . "$(cat network-params.json)" &
5959
wait
6060
bash provision-explorer-config.sh

ratelimit/ratelimit.go

Lines changed: 94 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,23 @@ const (
3030
)
3131

3232
const (
33-
HeaderRateLimitLimit = "X-RateLimit-Limit" // the rate limit ceiling that is applicable for the current request
34-
HeaderRateLimitRemaining = "X-RateLimit-Remaining" // the number of requests left for the current rate-limit window
35-
HeaderRateLimitReset = "X-RateLimit-Reset" // the number of seconds until the quota resets
36-
HeaderRetryAfter = "Retry-After" // the number of seconds until the quota resets, same as HeaderRateLimitReset, RFC 7231, 7.1.3
37-
HeaderRateLimitLimitSecond = "X-RateLimit-Limit-Second" // the rate limit ceiling that is applicable for the current user
38-
HeaderRateLimitLimitHour = "X-RateLimit-Limit-Hour" // the rate limit ceiling that is applicable for the current user
39-
HeaderRateLimitLimitMonth = "X-RateLimit-Limit-Month" // the rate limit ceiling that is applicable for the current user
33+
HeaderRateLimitLimit = "ratelimit-limit" // the rate limit ceiling that is applicable for the current request
34+
HeaderRateLimitRemaining = "ratelimit-remaining" // the number of requests left for the current rate-limit window
35+
HeaderRateLimitReset = "ratelimit-reset" // the number of seconds until the quota resets
36+
HeaderRateLimitWindow = "ratelimit-window" // what window the ratelimit represents
37+
HeaderRetryAfter = "retry-after" // the number of seconds until the quota resets, same as HeaderRateLimitReset, RFC 7231, 7.1.3
38+
39+
HeaderRateLimitRemainingSecond = "x-ratelimit-remaining-second" // the number of requests left for the current rate-limit window
40+
HeaderRateLimitRemainingMinute = "x-ratelimit-remaining-minute" // the number of requests left for the current rate-limit window
41+
HeaderRateLimitRemainingHour = "x-ratelimit-remaining-hour" // the number of requests left for the current rate-limit window
42+
HeaderRateLimitRemainingDay = "x-ratelimit-remaining-day" // the number of requests left for the current rate-limit window
43+
HeaderRateLimitRemainingMonth = "x-ratelimit-remaining-month" // the number of requests left for the current rate-limit window
44+
45+
HeaderRateLimitLimitSecond = "x-ratelimit-limit-second" // the rate limit ceiling that is applicable for the current user
46+
HeaderRateLimitLimitMinute = "x-ratelimit-limit-minute" // the rate limit ceiling that is applicable for the current user
47+
HeaderRateLimitLimitHour = "x-ratelimit-limit-hour" // the rate limit ceiling that is applicable for the current user
48+
HeaderRateLimitLimitDay = "x-ratelimit-limit-day" // the rate limit ceiling that is applicable for the current user
49+
HeaderRateLimitLimitMonth = "x-ratelimit-limit-month" // the rate limit ceiling that is applicable for the current user
4050

4151
DefaultRateLimitSecond = 2 // RateLimit per second if no ratelimits are set in database
4252
DefaultRateLimitHour = 500 // RateLimit per second if no ratelimits are set in database
@@ -111,11 +121,24 @@ type RateLimitResult struct {
111121
RedisKeys []RedisKey
112122
RedisStatsKey string
113123
RateLimit *RateLimit
114-
Limit int64
115-
Remaining int64
116-
Reset int64
117-
Bucket string
118-
Window TimeWindow
124+
125+
Limit int64
126+
LimitSecond int64
127+
LimitMinute int64
128+
LimitHour int64
129+
LimitDay int64
130+
LimitMonth int64
131+
132+
Remaining int64
133+
RemainingSecond int64
134+
RemainingMinute int64
135+
RemainingHour int64
136+
RemainingDay int64
137+
RemainingMonth int64
138+
139+
Reset int64
140+
Bucket string
141+
Window TimeWindow
119142
}
120143

121144
type RedisKey struct {
@@ -286,15 +309,19 @@ func HttpMiddleware(next http.Handler) http.Handler {
286309
w.Header().Set(HeaderRateLimitRemaining, strconv.FormatInt(rl.Remaining, 10))
287310
w.Header().Set(HeaderRateLimitReset, strconv.FormatInt(rl.Reset, 10))
288311

289-
if rl.RateLimit.Second > 0 {
290-
w.Header().Set(HeaderRateLimitLimitSecond, strconv.FormatInt(rl.RateLimit.Second, 10))
291-
}
292-
if rl.RateLimit.Hour > 0 {
293-
w.Header().Set(HeaderRateLimitLimitHour, strconv.FormatInt(rl.RateLimit.Hour, 10))
294-
}
295-
if rl.RateLimit.Month > 0 {
296-
w.Header().Set(HeaderRateLimitLimitMonth, strconv.FormatInt(rl.RateLimit.Month, 10))
297-
}
312+
w.Header().Set(HeaderRateLimitWindow, string(rl.Window))
313+
314+
w.Header().Set(HeaderRateLimitLimitMonth, strconv.FormatInt(rl.LimitMonth, 10))
315+
w.Header().Set(HeaderRateLimitLimitDay, strconv.FormatInt(rl.LimitDay, 10))
316+
w.Header().Set(HeaderRateLimitLimitHour, strconv.FormatInt(rl.LimitHour, 10))
317+
w.Header().Set(HeaderRateLimitLimitMinute, strconv.FormatInt(rl.LimitMinute, 10))
318+
w.Header().Set(HeaderRateLimitLimitSecond, strconv.FormatInt(rl.LimitSecond, 10))
319+
320+
w.Header().Set(HeaderRateLimitRemainingMonth, strconv.FormatInt(rl.RemainingMonth, 10))
321+
w.Header().Set(HeaderRateLimitRemainingDay, strconv.FormatInt(rl.RemainingDay, 10))
322+
w.Header().Set(HeaderRateLimitRemainingHour, strconv.FormatInt(rl.RemainingHour, 10))
323+
w.Header().Set(HeaderRateLimitRemainingMinute, strconv.FormatInt(rl.RemainingMinute, 10))
324+
w.Header().Set(HeaderRateLimitRemainingSecond, strconv.FormatInt(rl.RemainingSecond, 10))
298325

299326
if rl.Weight > rl.Remaining {
300327
w.Header().Set(HeaderRetryAfter, strconv.FormatInt(rl.Reset, 10))
@@ -783,6 +810,7 @@ func rateLimitRequest(r *http.Request) (*RateLimitResult, error) {
783810
} else if res.RateLimit.Second-rateLimitSecond.Val() > res.Limit {
784811
res.Limit = res.RateLimit.Second
785812
res.Remaining = res.RateLimit.Second - rateLimitSecond.Val()
813+
res.RemainingSecond = res.Remaining
786814
res.Reset = int64(1)
787815
res.Window = SecondTimeWindow
788816
}
@@ -798,6 +826,7 @@ func rateLimitRequest(r *http.Request) (*RateLimitResult, error) {
798826
} else if res.RateLimit.Hour-rateLimitHour.Val() > res.Limit {
799827
res.Limit = res.RateLimit.Hour
800828
res.Remaining = res.RateLimit.Hour - rateLimitHour.Val()
829+
res.RemainingHour = res.Remaining
801830
res.Reset = int64(timeUntilNextHourUtc.Seconds())
802831
res.Window = HourTimeWindow
803832
}
@@ -813,14 +842,58 @@ func rateLimitRequest(r *http.Request) (*RateLimitResult, error) {
813842
} else if res.RateLimit.Month-rateLimitMonth.Val() > res.Limit {
814843
res.Limit = res.RateLimit.Month
815844
res.Remaining = res.RateLimit.Month - rateLimitMonth.Val()
845+
res.RemainingMonth = res.Remaining
816846
res.Reset = int64(timeUntilNextMonthUtc.Seconds())
817847
res.Window = MonthTimeWindow
818848
}
819849
}
820850

851+
// normalize limit-headers to keep them consistent with previous versions
852+
if res.RateLimit.Month > 0 {
853+
res.LimitMonth = res.RateLimit.Month
854+
} else {
855+
res.LimitMonth = max(res.RateLimit.Month, res.RateLimit.Hour, res.RateLimit.Second)
856+
}
857+
res.LimitDay = res.LimitMonth
858+
859+
if res.RateLimit.Hour > 0 {
860+
res.LimitHour = res.RateLimit.Hour
861+
} else {
862+
res.LimitHour = res.LimitMonth
863+
}
864+
res.LimitMinute = res.LimitHour
865+
866+
if res.RateLimit.Second > 0 {
867+
res.LimitSecond = res.RateLimit.Second
868+
} else {
869+
res.LimitSecond = res.LimitHour
870+
}
871+
872+
if res.RemainingMonth == 0 {
873+
res.RemainingMonth = max(res.RemainingMonth, res.RemainingHour, res.RemainingSecond)
874+
}
875+
res.RemainingDay = res.RemainingMonth
876+
if res.RemainingHour == 0 {
877+
res.RemainingHour = res.RemainingMonth
878+
}
879+
res.RemainingMinute = res.RemainingHour
880+
if res.RemainingSecond == 0 {
881+
res.RemainingSecond = res.RemainingMinute
882+
}
883+
821884
return res, nil
822885
}
823886

887+
func max(vals ...int64) int64 {
888+
max := vals[0]
889+
for _, v := range vals {
890+
if v > max {
891+
max = v
892+
}
893+
}
894+
return max
895+
}
896+
824897
// getKey returns the key used for RateLimiting. It first checks the query params, then the header and finally the ip address.
825898
func getKey(r *http.Request) (key, ip string) {
826899
ip = getIP(r)

0 commit comments

Comments
 (0)