Skip to content

Commit a67d5c3

Browse files
committed
add ability to use http proxy when making web requests
1 parent 75aae12 commit a67d5c3

File tree

9 files changed

+177
-13
lines changed

9 files changed

+177
-13
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ The `HTTPConfigManager` asynchronously polls for datafiles from a specified URL
8888
error_handler: nil,
8989
skip_json_validation: false,
9090
notification_center: notification_center,
91-
datafile_access_token: nil
91+
datafile_access_token: nil,
92+
proxy_config: nil
9293
)
9394
~~~~~~
9495
**Note:** You must provide either the `sdk_key` or URL. If you provide both, the URL takes precedence.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module Optimizely
2+
class ProxyConfig
3+
attr_reader :host, :port, :username, :password
4+
5+
def initialize(host, port = nil, username = nil, password = nil)
6+
# host - DNS name or IP address of proxy
7+
# port - port to use to acess the proxy
8+
# username - username if authorization is required
9+
# password - password if authorization is required
10+
@host = host
11+
@port = port
12+
@username = username
13+
@password = password
14+
end
15+
end
16+
end

lib/optimizely/config_manager/http_project_config_manager.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class HTTPProjectConfigManager < ProjectConfigManager
5252
# skip_json_validation - Optional boolean param which allows skipping JSON schema
5353
# validation upon object invocation. By default JSON schema validation will be performed.
5454
# datafile_access_token - access token used to fetch private datafiles
55+
# proxy_config - Optional proxy config instancea to configure making web requests through a proxy server.
5556
def initialize(
5657
sdk_key: nil,
5758
url: nil,
@@ -65,7 +66,8 @@ def initialize(
6566
error_handler: nil,
6667
skip_json_validation: false,
6768
notification_center: nil,
68-
datafile_access_token: nil
69+
datafile_access_token: nil,
70+
proxy_config: nil
6971
)
7072
@logger = logger || NoOpLogger.new
7173
@error_handler = error_handler || NoOpErrorHandler.new
@@ -86,6 +88,7 @@ def initialize(
8688
# Start async scheduler in the end to avoid race condition where scheduler executes
8789
# callback which makes use of variables not yet initialized by the main thread.
8890
@async_scheduler.start! if start_by_default == true
91+
@proxy_config = proxy_config
8992
@stopped = false
9093
end
9194

@@ -159,7 +162,7 @@ def request_config
159162
headers['Authorization'] = "Bearer #{@access_token}" unless @access_token.nil?
160163

161164
response = Helpers::HttpUtils.make_request(
162-
@datafile_url, :get, nil, headers, Helpers::Constants::CONFIG_MANAGER['REQUEST_TIMEOUT']
165+
@datafile_url, :get, nil, headers, Helpers::Constants::CONFIG_MANAGER['REQUEST_TIMEOUT'], @proxy_config
163166
)
164167
rescue StandardError => e
165168
@logger.log(

lib/optimizely/event_dispatcher.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,18 @@ class EventDispatcher
2929
# @api constants
3030
REQUEST_TIMEOUT = 10
3131

32-
def initialize(logger: nil, error_handler: nil)
32+
def initialize(logger: nil, error_handler: nil, proxy_config: nil)
3333
@logger = logger || NoOpLogger.new
3434
@error_handler = error_handler || NoOpErrorHandler.new
35+
@proxy_config = proxy_config
3536
end
3637

3738
# Dispatch the event being represented by the Event object.
3839
#
3940
# @param event - Event object
4041
def dispatch_event(event)
4142
response = Helpers::HttpUtils.make_request(
42-
event.url, event.http_verb, event.params.to_json, event.headers, REQUEST_TIMEOUT
43+
event.url, event.http_verb, event.params.to_json, event.headers, REQUEST_TIMEOUT, @proxy_config
4344
)
4445

4546
error_msg = "Event failed to dispatch with response code: #{response.code}"

lib/optimizely/helpers/http_utils.rb

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,10 @@ module Helpers
2323
module HttpUtils
2424
module_function
2525

26-
def make_request(url, http_method, request_body = nil, headers = {}, read_timeout = nil)
26+
def make_request(url, http_method, request_body = nil, headers = {}, read_timeout = nil, proxy_config = nil)
2727
# makes http/https GET/POST request and returns response
28-
28+
#
2929
uri = URI.parse(url)
30-
http = Net::HTTP.new(uri.host, uri.port)
31-
32-
http.read_timeout = read_timeout if read_timeout
33-
http.use_ssl = uri.scheme == 'https'
3430

3531
if http_method == :get
3632
request = Net::HTTP::Get.new(uri.request_uri)
@@ -46,6 +42,21 @@ def make_request(url, http_method, request_body = nil, headers = {}, read_timeou
4642
request[key] = val
4743
end
4844

45+
# do not try to make request with proxy unless we have at least a host
46+
http_class = if proxy_config && proxy_config.host
47+
Net::HTTP::Proxy(
48+
proxy_config.host,
49+
proxy_config.port,
50+
proxy_config.username,
51+
proxy_config.password
52+
)
53+
else
54+
Net::HTTP
55+
end
56+
57+
http = http_class.new(uri.host, uri.port)
58+
http.read_timeout = read_timeout if read_timeout
59+
http.use_ssl = uri.scheme == 'https'
4960
http.request(request)
5061
end
5162
end

spec/config/proxy_config_spec.rb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# frozen_string_literal: true
2+
3+
#
4+
# Copyright 2019-2020, Optimizely and contributors
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
require 'spec_helper'
19+
require 'optimizely/config/proxy_config'
20+
21+
describe Optimizely::ProxyConfig do
22+
let(:host) { 'host' }
23+
let(:port) { 1234 }
24+
let(:username) { 'username' }
25+
let(:password) { 'password' }
26+
27+
describe '#initialize' do
28+
it 'defines getters for host, port, username, and password' do
29+
proxy_config = described_class.new(host, port, username, password)
30+
31+
expect(proxy_config.host).to eq(host)
32+
expect(proxy_config.port).to eq(port)
33+
expect(proxy_config.username).to eq(username)
34+
expect(proxy_config.password).to eq(password)
35+
end
36+
37+
it 'sets port, username, and password to nil if they are not passed in' do
38+
proxy_config = described_class.new(host)
39+
expect(proxy_config.port).to eq(nil)
40+
expect(proxy_config.username).to eq(nil)
41+
expect(proxy_config.password).to eq(nil)
42+
end
43+
end
44+
end

spec/config_manager/http_project_config_manager_spec.rb

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@
472472
datafile_access_token: 'the-token'
473473
)
474474
sleep 0.1
475-
expect(Optimizely::Helpers::HttpUtils).to have_received(:make_request).with(anything, anything, anything, hash_including('Authorization' => 'Bearer the-token'), anything)
475+
expect(Optimizely::Helpers::HttpUtils).to have_received(:make_request).with(anything, anything, anything, hash_including('Authorization' => 'Bearer the-token'), anything, anything)
476476
end
477477

478478
it 'should use authenticated datafile url when auth token is provided' do
@@ -504,5 +504,19 @@
504504
sleep 0.1
505505
expect(Optimizely::Helpers::HttpUtils).to have_received(:make_request).with('http://awesomeurl', any_args)
506506
end
507+
508+
it 'should pass the proxy config that is passed in' do
509+
proxy_config = double(:proxy_config)
510+
511+
allow(Optimizely::Helpers::HttpUtils).to receive(:make_request)
512+
@http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
513+
sdk_key: 'valid_sdk_key',
514+
datafile_access_token: 'the-token',
515+
proxy_config: proxy_config
516+
)
517+
sleep 0.1
518+
expect(Optimizely::Helpers::HttpUtils).to have_received(:make_request).with(anything, anything, anything, hash_including('Authorization' => 'Bearer the-token'), anything, proxy_config)
519+
520+
end
507521
end
508522
end

spec/event_dispatcher_spec.rb

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
describe Optimizely::EventDispatcher do
2323
let(:error_handler) { spy(Optimizely::NoOpErrorHandler.new) }
2424
let(:spy_logger) { spy('logger') }
25+
let(:proxy_config) { nil }
2526

2627
before(:context) do
2728
@url = 'https://www.optimizely.com'
@@ -37,10 +38,28 @@
3738
before(:example) do
3839
@event_dispatcher = Optimizely::EventDispatcher.new
3940
@customized_event_dispatcher = Optimizely::EventDispatcher.new(
40-
logger: spy_logger, error_handler: error_handler
41+
logger: spy_logger, error_handler: error_handler, proxy_config: proxy_config
4142
)
4243
end
4344

45+
context 'passing in proxy config' do
46+
let(:proxy_config) { double(:proxy_config) }
47+
48+
it 'should pass the proxy_config to the HttpUtils helper class' do
49+
event = Optimizely::Event.new(:post, @url, @params, @post_headers)
50+
expect(Optimizely::Helpers::HttpUtils).to receive(:make_request).with(
51+
event.url,
52+
event.http_verb,
53+
event.params.to_json,
54+
event.headers,
55+
Optimizely::EventDispatcher::REQUEST_TIMEOUT,
56+
proxy_config
57+
)
58+
59+
@customized_event_dispatcher.dispatch_event(event)
60+
end
61+
end
62+
4463
it 'should properly dispatch V2 (POST) events' do
4564
stub_request(:post, @url)
4665
event = Optimizely::Event.new(:post, @url, @params, @post_headers)

spec/helpers/http_utils_spec.rb

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright 2016-2017, 2019-2020, Optimizely and contributors
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
#
18+
require 'spec_helper'
19+
#require 'optimizely/helpers/http_utils'
20+
require 'optimizely/config/proxy_config'
21+
22+
describe Optimizely::Helpers::HttpUtils do
23+
context 'passing in a proxy config' do
24+
let(:url) { 'https://example.com' }
25+
let(:http_method) { :get }
26+
let(:host) { 'host' }
27+
let(:port) { 1234 }
28+
let(:username) { 'username' }
29+
let(:password) { 'password' }
30+
let(:http_class) { double(:http_class) }
31+
let(:http) { double(:http) }
32+
33+
before do
34+
allow(http_class).to receive(:new).and_return(http)
35+
allow(http).to receive(:use_ssl=)
36+
allow(http).to receive(:request)
37+
end
38+
39+
context 'with a proxy config that inclues host, port, username, and password' do
40+
let(:proxy_config) { Optimizely::ProxyConfig.new(host, port, username, password) }
41+
it 'with a full proxy config, it proxies the web request' do
42+
expect(Net::HTTP).to receive(:Proxy).with(host, port, username, password).and_return(http_class)
43+
described_class.make_request(url, http_method, nil, nil, nil, proxy_config)
44+
end
45+
end
46+
47+
context 'with a proxy config that only inclues host' do
48+
let(:proxy_config) { Optimizely::ProxyConfig.new(host) }
49+
it 'with a full proxy config, it proxies the web request' do
50+
expect(Net::HTTP).to receive(:Proxy).with(host, nil, nil, nil).and_return(http_class)
51+
described_class.make_request(url, http_method, nil, nil, nil, proxy_config)
52+
end
53+
end
54+
end
55+
end

0 commit comments

Comments
 (0)