optimizely-sdk 3.3.2.rc1 → 3.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|