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 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