lhc 10.3.0 → 10.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +27 -0
- data/friday.yml +3 -0
- data/lib/lhc.rb +2 -0
- data/lib/lhc/interceptors/throttle.rb +61 -0
- data/lib/lhc/version.rb +1 -1
- data/spec/interceptors/throttle/main_spec.rb +70 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 818f5150c8583bd190bf11aab7edf82aa4c42072f2fdd12d93552086f6693d27
|
4
|
+
data.tar.gz: 9c2cf115d254de301d29b07f4d7a238506fa50878066355b25776498bde245ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6430c14afed05c3aa09e4e05b5347c45db01f95bc46a8cd0ed47987bffe92cff256b1f944c6aaacc4697f6a0ed994e9a73b1a2ee89f51c331e6b742ac8b44cef
|
7
|
+
data.tar.gz: 7a599bda71021fd43bcd7bc80712dd044d090268cbcfd31fc1d2f9feba9139dde906114d93e9c224c307a34e1ff73301cb0334eaf58c052a21b95ba339fdb8d5
|
data/README.md
CHANGED
@@ -845,6 +845,33 @@ If it raises, it forwards the request and response object to rollbar, which cont
|
|
845
845
|
LHC.get('http://local.ch', rollbar: { tracking_key: 'this particular request' })
|
846
846
|
```
|
847
847
|
|
848
|
+
#### Throttle
|
849
|
+
|
850
|
+
The throttle interceptor allows you to raise an exception if a predefined quota of a provider request limit is reached in advance.
|
851
|
+
|
852
|
+
```ruby
|
853
|
+
LHC.configure do |c|
|
854
|
+
c.interceptors = [LHC::Throttle]
|
855
|
+
end
|
856
|
+
```
|
857
|
+
```ruby
|
858
|
+
options = {
|
859
|
+
throttle: {
|
860
|
+
track: true, # enables tracking of current limit/remaining requests of rate-limiting
|
861
|
+
break: '80%', # quota in percent after which errors are raised. Percentage symbol is optional, values will be converted to integer (e.g. '23.5' will become 23)
|
862
|
+
provider: 'local.ch', # name of the provider under which throttling tracking is aggregated,
|
863
|
+
limit: { header: 'Rate-Limit-Limit' }, # either a hard-coded integer, or a hash pointing at the response header containing the limit value
|
864
|
+
remaining: { header: 'Rate-Limit-Remaining' }, # a hash pointing at the response header containing the current amount of remaining requests
|
865
|
+
}
|
866
|
+
}
|
867
|
+
|
868
|
+
LHC.get('http://local.ch', options)
|
869
|
+
# { headers: { 'Rate-Limit-Limit' => 100, 'Rate-Limit-Remaining' => 19 } }
|
870
|
+
|
871
|
+
LHC.get('http://local.ch', options)
|
872
|
+
# raises LHC::Throttle::OutOfQuota: Reached predefined quota for local.ch
|
873
|
+
```
|
874
|
+
|
848
875
|
#### Zipkin
|
849
876
|
|
850
877
|
** Zipkin 0.33 breaks our current implementation of the Zipkin interceptor **
|
data/friday.yml
ADDED
data/lib/lhc.rb
CHANGED
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class LHC::Throttle < LHC::Interceptor
|
4
|
+
|
5
|
+
class OutOfQuota < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
class << self
|
9
|
+
attr_accessor :track
|
10
|
+
end
|
11
|
+
|
12
|
+
def before_request
|
13
|
+
options = request.options.dig(:throttle)
|
14
|
+
return unless options
|
15
|
+
break_options = options.dig(:break)
|
16
|
+
return unless break_options
|
17
|
+
break_when_quota_reached! if break_options.match('%')
|
18
|
+
end
|
19
|
+
|
20
|
+
def after_response
|
21
|
+
options = response.request.options.dig(:throttle)
|
22
|
+
return unless options
|
23
|
+
return unless options.dig(:track)
|
24
|
+
self.class.track ||= {}
|
25
|
+
self.class.track[options.dig(:provider)] = {
|
26
|
+
limit: limit(options: options[:limit], response: response),
|
27
|
+
remaining: remaining(options: options[:remaining], response: response)
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def break_when_quota_reached!
|
34
|
+
options = request.options.dig(:throttle)
|
35
|
+
track = (self.class.track || {}).dig(options[:provider])
|
36
|
+
return unless track
|
37
|
+
# avoid floats by multiplying with 100
|
38
|
+
remaining = track[:remaining] * 100
|
39
|
+
limit = track[:limit]
|
40
|
+
quota = 100 - options[:break].to_i
|
41
|
+
raise(OutOfQuota, "Reached predefined quota for #{options[:provider]}") if remaining < quota * limit
|
42
|
+
end
|
43
|
+
|
44
|
+
def limit(options:, response:)
|
45
|
+
@limit ||= begin
|
46
|
+
if options.is_a?(Integer)
|
47
|
+
options
|
48
|
+
elsif options.is_a?(Hash) && options[:header]
|
49
|
+
response.headers[options[:header]]&.to_i
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def remaining(options:, response:)
|
55
|
+
@remaining ||= begin
|
56
|
+
if options.is_a?(Hash) && options[:header]
|
57
|
+
response.headers[options[:header]]&.to_i
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/lhc/version.rb
CHANGED
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
describe LHC::Throttle do
|
6
|
+
let(:provider) { 'local.ch' }
|
7
|
+
let(:limit) { 10000 }
|
8
|
+
let(:remaining) { 1900 }
|
9
|
+
let(:options) do
|
10
|
+
{
|
11
|
+
throttle: {
|
12
|
+
provider: provider,
|
13
|
+
track: true,
|
14
|
+
limit: limit_options,
|
15
|
+
remaining: { header: 'Rate-Limit-Remaining' },
|
16
|
+
break: break_option
|
17
|
+
}
|
18
|
+
}
|
19
|
+
end
|
20
|
+
let(:limit_options) { { header: 'Rate-Limit-Limit' } }
|
21
|
+
let(:break_option) { false }
|
22
|
+
|
23
|
+
before(:each) do
|
24
|
+
LHC::Throttle.track = nil
|
25
|
+
LHC.config.interceptors = [LHC::Throttle]
|
26
|
+
|
27
|
+
stub_request(:get, 'http://local.ch')
|
28
|
+
.to_return(
|
29
|
+
headers: {
|
30
|
+
'Rate-Limit-Limit' => limit,
|
31
|
+
'Rate-Limit-Remaining' => remaining
|
32
|
+
}
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'tracks the request limits based on response data' do
|
37
|
+
LHC.get('http://local.ch', options)
|
38
|
+
expect(LHC::Throttle.track[provider][:limit]).to eq limit
|
39
|
+
expect(LHC::Throttle.track[provider][:remaining]).to eq remaining
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'fix predefined integer for limit' do
|
43
|
+
let(:limit_options) { 1000 }
|
44
|
+
|
45
|
+
it 'tracks the limit based on initialy provided data' do
|
46
|
+
LHC.get('http://local.ch', options)
|
47
|
+
expect(LHC::Throttle.track[provider][:limit]).to eq limit_options
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'breaks' do
|
52
|
+
let(:break_option) { '80%' }
|
53
|
+
|
54
|
+
it 'hit the breaks if throttling quota is reached' do
|
55
|
+
LHC.get('http://local.ch', options)
|
56
|
+
expect(-> {
|
57
|
+
LHC.get('http://local.ch', options)
|
58
|
+
}).to raise_error(LHC::Throttle::OutOfQuota, 'Reached predefined quota for local.ch')
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'still within quota' do
|
62
|
+
let(:break_option) { '90%' }
|
63
|
+
|
64
|
+
it 'does not hit the breaks' do
|
65
|
+
LHC.get('http://local.ch', options)
|
66
|
+
LHC.get('http://local.ch', options)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lhc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 10.
|
4
|
+
version: 10.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- https://github.com/local-ch/lhc/contributors
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-07-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -192,6 +192,7 @@ files:
|
|
192
192
|
- cider-ci/task_components/rspec.yml
|
193
193
|
- cider-ci/task_components/rubocop.yml
|
194
194
|
- cider-ci/task_components/ruby.yml
|
195
|
+
- friday.yml
|
195
196
|
- lhc.gemspec
|
196
197
|
- lib/lhc.rb
|
197
198
|
- lib/lhc/concerns/lhc/basic_methods_concern.rb
|
@@ -221,6 +222,7 @@ files:
|
|
221
222
|
- lib/lhc/interceptors/prometheus.rb
|
222
223
|
- lib/lhc/interceptors/retry.rb
|
223
224
|
- lib/lhc/interceptors/rollbar.rb
|
225
|
+
- lib/lhc/interceptors/throttle.rb
|
224
226
|
- lib/lhc/interceptors/zipkin.rb
|
225
227
|
- lib/lhc/railtie.rb
|
226
228
|
- lib/lhc/request.rb
|
@@ -319,6 +321,7 @@ files:
|
|
319
321
|
- spec/interceptors/retry/main_spec.rb
|
320
322
|
- spec/interceptors/return_response_spec.rb
|
321
323
|
- spec/interceptors/rollbar/main_spec.rb
|
324
|
+
- spec/interceptors/throttle/main_spec.rb
|
322
325
|
- spec/interceptors/zipkin/distributed_tracing_spec.rb
|
323
326
|
- spec/rails_helper.rb
|
324
327
|
- spec/request/body_spec.rb
|
@@ -371,7 +374,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
371
374
|
version: '0'
|
372
375
|
requirements:
|
373
376
|
- Ruby >= 2.0.0
|
374
|
-
|
377
|
+
rubyforge_project:
|
378
|
+
rubygems_version: 2.7.8
|
375
379
|
signing_key:
|
376
380
|
specification_version: 4
|
377
381
|
summary: Advanced HTTP Client for Ruby, fueled with interceptors
|
@@ -463,6 +467,7 @@ test_files:
|
|
463
467
|
- spec/interceptors/retry/main_spec.rb
|
464
468
|
- spec/interceptors/return_response_spec.rb
|
465
469
|
- spec/interceptors/rollbar/main_spec.rb
|
470
|
+
- spec/interceptors/throttle/main_spec.rb
|
466
471
|
- spec/interceptors/zipkin/distributed_tracing_spec.rb
|
467
472
|
- spec/rails_helper.rb
|
468
473
|
- spec/request/body_spec.rb
|