@@ -30,13 +30,23 @@ const (
3030)
3131
3232const (
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
121144type 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.
825898func getKey (r * http.Request ) (key , ip string ) {
826899 ip = getIP (r )
0 commit comments