Skip to content

Commit eeb2c7d

Browse files
thomaszurkan-optimizelyrloombaoakbani
authored
Rloomba/add proxy config (#262) (#263)
Co-authored-by: Ryan Loomba <[email protected]> Co-authored-by: Owais Akbani <[email protected]> * add ability to use http proxy when making web requests * fix: linting * ignore spellcheck * headers and missing test * update to use master mdspell Co-authored-by: Ryan Loomba <[email protected]> Co-authored-by: Owais Akbani <[email protected]>
1 parent 6ea2681 commit eeb2c7d

11 files changed

+195
-14
lines changed

.rubocop_todo.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Lint/HandleExceptions:
1414
# Offense count: 8
1515
# Configuration parameters: CountKeywordArgs.
1616
Metrics/ParameterLists:
17-
Max: 13
17+
Max: 14
1818

1919
# Offense count: 2
2020
Naming/AccessorMethodName:

.travis.yml

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ jobs:
4343
install:
4444
- npm i -g markdown-spellcheck
4545
before_script:
46+
# todo: change branch to master once merged.
4647
- wget --quiet https://raw.githubusercontent.com/optimizely/mdspell-config/master/.spelling
4748
script:
4849
- mdspell -a -n -r --en-us '**/*.md'

README.md

+2-1
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.

lib/optimizely/config/proxy_config.rb

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright 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+
19+
module Optimizely
20+
class ProxyConfig
21+
attr_reader :host, :port, :username, :password
22+
23+
def initialize(host, port = nil, username = nil, password = nil)
24+
# host - DNS name or IP address of proxy
25+
# port - port to use to acess the proxy
26+
# username - username if authorization is required
27+
# password - password if authorization is required
28+
@host = host
29+
@port = port
30+
@username = username
31+
@password = password
32+
end
33+
end
34+
end

lib/optimizely/config_manager/http_project_config_manager.rb

+5-2
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

@@ -161,7 +164,7 @@ def request_config
161164

162165
begin
163166
response = Helpers::HttpUtils.make_request(
164-
@datafile_url, :get, nil, headers, Helpers::Constants::CONFIG_MANAGER['REQUEST_TIMEOUT']
167+
@datafile_url, :get, nil, headers, Helpers::Constants::CONFIG_MANAGER['REQUEST_TIMEOUT'], @proxy_config
165168
)
166169
rescue StandardError => e
167170
@logger.log(

lib/optimizely/event_dispatcher.rb

+3-2
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

+17-6
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&.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

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# frozen_string_literal: true
2+
3+
#
4+
# Copyright 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

+14-1
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@
483483
datafile_access_token: 'the-token'
484484
)
485485
sleep 0.1
486-
expect(Optimizely::Helpers::HttpUtils).to have_received(:make_request).with(anything, anything, anything, hash_including('Authorization' => 'Bearer the-token'), anything)
486+
expect(Optimizely::Helpers::HttpUtils).to have_received(:make_request).with(anything, anything, anything, hash_including('Authorization' => 'Bearer the-token'), anything, anything)
487487
end
488488

489489
it 'should use authenticated datafile url when auth token is provided' do
@@ -526,5 +526,18 @@
526526
sleep 0.1
527527
expect(spy_logger).to have_received(:log).with(Logger::DEBUG, 'Datafile request headers: {"Content-Type"=>"application/json", "Authorization"=>"********"}').once
528528
end
529+
530+
it 'should pass the proxy config that is passed in' do
531+
proxy_config = double(:proxy_config)
532+
533+
allow(Optimizely::Helpers::HttpUtils).to receive(:make_request)
534+
@http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
535+
sdk_key: 'valid_sdk_key',
536+
datafile_access_token: 'the-token',
537+
proxy_config: proxy_config
538+
)
539+
sleep 0.1
540+
expect(Optimizely::Helpers::HttpUtils).to have_received(:make_request).with(anything, anything, anything, hash_including('Authorization' => 'Bearer the-token'), anything, proxy_config)
541+
end
529542
end
530543
end

spec/event_dispatcher_spec.rb

+20-1
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

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright 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/config/proxy_config'
20+
21+
describe Optimizely::Helpers::HttpUtils do
22+
context 'passing in a proxy config' do
23+
let(:url) { 'https://example.com' }
24+
let(:http_method) { :get }
25+
let(:host) { 'host' }
26+
let(:port) { 1234 }
27+
let(:username) { 'username' }
28+
let(:password) { 'password' }
29+
let(:http_class) { double(:http_class) }
30+
let(:http) { double(:http) }
31+
32+
before do
33+
allow(http_class).to receive(:new).and_return(http)
34+
allow(http).to receive(:use_ssl=)
35+
allow(http).to receive(:request)
36+
end
37+
38+
context 'with a proxy config that inclues host, port, username, and password' do
39+
let(:proxy_config) { Optimizely::ProxyConfig.new(host, port, username, password) }
40+
it 'with a full proxy config, it proxies the web request' do
41+
expect(Net::HTTP).to receive(:Proxy).with(host, port, username, password).and_return(http_class)
42+
described_class.make_request(url, http_method, nil, nil, nil, proxy_config)
43+
end
44+
end
45+
46+
context 'with a proxy config that only inclues host' do
47+
let(:proxy_config) { Optimizely::ProxyConfig.new(host) }
48+
it 'with a full proxy config, it proxies the web request' do
49+
expect(Net::HTTP).to receive(:Proxy).with(host, nil, nil, nil).and_return(http_class)
50+
described_class.make_request(url, http_method, nil, nil, nil, proxy_config)
51+
end
52+
end
53+
end
54+
end

0 commit comments

Comments
 (0)