Skip to content

Commit fba6ce0

Browse files
authored
Configurable http_timeout on API requests (#70)
This PR adds support for setting a timeout for executing API requests. By default, it will use 60 seconds (which is also the default RestClient uses) but this can be changed by setting `config.http_timeout = xxx` to the desired number of seconds. To support this feature, the code calling RestClient had to be fully refactored. Fixes #63
1 parent e6ca956 commit fba6ce0

12 files changed

Lines changed: 121 additions & 87 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
info
22
*.gem
33
Gemfile.lock
4-
.ruby-version
4+
.ruby-version
5+
.zed

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
- Added Zeitwerk for improved code autoloading
1010

11+
- Added `http_timeout` configuration option. By default it is set to 60 (seconds).
12+
1113
## 0.9.0
1214

1315
- **Added support for Sidekiq**. You can now use Sidekiq to deliver API requests to Vero. To do so, just specify `config.async = :sidekiq` in your config.rb file.

README.markdown

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ To get started with a Ruby on Rails application, add the following initializer:
2525
# config/initializers/vero.rb
2626
Vero::App.init do |config|
2727
config.tracking_api_key = ENV['VERO_TRACKING_API_KEY']
28+
29+
config.http_timeout = 30 # default timeout per API request is set to 60 (seconds)
2830
end
2931
```
3032

lib/vero/api/workers/base_api.rb

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ def self.perform(domain, options)
1313
def initialize(domain, options)
1414
@domain = domain
1515
self.options = options
16-
setup_logging
1716
end
1817

1918
def perform
@@ -22,21 +21,11 @@ def perform
2221
end
2322

2423
def options=(val)
25-
@options = options_with_symbolized_keys(val)
24+
@options = val.transform_keys(&:to_sym)
2625
end
2726

2827
protected
2928

30-
def setup_logging
31-
return unless Vero::App.logger
32-
33-
RestClient.log = Object.new.tap do |proxy|
34-
def proxy.<<(message)
35-
Vero::App.logger.info message
36-
end
37-
end
38-
end
39-
4029
def url
4130
end
4231

@@ -45,20 +34,17 @@ def validate!
4534
end
4635

4736
def request
48-
request_headers = {content_type: :json, accept: :json}
49-
50-
if request_method == :get
51-
RestClient.get(url, request_headers)
52-
else
53-
RestClient.send(request_method, url, JSON.dump(@options), request_headers)
54-
end
37+
http_client.do_request(request_method, url, @options.except(:_config))
5538
end
5639

5740
def request_method
5841
raise NotImplementedError, "#{self.class.name}#request_method should be overridden"
5942
end
6043

61-
def options_with_symbolized_keys(val)
62-
val.transform_keys(&:to_sym)
44+
def http_client
45+
Vero::HttpClient.new(
46+
logger: Vero::App.logger,
47+
http_timeout: @options.dig(:_config, :http_timeout)
48+
)
6349
end
6450
end

lib/vero/config.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ class Vero::Config
44
attr_writer :development_mode # Deprecated field
55

66
attr_writer :domain
7-
attr_accessor :tracking_api_key, :async, :disabled, :logging
7+
attr_accessor :tracking_api_key, :async, :disabled, :logging, :http_timeout
88

9-
ACCEPTED_ATTRIBUTES = %i[tracking_api_key async disabled logging domain]
9+
ACCEPTED_ATTRIBUTES = %i[tracking_api_key async disabled logging domain http_timeout]
1010

1111
# Extracts accepted attributes from the given object. It isn't necessarily a Vero::Config instance.
1212
def self.extract_accepted_attrs_from(object)
@@ -24,7 +24,10 @@ def config_params
2424
end
2525

2626
def request_params
27-
{tracking_api_key: tracking_api_key}.compact
27+
{
28+
tracking_api_key: tracking_api_key,
29+
_config: {http_timeout: http_timeout}
30+
}.compact
2831
end
2932

3033
def domain
@@ -49,6 +52,7 @@ def reset!
4952
self.async = true
5053
self.logging = false
5154
self.tracking_api_key = nil
55+
self.http_timeout = Vero::HttpClient::DEFAULT_HTTP_TIMEOUT
5256
end
5357

5458
def update_attributes(attributes = {})

lib/vero/http_client.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
class Vero::HttpClient
2+
DEFAULT_HTTP_TIMEOUT = 60
3+
4+
def initialize(http_timeout:, logger: nil)
5+
@http_timeout = http_timeout
6+
setup_logging!(logger) if logger
7+
end
8+
9+
def get(url, headers = {})
10+
do_request(:get, url, nil, headers)
11+
end
12+
13+
def post(url, body, headers = {})
14+
do_request(:post, url, body, headers)
15+
end
16+
17+
def put(url, body, headers = {})
18+
do_request(:put, url, body, headers)
19+
end
20+
21+
def do_request(method, url, body = nil, headers = {})
22+
request_params = {method: method, url: url, headers: default_headers.merge(headers), timeout: @http_timeout}
23+
request_params[:payload] = JSON.dump(body) unless method == :get
24+
RestClient::Request.execute(request_params)
25+
end
26+
27+
private
28+
29+
def default_headers
30+
{content_type: :json, accept: :json}
31+
end
32+
33+
def setup_logging!(logger)
34+
RestClient.log = Object.new.tap do |proxy|
35+
def proxy.<<(message)
36+
logger.info message
37+
end
38+
end
39+
end
40+
end

spec/lib/api/base_api_spec.rb

Lines changed: 0 additions & 19 deletions
This file was deleted.

spec/lib/api/events/track_api_spec.rb

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
require "spec_helper"
44

55
describe Vero::Api::Workers::Events::TrackAPI do
6-
subject { Vero::Api::Workers::Events::TrackAPI.new("https://api.getvero.com", {auth_token: "abcd", identity: {email: "test@test.com"}, event_name: "test_event"}) }
6+
let(:payload) do
7+
{auth_token: "abcd", identity: {email: "test@test.com"}, event_name: "test_event"}
8+
end
9+
10+
subject { Vero::Api::Workers::Events::TrackAPI.new("https://api.getvero.com", payload) }
711

812
it_behaves_like "a Vero wrapper" do
913
let(:end_point) { "/api/v2/events/track.json" }
@@ -12,48 +16,44 @@
1216
context "request with properties" do
1317
describe :validate! do
1418
it "should raise an error if event_name is a blank String" do
15-
options = {auth_token: "abcd", identity: {email: "test@test.com"}, event_name: nil}
16-
subject.options = options
19+
subject.options = payload.except(:event_name)
1720
expect { subject.send(:validate!) }.to raise_error(ArgumentError)
1821

19-
options = {auth_token: "abcd", identity: {email: "test@test.com"}, event_name: "test_event"}
20-
subject.options = options
22+
subject.options = payload
2123
expect { subject.send(:validate!) }.to_not raise_error
2224
end
2325

2426
it "should raise an error if data is not either nil or a Hash" do
25-
options = {auth_token: "abcd", identity: {email: "test@test.com"}, event_name: "test_event", data: []}
26-
subject.options = options
27+
subject.options = payload.merge(data: [])
2728
expect { subject.send(:validate!) }.to raise_error(ArgumentError)
2829

29-
options = {auth_token: "abcd", identity: {email: "test@test.com"}, event_name: "test_event", data: nil}
30-
subject.options = options
30+
subject.options = payload.merge(data: nil)
3131
expect { subject.send(:validate!) }.to_not raise_error
3232

33-
options = {auth_token: "abcd", identity: {email: "test@test.com"}, event_name: "test_event", data: {}}
34-
subject.options = options
33+
subject.options = payload.merge(data: {})
3534
expect { subject.send(:validate!) }.to_not raise_error
3635
end
3736

3837
it "should not raise an error when the keys are Strings" do
39-
options = {"auth_token" => "abcd", "identity" => {"email" => "test@test.com"}, "event_name" => "test_event", "data" => {}}
38+
options = {"auth_token" => "abcd", "identity" => {"email" => "test@test.com"}, "event_name" => "test_event",
39+
"data" => {}}
4040
subject.options = options
4141
expect { subject.send(:validate!) }.to_not raise_error
4242
end
4343

4444
it "should not raise an error when keys are Strings for initialization" do
45-
options = {"auth_token" => "abcd", "identity" => {"email" => "test@test.com"}, "event_name" => "test_event", "data" => {}}
46-
expect { Vero::Api::Workers::Events::TrackAPI.new("https://api.getvero.com", options).send(:validate!) }.to_not raise_error
45+
payload.transform_keys!(&:to_s)
46+
47+
expect do
48+
Vero::Api::Workers::Events::TrackAPI.new("https://api.getvero.com", payload).send(:validate!)
49+
end.to_not raise_error
4750
end
4851
end
4952

5053
describe "request" do
5154
it "should send a request to the Vero API" do
5255
stub = stub_request(:post, "https://api.getvero.com/api/v2/events/track.json")
53-
.with(
54-
body: {auth_token: "abcd", identity: {email: "test@test.com"}, event_name: "test_event"}.to_json,
55-
headers: {"Content-Type" => "application/json", "Accept" => "application/json"}
56-
)
56+
.with(body: payload.to_json)
5757
.to_return(status: 200)
5858

5959
subject.send(:request)
@@ -65,9 +65,12 @@
6565

6666
describe "integration test" do
6767
it "should not raise any errors" do
68-
obj = Vero::Api::Workers::Events::TrackAPI.new("https://api.getvero.com", {auth_token: "abcd", identity: {email: "test@test.com"}, event_name: "test_event"})
68+
obj = Vero::Api::Workers::Events::TrackAPI.new("https://api.getvero.com", payload)
69+
70+
stub_request(:post, "https://api.getvero.com/api/v2/events/track.json")
71+
.with(body: payload.to_json)
72+
.to_return(status: 200)
6973

70-
allow(RestClient).to receive(:post).and_return(200)
7174
expect { obj.perform }.to_not raise_error
7275
end
7376
end

spec/lib/api_spec.rb

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,44 @@
44

55
describe Vero::Api::Events do
66
let(:subject) { Vero::Api::Events }
7+
let(:mock_context) { Vero::Context.new }
78

8-
describe :track! do
9-
it "should call the TrackAPI object via the configured sender" do
10-
input = {event_name: "test_event", identity: {email: "james@getvero.com"}, data: {test: "test"}}
11-
expected = input.merge(tracking_api_key: "abc123")
9+
let(:input) { {event_name: "test_event", identity: {email: "james@getvero.com"}, data: {test: "test"}} }
10+
let(:expected) { input.merge(tracking_api_key: "abc123", _config: {http_timeout: 60}) }
1211

13-
mock_context = Vero::Context.new
14-
allow(mock_context.config).to receive(:configured?).and_return(true)
15-
allow(mock_context.config).to receive(:tracking_api_key).and_return("abc123")
12+
before do
13+
allow(mock_context.config).to receive(:configured?).and_return(true)
14+
allow(mock_context.config).to receive(:tracking_api_key).and_return("abc123")
15+
allow(Vero::App).to receive(:default_context).and_return(mock_context)
16+
end
1617

17-
allow(Vero::App).to receive(:default_context).and_return(mock_context)
18+
it "should pass http_timeout to API requests" do
19+
allow(mock_context.config).to receive(:http_timeout).and_return(30)
1820

19-
expect(Vero::Sender).to receive(:call).with(Vero::Api::Workers::Events::TrackAPI, true, "https://api.getvero.com", expected)
21+
expect(Vero::Sender).to(
22+
receive(:call).with(
23+
Vero::Api::Workers::Events::TrackAPI, true, "https://api.getvero.com", expected.merge(_config: {http_timeout: 30})
24+
)
25+
)
26+
subject.track!(input)
27+
end
2028

21-
subject.track!(input)
29+
describe :track! do
30+
context "should call the TrackAPI object via the configured sender" do
31+
specify do
32+
expect(Vero::Sender).to receive(:call).with(Vero::Api::Workers::Events::TrackAPI, true, "https://api.getvero.com", expected)
33+
subject.track!(input)
34+
end
2235
end
2336
end
2437
end
2538

2639
describe Vero::Api::Users do
2740
let(:subject) { Vero::Api::Users }
2841
let(:mock_context) { Vero::Context.new }
29-
let(:expected) { input.merge(tracking_api_key: "abc123") }
42+
let(:expected) { input.merge(tracking_api_key: "abc123", _config: {http_timeout: 60}) }
3043

31-
before :each do
44+
before do
3245
allow(mock_context.config).to receive(:configured?).and_return(true)
3346
allow(mock_context.config).to receive(:tracking_api_key).and_return("abc123")
3447
allow(Vero::App).to receive(:default_context).and_return(mock_context)

spec/lib/config_spec.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@
3131
end
3232

3333
describe :request_params do
34-
it "should return a hash of tracking_api_key and development_mode if they are set" do
34+
it "should return a hash containing tracking_api_key if set" do
3535
config.tracking_api_key = nil
36-
expect(config.request_params).to eq({})
36+
expect(config.request_params.key?(:tracking_api_key)).to be_falsey
3737

3838
config.tracking_api_key = "abcd1234"
39-
expect(config.request_params).to eq({tracking_api_key: "abcd1234"})
39+
expect(config.request_params).to include(tracking_api_key: "abcd1234")
4040
end
4141
end
4242

0 commit comments

Comments
 (0)