Skip to content

Commit 7296765

Browse files
authored
Handle rate limits (#354)
* Retry on RateLimitError after reset time * Make feature optional * Adding limit to number of retries after 429 * Minor change to retries limit variable * Corrected minor grammer error * Setting rate limit flag in init * Adding bundle updates to travis file * Adding bundle command to address travis failing issue
1 parent a06dc87 commit 7296765

File tree

7 files changed

+71
-4
lines changed

7 files changed

+71
-4
lines changed

.travis.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
language: ruby
22
sudo: false
3+
before_install:
4+
- gem install bundler
5+
- gem update --system
6+
- gem --version
37
rvm:
48
- 2.1.0
59
- 2.2.0

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
source "http://rubygems.org"
22

3+
gem 'webmock'
34
gemspec
45

56
group :development, :test do

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,12 @@ intercom.rate_limit_details
440440
#=> {:limit=>180, :remaining=>179, :reset_at=>2014-10-07 14:58:00 +0100}
441441
```
442442

443+
You can handle the rate limits yourself but a simple option is to use the handle_rate_limit flag.
444+
This will automatically catch the 429 rate limit exceeded error and wait until the reset time to retry.
445+
446+
```
447+
intercom = Intercom::Client.new(token: ENV['AT'], handle_rate_limit: true)
448+
```
443449

444450
### Pull Requests
445451

lib/intercom/client.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module Intercom
22
class MisconfiguredClientError < StandardError; end
33
class Client
44
include Options
5-
attr_reader :base_url, :rate_limit_details, :username_part, :password_part
5+
attr_reader :base_url, :rate_limit_details, :username_part, :password_part, :handle_rate_limit
66

77
class << self
88
def set_base_url(base_url)
@@ -14,7 +14,7 @@ def set_base_url(base_url)
1414
end
1515
end
1616

17-
def initialize(app_id: 'my_app_id', api_key: 'my_api_key', token: nil, base_url:'https://api.intercom.io')
17+
def initialize(app_id: 'my_app_id', api_key: 'my_api_key', token: nil, base_url:'https://api.intercom.io', handle_rate_limit: false)
1818
if token
1919
@username_part = token
2020
@password_part = ""
@@ -26,6 +26,7 @@ def initialize(app_id: 'my_app_id', api_key: 'my_api_key', token: nil, base_url:
2626

2727
@base_url = base_url
2828
@rate_limit_details = {}
29+
@handle_rate_limit = handle_rate_limit
2930
end
3031

3132
def admins
@@ -108,6 +109,7 @@ def validate_credentials!
108109
end
109110

110111
def execute_request(request)
112+
request.handle_rate_limit = handle_rate_limit
111113
request.execute(@base_url, username: @username_part, secret: @password_part)
112114
ensure
113115
@rate_limit_details = request.rate_limit_details

lib/intercom/request.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33

44
module Intercom
55
class Request
6-
attr_accessor :path, :net_http_method, :rate_limit_details
6+
attr_accessor :path, :net_http_method, :rate_limit_details, :handle_rate_limit
77

88
def initialize(path, net_http_method)
99
self.path = path
1010
self.net_http_method = net_http_method
11+
self.handle_rate_limit = false
1112
end
1213

1314
def set_common_headers(method, base_uri)
@@ -58,6 +59,7 @@ def client(uri)
5859
end
5960

6061
def execute(target_base_url=nil, username:, secret: nil)
62+
retries = 3
6163
base_uri = URI.parse(target_base_url)
6264
set_common_headers(net_http_method, base_uri)
6365
set_basic_auth(net_http_method, username, secret)
@@ -70,6 +72,13 @@ def execute(target_base_url=nil, username:, secret: nil)
7072
parsed_body = parse_body(decoded_body, response)
7173
raise_errors_on_failure(response)
7274
parsed_body
75+
rescue Intercom::RateLimitExceeded => e
76+
if @handle_rate_limit
77+
sleep (@rate_limit_details[:reset_at] - Time.now.utc).ceil
78+
retry unless (retries -=1).zero?
79+
else
80+
raise e
81+
end
7382
rescue Timeout::Error
7483
raise Intercom::ServiceUnavailableError.new('Service Unavailable [request timed out]')
7584
end

spec/spec_helper.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
require 'intercom'
22
require 'minitest/autorun'
33
require 'mocha/setup'
4+
require 'webmock'
5+
include WebMock::API
46

57
def test_user(email="[email protected]")
68
{

spec/unit/intercom/request_spec.rb

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
require 'spec_helper'
22
require 'ostruct'
33

4+
WebMock.enable!
5+
46
describe 'Intercom::Request' do
57
it 'raises an error when a html error page rendered' do
68
response = OpenStruct.new(:code => 500)
@@ -14,9 +16,50 @@
1416
proc {req.parse_body('<html>somethjing</html>', response)}.must_raise(Intercom::RateLimitExceeded)
1517
end
1618

19+
describe 'Intercom::Client' do
20+
let (:client) { Intercom::Client.new(token: 'foo', handle_rate_limit: true) }
21+
let (:uri) {"https://api.intercom.io/users"}
22+
23+
it 'should have handle_rate_limit set' do
24+
client.handle_rate_limit.must_equal(true)
25+
end
26+
27+
it 'should call sleep for rate limit error three times' do
28+
# Use webmock to mock the HTTP request
29+
stub_request(:any, uri).\
30+
to_return(status: [429, "Too Many Requests"], headers: { 'X-RateLimit-Reset' => Time.now.utc + 10 })
31+
req = Intercom::Request.get(uri, "")
32+
req.handle_rate_limit=true
33+
req.expects(:sleep).times(3).with(any_parameters)
34+
req.execute(target_base_url=uri, username: "ted", secret: "")
35+
end
36+
37+
it 'should not call sleep for rate limit error' do
38+
# Use webmock to mock the HTTP request
39+
stub_request(:any, uri).\
40+
to_return(status: [200, "OK"], headers: { 'X-RateLimit-Reset' => Time.now.utc + 10 })
41+
req = Intercom::Request.get(uri, "")
42+
req.handle_rate_limit=true
43+
req.expects(:sleep).never.with(any_parameters)
44+
req.execute(target_base_url=uri, username: "ted", secret: "")
45+
end
46+
47+
it 'should call sleep for rate limit error just once' do
48+
# Use webmock to mock the HTTP request
49+
stub_request(:any, uri).\
50+
to_return(status: [429, "Too Many Requests"], headers: { 'X-RateLimit-Reset' => Time.now.utc + 10 }).\
51+
then.to_return(status: [200, "OK"])
52+
req = Intercom::Request.get(uri, "")
53+
req.handle_rate_limit=true
54+
req.expects(:sleep).with(any_parameters)
55+
req.execute(target_base_url=uri, username: "ted", secret: "")
56+
end
57+
58+
end
59+
1760
it 'parse_body returns nil if decoded_body is nil' do
1861
response = OpenStruct.new(:code => 500)
1962
req = Intercom::Request.new('path/', 'GET')
20-
req.parse_body(nil, response).must_equal(nil)
63+
assert_nil(req.parse_body(nil, response))
2164
end
2265
end

0 commit comments

Comments
 (0)