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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a923e58a34351a5d61e64d0f3bcc7516bcbfc17d7f06d0ef6cc3af9531b91bcc
4
- data.tar.gz: 96f10388992ef3687fa01a62ef0e99fc831f8fbe1ce15d8abc15e2eb39d78a30
3
+ metadata.gz: 818f5150c8583bd190bf11aab7edf82aa4c42072f2fdd12d93552086f6693d27
4
+ data.tar.gz: 9c2cf115d254de301d29b07f4d7a238506fa50878066355b25776498bde245ac
5
5
  SHA512:
6
- metadata.gz: 3bb29402da0b779d8f485274defb9481fc78f0566f18bbbe3b3235ce4b214e1d936255d2293a0a9f7fc6c9694ca04458ec5ee927f53296aabcce5cf955f87def
7
- data.tar.gz: 352657bdbc5d76f595ea5af072c794c02ecf64641f0c2fbcb2932948731ff7c5b11d26f75b03b03a3244b4bd60eb0417169f8a43951d5a90549e0541cced337f
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
@@ -0,0 +1,3 @@
1
+ ---
2
+ request_reviews: true
3
+ cleanup_stale_branches: 8.days
data/lib/lhc.rb CHANGED
@@ -28,6 +28,8 @@ module LHC
28
28
  'lhc/interceptors/prometheus'
29
29
  autoload :Retry,
30
30
  'lhc/interceptors/retry'
31
+ autoload :Throttle,
32
+ 'lhc/interceptors/throttle'
31
33
 
32
34
  autoload :Config,
33
35
  'lhc/config'
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LHC
4
- VERSION ||= '10.3.0'
4
+ VERSION ||= '10.4.0'
5
5
  end
@@ -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.3.0
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-04-18 00:00:00.000000000 Z
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
- rubygems_version: 3.0.3
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