lhc 12.1.0 → 12.2.1

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: 20beb1550ea6286010ac46dd969bcdd8cdcc4dbb968d5ebbfdf3b591eb118fb1
4
- data.tar.gz: c219d6d5bfb4b7a00e7c2d2273162d2cf3b1d106742dc803e9abfaa0987c0fae
3
+ metadata.gz: 41b0c46c3ced081c73e722058eace5a163bb5f84516fb4c6a6d074800ade7d7d
4
+ data.tar.gz: e4f37fdf66fa76cc137ec6142bc445468a904f0cbaea2578850c6984478f4228
5
5
  SHA512:
6
- metadata.gz: 609167a963677ba5191a5579a43b9740c08b16be259041da52cb5459e5e5c0eb481361a4115323b6bfa1c444b068669760dc530a5cbaea35af747f63650cea62
7
- data.tar.gz: 47e4cbc20c4ab60befb908c719b0c7625a982325b7b756c48ceedec11f649c082c0715a26b0e8f76ce6124b1ef5dd2b0df475e1c13cfef568147b989a317dbe7
6
+ metadata.gz: b94b128def1cea71f871c2a14787a52e1ca76755b849fb4504b044f99c9ce4d35ce5a89b8d54621f539f83d51dfa72047b2538adc33f8ab4713f030dcefd31db
7
+ data.tar.gz: e1354bbb77a671ef3d98ef33755ada6c4d32373f34609fc858c57744f4c4aed9543373ebf4f837d0ce4bc8c8f8962bcdef60cc2f0ed2045b1dac00f4139ce054
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
 
@@ -80,7 +80,7 @@ class LHC::Auth < LHC::Interceptor
80
80
  end
81
81
 
82
82
  def auth_options
83
- @auth_options ||= request.options[:auth].dup || {}
83
+ request.options[:auth] || {}
84
84
  end
85
85
 
86
86
  def configuration_correct?
@@ -3,7 +3,6 @@
3
3
  require 'active_support/duration'
4
4
 
5
5
  class LHC::Throttle < LHC::Interceptor
6
-
7
6
  class OutOfQuota < StandardError
8
7
  end
9
8
 
@@ -21,8 +20,7 @@ class LHC::Throttle < LHC::Interceptor
21
20
 
22
21
  def after_response
23
22
  options = response.request.options.dig(:throttle)
24
- return unless options
25
- return unless options.dig(:track)
23
+ return unless throttle?(options)
26
24
  self.class.track ||= {}
27
25
  self.class.track[options.dig(:provider)] = {
28
26
  limit: limit(options: options[:limit], response: response),
@@ -33,6 +31,10 @@ class LHC::Throttle < LHC::Interceptor
33
31
 
34
32
  private
35
33
 
34
+ def throttle?(options)
35
+ [options&.dig(:track), response.headers].none?(&:blank?)
36
+ end
37
+
36
38
  def break_when_quota_reached!
37
39
  options = request.options.dig(:throttle)
38
40
  track = (self.class.track || {}).dig(options[:provider])
@@ -46,36 +48,38 @@ class LHC::Throttle < LHC::Interceptor
46
48
  end
47
49
 
48
50
  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
51
+ @limit ||=
52
+ begin
53
+ if options.is_a?(Integer)
54
+ options
55
+ elsif options.is_a?(Hash) && options[:header]
56
+ response.headers[options[:header]]&.to_i
57
+ end
54
58
  end
55
- end
56
59
  end
57
60
 
58
61
  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
62
+ @remaining ||=
63
+ begin
64
+ if options.is_a?(Proc)
65
+ options.call(response)
66
+ elsif options.is_a?(Hash) && options[:header]
67
+ response.headers[options[:header]]&.to_i
68
+ end
62
69
  end
63
- end
64
70
  end
65
71
 
66
72
  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
73
+ @expires ||= convert_expires(read_expire_option(options, response))
74
+ end
75
+
76
+ def read_expire_option(options, response)
77
+ (options.is_a?(Hash) && options[:header]) ? response.headers[options[:header]] : options
74
78
  end
75
79
 
76
80
  def convert_expires(value)
77
- if value.is_a?(Integer)
78
- Time.zone.at(value).to_datetime
79
- end
81
+ return if value.blank?
82
+ return Time.parse(value) if value.match(/GMT/)
83
+ Time.zone.at(value.to_i).to_datetime
80
84
  end
81
85
  end
@@ -12,9 +12,9 @@ class LHC::Response::Data
12
12
  @data = data
13
13
 
14
14
  if as_json.is_a?(Hash)
15
- @base = LHC::Response::Data::Item.new(response, data: data)
15
+ @base = LHC::Response::Data::Item.new(@response, data: data)
16
16
  elsif as_json.is_a?(Array)
17
- @base = LHC::Response::Data::Collection.new(response, data: data)
17
+ @base = LHC::Response::Data::Collection.new(@response, data: data)
18
18
  end
19
19
  end
20
20
 
@@ -4,7 +4,7 @@
4
4
  # but made accssible in the ruby world
5
5
  module LHC::Response::Data::Base
6
6
  def as_json
7
- @json ||= (@data || response.format.as_json(response.body))
7
+ @json ||= (@data || @response.format.as_json(@response.body))
8
8
  end
9
9
 
10
10
  def as_open_struct
@@ -12,11 +12,7 @@ module LHC::Response::Data::Base
12
12
  if @data
13
13
  JSON.parse(@data.to_json, object_class: OpenStruct)
14
14
  else
15
- response.format.as_open_struct(response.body)
15
+ @response.format.as_open_struct(@response.body)
16
16
  end
17
17
  end
18
-
19
- private
20
-
21
- attr_reader :response
22
18
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LHC
4
- VERSION ||= '12.1.0'
4
+ VERSION ||= '12.2.1'
5
5
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe LHC::Auth do
6
+ before(:each) do
7
+ class AuthPrepInterceptor < LHC::Interceptor
8
+
9
+ def before_request
10
+ request.options[:auth] = { bearer: 'sometoken' }
11
+ end
12
+ end
13
+
14
+ LHC.config.interceptors = [AuthPrepInterceptor, LHC::Auth]
15
+ end
16
+
17
+ after do
18
+ LHC.config.reset
19
+ end
20
+
21
+ it 'does not use instance variables internally so that other interceptors can still change auth options' do
22
+ stub_request(:get, "http://local.ch/")
23
+ .with(headers: { 'Authorization' => 'Bearer sometoken' })
24
+ .to_return(status: 200)
25
+ LHC.get('http://local.ch')
26
+ end
27
+ 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,89 @@ 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
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ describe 'parsing reset time given in prose' do
137
+ let(:quota_reset) { (Time.zone.now + 1.day).strftime('%A, %B %d, %Y 12:00:00 AM GMT').to_s }
138
+
139
+ before { LHC.get('http://local.ch', options) }
140
+
141
+ context 'breaks' do
142
+ let(:options_break) { '80%' }
143
+
144
+ it 'hit the breaks if throttling quota is reached' do
145
+ expect { LHC.get('http://local.ch', options) }.to raise_error(
146
+ LHC::Throttle::OutOfQuota,
147
+ 'Reached predefined quota for local.ch'
148
+ )
149
+ end
150
+
151
+ context 'still within quota' do
152
+ let(:options_break) { '90%' }
153
+
154
+ it 'does not hit the breaks' do
155
+ LHC.get('http://local.ch', options)
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ context 'when value is empty' do
162
+ let(:quota_reset) { nil }
163
+
164
+ before do
165
+ stub_request(:get, 'http://local.ch').to_return(
166
+ headers: { 'limit' => quota_limit, 'remaining' => quota_remaining }
167
+ )
168
+ LHC.get('http://local.ch', options)
169
+ end
170
+
171
+ it 'still runs' do
172
+ LHC.get('http://local.ch', options)
173
+ end
174
+ end
106
175
  end
@@ -58,4 +58,28 @@ describe LHC::Response do
58
58
  end
59
59
  end
60
60
  end
61
+
62
+ context 'response data if responding error data contains a response' do
63
+ before do
64
+ stub_request(:get, "http://listings/")
65
+ .to_return(status: 404, body: {
66
+ meta: {
67
+ errors: [
68
+ { code: 2000, msg: 'I like to hide error messages (this is meta).' }
69
+ ]
70
+ },
71
+ response: 'why not?'
72
+ }.to_json)
73
+ end
74
+
75
+ it 'does not throw a stack level to deep issue when accessing data in a rescue context' do
76
+ begin
77
+ LHC.get('http://listings')
78
+ rescue LHC::Error => error
79
+ expect(
80
+ error.response.request.response.data.meta.errors.detect { |item| item.code == 2000 }.msg
81
+ ).to eq 'I like to hide error messages (this is meta).'
82
+ end
83
+ end
84
+ end
61
85
  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.0
4
+ version: 12.2.1
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-07-27 00:00:00.000000000 Z
11
+ date: 2020-08-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -327,6 +327,7 @@ files:
327
327
  - spec/interceptors/auth/bearer_spec.rb
328
328
  - spec/interceptors/auth/body_spec.rb
329
329
  - spec/interceptors/auth/long_basic_auth_credentials_spec.rb
330
+ - spec/interceptors/auth/no_instance_var_for_options_spec.rb
330
331
  - spec/interceptors/auth/reauthentication_configuration_spec.rb
331
332
  - spec/interceptors/auth/reauthentication_spec.rb
332
333
  - spec/interceptors/before_request_spec.rb
@@ -388,7 +389,7 @@ homepage: https://github.com/local-ch/lhc
388
389
  licenses:
389
390
  - GPL-3.0
390
391
  metadata: {}
391
- post_install_message:
392
+ post_install_message:
392
393
  rdoc_options: []
393
394
  require_paths:
394
395
  - lib
@@ -404,8 +405,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
404
405
  version: '0'
405
406
  requirements:
406
407
  - Ruby >= 2.0.0
407
- rubygems_version: 3.0.6
408
- signing_key:
408
+ rubygems_version: 3.0.3
409
+ signing_key:
409
410
  specification_version: 4
410
411
  summary: Advanced HTTP Client for Ruby, fueled with interceptors
411
412
  test_files:
@@ -479,6 +480,7 @@ test_files:
479
480
  - spec/interceptors/auth/bearer_spec.rb
480
481
  - spec/interceptors/auth/body_spec.rb
481
482
  - spec/interceptors/auth/long_basic_auth_credentials_spec.rb
483
+ - spec/interceptors/auth/no_instance_var_for_options_spec.rb
482
484
  - spec/interceptors/auth/reauthentication_configuration_spec.rb
483
485
  - spec/interceptors/auth/reauthentication_spec.rb
484
486
  - spec/interceptors/before_request_spec.rb