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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3191ba404199a9c372100cfbaf4d098b4bf421ea
4
- data.tar.gz: 6af16dbe18fe503f3186cb8ed1110bf8e00b518c
3
+ metadata.gz: 8703ef24c263256f975b6bcc841ea70b2b9dd9fb
4
+ data.tar.gz: b7d384aab10d84b2b1cab2e33fff2975fbf69a40
5
5
  SHA512:
6
- metadata.gz: 722a14bab6be83a23c483d2dece405bf680901dbdff3f7389211e7dcea9f6af2028cdc0f07510e091f045ce8909fa7fbb432e486436307e10481ad96150a583e
7
- data.tar.gz: fb8f9090c5ce0c3da2c81e4d8431521a132f448c94a493ebed8ff459f527eecf0b5173341ad5e36a949f2f5fe3438d170d5f5790c0c7d048905cbbbb64bd029a
6
+ metadata.gz: 66205dde73494d6e6a95a45a316bf80522f5a61106735b315ad8d9636a8bd3d2fd6a375228476f42ad1b154c40933d3a6e24538afd20a1fea98e6ba46e2df40e
7
+ data.tar.gz: 213c2468c8be27d03b53f318a3b6aa2cf7e872a05be9042128b88db7bd1f90327a8d91170ac0c23227720a7fe99e51b35668cae647d924872dd32439a7c8d085
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- itrp-client (1.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 (4.2.7.1)
13
- i18n (~> 0.7)
14
- json (~> 1.7, >= 1.7.7)
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 (0.7.0)
26
+ i18n (1.5.1)
27
+ concurrent-ruby (~> 1.0)
27
28
  json (1.8.3)
28
- mime-types (3.1)
29
+ mime-types (3.2.2)
29
30
  mime-types-data (~> 3.2015)
30
- mime-types-data (3.2016.0521)
31
- minitest (5.9.1)
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.5)
54
- tzinfo (1.2.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)
@@ -1,4 +1,4 @@
1
- Copyright (c) 2017 ITRP Inc.
1
+ Copyright (c) 2019 ITRP Inc.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
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
- One retry will always be performed unless you set the value to -1.
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
@@ -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 responsd, see +initialize+ option +:rate_limit_block+
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
- @logger.warn { "Request throttled, trying again in 5 minutes: #{_response.message}" } and sleep(300) if _response.throttled?
308
- end while _response.throttled? && (Time.now - now) < 3660 # max 1 hour and 1 minute
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 = 2
319
- total_retry_time = 0
330
+ sleep_time = 1
331
+ now = Time.now
332
+ timed_out = false
320
333
  begin
321
334
  _response = super(request, domain, port, ssl)
322
- @logger.warn { "Request failed, retry ##{retries += 1} in #{sleep_time} seconds: #{_response.message}" } and sleep(sleep_time) if (_response.raw.code.to_s != '204' && _response.empty?) && option(:max_retry_time) > 0
323
- total_retry_time += sleep_time
324
- sleep_time *= 2
325
- end while (_response.raw.code.to_s != '204' && _response.empty?) && total_retry_time < option(:max_retry_time)
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  module Itrp
2
2
  class Client
3
- VERSION = '1.1.4'
3
+ VERSION = '1.1.5'
4
4
  end
5
5
  end
@@ -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: text/comma-separated-values\r\n\r\nPrimary Email,Name\nchess.cole@example.com,Chess Cole\ned.turner@example.com,Ed Turner\r\n--0123456789ABLEWASIEREISAWELBA9876543210--"
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,16].each_with_index do |secs, i|
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 5 minutes: 429: Too Many Requests', :warn)
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 = "Invalid JSON - 757: unexpected token at '==$$!invalid' for:\n#{response.body}"
97
- expect(response.json[:message]).to eq(message)
98
- expect(response.json['message']).to eq(message)
99
- expect(response.message).to eq(message)
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
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: 2018-11-21 00:00:00.000000000 Z
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.4.6
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