configcat 9.0.0 → 10.0.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/configcat/configcatclient.rb +3 -2
- data/lib/configcat/configcatlogger.rb +7 -0
- data/lib/configcat/configentry.rb +1 -0
- data/lib/configcat/configfetcher.rb +63 -20
- data/lib/configcat/configservice.rb +1 -0
- data/lib/configcat/localfiledatasource.rb +1 -0
- data/lib/configcat/rolloutevaluator.rb +2 -0
- data/lib/configcat/user.rb +3 -0
- data/lib/configcat/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4d2b7f4e5d47d25f547528e0168dd531f50c84e44a9a1e9f6634c5f68db29dd0
|
|
4
|
+
data.tar.gz: e92023320e954118f80842efd483efd9cdb6865d4ac33f8d5c02230b0b11a981
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e72cf2f0a18bb717468a45727f924af34fbdf2dd188e2605243250112494eca928903f984248bc3edf15cf9ea32bb2975bfbec055f5c40fb78675ecf70f71237
|
|
7
|
+
data.tar.gz: 8172327e157b399f1ad53b1583a474af2c03ad012796523c68522568872801c63949027b89ec823a6b9459811e3181e1760bb4484a0c07cbc5bbc2b5691bf0a5
|
|
@@ -28,9 +28,10 @@ module ConfigCat
|
|
|
28
28
|
client = @@instances[sdk_key]
|
|
29
29
|
if client
|
|
30
30
|
if options
|
|
31
|
+
masked_sdk_key = ConfigCatLogger.mask_sdk_key(sdk_key)
|
|
31
32
|
client.log.warn(3000, "There is an existing client instance for the specified SDK Key. " \
|
|
32
33
|
"No new client instance will be created and the specified options are ignored. " \
|
|
33
|
-
"Returning the existing client instance. SDK Key: '#{
|
|
34
|
+
"Returning the existing client instance. SDK Key: '#{masked_sdk_key}'.")
|
|
34
35
|
end
|
|
35
36
|
return client
|
|
36
37
|
end
|
|
@@ -98,7 +99,7 @@ module ConfigCat
|
|
|
98
99
|
read_timeout: options.read_timeout_seconds,
|
|
99
100
|
data_governance: options.data_governance)
|
|
100
101
|
|
|
101
|
-
@_config_service = ConfigService.new(@
|
|
102
|
+
@_config_service = ConfigService.new(@_sdk_key,
|
|
102
103
|
options.polling_mode,
|
|
103
104
|
@hooks,
|
|
104
105
|
@_config_fetcher,
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
module ConfigCat
|
|
2
2
|
class ConfigCatLogger
|
|
3
|
+
def self.mask_sdk_key(sdk_key)
|
|
4
|
+
num_chars_to_keep = 6
|
|
5
|
+
return sdk_key if sdk_key.length <= num_chars_to_keep
|
|
6
|
+
|
|
7
|
+
sdk_key[0, sdk_key.length - num_chars_to_keep].gsub(%r{[^/]}, '*') + sdk_key[-num_chars_to_keep, num_chars_to_keep]
|
|
8
|
+
end
|
|
9
|
+
|
|
3
10
|
def initialize(hooks)
|
|
4
11
|
@hooks = hooks
|
|
5
12
|
end
|
|
@@ -26,13 +26,14 @@ module ConfigCat
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
class FetchResponse
|
|
29
|
-
attr_reader :entry, :error, :is_transient_error
|
|
29
|
+
attr_reader :entry, :error, :is_transient_error, :cf_ray_id
|
|
30
30
|
|
|
31
|
-
def initialize(status, entry, error = nil, is_transient_error = false)
|
|
31
|
+
def initialize(status, entry, error = nil, is_transient_error = false, cf_ray_id = nil)
|
|
32
32
|
@status = status
|
|
33
33
|
@entry = entry
|
|
34
34
|
@error = error
|
|
35
35
|
@is_transient_error = is_transient_error
|
|
36
|
+
@cf_ray_id = cf_ray_id
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
# Gets whether a new configuration value was fetched or not.
|
|
@@ -53,19 +54,22 @@ module ConfigCat
|
|
|
53
54
|
@status == Status::FAILURE
|
|
54
55
|
end
|
|
55
56
|
|
|
56
|
-
def self.success(entry)
|
|
57
|
-
FetchResponse.new(Status::FETCHED, entry)
|
|
57
|
+
def self.success(entry, cf_ray_id = nil)
|
|
58
|
+
FetchResponse.new(Status::FETCHED, entry, nil, false, cf_ray_id)
|
|
58
59
|
end
|
|
59
60
|
|
|
60
|
-
def self.not_modified
|
|
61
|
-
FetchResponse.new(Status::NOT_MODIFIED, ConfigEntry::EMPTY)
|
|
61
|
+
def self.not_modified(cf_ray_id = nil)
|
|
62
|
+
FetchResponse.new(Status::NOT_MODIFIED, ConfigEntry::EMPTY, nil, false, cf_ray_id)
|
|
62
63
|
end
|
|
63
64
|
|
|
64
|
-
def self.failure(error, is_transient_error)
|
|
65
|
-
FetchResponse.new(Status::FAILURE, ConfigEntry::EMPTY, error, is_transient_error)
|
|
65
|
+
def self.failure(error, is_transient_error, cf_ray_id = nil)
|
|
66
|
+
FetchResponse.new(Status::FAILURE, ConfigEntry::EMPTY, error, is_transient_error, cf_ray_id)
|
|
66
67
|
end
|
|
67
68
|
end
|
|
68
69
|
|
|
70
|
+
RETRY_DELAY_SECONDS = 0.05
|
|
71
|
+
CONNECTION_RESET_THRESHOLD_SECONDS = 30
|
|
72
|
+
|
|
69
73
|
class ConfigFetcher
|
|
70
74
|
def initialize(sdk_key, log, mode, base_url: nil, proxy_address: nil, proxy_port: nil, proxy_user: nil, proxy_pass: nil,
|
|
71
75
|
open_timeout: 10, read_timeout: 30,
|
|
@@ -79,6 +83,7 @@ module ConfigCat
|
|
|
79
83
|
@_open_timeout = open_timeout
|
|
80
84
|
@_read_timeout = read_timeout
|
|
81
85
|
@_headers = { "User-Agent" => ((("ConfigCat-Ruby/") + mode) + ("-")) + VERSION, "X-ConfigCat-UserAgent" => ((("ConfigCat-Ruby/") + mode) + ("-")) + VERSION, "Content-Type" => "application/json" }
|
|
86
|
+
@_last_connection_reset = nil
|
|
82
87
|
if !base_url.equal?(nil)
|
|
83
88
|
@_base_url_overridden = true
|
|
84
89
|
@_base_url = base_url.chomp("/")
|
|
@@ -146,7 +151,9 @@ module ConfigCat
|
|
|
146
151
|
|
|
147
152
|
# To prevent loops we check if we retried at least 3 times with the new base_url
|
|
148
153
|
if retries >= 2
|
|
149
|
-
|
|
154
|
+
error = "Redirection loop encountered while trying to fetch config JSON. Please contact us at https://configcat.com/support/"
|
|
155
|
+
error = "%s (Ray ID: %s)" % [error, fetch_response.cf_ray_id] if fetch_response.cf_ray_id
|
|
156
|
+
@log.error(1104, error)
|
|
150
157
|
return fetch_response
|
|
151
158
|
end
|
|
152
159
|
|
|
@@ -163,6 +170,25 @@ module ConfigCat
|
|
|
163
170
|
private
|
|
164
171
|
|
|
165
172
|
def _fetch(etag)
|
|
173
|
+
response = _fetch_http(etag)
|
|
174
|
+
if response.is_failed && response.is_transient_error
|
|
175
|
+
_reset_http_if_needed
|
|
176
|
+
sleep(RETRY_DELAY_SECONDS)
|
|
177
|
+
response = _fetch_http(etag)
|
|
178
|
+
end
|
|
179
|
+
response
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def _reset_http_if_needed
|
|
183
|
+
now = Utils.get_utc_now_seconds_since_epoch
|
|
184
|
+
if @_last_connection_reset.nil? || now - @_last_connection_reset >= CONNECTION_RESET_THRESHOLD_SECONDS
|
|
185
|
+
close
|
|
186
|
+
@_last_connection_reset = now
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def _fetch_http(etag)
|
|
191
|
+
response_cf_ray_id = nil
|
|
166
192
|
begin
|
|
167
193
|
@log.debug("Fetching configuration from ConfigCat")
|
|
168
194
|
uri = URI.parse((((@_base_url + ("/")) + BASE_PATH) + @_sdk_key) + BASE_EXTENSION)
|
|
@@ -170,40 +196,57 @@ module ConfigCat
|
|
|
170
196
|
headers["If-None-Match"] = etag.empty? ? nil : etag
|
|
171
197
|
_create_http()
|
|
172
198
|
request = Net::HTTP::Get.new(uri.request_uri, headers)
|
|
173
|
-
response = @_http.request(request)
|
|
199
|
+
response = @_http.request(request) do |res|
|
|
200
|
+
# Capture the response before reading the body, so headers remain accessible even if the body download fails
|
|
201
|
+
response_cf_ray_id = res["CF-RAY"]
|
|
202
|
+
res.read_body
|
|
203
|
+
end
|
|
204
|
+
|
|
174
205
|
case response
|
|
175
|
-
when Net::
|
|
206
|
+
when Net::HTTPOK
|
|
176
207
|
@log.debug("ConfigCat configuration json fetch response code:#{response.code} Cached:#{response['ETag']}")
|
|
208
|
+
content = response.body
|
|
209
|
+
if content.nil? || content.empty?
|
|
210
|
+
raise ArgumentError.new("Config JSON content cannot be null or empty.")
|
|
211
|
+
end
|
|
212
|
+
config = JSON.parse(content)
|
|
213
|
+
if config.nil?
|
|
214
|
+
raise ArgumentError.new("Invalid config JSON content: #{content}")
|
|
215
|
+
end
|
|
216
|
+
Config.fixup_config_salt_and_segments(config)
|
|
177
217
|
response_etag = response["ETag"]
|
|
178
218
|
if response_etag.nil?
|
|
179
219
|
response_etag = ""
|
|
180
220
|
end
|
|
181
|
-
config
|
|
182
|
-
Config.fixup_config_salt_and_segments(config)
|
|
183
|
-
return FetchResponse.success(ConfigEntry.new(config, response_etag, response.body, Utils.get_utc_now_seconds_since_epoch))
|
|
221
|
+
return FetchResponse.success(ConfigEntry.new(config, response_etag, response.body, Utils.get_utc_now_seconds_since_epoch), response_cf_ray_id)
|
|
184
222
|
when Net::HTTPNotModified
|
|
185
|
-
return FetchResponse.not_modified
|
|
223
|
+
return FetchResponse.not_modified(response_cf_ray_id)
|
|
186
224
|
when Net::HTTPNotFound, Net::HTTPForbidden
|
|
187
|
-
|
|
225
|
+
masked_sdk_key = ConfigCatLogger.mask_sdk_key(@_sdk_key)
|
|
226
|
+
error = "Your SDK Key seems to be wrong: '#{masked_sdk_key}'. You can find the valid SDK Key at https://app.configcat.com/sdkkey. Received unexpected response: #{response}"
|
|
227
|
+
error = "%s (Ray ID: %s)" % [error, response_cf_ray_id] if response_cf_ray_id
|
|
188
228
|
@log.error(1100, error)
|
|
189
|
-
return FetchResponse.failure(error, false)
|
|
229
|
+
return FetchResponse.failure(error, false, response_cf_ray_id)
|
|
190
230
|
else
|
|
191
231
|
raise Net::HTTPError.new("", response)
|
|
192
232
|
end
|
|
193
233
|
rescue Net::HTTPError => e
|
|
194
234
|
error = "Unexpected HTTP response was received while trying to fetch config JSON: #{e}"
|
|
235
|
+
error = "%s (Ray ID: %s)" % [error, response_cf_ray_id] if response_cf_ray_id
|
|
195
236
|
@log.error(1101, error)
|
|
196
|
-
return FetchResponse.failure(error, true)
|
|
237
|
+
return FetchResponse.failure(error, true, response_cf_ray_id)
|
|
197
238
|
rescue Timeout::Error => e
|
|
198
239
|
error = "Request timed out while trying to fetch config JSON. Timeout values: [connect: #{get_open_timeout()}s, read: #{get_read_timeout()}s]"
|
|
240
|
+
error = "%s (Ray ID: %s)" % [error, response_cf_ray_id] if response_cf_ray_id
|
|
199
241
|
@log.error(1102, error)
|
|
200
|
-
return FetchResponse.failure(error, true)
|
|
242
|
+
return FetchResponse.failure(error, true, response_cf_ray_id)
|
|
201
243
|
rescue Exception => e
|
|
202
244
|
error = "Unexpected error occurred while trying to fetch config JSON. It is most likely due to a local network " \
|
|
203
245
|
"issue. Please make sure your application can reach the ConfigCat CDN servers (or your proxy server) " \
|
|
204
246
|
"over HTTP. #{e}"
|
|
247
|
+
error = "%s (Ray ID: %s)" % [error, response_cf_ray_id] if response_cf_ray_id
|
|
205
248
|
@log.error(1103, error)
|
|
206
|
-
return FetchResponse.failure(error, true)
|
|
249
|
+
return FetchResponse.failure(error, true, response_cf_ray_id)
|
|
207
250
|
end
|
|
208
251
|
end
|
|
209
252
|
|
data/lib/configcat/user.rb
CHANGED
data/lib/configcat/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: configcat
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 10.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ConfigCat
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09
|
|
11
|
+
date: 2026-10-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: concurrent-ruby
|