gitlab-labkit 5.5.0 → 5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitlab/CODEOWNERS +1 -1
- data/CODEOWNERS +1 -1
- data/lib/labkit/rate_limit/README.md +23 -1
- data/lib/labkit/rate_limit/result.rb +32 -9
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fb00f476a5fed0be71839727d4fb5d8230f773831c8c8cca40ff8090c76cb2c5
|
|
4
|
+
data.tar.gz: c382bf0eed9e55b283eb90c3956a00c8dbf73ad5819fb22e8dd6a1be931fd9ff
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5932210330aa7c8c4413de43f8ae710508658846c89f1b7b74e1d8d36af564b61034988a6248bf9795d6343602d4b1579d6a9aff48795cda1163c545377932b9
|
|
7
|
+
data.tar.gz: ddc501d350806e384f122f152f6ceb7066487bc4f41078341f1ae13373631adf52a1f5cd427a69e879482cf3dc1dff75bbfe7cd57dd1237094c595e615acc591
|
data/.gitlab/CODEOWNERS
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
* @reprazent @
|
|
1
|
+
* @reprazent @mkaeppler @ayufan @hmerscher @sankalp_gl @hardikgala @nindurkar @ashs2
|
data/CODEOWNERS
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
# CODEOWNERS is used to lookup assignees for
|
|
2
2
|
# Renovate Bot dependency change Merge Requests.
|
|
3
3
|
# https://docs.renovatebot.com/configuration-options/#assigneesfromcodeowners
|
|
4
|
-
* @reprazent @
|
|
4
|
+
* @reprazent @ayufan @hmerscher @e_forbes @M_Alvarez @mwoolf @sankalp_gl @hardikgala @nindurkar @ashs2
|
|
@@ -512,7 +512,8 @@ result.degraded? # => true if a matched count_distinct rule was skipped
|
|
|
512
512
|
result.info # => Result::Info or nil
|
|
513
513
|
result.evaluations # => every counted Result::Evaluation, in rule order
|
|
514
514
|
result.to_response_headers
|
|
515
|
-
# => { "RateLimit-
|
|
515
|
+
# => { "RateLimit-Name" => "<rule name>", "RateLimit-Limit" => "...", "RateLimit-Observed" => "...",
|
|
516
|
+
# "RateLimit-Remaining" => "...", "RateLimit-Reset" => "<unix-ts>" }
|
|
516
517
|
```
|
|
517
518
|
|
|
518
519
|
`Result::Info` holds the per-window counter snapshot:
|
|
@@ -528,6 +529,27 @@ result.to_response_headers
|
|
|
528
529
|
`to_response_headers` returns `{}` for an unmatched or error result, so it is
|
|
529
530
|
safe to merge unconditionally.
|
|
530
531
|
|
|
532
|
+
### Response headers
|
|
533
|
+
|
|
534
|
+
`Result#to_response_headers` reports the most constraining evaluation.
|
|
535
|
+
`Result::Evaluation#to_response_headers` builds the same headers for one evaluation, for a caller that picks its own evaluation across several results.
|
|
536
|
+
|
|
537
|
+
| header | present | value |
|
|
538
|
+
|-----------------------|-------------------|-----------------------------------------------------|
|
|
539
|
+
| `RateLimit-Name` | always | The rule's `name`. |
|
|
540
|
+
| `RateLimit-Limit` | always | `resolved_limit` in the rule's period, at least 0. |
|
|
541
|
+
| `RateLimit-Observed` | always | `count`, as an Integer, at least 0. |
|
|
542
|
+
| `RateLimit-Remaining` | always | `remaining`, as an Integer. |
|
|
543
|
+
| `RateLimit-Reset` | always | `reset_at` as a Unix timestamp. |
|
|
544
|
+
| `Retry-After` | blocking only | Seconds to `reset_at`, rounded up, at least 0. |
|
|
545
|
+
| `RateLimit-ResetTime` | blocking only | `reset_at` as an HTTP date. |
|
|
546
|
+
|
|
547
|
+
`Retry-After` and `RateLimit-ResetTime` belong on the 429, so they appear only when the evaluation blocks.
|
|
548
|
+
For a rule with `ban_for`, `reset_at` is the end of the ban, so these headers report when the ban lifts.
|
|
549
|
+
|
|
550
|
+
`RateLimit-Limit`, `RateLimit-Observed` and `RateLimit-Remaining` all count within the rule's `period`.
|
|
551
|
+
The headers do not state the period, so a client that needs it must know the rule.
|
|
552
|
+
|
|
531
553
|
## Fail-open
|
|
532
554
|
|
|
533
555
|
The evaluator wraps `check` and `peek` in a broad rescue. Any `StandardError`
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "time" # Time#httpdate
|
|
4
|
+
|
|
3
5
|
module Labkit
|
|
4
6
|
module RateLimit
|
|
5
7
|
# Result is the return value of Limiter#check.
|
|
@@ -113,18 +115,13 @@ module Labkit
|
|
|
113
115
|
@most_constraining ||= @evaluations.min
|
|
114
116
|
end
|
|
115
117
|
|
|
116
|
-
# Returns
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
# the RateLimit header spec requires integer values.
|
|
118
|
+
# Returns the rate limit response headers of the most constraining
|
|
119
|
+
# evaluation, or {} when no rule matched, a :skip rule matched, or an
|
|
120
|
+
# error occurred. See Result::Evaluation#to_response_headers for the keys.
|
|
120
121
|
def to_response_headers
|
|
121
122
|
return {} unless matched? && !error? && info
|
|
122
123
|
|
|
123
|
-
|
|
124
|
-
"RateLimit-Limit" => info.resolved_limit.to_i.to_s,
|
|
125
|
-
"RateLimit-Remaining" => info.remaining.to_i.to_s,
|
|
126
|
-
"RateLimit-Reset" => info.reset_at.to_i.to_s
|
|
127
|
-
}
|
|
124
|
+
most_constraining.to_response_headers
|
|
128
125
|
end
|
|
129
126
|
end
|
|
130
127
|
|
|
@@ -155,6 +152,32 @@ module Labkit
|
|
|
155
152
|
constraint_rank <=> other.constraint_rank
|
|
156
153
|
end
|
|
157
154
|
|
|
155
|
+
# Returns the rate limit response headers for this evaluation. Values are
|
|
156
|
+
# Strings, and counts are coerced to Integer because the RateLimit header
|
|
157
|
+
# spec requires integer values even when count is fractional.
|
|
158
|
+
#
|
|
159
|
+
# RateLimit-Name (the rule name), RateLimit-Limit, RateLimit-Observed,
|
|
160
|
+
# RateLimit-Remaining and RateLimit-Reset (Unix timestamp) are always
|
|
161
|
+
# present. Limit, Observed and Remaining all count within the rule's own
|
|
162
|
+
# period. A blocking evaluation also gets Retry-After (seconds) and
|
|
163
|
+
# RateLimit-ResetTime (HTTP date), which belong on the 429.
|
|
164
|
+
def to_response_headers
|
|
165
|
+
headers = {}
|
|
166
|
+
headers["RateLimit-Name"] = rule.name
|
|
167
|
+
# Floored at 0 like remaining: a callable limit can resolve below zero
|
|
168
|
+
# to deny all, and an older client could credit a counter below zero.
|
|
169
|
+
headers["RateLimit-Limit"] = [info.resolved_limit.to_i, 0].max.to_s
|
|
170
|
+
headers["RateLimit-Observed"] = [info.count.to_i, 0].max.to_s
|
|
171
|
+
headers["RateLimit-Remaining"] = info.remaining.to_i.to_s
|
|
172
|
+
headers["RateLimit-Reset"] = info.reset_at.to_i.to_s
|
|
173
|
+
return headers unless block?
|
|
174
|
+
|
|
175
|
+
# Rounded up so a client that honours it does not retry before the reset.
|
|
176
|
+
headers["Retry-After"] = [(info.reset_at - Time.now.utc).ceil, 0].max.to_s
|
|
177
|
+
headers["RateLimit-ResetTime"] = info.reset_at.httpdate
|
|
178
|
+
headers
|
|
179
|
+
end
|
|
180
|
+
|
|
158
181
|
protected
|
|
159
182
|
|
|
160
183
|
def constraint_rank
|