lhc 10.3.0 → 10.4.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/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
|