dhc 1.0.0 → 2.1.1
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/.github/workflows/rubocop.yml +2 -13
- data/.github/workflows/test.yml +2 -13
- data/.rubocop.yml +3 -0
- data/README.md +26 -1
- data/lib/dhc/error.rb +0 -3
- data/lib/dhc/interceptor.rb +1 -1
- data/lib/dhc/interceptors/auth.rb +1 -1
- data/lib/dhc/interceptors/default_timeout.rb +1 -1
- data/lib/dhc/interceptors/throttle.rb +81 -44
- data/lib/dhc/request.rb +1 -1
- data/lib/dhc/rspec.rb +1 -1
- data/lib/dhc/version.rb +1 -1
- data/spec/core_ext/hash/deep_transform_values_spec.rb +1 -1
- data/spec/error/to_s_spec.rb +0 -3
- data/spec/interceptors/throttle/main_spec.rb +4 -4
- data/spec/interceptors/throttle/manually_spec.rb +101 -0
- data/spec/interceptors/throttle/reset_track_spec.rb +0 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a8e0bb9f278c6894075dfd3004f3a639be1d5da7dd02df7b4e6d3f6169ee33ef
|
4
|
+
data.tar.gz: f3ee060e925fca3a629f8ca3c53e815c9c33bcb6dcbba75ff57651d1f57595e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fbdd2700bd73f043a0d6aa4f37c2505255451be5fd6228df2013951df03f63c3dd964116bcf5a7c05a273a28025d06cab319aca08f19fa7339a7eb349e39c9a9
|
7
|
+
data.tar.gz: 99800e3225e33e433218698f075c492be9fa6f59d11c457a09ab3558eabbebb5ecb68b15ed796c2ffea6803cd02199f3d1ffda3d52717a8f0e543e56b55c6982
|
@@ -8,20 +8,9 @@ jobs:
|
|
8
8
|
|
9
9
|
steps:
|
10
10
|
- uses: actions/checkout@v2
|
11
|
-
- uses:
|
11
|
+
- uses: ruby/setup-ruby@master
|
12
12
|
with:
|
13
|
-
|
14
|
-
- name: Cache Ruby Gems
|
15
|
-
uses: actions/cache@v2
|
16
|
-
with:
|
17
|
-
path: /.tmp/vendor/bundle
|
18
|
-
key: ${{ runner.os }}-gems-latest-${{ hashFiles('**/Gemfile.lock') }}
|
19
|
-
restore-keys: |
|
20
|
-
${{ runner.os }}-gems-latest-
|
21
|
-
- name: Bundle Install
|
22
|
-
run: |
|
23
|
-
bundle config path /.tmp/vendor/bundle
|
24
|
-
bundle install --jobs 4 --retry 3
|
13
|
+
bundler-cache: true
|
25
14
|
- name: Run Rubocop
|
26
15
|
run: |
|
27
16
|
bundle exec rubocop
|
data/.github/workflows/test.yml
CHANGED
@@ -8,20 +8,9 @@ jobs:
|
|
8
8
|
|
9
9
|
steps:
|
10
10
|
- uses: actions/checkout@v2
|
11
|
-
- uses:
|
11
|
+
- uses: ruby/setup-ruby@master
|
12
12
|
with:
|
13
|
-
|
14
|
-
- name: Cache Ruby Gems
|
15
|
-
uses: actions/cache@v2
|
16
|
-
with:
|
17
|
-
path: /.tmp/vendor/bundle
|
18
|
-
key: ${{ runner.os }}-gems-latest-${{ hashFiles('**/Gemfile.lock') }}
|
19
|
-
restore-keys: |
|
20
|
-
${{ runner.os }}-gems-latest-
|
21
|
-
- name: Bundle Install
|
22
|
-
run: |
|
23
|
-
bundle config path /.tmp/vendor/bundle
|
24
|
-
bundle install --jobs 4 --retry 3
|
13
|
+
bundler-cache: true
|
25
14
|
- name: Run Tests
|
26
15
|
run: |
|
27
16
|
bundle exec rspec
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
@@ -854,6 +854,8 @@ If it raises, it forwards the request and response object to rollbar, which cont
|
|
854
854
|
|
855
855
|
The throttle interceptor allows you to raise an exception if a predefined quota of a provider request limit is reached in advance.
|
856
856
|
|
857
|
+
The throttle state (tracker) is stored in Rails.cache.
|
858
|
+
|
857
859
|
```ruby
|
858
860
|
DHC.configure do |c|
|
859
861
|
c.interceptors = [DHC::Throttle]
|
@@ -889,10 +891,33 @@ DHC.get('http://depay.fi', options)
|
|
889
891
|
* `remaining`:
|
890
892
|
* a hash pointing at the response header containing the current amount of remaining requests
|
891
893
|
* a proc that receives the response as argument and returns the current amount of remaining requests
|
894
|
+
* not set: will track remaining by counting down limit until `expires`
|
892
895
|
* `expires`:
|
893
896
|
* a hash pointing at the response header containing the timestamp when the quota will reset
|
894
897
|
* a proc that receives the response as argument and returns the timestamp when the quota will reset
|
898
|
+
* an ActiveSupport::Duration e.g. 1.minute
|
899
|
+
|
900
|
+
Example for throttling manually without relating to any information in the response:
|
895
901
|
|
902
|
+
```ruby
|
903
|
+
options = {
|
904
|
+
throttle: {
|
905
|
+
track: true,
|
906
|
+
break: '80%',
|
907
|
+
provider: 'depay.fi',
|
908
|
+
limit: 100,
|
909
|
+
expires: 1.minute
|
910
|
+
}
|
911
|
+
}
|
912
|
+
|
913
|
+
DHC.get('http://depay.fi', options)
|
914
|
+
```
|
915
|
+
|
916
|
+
Will reset every minute, and will allow up to 80 requests per minute. The 81st request attempt within a minute will raise:
|
917
|
+
|
918
|
+
```ruby
|
919
|
+
DHC::Throttle::OutOfQuota: Reached predefined quota for depay.fi
|
920
|
+
```
|
896
921
|
|
897
922
|
#### Zipkin
|
898
923
|
|
@@ -941,7 +966,7 @@ config.middleware.use ZipkinTracer::RackHandler, {
|
|
941
966
|
|
942
967
|
#### Interceptor callbacks
|
943
968
|
|
944
|
-
`
|
969
|
+
`before_init` is called before the raw typhoeus request has been initialized.
|
945
970
|
|
946
971
|
`before_request` is called when the request is prepared and about to be executed.
|
947
972
|
|
data/lib/dhc/error.rb
CHANGED
@@ -70,10 +70,7 @@ class DHC::Error < StandardError
|
|
70
70
|
|
71
71
|
debug = []
|
72
72
|
debug << [request.method, request.url].map { |str| self.class.fix_invalid_encoding(str) }.join(' ')
|
73
|
-
debug << "Options: #{request.options}"
|
74
|
-
debug << "Headers: #{request.headers}"
|
75
73
|
debug << "Response Code: #{response.code} (#{response.options[:return_code]})"
|
76
|
-
debug << "Response Options: #{response.options}"
|
77
74
|
debug << response.body
|
78
75
|
debug << _message
|
79
76
|
|
data/lib/dhc/interceptor.rb
CHANGED
@@ -8,7 +8,7 @@ class DHC::DefaultTimeout < DHC::Interceptor
|
|
8
8
|
CONNECTTIMEOUT = 2 # seconds
|
9
9
|
TIMEOUT = 15 # seconds
|
10
10
|
|
11
|
-
def
|
11
|
+
def before_init
|
12
12
|
request_options = (request.options || {})
|
13
13
|
request_options[:timeout] ||= timeout || TIMEOUT
|
14
14
|
request_options[:connecttimeout] ||= connecttimeout || CONNECTTIMEOUT
|
@@ -6,80 +6,117 @@ class DHC::Throttle < DHC::Interceptor
|
|
6
6
|
class OutOfQuota < StandardError
|
7
7
|
end
|
8
8
|
|
9
|
+
CACHE_KEY = 'DHC/throttle/tracker/v1'
|
10
|
+
|
9
11
|
class << self
|
10
|
-
|
12
|
+
|
13
|
+
def tracker(provider)
|
14
|
+
(Rails.cache.read(CACHE_KEY) || {})[provider] || {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def tracker=(track)
|
18
|
+
Rails.cache.write(CACHE_KEY, (Rails.cache.read(CACHE_KEY) || {}).merge({ track[:provider] => track }))
|
19
|
+
end
|
11
20
|
end
|
12
21
|
|
13
22
|
def before_request
|
14
|
-
options = request.options.dig(:throttle)
|
15
23
|
return unless options
|
16
|
-
|
17
|
-
return unless break_options
|
18
|
-
break_when_quota_reached! if break_options.match('%')
|
24
|
+
break! if break?
|
19
25
|
end
|
20
26
|
|
21
27
|
def after_response
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
expires: expires(options: options[:expires], response: response)
|
28
|
+
return unless track?
|
29
|
+
self.class.tracker = {
|
30
|
+
provider: options.dig(:provider),
|
31
|
+
limit: limit,
|
32
|
+
remaining: remaining,
|
33
|
+
expires: expires
|
29
34
|
}
|
30
35
|
end
|
31
36
|
|
32
37
|
private
|
33
38
|
|
34
|
-
def
|
35
|
-
|
39
|
+
def options
|
40
|
+
@options ||= request.options.dig(:throttle) || {}
|
41
|
+
end
|
42
|
+
|
43
|
+
def provider
|
44
|
+
@provider ||= request.options.dig(:throttle, :provider)
|
45
|
+
end
|
46
|
+
|
47
|
+
def track?
|
48
|
+
(options.dig(:remaining) && [options.dig(:track), response.headers].none?(&:blank?) ||
|
49
|
+
options.dig(:track).present?
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def break?
|
54
|
+
@do_break ||= begin
|
55
|
+
return if options.dig(:break) && !options.dig(:break).match('%')
|
56
|
+
tracker = self.class.tracker(options[:provider])
|
57
|
+
return if tracker.blank? || tracker[:remaining].blank? || tracker[:limit].blank? || tracker[:expires].blank?
|
58
|
+
return if Time.zone.now > tracker[:expires]
|
59
|
+
remaining = tracker[:remaining] * 100
|
60
|
+
limit = tracker[:limit]
|
61
|
+
remaining_quota = 100 - options[:break].to_i
|
62
|
+
remaining < remaining_quota * limit
|
63
|
+
end
|
36
64
|
end
|
37
65
|
|
38
|
-
def
|
39
|
-
|
40
|
-
track = (self.class.track || {}).dig(options[:provider])
|
41
|
-
return if track.blank? || track[:remaining].blank? || track[:limit].blank? || track[:expires].blank?
|
42
|
-
return if Time.zone.now > track[:expires]
|
43
|
-
# avoid floats by multiplying with 100
|
44
|
-
remaining = track[:remaining] * 100
|
45
|
-
limit = track[:limit]
|
46
|
-
quota = 100 - options[:break].to_i
|
47
|
-
raise(OutOfQuota, "Reached predefined quota for #{options[:provider]}") if remaining < quota * limit
|
66
|
+
def break!
|
67
|
+
raise(OutOfQuota, "Reached predefined quota for #{provider}")
|
48
68
|
end
|
49
69
|
|
50
|
-
def limit
|
70
|
+
def limit
|
51
71
|
@limit ||=
|
52
|
-
if options.is_a?(Proc)
|
53
|
-
options.call(response)
|
54
|
-
elsif options.is_a?(Integer)
|
55
|
-
options
|
56
|
-
elsif options.is_a?(Hash) && options
|
57
|
-
response.headers[options
|
72
|
+
if options.dig(:limit).is_a?(Proc)
|
73
|
+
options.dig(:limit).call(response)
|
74
|
+
elsif options.dig(:limit).is_a?(Integer)
|
75
|
+
options.dig(:limit)
|
76
|
+
elsif options.dig(:limit).is_a?(Hash) && options.dig(:limit, :header) && response.headers
|
77
|
+
response.headers[options.dig(:limit, :header)]&.to_i
|
58
78
|
end
|
59
79
|
end
|
60
80
|
|
61
|
-
def remaining
|
62
|
-
@remaining ||=
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
81
|
+
def remaining
|
82
|
+
@remaining ||= begin
|
83
|
+
if options.dig(:remaining).is_a?(Proc)
|
84
|
+
options.dig(:remaining).call(response)
|
85
|
+
elsif options.dig(:remaining).is_a?(Hash) && options.dig(:remaining, :header) && response.headers
|
86
|
+
response.headers[options.dig(:remaining, :header)]&.to_i
|
87
|
+
elsif options.dig(:remaining).blank?
|
88
|
+
remaining_before = self.class.tracker(provider).dig(:remaining) || request.options.dig(:throttle, :limit)
|
89
|
+
expires = self.class.tracker(provider).dig(:expires)
|
90
|
+
if expires && expires > DateTime.now
|
91
|
+
remaining_before - 1
|
92
|
+
else
|
93
|
+
request.options.dig(:throttle, :limit) - 1
|
68
94
|
end
|
69
95
|
end
|
96
|
+
end
|
70
97
|
end
|
71
98
|
|
72
|
-
def expires
|
73
|
-
@expires ||=
|
74
|
-
|
99
|
+
def expires
|
100
|
+
@expires ||= begin
|
101
|
+
if options.dig(:expires).is_a?(ActiveSupport::Duration) && self.class.tracker(provider).dig(:expires).present?
|
102
|
+
if self.class.tracker(provider)[:expires] > DateTime.now
|
103
|
+
self.class.tracker(provider)[:expires]
|
104
|
+
else
|
105
|
+
DateTime.now + options.dig(:expires)
|
106
|
+
end
|
107
|
+
elsif options.dig(:expires).is_a?(Hash) && options.dig(:expires, :header)
|
75
108
|
|
76
|
-
|
77
|
-
|
109
|
+
convert_expire_value(response.headers[options.dig(:expires, :header)]) if response.headers
|
110
|
+
else
|
111
|
+
convert_expire_value(options.dig(:expires))
|
112
|
+
end
|
113
|
+
end
|
78
114
|
end
|
79
115
|
|
80
|
-
def
|
116
|
+
def convert_expire_value(value)
|
81
117
|
return if value.blank?
|
82
118
|
return value.call(response) if value.is_a?(Proc)
|
119
|
+
return DateTime.now + value if value.is_a?(ActiveSupport::Duration)
|
83
120
|
return Time.parse(value) if value.match(/GMT/)
|
84
121
|
Time.zone.at(value.to_i).to_datetime
|
85
122
|
end
|
data/lib/dhc/request.rb
CHANGED
@@ -22,7 +22,7 @@ class DHC::Request
|
|
22
22
|
use_configured_endpoint!
|
23
23
|
generate_url_from_template!
|
24
24
|
self.interceptors = DHC::Interceptors.new(self)
|
25
|
-
interceptors.intercept(:
|
25
|
+
interceptors.intercept(:before_init)
|
26
26
|
self.raw = create_request
|
27
27
|
interceptors.intercept(:before_request)
|
28
28
|
if self_executing && !response
|
data/lib/dhc/rspec.rb
CHANGED
data/lib/dhc/version.rb
CHANGED
data/spec/error/to_s_spec.rb
CHANGED
@@ -72,10 +72,7 @@ describe DHC::Error do
|
|
72
72
|
it 'produces correct debug output' do
|
73
73
|
expect(subject.to_s.split("\n")).to eq(<<-MSG.strip_heredoc.split("\n"))
|
74
74
|
GET http://example.com/sessions
|
75
|
-
Options: {:followlocation=>true, :auth=>{:bearer=>"aaaaaaaa-bbbb-cccc-dddd-eeee"}, :params=>{:limit=>20}, :url=>"http://example.com/sessions"}
|
76
|
-
Headers: {"Bearer Token"=>"aaaaaaaa-bbbb-cccc-dddd-eeee"}
|
77
75
|
Response Code: 500 (internal_error)
|
78
|
-
Response Options: {:return_code=>:internal_error, :response_headers=>""}
|
79
76
|
{"status":500,"message":"undefined"}
|
80
77
|
The error message
|
81
78
|
MSG
|
@@ -25,8 +25,8 @@ describe DHC::Throttle do
|
|
25
25
|
end
|
26
26
|
|
27
27
|
before(:each) do
|
28
|
-
DHC::Throttle.track = nil
|
29
28
|
DHC.config.interceptors = [DHC::Throttle]
|
29
|
+
Rails.cache.write(DHC::Throttle::CACHE_KEY, nil)
|
30
30
|
|
31
31
|
stub_request(:get, 'http://depay.fi').to_return(
|
32
32
|
headers: { 'limit' => quota_limit, 'remaining' => quota_remaining, 'reset' => quota_reset }
|
@@ -35,8 +35,8 @@ describe DHC::Throttle do
|
|
35
35
|
|
36
36
|
it 'tracks the request limits based on response data' do
|
37
37
|
DHC.get('http://depay.fi', options)
|
38
|
-
expect(DHC
|
39
|
-
expect(DHC
|
38
|
+
expect(Rails.cache.read('DHC/throttle/tracker/v1')[provider][:limit]).to eq quota_limit
|
39
|
+
expect(Rails.cache.read('DHC/throttle/tracker/v1')[provider][:remaining]).to eq quota_remaining
|
40
40
|
end
|
41
41
|
|
42
42
|
context 'fix predefined integer for limit' do
|
@@ -44,7 +44,7 @@ describe DHC::Throttle do
|
|
44
44
|
|
45
45
|
it 'tracks the limit based on initialy provided data' do
|
46
46
|
DHC.get('http://depay.fi', options)
|
47
|
-
expect(DHC
|
47
|
+
expect(Rails.cache.read('DHC/throttle/tracker/v1')[provider][:limit]).to eq options_limit
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
describe DHC::Throttle do
|
6
|
+
let(:options) do
|
7
|
+
{
|
8
|
+
throttle: {
|
9
|
+
provider: provider,
|
10
|
+
track: true,
|
11
|
+
limit: quota_limit,
|
12
|
+
expires: 1.minute,
|
13
|
+
break: break_after
|
14
|
+
}
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:provider) { 'depay.fi' }
|
19
|
+
let(:quota_limit) { 100 }
|
20
|
+
let(:break_after) { '80%' }
|
21
|
+
|
22
|
+
before(:each) do
|
23
|
+
DHC.config.interceptors = [DHC::Throttle]
|
24
|
+
Rails.cache.write(DHC::Throttle::CACHE_KEY, nil)
|
25
|
+
|
26
|
+
stub_request(:get, 'http://depay.fi').to_return(status: 200)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'tracks the request limits based on response data' do
|
30
|
+
DHC.get('http://depay.fi', options)
|
31
|
+
expect(Rails.cache.read('DHC/throttle/tracker/v1')[provider][:limit]).to eq 100
|
32
|
+
expect(Rails.cache.read('DHC/throttle/tracker/v1')[provider][:remaining]).to eq quota_limit - 1
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'breaks' do
|
36
|
+
let(:quota_limit) { 10 }
|
37
|
+
let(:break_after) { '79%' }
|
38
|
+
|
39
|
+
it 'hit the breaks if throttling quota is reached' do
|
40
|
+
8.times do
|
41
|
+
DHC.get('http://depay.fi', options)
|
42
|
+
end
|
43
|
+
expect { DHC.get('http://depay.fi', options) }.to raise_error(
|
44
|
+
DHC::Throttle::OutOfQuota,
|
45
|
+
'Reached predefined quota for depay.fi'
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'still within quota' do
|
50
|
+
let(:break_after) { '80%' }
|
51
|
+
|
52
|
+
it 'does not hit the breaks' do
|
53
|
+
9.times do
|
54
|
+
DHC.get('http://depay.fi', options)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'expires' do
|
61
|
+
let(:break_after) { '80%' }
|
62
|
+
let(:quota_limit) { 10 }
|
63
|
+
|
64
|
+
it 'attempts another request if the quota expired' do
|
65
|
+
9.times do
|
66
|
+
DHC.get('http://depay.fi', options)
|
67
|
+
end
|
68
|
+
expect { DHC.get('http://depay.fi', options) }.to raise_error(
|
69
|
+
DHC::Throttle::OutOfQuota,
|
70
|
+
'Reached predefined quota for depay.fi'
|
71
|
+
)
|
72
|
+
Timecop.travel(Time.zone.now + 1.minute)
|
73
|
+
9.times do
|
74
|
+
DHC.get('http://depay.fi', options)
|
75
|
+
end
|
76
|
+
expect { DHC.get('http://depay.fi', options) }.to raise_error(
|
77
|
+
DHC::Throttle::OutOfQuota,
|
78
|
+
'Reached predefined quota for depay.fi'
|
79
|
+
)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'multiple provider' do
|
84
|
+
|
85
|
+
it 'tracks multiple providers without a problem' do
|
86
|
+
DHC.get('http://depay.fi', options)
|
87
|
+
stub_request(:get, 'http://depay.app').to_return(status: 200)
|
88
|
+
DHC.get('http://depay.app', {
|
89
|
+
throttle: {
|
90
|
+
provider: 'depay.app',
|
91
|
+
track: true,
|
92
|
+
limit: quota_limit,
|
93
|
+
expires: 1.minute,
|
94
|
+
break: break_after
|
95
|
+
}
|
96
|
+
})
|
97
|
+
expect(Rails.cache.read('DHC/throttle/tracker/v1')['depay.fi']).to be_present
|
98
|
+
expect(Rails.cache.read('DHC/throttle/tracker/v1')['depay.app']).to be_present
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dhc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 2.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- https://github.com/DePayFi/dhc/contributors
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -338,6 +338,7 @@ files:
|
|
338
338
|
- spec/interceptors/rollbar/invalid_encoding_spec.rb
|
339
339
|
- spec/interceptors/rollbar/main_spec.rb
|
340
340
|
- spec/interceptors/throttle/main_spec.rb
|
341
|
+
- spec/interceptors/throttle/manually_spec.rb
|
341
342
|
- spec/interceptors/throttle/reset_track_spec.rb
|
342
343
|
- spec/interceptors/zipkin/distributed_tracing_spec.rb
|
343
344
|
- spec/rails_helper.rb
|
@@ -391,7 +392,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
391
392
|
version: '0'
|
392
393
|
requirements:
|
393
394
|
- Ruby >= 2.0.0
|
394
|
-
rubygems_version: 3.2.
|
395
|
+
rubygems_version: 3.2.22
|
395
396
|
signing_key:
|
396
397
|
specification_version: 4
|
397
398
|
summary: Advanced HTTP Client for Ruby, fueled with interceptors
|
@@ -492,6 +493,7 @@ test_files:
|
|
492
493
|
- spec/interceptors/rollbar/invalid_encoding_spec.rb
|
493
494
|
- spec/interceptors/rollbar/main_spec.rb
|
494
495
|
- spec/interceptors/throttle/main_spec.rb
|
496
|
+
- spec/interceptors/throttle/manually_spec.rb
|
495
497
|
- spec/interceptors/throttle/reset_track_spec.rb
|
496
498
|
- spec/interceptors/zipkin/distributed_tracing_spec.rb
|
497
499
|
- spec/rails_helper.rb
|