itrp-client 1.1.4 → 1.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +12 -11
- data/LICENSE.txt +1 -1
- data/README.md +3 -2
- data/lib/itrp/client.rb +29 -9
- data/lib/itrp/client/response.rb +4 -0
- data/lib/itrp/client/version.rb +1 -1
- data/spec/lib/itrp/client_spec.rb +36 -4
- data/spec/lib/itrp/response_spec.rb +4 -4
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8703ef24c263256f975b6bcc841ea70b2b9dd9fb
|
4
|
+
data.tar.gz: b7d384aab10d84b2b1cab2e33fff2975fbf69a40
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 66205dde73494d6e6a95a45a316bf80522f5a61106735b315ad8d9636a8bd3d2fd6a375228476f42ad1b154c40933d3a6e24538afd20a1fea98e6ba46e2df40e
|
7
|
+
data.tar.gz: 213c2468c8be27d03b53f318a3b6aa2cf7e872a05be9042128b88db7bd1f90327a8d91170ac0c23227720a7fe99e51b35668cae647d924872dd32439a7c8d085
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
itrp-client (1.1.
|
4
|
+
itrp-client (1.1.5)
|
5
5
|
activesupport
|
6
6
|
gem_config
|
7
7
|
mime-types
|
@@ -9,26 +9,27 @@ PATH
|
|
9
9
|
GEM
|
10
10
|
remote: https://rubygems.org/
|
11
11
|
specs:
|
12
|
-
activesupport (
|
13
|
-
|
14
|
-
|
12
|
+
activesupport (5.2.2)
|
13
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
14
|
+
i18n (>= 0.7, < 2)
|
15
15
|
minitest (~> 5.1)
|
16
|
-
thread_safe (~> 0.3, >= 0.3.4)
|
17
16
|
tzinfo (~> 1.1)
|
18
17
|
addressable (2.5.0)
|
19
18
|
public_suffix (~> 2.0, >= 2.0.2)
|
19
|
+
concurrent-ruby (1.1.4)
|
20
20
|
crack (0.4.3)
|
21
21
|
safe_yaml (~> 1.0.0)
|
22
22
|
diff-lcs (1.2.5)
|
23
23
|
docile (1.1.5)
|
24
24
|
gem_config (0.3.1)
|
25
25
|
hashdiff (0.3.1)
|
26
|
-
i18n (
|
26
|
+
i18n (1.5.1)
|
27
|
+
concurrent-ruby (~> 1.0)
|
27
28
|
json (1.8.3)
|
28
|
-
mime-types (3.
|
29
|
+
mime-types (3.2.2)
|
29
30
|
mime-types-data (~> 3.2015)
|
30
|
-
mime-types-data (3.
|
31
|
-
minitest (5.
|
31
|
+
mime-types-data (3.2018.0812)
|
32
|
+
minitest (5.11.3)
|
32
33
|
public_suffix (2.0.4)
|
33
34
|
rake (11.3.0)
|
34
35
|
rspec (3.3.0)
|
@@ -50,8 +51,8 @@ GEM
|
|
50
51
|
json (>= 1.8, < 3)
|
51
52
|
simplecov-html (~> 0.10.0)
|
52
53
|
simplecov-html (0.10.0)
|
53
|
-
thread_safe (0.3.
|
54
|
-
tzinfo (1.2.
|
54
|
+
thread_safe (0.3.6)
|
55
|
+
tzinfo (1.2.5)
|
55
56
|
thread_safe (~> 0.1)
|
56
57
|
webmock (2.1.0)
|
57
58
|
addressable (>= 2.3.6)
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -40,9 +40,10 @@ All options available:
|
|
40
40
|
* _max_retry_time_: maximum nr of seconds to wait for server to respond (default = 5400 = 1.5 hours)<br/>
|
41
41
|
The sleep time between retries starts at 2 seconds and doubles after each retry, i.e.
|
42
42
|
2, 6, 18, 54, 162, 486, 1458, 4374, 13122, ... seconds.<br/>
|
43
|
-
|
43
|
+
Set to 0 to prevent retries.
|
44
44
|
* _read_timeout_: [HTTP read timeout](http://ruby-doc.org/stdlib-2.0.0/libdoc/net/http/rdoc/Net/HTTP.html#method-i-read_timeout-3D) in seconds (default = 25)
|
45
|
-
* _block_at_rate_limit_: Set to `true` to block the request until the [rate limit](http://developer.itrp.com/v1/#rate-limiting) is lifted, default: `false
|
45
|
+
* _block_at_rate_limit_: Set to `true` to block the request until the [rate limit](http://developer.itrp.com/v1/#rate-limiting) is lifted, default: `false`<br/>
|
46
|
+
The `Retry-After` header is used to compute when the retry should be performed. If that moment is later than the _max_retry_time_ the request will not be blocked and the throttled response is returned.
|
46
47
|
* _proxy_host_: Define in case HTTP traffic needs to go through a proxy
|
47
48
|
* _proxy_port_: Port of the proxy, defaults to 8080
|
48
49
|
* _proxy_user_: Proxy user
|
data/lib/itrp/client.rb
CHANGED
@@ -298,14 +298,25 @@ module Itrp
|
|
298
298
|
end
|
299
299
|
|
300
300
|
module SendWithRateLimitBlock
|
301
|
-
# Wraps the _send method with retries when the server does not
|
301
|
+
# Wraps the _send method with retries when the server does not respond, see +initialize+ option +:rate_limit_block+
|
302
302
|
def _send(request, domain = @domain, port = @port, ssl = @ssl)
|
303
303
|
return super(request, domain, port, ssl) unless option(:block_at_rate_limit)
|
304
304
|
now = Time.now
|
305
|
+
timed_out = false
|
306
|
+
# respect the max_retry_time with fallback to max 1 hour and 1 minute wait time
|
307
|
+
max_retry_time = option(:max_retry_time) > 0 ? option(:max_retry_time) : 3660
|
305
308
|
begin
|
306
309
|
_response = super(request, domain, port, ssl)
|
307
|
-
|
308
|
-
|
310
|
+
if _response.throttled?
|
311
|
+
retry_after = _response.retry_after == 0 ? 300 : [_response.retry_after, 2].max
|
312
|
+
if (Time.now - now + retry_after) < max_retry_time
|
313
|
+
@logger.warn { "Request throttled, trying again in #{retry_after} seconds: #{_response.message}" }
|
314
|
+
sleep(retry_after)
|
315
|
+
else
|
316
|
+
timed_out = true
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end while _response.throttled? && !timed_out
|
309
320
|
_response
|
310
321
|
end
|
311
322
|
end
|
@@ -314,15 +325,24 @@ module Itrp
|
|
314
325
|
module SendWithRetries
|
315
326
|
# Wraps the _send method with retries when the server does not respond, see +initialize+ option +:retries+
|
316
327
|
def _send(request, domain = @domain, port = @port, ssl = @ssl)
|
328
|
+
return super(request, domain, port, ssl) unless option(:max_retry_time) > 0
|
317
329
|
retries = 0
|
318
|
-
sleep_time =
|
319
|
-
|
330
|
+
sleep_time = 1
|
331
|
+
now = Time.now
|
332
|
+
timed_out = false
|
320
333
|
begin
|
321
334
|
_response = super(request, domain, port, ssl)
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
335
|
+
# throttling is handled separately
|
336
|
+
if !_response.success? && !_response.throttled?
|
337
|
+
sleep_time *= 2
|
338
|
+
if (Time.now - now + sleep_time) < option(:max_retry_time)
|
339
|
+
@logger.warn { "Request failed, retry ##{retries += 1} in #{sleep_time} seconds: #{_response.message}" }
|
340
|
+
sleep(sleep_time)
|
341
|
+
else
|
342
|
+
timed_out = true
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end while !_response.success? && !_response.throttled? && !timed_out
|
326
346
|
_response
|
327
347
|
end
|
328
348
|
end
|
data/lib/itrp/client/response.rb
CHANGED
@@ -111,6 +111,10 @@ module Itrp
|
|
111
111
|
!!(@response.code.to_s == '429' || (message && message =~ /Too Many Requests/))
|
112
112
|
end
|
113
113
|
|
114
|
+
def retry_after
|
115
|
+
@current_page ||= @response.header['Retry-After'].to_i
|
116
|
+
end
|
117
|
+
|
114
118
|
def to_s
|
115
119
|
valid? ? json.to_s : message
|
116
120
|
end
|
data/lib/itrp/client/version.rb
CHANGED
@@ -281,8 +281,9 @@ describe Itrp::Client do
|
|
281
281
|
|
282
282
|
context 'import' do
|
283
283
|
before(:each) do
|
284
|
+
csv_mime_type = ['text/csv', 'text/comma-separated-values'].detect{|t| MIME::Types[t].any?} # which mime type is used depends on version of mime-types gem
|
284
285
|
@client = Itrp::Client.new(api_token: 'secret', max_retry_time: -1)
|
285
|
-
@multi_part_body = "--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\npeople\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"file\"; filename=\"#{@fixture_dir}/people.csv\"\r\nContent-Type:
|
286
|
+
@multi_part_body = "--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\npeople\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"file\"; filename=\"#{@fixture_dir}/people.csv\"\r\nContent-Type: #{csv_mime_type}\r\n\r\nPrimary Email,Name\nchess.cole@example.com,Chess Cole\ned.turner@example.com,Ed Turner\r\n--0123456789ABLEWASIEREISAWELBA9876543210--"
|
286
287
|
@multi_part_headers = {'Accept'=>'*/*', 'Content-Type'=>'multipart/form-data; boundary=0123456789ABLEWASIEREISAWELBA9876543210', 'User-Agent'=>'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/523.10.6 (KHTML, like Gecko) Version/3.0.4 Safari/523.10.6'}
|
287
288
|
|
288
289
|
@import_queued_response = {body: {state: 'queued'}.to_json}
|
@@ -476,10 +477,11 @@ describe Itrp::Client do
|
|
476
477
|
|
477
478
|
it 'should not retry 4 times when max_retry_time = 16' do
|
478
479
|
stub = stub_request(:get, 'https://api.itrp.com/v1/me').with(basic_auth: ['secret', 'x']).to_raise(StandardError.new('network error'))
|
479
|
-
[2,4,8
|
480
|
+
[2,4,8].each_with_index do |secs, i|
|
480
481
|
expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
|
481
482
|
expect_log("Request failed, retry ##{i+1} in #{secs} seconds: 500: No Response from Server - network error for 'api.itrp.com:443/v1/me'", :warn)
|
482
483
|
end
|
484
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
|
483
485
|
|
484
486
|
client = Itrp::Client.new(api_token: 'secret', max_retry_time: 16)
|
485
487
|
allow(client).to receive(:sleep)
|
@@ -518,10 +520,40 @@ describe Itrp::Client do
|
|
518
520
|
expect(response.message).to eq('429: Too Many Requests')
|
519
521
|
end
|
520
522
|
|
521
|
-
it 'should block on rate limit when block_at_rate_limit is true' do
|
523
|
+
it 'should block on rate limit (max 300 seconds) when block_at_rate_limit is true' do
|
522
524
|
stub = stub_request(:get, 'https://api.itrp.com/v1/me').with(basic_auth: ['secret', 'x']).to_return(status: 429, body: {message: 'Too Many Requests'}.to_json).then.to_return(body: {name: 'my name'}.to_json)
|
523
525
|
expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
|
524
|
-
expect_log('Request throttled, trying again in
|
526
|
+
expect_log('Request throttled, trying again in 300 seconds: 429: Too Many Requests', :warn)
|
527
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
|
528
|
+
expect_log(%(Response:\n{\n "name": "my name"\n}), :debug )
|
529
|
+
|
530
|
+
client = Itrp::Client.new(api_token: 'secret', block_at_rate_limit: true)
|
531
|
+
allow(client).to receive(:sleep)
|
532
|
+
response = client.get('me')
|
533
|
+
expect(stub).to have_been_requested.times(2)
|
534
|
+
expect(response.valid?).to be_truthy
|
535
|
+
expect(response[:name]).to eq('my name')
|
536
|
+
end
|
537
|
+
|
538
|
+
it 'should block on rate limit using Retry-After when block_at_rate_limit is true' do
|
539
|
+
stub = stub_request(:get, 'https://api.itrp.com/v1/me').with(basic_auth: ['secret', 'x']).to_return(status: 429, body: {message: 'Too Many Requests'}.to_json, headers: {'Retry-After' => '20'}).then.to_return(body: {name: 'my name'}.to_json)
|
540
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
|
541
|
+
expect_log('Request throttled, trying again in 20 seconds: 429: Too Many Requests', :warn)
|
542
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
|
543
|
+
expect_log(%(Response:\n{\n "name": "my name"\n}), :debug )
|
544
|
+
|
545
|
+
client = Itrp::Client.new(api_token: 'secret', block_at_rate_limit: true)
|
546
|
+
allow(client).to receive(:sleep)
|
547
|
+
response = client.get('me')
|
548
|
+
expect(stub).to have_been_requested.times(2)
|
549
|
+
expect(response.valid?).to be_truthy
|
550
|
+
expect(response[:name]).to eq('my name')
|
551
|
+
end
|
552
|
+
|
553
|
+
it 'should block on rate limit using Retry-After with minimum of 2 seconds when block_at_rate_limit is true' do
|
554
|
+
stub = stub_request(:get, 'https://api.itrp.com/v1/me').with(basic_auth: ['secret', 'x']).to_return(status: 429, body: {message: 'Too Many Requests'}.to_json, headers: {'Retry-After' => '1'}).then.to_return(body: {name: 'my name'}.to_json)
|
555
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
|
556
|
+
expect_log('Request throttled, trying again in 2 seconds: 429: Too Many Requests', :warn)
|
525
557
|
expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
|
526
558
|
expect_log(%(Response:\n{\n "name": "my name"\n}), :debug )
|
527
559
|
|
@@ -93,10 +93,10 @@ describe Itrp::Response do
|
|
93
93
|
stub_request(:get, 'https://api.itrp.com/v1/organizations').with(basic_auth: ['secret', 'x']).to_return(body: '==$$!invalid')
|
94
94
|
response = @client.get('organizations')
|
95
95
|
|
96
|
-
message = "
|
97
|
-
expect(response.json[:message]).to
|
98
|
-
expect(response.json['message']).to
|
99
|
-
expect(response.message).to
|
96
|
+
message = "unexpected token at '==$$!invalid' for:\n#{response.body}"
|
97
|
+
expect(response.json[:message]).to include(message)
|
98
|
+
expect(response.json['message']).to include(message)
|
99
|
+
expect(response.message).to include(message)
|
100
100
|
end
|
101
101
|
|
102
102
|
it 'should have a blank message when single record is succesfully retrieved' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: itrp-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ITRP
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-01-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gem_config
|
@@ -171,7 +171,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
171
171
|
version: '0'
|
172
172
|
requirements: []
|
173
173
|
rubyforge_project:
|
174
|
-
rubygems_version: 2.
|
174
|
+
rubygems_version: 2.5.1
|
175
175
|
signing_key:
|
176
176
|
specification_version: 4
|
177
177
|
summary: Client for accessing the ITRP REST API
|