lhc 12.1.3 → 12.2.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: c1d43867c7519eb1697f118fb44668142aa46bcdfb13d725be6617c74f4ed47f
4
- data.tar.gz: cdfa710bc5c7ae5297eeceec6858738ad414e828237038b92a3dcbaa9c088fed
3
+ metadata.gz: 1a536d04e90db04fdeca6edd06c3e88782106a660d410a1b908b94147a3e9154
4
+ data.tar.gz: d0d89904a00069fdd8f3ef297f296e5da9d1383a4b4239aeebf00b6869be082a
5
5
  SHA512:
6
- metadata.gz: 9e11b8821556d2bb0e40b91cb080379fc82a3a90453fe36dd6127b4d26a571d065637275988ba03b46d96f6217a697022d0a6aa46532ab20a5d46e1ef5dbadfd
7
- data.tar.gz: 192cabec15b2d1881a14505343f2e2f4218fad00ffaac4935dd3d1edbdc6132edb6b9ae0834f2d556dfbcacca9221f473f39dba37071ec40949e407bc953813f
6
+ metadata.gz: 858291c8b53ce6447df1cc23fabd7c74ca4307ff7590eaebb76f4d538b769f81d9d89055bf79bf564814cf55c6832fccee42918100433061331252c6aafd8042
7
+ data.tar.gz: 49356178736094b85a4d9c1eab8dcf390024df5b580cb696e7f02683cbc1f1cf4a3e5bcfc6e03f5e8b9330fed1e02b94dd8bbbe17ec3c58e14b91b7352df9030
data/README.md CHANGED
@@ -94,6 +94,7 @@ use it like:
94
94
 
95
95
 
96
96
 
97
+
97
98
  ## Basic methods
98
99
 
99
100
  Available are `get`, `post`, `put` & `delete`.
@@ -263,7 +264,7 @@ You can also use URL templates, when [configuring endpoints](#configuring-endpoi
263
264
  LHC.configure do |c|
264
265
  c.endpoint(:find_feedback, 'http://datastore/v2/feedbacks/{id}')
265
266
  end
266
-
267
+
267
268
  LHC.get(:find_feedback, params:{ id: 123 }) # GET http://datastore/v2/feedbacks/123
268
269
  ```
269
270
 
@@ -276,7 +277,7 @@ Working and configuring timeouts is important, to ensure your app stays alive wh
276
277
  LHC forwards two timeout options directly to typhoeus:
277
278
 
278
279
  `timeout` (in seconds) - The maximum time in seconds that you allow the libcurl transfer operation to take. Normally, name lookups can take a considerable time and limiting operations to less than a few seconds risk aborting perfectly normal operations. This option may cause libcurl to use the SIGALRM signal to timeout system calls.
279
- `connecttimeout` (in seconds) - It should contain the maximum time in seconds that you allow the connection phase to the server to take. This only limits the connection phase, it has no impact once it has connected. Set to zero to switch to the default built-in connection timeout - 300 seconds.
280
+ `connecttimeout` (in seconds) - It should contain the maximum time in seconds that you allow the connection phase to the server to take. This only limits the connection phase, it has no impact once it has connected. Set to zero to switch to the default built-in connection timeout - 300 seconds.
280
281
 
281
282
  ```ruby
282
283
  LHC.get('http://local.ch', timeout: 5, connecttimeout: 1)
@@ -481,7 +482,7 @@ You can configure global placeholders, that are used when generating urls from u
481
482
  c.placeholder(:datastore, 'http://datastore')
482
483
  c.endpoint(:feedbacks, '{+datastore}/feedbacks', { params: { has_reviews: true } })
483
484
  end
484
-
485
+
485
486
  LHC.get(:feedbacks) # http://datastore/v2/feedbacks
486
487
  ```
487
488
 
@@ -729,7 +730,7 @@ LHC::Monitoring.env = ENV['DEPLOYMENT_TYPE'] || Rails.env
729
730
 
730
731
  It tracks request attempts with `before_request` and `after_request` (counts).
731
732
 
732
- In case your workers/processes are getting killed due limited time constraints,
733
+ In case your workers/processes are getting killed due limited time constraints,
733
734
  you are able to detect deltas with relying on "before_request", and "after_request" counts:
734
735
 
735
736
  ```ruby
@@ -780,7 +781,7 @@ Logs basic request/response information to prometheus.
780
781
  LHC.configure do |c|
781
782
  c.interceptors = [LHC::Prometheus]
782
783
  end
783
-
784
+
784
785
  LHC::Prometheus.client = Prometheus::Client
785
786
  LHC::Prometheus.namespace = 'web_location_app'
786
787
  ```
@@ -802,7 +803,7 @@ If you enable the retry interceptor, you can have LHC retry requests for you:
802
803
  LHC.configure do |c|
803
804
  c.interceptors = [LHC::Retry]
804
805
  end
805
-
806
+
806
807
  response = LHC.get('http://local.ch', retry: true)
807
808
  ```
808
809
 
@@ -877,15 +878,15 @@ The throttle interceptor allows you to raise an exception if a predefined quota
877
878
  end
878
879
  ```
879
880
  ```ruby
880
- options = {
881
+ options = {
881
882
  throttle: {
882
- track: true, # enables tracking of current limit/remaining requests of rate-limiting
883
- 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)
884
- provider: 'local.ch', # name of the provider under which throttling tracking is aggregated,
885
- limit: { header: 'Rate-Limit-Limit' }, # either a hard-coded integer, or a hash pointing at the response header containing the limit value
886
- remaining: { header: 'Rate-Limit-Remaining' }, # a hash pointing at the response header containing the current amount of remaining requests
887
- expires: { header: 'Rate-Limit-Reset' } # a hash pointing at the response header containing the timestamp when the quota will reset
888
- }
883
+ track: true,
884
+ break: '80%',
885
+ provider: 'local.ch',
886
+ limit: { header: 'Rate-Limit-Limit' },
887
+ remaining: { header: 'Rate-Limit-Remaining' },
888
+ expires: { header: 'Rate-Limit-Reset' }
889
+ }
889
890
  }
890
891
 
891
892
  LHC.get('http://local.ch', options)
@@ -894,6 +895,16 @@ LHC.get('http://local.ch', options)
894
895
  LHC.get('http://local.ch', options)
895
896
  # raises LHC::Throttle::OutOfQuota: Reached predefined quota for local.ch
896
897
  ```
898
+ **Options Description**
899
+ * `track`: enables tracking of current limit/remaining requests of rate-limiting
900
+ * `break`: 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)
901
+ * `provider`: name of the provider under which throttling tracking is aggregated,
902
+ * `limit`: either a hard-coded integer, or a hash pointing at the response header containing the limit value
903
+ * `remaining`:
904
+ * a hash pointing at the response header containing the current amount of remaining requests
905
+ * a proc that receives the response as argument and returns the current amount
906
+ of remaining requests
907
+ * `expires`: a hash pointing at the response header containing the timestamp when the quota will reset
897
908
 
898
909
  #### Zipkin
899
910
 
@@ -21,8 +21,7 @@ class LHC::Throttle < LHC::Interceptor
21
21
 
22
22
  def after_response
23
23
  options = response.request.options.dig(:throttle)
24
- return unless options
25
- return unless options.dig(:track)
24
+ return unless throttle?(options)
26
25
  self.class.track ||= {}
27
26
  self.class.track[options.dig(:provider)] = {
28
27
  limit: limit(options: options[:limit], response: response),
@@ -33,6 +32,10 @@ class LHC::Throttle < LHC::Interceptor
33
32
 
34
33
  private
35
34
 
35
+ def throttle?(options)
36
+ [options&.dig(:track), response.headers].none?(&:blank?)
37
+ end
38
+
36
39
  def break_when_quota_reached!
37
40
  options = request.options.dig(:throttle)
38
41
  track = (self.class.track || {}).dig(options[:provider])
@@ -46,36 +49,37 @@ class LHC::Throttle < LHC::Interceptor
46
49
  end
47
50
 
48
51
  def limit(options:, response:)
49
- @limit ||= begin
50
- if options.is_a?(Integer)
51
- options
52
- elsif options.is_a?(Hash) && options[:header] && response.headers.present?
53
- response.headers[options[:header]]&.to_i
52
+ @limit ||=
53
+ begin
54
+ if options.is_a?(Integer)
55
+ options
56
+ elsif options.is_a?(Hash) && options[:header]
57
+ response.headers[options[:header]]&.to_i
58
+ end
54
59
  end
55
- end
56
60
  end
57
61
 
58
62
  def remaining(options:, response:)
59
- @remaining ||= begin
60
- if options.is_a?(Hash) && options[:header] && response.headers.present?
61
- response.headers[options[:header]]&.to_i
63
+ @remaining ||=
64
+ begin
65
+ if options.is_a?(Proc)
66
+ options.call(response)
67
+ elsif options.is_a?(Hash) && options[:header]
68
+ response.headers[options[:header]]&.to_i
69
+ end
62
70
  end
63
- end
64
71
  end
65
72
 
66
73
  def expires(options:, response:)
67
- @expires ||= begin
68
- if options.is_a?(Hash) && options[:header] && response.headers.present?
69
- convert_expires(response.headers[options[:header]]&.to_i)
70
- else
71
- convert_expires(options)
72
- end
73
- end
74
+ @expires ||= convert_expires(read_expire_option(options, response))
75
+ end
76
+
77
+ def read_expire_option(options, response)
78
+ (options.is_a?(Hash) && options[:header]) ? response.headers[options[:header]] : options
74
79
  end
75
80
 
76
81
  def convert_expires(value)
77
- if value.is_a?(Integer)
78
- Time.zone.at(value).to_datetime
79
- end
82
+ return Time.parse(value) if value.match(/GMT/)
83
+ Time.zone.at(value.to_i).to_datetime if value.present?
80
84
  end
81
85
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LHC
4
- VERSION ||= '12.1.3'
4
+ VERSION ||= '12.2.0'
5
5
  end
@@ -3,66 +3,64 @@
3
3
  require 'rails_helper'
4
4
 
5
5
  describe LHC::Throttle do
6
+ let(:options_break) { false }
7
+ let(:options_expires) { { header: 'reset' } }
8
+ let(:options_limit) { { header: 'limit' } }
9
+ let(:options_remaining) { { header: 'remaining' } }
6
10
  let(:provider) { 'local.ch' }
7
- let(:limit) { 10000 }
8
- let(:remaining) { 1900 }
11
+ let(:quota_limit) { 10_000 }
12
+ let(:quota_remaining) { 1900 }
13
+ let(:quota_reset) { (Time.zone.now + 1.hour).to_i }
9
14
  let(:options) do
10
15
  {
11
16
  throttle: {
12
17
  provider: provider,
13
18
  track: true,
14
- limit: limit_options,
15
- remaining: { header: 'Rate-Limit-Remaining' },
16
- expires: { header: 'Rate-Limit-Reset' },
17
- break: break_option
19
+ limit: options_limit,
20
+ remaining: options_remaining,
21
+ expires: options_expires,
22
+ break: options_break
18
23
  }
19
24
  }
20
25
  end
21
- let(:limit_options) { { header: 'Rate-Limit-Limit' } }
22
- let(:break_option) { false }
23
- let(:expires_in) { (Time.zone.now + 1.hour).to_i }
24
26
 
25
27
  before(:each) do
26
28
  LHC::Throttle.track = nil
27
29
  LHC.config.interceptors = [LHC::Throttle]
28
30
 
29
- stub_request(:get, 'http://local.ch')
30
- .to_return(
31
- headers: {
32
- 'Rate-Limit-Limit' => limit,
33
- 'Rate-Limit-Remaining' => remaining,
34
- 'Rate-Limit-Reset' => expires_in
35
- }
36
- )
31
+ stub_request(:get, 'http://local.ch').to_return(
32
+ headers: { 'limit' => quota_limit, 'remaining' => quota_remaining, 'reset' => quota_reset }
33
+ )
37
34
  end
38
35
 
39
36
  it 'tracks the request limits based on response data' do
40
37
  LHC.get('http://local.ch', options)
41
- expect(LHC::Throttle.track[provider][:limit]).to eq limit
42
- expect(LHC::Throttle.track[provider][:remaining]).to eq remaining
38
+ expect(LHC::Throttle.track[provider][:limit]).to eq quota_limit
39
+ expect(LHC::Throttle.track[provider][:remaining]).to eq quota_remaining
43
40
  end
44
41
 
45
42
  context 'fix predefined integer for limit' do
46
- let(:limit_options) { 1000 }
43
+ let(:options_limit) { 1000 }
47
44
 
48
45
  it 'tracks the limit based on initialy provided data' do
49
46
  LHC.get('http://local.ch', options)
50
- expect(LHC::Throttle.track[provider][:limit]).to eq limit_options
47
+ expect(LHC::Throttle.track[provider][:limit]).to eq options_limit
51
48
  end
52
49
  end
53
50
 
54
51
  context 'breaks' do
55
- let(:break_option) { '80%' }
52
+ let(:options_break) { '80%' }
56
53
 
57
54
  it 'hit the breaks if throttling quota is reached' do
58
55
  LHC.get('http://local.ch', options)
59
- expect(-> {
60
- LHC.get('http://local.ch', options)
61
- }).to raise_error(LHC::Throttle::OutOfQuota, 'Reached predefined quota for local.ch')
56
+ expect { LHC.get('http://local.ch', options) }.to raise_error(
57
+ LHC::Throttle::OutOfQuota,
58
+ 'Reached predefined quota for local.ch'
59
+ )
62
60
  end
63
61
 
64
62
  context 'still within quota' do
65
- let(:break_option) { '90%' }
63
+ let(:options_break) { '90%' }
66
64
 
67
65
  it 'does not hit the breaks' do
68
66
  LHC.get('http://local.ch', options)
@@ -72,17 +70,14 @@ describe LHC::Throttle do
72
70
  end
73
71
 
74
72
  context 'no response headers' do
75
- before do
76
- stub_request(:get, 'http://local.ch')
77
- .to_return(status: 200)
78
- end
73
+ before { stub_request(:get, 'http://local.ch').to_return(status: 200) }
79
74
 
80
75
  it 'does not raise an exception' do
81
76
  LHC.get('http://local.ch', options)
82
77
  end
83
78
 
84
79
  context 'no remaining tracked, but break enabled' do
85
- let(:break_option) { '90%' }
80
+ let(:options_break) { '90%' }
86
81
 
87
82
  it 'does not fail if a remaining was not tracked yet' do
88
83
  LHC.get('http://local.ch', options)
@@ -92,15 +87,76 @@ describe LHC::Throttle do
92
87
  end
93
88
 
94
89
  context 'expires' do
95
- let(:break_option) { '80%' }
90
+ let(:options_break) { '80%' }
96
91
 
97
92
  it 'attempts another request if the quota expired' do
98
93
  LHC.get('http://local.ch', options)
99
- expect(-> {
100
- LHC.get('http://local.ch', options)
101
- }).to raise_error(LHC::Throttle::OutOfQuota, 'Reached predefined quota for local.ch')
94
+ expect { LHC.get('http://local.ch', options) }.to raise_error(
95
+ LHC::Throttle::OutOfQuota,
96
+ 'Reached predefined quota for local.ch'
97
+ )
102
98
  Timecop.travel(Time.zone.now + 2.hours)
103
99
  LHC.get('http://local.ch', options)
104
100
  end
105
101
  end
102
+
103
+ describe 'calculate "remaining" in proc' do
104
+ let(:quota_current) { 8100 }
105
+ let(:options_remaining) do
106
+ ->(response) { (response.headers['limit']).to_i - (response.headers['current']).to_i }
107
+ end
108
+
109
+ before(:each) do
110
+ stub_request(:get, 'http://local.ch').to_return(
111
+ headers: { 'limit' => quota_limit, 'current' => quota_current, 'reset' => quota_reset }
112
+ )
113
+ LHC.get('http://local.ch', options)
114
+ end
115
+
116
+ context 'breaks' do
117
+ let(:options_break) { '80%' }
118
+
119
+ it 'hit the breaks if throttling quota is reached' do
120
+ expect { LHC.get('http://local.ch', options) }.to raise_error(
121
+ LHC::Throttle::OutOfQuota,
122
+ 'Reached predefined quota for local.ch'
123
+ )
124
+ end
125
+
126
+ context 'still within quota' do
127
+ let(:options_break) { '90%' }
128
+
129
+ it 'does not hit the breaks' do
130
+ LHC.get('http://local.ch', options)
131
+ LHC.get('http://local.ch', options)
132
+ end
133
+ end
134
+ end
135
+ end
136
+
137
+ describe 'parsing reset time given in prose' do
138
+ let(:quota_reset) { (Time.zone.now + 1.day).strftime('%A, %B %d, %Y 12:00:00 AM GMT').to_s }
139
+
140
+ before { LHC.get('http://local.ch', options) }
141
+
142
+ context 'breaks' do
143
+ let(:options_break) { '80%' }
144
+
145
+ it 'hit the breaks if throttling quota is reached' do
146
+ expect { LHC.get('http://local.ch', options) }.to raise_error(
147
+ LHC::Throttle::OutOfQuota,
148
+ 'Reached predefined quota for local.ch'
149
+ )
150
+ end
151
+
152
+ context 'still within quota' do
153
+ let(:options_break) { '90%' }
154
+
155
+ it 'does not hit the breaks' do
156
+ LHC.get('http://local.ch', options)
157
+ LHC.get('http://local.ch', options)
158
+ end
159
+ end
160
+ end
161
+ end
106
162
  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: 12.1.3
4
+ version: 12.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - https://github.com/local-ch/lhc/contributors
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-20 00:00:00.000000000 Z
11
+ date: 2020-08-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -389,7 +389,7 @@ homepage: https://github.com/local-ch/lhc
389
389
  licenses:
390
390
  - GPL-3.0
391
391
  metadata: {}
392
- post_install_message:
392
+ post_install_message:
393
393
  rdoc_options: []
394
394
  require_paths:
395
395
  - lib
@@ -405,8 +405,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
405
405
  version: '0'
406
406
  requirements:
407
407
  - Ruby >= 2.0.0
408
- rubygems_version: 3.0.6
409
- signing_key:
408
+ rubygems_version: 3.0.3
409
+ signing_key:
410
410
  specification_version: 4
411
411
  summary: Advanced HTTP Client for Ruby, fueled with interceptors
412
412
  test_files: