optimizely-sdk 3.3.2.rc1 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/optimizely.rb +198 -36
- data/lib/optimizely/audience.rb +22 -13
- data/lib/optimizely/bucketer.rb +3 -8
- data/lib/optimizely/config/datafile_project_config.rb +17 -3
- data/lib/optimizely/config/proxy_config.rb +34 -0
- data/lib/optimizely/config_manager/async_scheduler.rb +6 -2
- data/lib/optimizely/config_manager/http_project_config_manager.rb +45 -25
- data/lib/optimizely/config_manager/static_project_config_manager.rb +6 -2
- data/lib/optimizely/custom_attribute_condition_evaluator.rb +133 -37
- data/lib/optimizely/decision_service.rb +30 -29
- data/lib/optimizely/event/batch_event_processor.rb +47 -39
- data/lib/optimizely/event_dispatcher.rb +8 -14
- data/lib/optimizely/exceptions.rb +17 -9
- data/lib/optimizely/helpers/constants.rb +18 -5
- data/lib/optimizely/helpers/http_utils.rb +64 -0
- data/lib/optimizely/helpers/variable_type.rb +8 -1
- data/lib/optimizely/optimizely_config.rb +117 -0
- data/lib/optimizely/optimizely_factory.rb +54 -5
- data/lib/optimizely/project_config.rb +3 -1
- data/lib/optimizely/semantic_version.rb +166 -0
- data/lib/optimizely/version.rb +1 -1
- metadata +9 -20
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2017-
|
4
|
+
# Copyright 2017-2020, Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -94,7 +94,7 @@ module Optimizely
|
|
94
94
|
end
|
95
95
|
|
96
96
|
# Check audience conditions
|
97
|
-
unless Audience.
|
97
|
+
unless Audience.user_meets_audience_conditions?(project_config, experiment, attributes, @logger)
|
98
98
|
@logger.log(
|
99
99
|
Logger::INFO,
|
100
100
|
"User '#{user_id}' does not meet the conditions to be in experiment '#{experiment_key}'."
|
@@ -106,6 +106,16 @@ module Optimizely
|
|
106
106
|
variation = @bucketer.bucket(project_config, experiment, bucketing_id, user_id)
|
107
107
|
variation_id = variation ? variation['id'] : nil
|
108
108
|
|
109
|
+
if variation_id
|
110
|
+
variation_key = variation['key']
|
111
|
+
@logger.log(
|
112
|
+
Logger::INFO,
|
113
|
+
"User '#{user_id}' is in variation '#{variation_key}' of experiment '#{experiment_key}'."
|
114
|
+
)
|
115
|
+
else
|
116
|
+
@logger.log(Logger::INFO, "User '#{user_id}' is in no variation.")
|
117
|
+
end
|
118
|
+
|
109
119
|
# Persist bucketing decision
|
110
120
|
save_user_profile(user_profile, experiment_id, variation_id)
|
111
121
|
variation_id
|
@@ -125,21 +135,9 @@ module Optimizely
|
|
125
135
|
decision = get_variation_for_feature_experiment(project_config, feature_flag, user_id, attributes)
|
126
136
|
return decision unless decision.nil?
|
127
137
|
|
128
|
-
feature_flag_key = feature_flag['key']
|
129
138
|
decision = get_variation_for_feature_rollout(project_config, feature_flag, user_id, attributes)
|
130
|
-
if decision
|
131
|
-
@logger.log(
|
132
|
-
Logger::INFO,
|
133
|
-
"User '#{user_id}' is bucketed into a rollout for feature flag '#{feature_flag_key}'."
|
134
|
-
)
|
135
|
-
return decision
|
136
|
-
end
|
137
|
-
@logger.log(
|
138
|
-
Logger::INFO,
|
139
|
-
"User '#{user_id}' is not bucketed into a rollout for feature flag '#{feature_flag_key}'."
|
140
|
-
)
|
141
139
|
|
142
|
-
|
140
|
+
decision
|
143
141
|
end
|
144
142
|
|
145
143
|
def get_variation_for_feature_experiment(project_config, feature_flag, user_id, attributes = nil)
|
@@ -178,10 +176,7 @@ module Optimizely
|
|
178
176
|
next unless variation_id
|
179
177
|
|
180
178
|
variation = project_config.variation_id_map[experiment_key][variation_id]
|
181
|
-
|
182
|
-
Logger::INFO,
|
183
|
-
"The user '#{user_id}' is bucketed into experiment '#{experiment_key}' of feature '#{feature_flag_key}'."
|
184
|
-
)
|
179
|
+
|
185
180
|
return Decision.new(experiment, variation, DECISION_SOURCES['FEATURE_TEST'])
|
186
181
|
end
|
187
182
|
|
@@ -231,20 +226,23 @@ module Optimizely
|
|
231
226
|
# Go through each experiment in order and try to get the variation for the user
|
232
227
|
number_of_rules.times do |index|
|
233
228
|
rollout_rule = rollout_rules[index]
|
234
|
-
|
235
|
-
audience = project_config.get_audience_from_id(audience_id)
|
236
|
-
audience_name = audience['name']
|
229
|
+
logging_key = index + 1
|
237
230
|
|
238
231
|
# Check that user meets audience conditions for targeting rule
|
239
|
-
unless Audience.
|
232
|
+
unless Audience.user_meets_audience_conditions?(project_config, rollout_rule, attributes, @logger, 'ROLLOUT_AUDIENCE_EVALUATION_LOGS', logging_key)
|
240
233
|
@logger.log(
|
241
234
|
Logger::DEBUG,
|
242
|
-
"User '#{user_id}' does not meet the conditions
|
235
|
+
"User '#{user_id}' does not meet the audience conditions for targeting rule '#{logging_key}'."
|
243
236
|
)
|
244
237
|
# move onto the next targeting rule
|
245
238
|
next
|
246
239
|
end
|
247
240
|
|
241
|
+
@logger.log(
|
242
|
+
Logger::DEBUG,
|
243
|
+
"User '#{user_id}' meets the audience conditions for targeting rule '#{logging_key}'."
|
244
|
+
)
|
245
|
+
|
248
246
|
# Evaluate if user satisfies the traffic allocation for this rollout rule
|
249
247
|
variation = @bucketer.bucket(project_config, rollout_rule, bucketing_id, user_id)
|
250
248
|
return Decision.new(rollout_rule, variation, DECISION_SOURCES['ROLLOUT']) unless variation.nil?
|
@@ -254,17 +252,20 @@ module Optimizely
|
|
254
252
|
|
255
253
|
# get last rule which is the everyone else rule
|
256
254
|
everyone_else_experiment = rollout_rules[number_of_rules]
|
255
|
+
logging_key = 'Everyone Else'
|
257
256
|
# Check that user meets audience conditions for last rule
|
258
|
-
unless Audience.
|
259
|
-
audience_id = everyone_else_experiment['audienceIds'][0]
|
260
|
-
audience = project_config.get_audience_from_id(audience_id)
|
261
|
-
audience_name = audience['name']
|
257
|
+
unless Audience.user_meets_audience_conditions?(project_config, everyone_else_experiment, attributes, @logger, 'ROLLOUT_AUDIENCE_EVALUATION_LOGS', logging_key)
|
262
258
|
@logger.log(
|
263
259
|
Logger::DEBUG,
|
264
|
-
"User '#{user_id}' does not meet the conditions
|
260
|
+
"User '#{user_id}' does not meet the audience conditions for targeting rule '#{logging_key}'."
|
265
261
|
)
|
266
262
|
return nil
|
267
263
|
end
|
264
|
+
|
265
|
+
@logger.log(
|
266
|
+
Logger::DEBUG,
|
267
|
+
"User '#{user_id}' meets the audience conditions for targeting rule '#{logging_key}'."
|
268
|
+
)
|
268
269
|
variation = @bucketer.bucket(project_config, everyone_else_experiment, bucketing_id, user_id)
|
269
270
|
return Decision.new(everyone_else_experiment, variation, DECISION_SOURCES['ROLLOUT']) unless variation.nil?
|
270
271
|
|
@@ -31,7 +31,6 @@ module Optimizely
|
|
31
31
|
DEFAULT_BATCH_INTERVAL = 30_000 # interval in milliseconds
|
32
32
|
DEFAULT_QUEUE_CAPACITY = 1000
|
33
33
|
DEFAULT_TIMEOUT_INTERVAL = 5 # interval in seconds
|
34
|
-
MAX_NIL_COUNT = 3
|
35
34
|
|
36
35
|
FLUSH_SIGNAL = 'FLUSH_SIGNAL'
|
37
36
|
SHUTDOWN_SIGNAL = 'SHUTDOWN_SIGNAL'
|
@@ -62,7 +61,7 @@ module Optimizely
|
|
62
61
|
@notification_center = notification_center
|
63
62
|
@current_batch = []
|
64
63
|
@started = false
|
65
|
-
|
64
|
+
@stopped = false
|
66
65
|
end
|
67
66
|
|
68
67
|
def start!
|
@@ -72,26 +71,37 @@ module Optimizely
|
|
72
71
|
end
|
73
72
|
@flushing_interval_deadline = Helpers::DateTimeUtils.create_timestamp + @flush_interval
|
74
73
|
@logger.log(Logger::INFO, 'Starting scheduler.')
|
75
|
-
@
|
74
|
+
if @wait_mutex.nil?
|
75
|
+
@wait_mutex = Mutex.new
|
76
|
+
@resource = ConditionVariable.new
|
77
|
+
end
|
78
|
+
@thread = Thread.new { run_queue }
|
76
79
|
@started = true
|
80
|
+
@stopped = false
|
77
81
|
end
|
78
82
|
|
79
83
|
def flush
|
80
84
|
@event_queue << FLUSH_SIGNAL
|
85
|
+
@wait_mutex.synchronize { @resource.signal }
|
81
86
|
end
|
82
87
|
|
83
88
|
def process(user_event)
|
84
89
|
@logger.log(Logger::DEBUG, "Received userEvent: #{user_event}")
|
85
90
|
|
86
|
-
if
|
91
|
+
# if the processor has been explicitly stopped. Don't accept tasks
|
92
|
+
if @stopped
|
87
93
|
@logger.log(Logger::WARN, 'Executor shutdown, not accepting tasks.')
|
88
94
|
return
|
89
95
|
end
|
90
96
|
|
97
|
+
# start if the processor hasn't been started
|
98
|
+
start! unless @started
|
99
|
+
|
91
100
|
begin
|
92
101
|
@event_queue.push(user_event, true)
|
93
|
-
|
94
|
-
|
102
|
+
@wait_mutex.synchronize { @resource.signal }
|
103
|
+
rescue => e
|
104
|
+
@logger.log(Logger::WARN, 'Payload not accepted by the queue: ' + e.message)
|
95
105
|
return
|
96
106
|
end
|
97
107
|
end
|
@@ -101,42 +111,20 @@ module Optimizely
|
|
101
111
|
|
102
112
|
@logger.log(Logger::INFO, 'Stopping scheduler.')
|
103
113
|
@event_queue << SHUTDOWN_SIGNAL
|
114
|
+
@wait_mutex.synchronize { @resource.signal }
|
104
115
|
@thread.join(DEFAULT_TIMEOUT_INTERVAL)
|
105
116
|
@started = false
|
117
|
+
@stopped = true
|
106
118
|
end
|
107
119
|
|
108
120
|
private
|
109
121
|
|
110
|
-
def
|
111
|
-
|
112
|
-
|
113
|
-
@nil_count = 0
|
114
|
-
# hang on pop if true
|
115
|
-
@use_pop = false
|
116
|
-
loop do
|
117
|
-
if Helpers::DateTimeUtils.create_timestamp >= @flushing_interval_deadline
|
118
|
-
@logger.log(Logger::DEBUG, 'Deadline exceeded flushing current batch.')
|
119
|
-
flush_queue!
|
120
|
-
@flushing_interval_deadline = Helpers::DateTimeUtils.create_timestamp + @flush_interval
|
121
|
-
@use_pop = true if @nil_count > MAX_NIL_COUNT
|
122
|
-
end
|
123
|
-
|
124
|
-
item = @event_queue.pop if @event_queue.length.positive? || @use_pop
|
125
|
-
|
126
|
-
if item.nil?
|
127
|
-
# when nil count is greater than MAX_NIL_COUNT, we hang on the pop until there is an item available.
|
128
|
-
# this avoids to much spinning of the loop.
|
129
|
-
@nil_count += 1
|
130
|
-
next
|
131
|
-
end
|
132
|
-
|
133
|
-
# reset nil_count and use_pop if we have received an item.
|
134
|
-
@nil_count = 0
|
135
|
-
@use_pop = false
|
136
|
-
|
122
|
+
def process_queue
|
123
|
+
while @event_queue.length.positive?
|
124
|
+
item = @event_queue.pop
|
137
125
|
if item == SHUTDOWN_SIGNAL
|
138
126
|
@logger.log(Logger::DEBUG, 'Received shutdown signal.')
|
139
|
-
|
127
|
+
return false
|
140
128
|
end
|
141
129
|
|
142
130
|
if item == FLUSH_SIGNAL
|
@@ -147,15 +135,35 @@ module Optimizely
|
|
147
135
|
|
148
136
|
add_to_batch(item) if item.is_a? Optimizely::UserEvent
|
149
137
|
end
|
138
|
+
true
|
139
|
+
end
|
140
|
+
|
141
|
+
def run_queue
|
142
|
+
loop do
|
143
|
+
if Helpers::DateTimeUtils.create_timestamp >= @flushing_interval_deadline
|
144
|
+
@logger.log(Logger::DEBUG, 'Deadline exceeded flushing current batch.')
|
145
|
+
|
146
|
+
break unless process_queue
|
147
|
+
|
148
|
+
flush_queue!
|
149
|
+
@flushing_interval_deadline = Helpers::DateTimeUtils.create_timestamp + @flush_interval
|
150
|
+
end
|
151
|
+
|
152
|
+
break unless process_queue
|
153
|
+
|
154
|
+
# what is the current interval to flush in seconds
|
155
|
+
interval = (@flushing_interval_deadline - Helpers::DateTimeUtils.create_timestamp) * 0.001
|
156
|
+
|
157
|
+
next unless interval.positive?
|
158
|
+
|
159
|
+
@wait_mutex.synchronize { @resource.wait(@wait_mutex, interval) }
|
160
|
+
end
|
150
161
|
rescue SignalException
|
151
162
|
@logger.log(Logger::ERROR, 'Interrupted while processing buffer.')
|
152
|
-
rescue
|
163
|
+
rescue => e
|
153
164
|
@logger.log(Logger::ERROR, "Uncaught exception processing buffer. #{e.message}")
|
154
165
|
ensure
|
155
|
-
@logger.log(
|
156
|
-
Logger::INFO,
|
157
|
-
'Exiting processing loop. Attempting to flush pending events.'
|
158
|
-
)
|
166
|
+
@logger.log(Logger::INFO, 'Exiting processing loop. Attempting to flush pending events.')
|
159
167
|
flush_queue!
|
160
168
|
end
|
161
169
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2016-2017, 2019
|
4
|
+
# Copyright 2016-2017, 2019-2020 Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -16,8 +16,7 @@
|
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
18
|
require_relative 'exceptions'
|
19
|
-
|
20
|
-
require 'httparty'
|
19
|
+
require_relative 'helpers/http_utils'
|
21
20
|
|
22
21
|
module Optimizely
|
23
22
|
class NoOpEventDispatcher
|
@@ -30,28 +29,23 @@ module Optimizely
|
|
30
29
|
# @api constants
|
31
30
|
REQUEST_TIMEOUT = 10
|
32
31
|
|
33
|
-
def initialize(logger: nil, error_handler: nil)
|
32
|
+
def initialize(logger: nil, error_handler: nil, proxy_config: nil)
|
34
33
|
@logger = logger || NoOpLogger.new
|
35
34
|
@error_handler = error_handler || NoOpErrorHandler.new
|
35
|
+
@proxy_config = proxy_config
|
36
36
|
end
|
37
37
|
|
38
38
|
# Dispatch the event being represented by the Event object.
|
39
39
|
#
|
40
40
|
# @param event - Event object
|
41
41
|
def dispatch_event(event)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
elsif event.http_verb == :post
|
46
|
-
response = HTTParty.post(event.url,
|
47
|
-
body: event.params.to_json,
|
48
|
-
headers: event.headers,
|
49
|
-
timeout: REQUEST_TIMEOUT)
|
50
|
-
end
|
42
|
+
response = Helpers::HttpUtils.make_request(
|
43
|
+
event.url, event.http_verb, event.params.to_json, event.headers, REQUEST_TIMEOUT, @proxy_config
|
44
|
+
)
|
51
45
|
|
52
46
|
error_msg = "Event failed to dispatch with response code: #{response.code}"
|
53
47
|
|
54
|
-
case response.code
|
48
|
+
case response.code.to_i
|
55
49
|
when 400...500
|
56
50
|
@logger.log(Logger::ERROR, error_msg)
|
57
51
|
@error_handler.handle_error(HTTPCallError.new("HTTP Client Error: #{response.code}"))
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2016-
|
4
|
+
# Copyright 2016-2020, Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -81,14 +81,6 @@ module Optimizely
|
|
81
81
|
end
|
82
82
|
end
|
83
83
|
|
84
|
-
class InvalidDatafileError < Error
|
85
|
-
# Raised when a public method fails due to an invalid datafile
|
86
|
-
|
87
|
-
def initialize(aborted_method)
|
88
|
-
super("Provided datafile is in an invalid format. Aborting #{aborted_method}.")
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
84
|
class InvalidDatafileVersionError < Error
|
93
85
|
# Raised when a datafile with an unsupported version is provided
|
94
86
|
|
@@ -128,4 +120,20 @@ module Optimizely
|
|
128
120
|
super("Optimizely instance is not valid. Failing '#{aborted_method}'.")
|
129
121
|
end
|
130
122
|
end
|
123
|
+
|
124
|
+
class InvalidAttributeType < Error
|
125
|
+
# Raised when an attribute is not provided in expected type.
|
126
|
+
|
127
|
+
def initialize(msg = 'Provided attribute value is not in the expected data type.')
|
128
|
+
super
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class InvalidSemanticVersion < Error
|
133
|
+
# Raised when an invalid value is provided as semantic version.
|
134
|
+
|
135
|
+
def initialize(msg = 'Provided semantic version is invalid.')
|
136
|
+
super
|
137
|
+
end
|
138
|
+
end
|
131
139
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2016-
|
4
|
+
# Copyright 2016-2020, Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -304,7 +304,8 @@ module Optimizely
|
|
304
304
|
'BOOLEAN' => 'boolean',
|
305
305
|
'DOUBLE' => 'double',
|
306
306
|
'INTEGER' => 'integer',
|
307
|
-
'STRING' => 'string'
|
307
|
+
'STRING' => 'string',
|
308
|
+
'JSON' => 'json'
|
308
309
|
}.freeze
|
309
310
|
|
310
311
|
INPUT_VARIABLES = {
|
@@ -334,11 +335,11 @@ module Optimizely
|
|
334
335
|
|
335
336
|
AUDIENCE_EVALUATION_LOGS = {
|
336
337
|
'AUDIENCE_EVALUATION_RESULT' => "Audience '%s' evaluated to %s.",
|
337
|
-
'AUDIENCE_EVALUATION_RESULT_COMBINED' => "Audiences for experiment '%s' collectively evaluated to %s.",
|
338
338
|
'EVALUATING_AUDIENCE' => "Starting to evaluate audience '%s' with conditions: %s.",
|
339
|
-
'EVALUATING_AUDIENCES_COMBINED' => "Evaluating audiences for experiment '%s': %s.",
|
340
339
|
'INFINITE_ATTRIBUTE_VALUE' => 'Audience condition %s evaluated to UNKNOWN because the number value ' \
|
341
340
|
"for user attribute '%s' is not in the range [-2^53, +2^53].",
|
341
|
+
'INVALID_SEMANTIC_VERSION' => 'Audience condition %s evaluated as UNKNOWN because an invalid semantic version ' \
|
342
|
+
"was passed for user attribute '%s'.",
|
342
343
|
'MISSING_ATTRIBUTE_VALUE' => 'Audience condition %s evaluated as UNKNOWN because no value ' \
|
343
344
|
"was passed for user attribute '%s'.",
|
344
345
|
'NULL_ATTRIBUTE_VALUE' => 'Audience condition %s evaluated to UNKNOWN because a nil value was passed ' \
|
@@ -353,15 +354,27 @@ module Optimizely
|
|
353
354
|
'to upgrade to a newer release of the Optimizely SDK.'
|
354
355
|
}.freeze
|
355
356
|
|
357
|
+
EXPERIMENT_AUDIENCE_EVALUATION_LOGS = {
|
358
|
+
'AUDIENCE_EVALUATION_RESULT_COMBINED' => "Audiences for experiment '%s' collectively evaluated to %s.",
|
359
|
+
'EVALUATING_AUDIENCES_COMBINED' => "Evaluating audiences for experiment '%s': %s."
|
360
|
+
}.merge(AUDIENCE_EVALUATION_LOGS).freeze
|
361
|
+
|
362
|
+
ROLLOUT_AUDIENCE_EVALUATION_LOGS = {
|
363
|
+
'AUDIENCE_EVALUATION_RESULT_COMBINED' => "Audiences for rule '%s' collectively evaluated to %s.",
|
364
|
+
'EVALUATING_AUDIENCES_COMBINED' => "Evaluating audiences for rule '%s': %s."
|
365
|
+
}.merge(AUDIENCE_EVALUATION_LOGS).freeze
|
366
|
+
|
356
367
|
DECISION_NOTIFICATION_TYPES = {
|
357
368
|
'AB_TEST' => 'ab-test',
|
358
369
|
'FEATURE' => 'feature',
|
359
370
|
'FEATURE_TEST' => 'feature-test',
|
360
|
-
'FEATURE_VARIABLE' => 'feature-variable'
|
371
|
+
'FEATURE_VARIABLE' => 'feature-variable',
|
372
|
+
'ALL_FEATURE_VARIABLES' => 'all-feature-variables'
|
361
373
|
}.freeze
|
362
374
|
|
363
375
|
CONFIG_MANAGER = {
|
364
376
|
'DATAFILE_URL_TEMPLATE' => 'https://cdn.optimizely.com/datafiles/%s.json',
|
377
|
+
'AUTHENTICATED_DATAFILE_URL_TEMPLATE' => 'https://config.optimizely.com/datafiles/auth/%s.json',
|
365
378
|
# Default time in seconds to block the 'config' method call until 'config' instance has been initialized.
|
366
379
|
'DEFAULT_BLOCKING_TIMEOUT' => 15,
|
367
380
|
# Default config update interval of 5 minutes
|
@@ -0,0 +1,64 @@
|
|
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
|
+
|
19
|
+
require 'net/http'
|
20
|
+
|
21
|
+
module Optimizely
|
22
|
+
module Helpers
|
23
|
+
module HttpUtils
|
24
|
+
module_function
|
25
|
+
|
26
|
+
def make_request(url, http_method, request_body = nil, headers = {}, read_timeout = nil, proxy_config = nil)
|
27
|
+
# makes http/https GET/POST request and returns response
|
28
|
+
#
|
29
|
+
uri = URI.parse(url)
|
30
|
+
|
31
|
+
if http_method == :get
|
32
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
33
|
+
elsif http_method == :post
|
34
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
35
|
+
request.body = request_body if request_body
|
36
|
+
else
|
37
|
+
return nil
|
38
|
+
end
|
39
|
+
|
40
|
+
# set headers
|
41
|
+
headers&.each do |key, val|
|
42
|
+
request[key] = val
|
43
|
+
end
|
44
|
+
|
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'
|
60
|
+
http.request(request)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|