ldclient-rb 5.5.2 → 5.5.3
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/CHANGELOG.md +9 -0
- data/Gemfile.lock +3 -9
- data/README.md +29 -29
- data/azure-pipelines.yml +51 -0
- data/ldclient-rb.gemspec +0 -3
- data/lib/ldclient-rb/cache_store.rb +2 -4
- data/lib/ldclient-rb/config.rb +4 -18
- data/lib/ldclient-rb/events.rb +31 -20
- data/lib/ldclient-rb/polling.rb +2 -3
- data/lib/ldclient-rb/requestor.rb +59 -22
- data/lib/ldclient-rb/stream.rb +0 -1
- data/lib/ldclient-rb/util.rb +10 -0
- data/lib/ldclient-rb/version.rb +1 -1
- data/spec/events_spec.rb +62 -18
- data/spec/file_data_source_spec.rb +5 -5
- data/spec/http_util.rb +6 -0
- data/spec/requestor_spec.rb +205 -50
- metadata +3 -62
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8e5398b81dc216177803939d535b130edbb0361a
|
4
|
+
data.tar.gz: 4c50f29da29da753db0a36064c29a22191960808
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 894d4f29200b6a5df70608942f15223a9b9d5bdeb426913f3afe9feb7d23b02b2ef544874ba7c39570b3f8e8c098caafab638ff9b779b9aec69ca444d1115b62
|
7
|
+
data.tar.gz: 41c088debbc36d16b7518b20af0ddc4c9b488e0da16b9e465d573ab6e715f00612fb53ee4fe492692cf0d065aa16c3e85ba2d17314c1c326110415ac4667b310
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,15 @@
|
|
2
2
|
|
3
3
|
All notable changes to the LaunchDarkly Ruby SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org).
|
4
4
|
|
5
|
+
## [5.5.3] - 2019-02-13
|
6
|
+
### Changed:
|
7
|
+
- The SDK previously used the `faraday` and `net-http-persistent` gems for all HTTP requests other than streaming connections. Since `faraday` lacks a stable version and has a known issue with character encoding, and `net-http-persistent` is no longer maintained, these have both been removed. This should not affect any SDK functionality.
|
8
|
+
|
9
|
+
### Fixed:
|
10
|
+
- The SDK was not usable in Windows because of `net-http-persistent`. That gem has been removed.
|
11
|
+
- When running in Windows, the event-processing thread threw a `RangeError` due to a difference in the Windows implementation of `concurrent-ruby`. This has been fixed.
|
12
|
+
- Windows incompatibilities were undetected before because we were not running a Windows CI job. We are now testing on Windows with Ruby 2.5.
|
13
|
+
|
5
14
|
## [5.5.2] - 2019-01-18
|
6
15
|
### Fixed:
|
7
16
|
- Like 5.5.1, this release contains only documentation fixes. Implementation classes that are not part of the supported API are now hidden from the [generated documentation](https://www.rubydoc.info/gems/ldclient-rb).
|
data/Gemfile.lock
CHANGED
@@ -1,13 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
ldclient-rb (5.
|
4
|
+
ldclient-rb (5.5.2)
|
5
5
|
concurrent-ruby (~> 1.0)
|
6
|
-
faraday (>= 0.9, < 2)
|
7
|
-
faraday-http-cache (>= 1.3.0, < 3)
|
8
6
|
json (>= 1.8, < 3)
|
9
7
|
ld-eventsource (~> 1.0)
|
10
|
-
net-http-persistent (>= 2.9, < 4.0)
|
11
8
|
semantic (~> 1.6)
|
12
9
|
|
13
10
|
GEM
|
@@ -35,11 +32,10 @@ GEM
|
|
35
32
|
docile (1.1.5)
|
36
33
|
faraday (0.15.4)
|
37
34
|
multipart-post (>= 1.2, < 3)
|
38
|
-
faraday-http-cache (2.0.0)
|
39
|
-
faraday (~> 0.8)
|
40
35
|
ffi (1.9.25)
|
41
36
|
ffi (1.9.25-java)
|
42
|
-
hitimes (1.3.
|
37
|
+
hitimes (1.3.1)
|
38
|
+
hitimes (1.3.1-java)
|
43
39
|
http_tools (0.4.5)
|
44
40
|
jmespath (1.4.0)
|
45
41
|
json (1.8.6)
|
@@ -53,8 +49,6 @@ GEM
|
|
53
49
|
rb-inotify (~> 0.9, >= 0.9.7)
|
54
50
|
ruby_dep (~> 1.2)
|
55
51
|
multipart-post (2.0.0)
|
56
|
-
net-http-persistent (3.0.0)
|
57
|
-
connection_pool (~> 2.2)
|
58
52
|
rake (10.5.0)
|
59
53
|
rb-fsevent (0.10.3)
|
60
54
|
rb-inotify (0.9.10)
|
data/README.md
CHANGED
@@ -17,19 +17,19 @@ Quick setup
|
|
17
17
|
|
18
18
|
1. Install the Ruby SDK with `gem`
|
19
19
|
|
20
|
-
|
20
|
+
```shell
|
21
21
|
gem install ldclient-rb
|
22
22
|
```
|
23
23
|
|
24
24
|
2. Require the LaunchDarkly client:
|
25
25
|
|
26
|
-
|
26
|
+
```ruby
|
27
27
|
require 'ldclient-rb'
|
28
28
|
```
|
29
29
|
|
30
30
|
3. Create a new LDClient with your SDK key:
|
31
31
|
|
32
|
-
|
32
|
+
```ruby
|
33
33
|
client = LaunchDarkly::LDClient.new("your_sdk_key")
|
34
34
|
```
|
35
35
|
|
@@ -39,42 +39,42 @@ client = LaunchDarkly::LDClient.new("your_sdk_key")
|
|
39
39
|
|
40
40
|
2. Initialize the launchdarkly client in `config/initializers/launchdarkly.rb`:
|
41
41
|
|
42
|
-
|
42
|
+
```ruby
|
43
43
|
Rails.configuration.ld_client = LaunchDarkly::LDClient.new("your_sdk_key")
|
44
44
|
```
|
45
45
|
|
46
46
|
3. You may want to include a function in your ApplicationController
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
65
|
-
# session ids should be private to prevent session hijacking
|
66
|
-
hash_key = Digest::SHA256.base64digest hash_key
|
67
|
-
{
|
68
|
-
key: hash_key,
|
69
|
-
anonymous: true,
|
70
|
-
}
|
71
|
-
end
|
48
|
+
```ruby
|
49
|
+
def launchdarkly_settings
|
50
|
+
if current_user.present?
|
51
|
+
{
|
52
|
+
key: current_user.id,
|
53
|
+
anonymous: false,
|
54
|
+
email: current_user.email,
|
55
|
+
custom: { groups: current_user.groups.pluck(:name) },
|
56
|
+
# Any other fields you may have
|
57
|
+
# e.g. lastName: current_user.last_name,
|
58
|
+
}
|
59
|
+
else
|
60
|
+
if Rails::VERSION::MAJOR <= 3
|
61
|
+
hash_key = request.session_options[:id]
|
62
|
+
else
|
63
|
+
hash_key = session.id
|
72
64
|
end
|
65
|
+
# session ids should be private to prevent session hijacking
|
66
|
+
hash_key = Digest::SHA256.base64digest hash_key
|
67
|
+
{
|
68
|
+
key: hash_key,
|
69
|
+
anonymous: true,
|
70
|
+
}
|
71
|
+
end
|
72
|
+
end
|
73
73
|
```
|
74
74
|
|
75
75
|
4. In your controllers, access the client using
|
76
76
|
|
77
|
-
|
77
|
+
```ruby
|
78
78
|
Rails.application.config.ld_client.variation('your.flag.key', launchdarkly_settings, false)
|
79
79
|
```
|
80
80
|
|
data/azure-pipelines.yml
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
jobs:
|
2
|
+
- job: build
|
3
|
+
pool:
|
4
|
+
vmImage: 'vs2017-win2016'
|
5
|
+
steps:
|
6
|
+
- task: PowerShell@2
|
7
|
+
displayName: 'Setup Dynamo'
|
8
|
+
inputs:
|
9
|
+
targetType: inline
|
10
|
+
workingDirectory: $(System.DefaultWorkingDirectory)
|
11
|
+
script: |
|
12
|
+
iwr -outf dynamo.zip https://s3-us-west-2.amazonaws.com/dynamodb-local/dynamodb_local_latest.zip
|
13
|
+
mkdir dynamo
|
14
|
+
Expand-Archive -Path dynamo.zip -DestinationPath dynamo
|
15
|
+
cd dynamo
|
16
|
+
javaw -D"java.library.path=./DynamoDBLocal_lib" -jar DynamoDBLocal.jar
|
17
|
+
- task: PowerShell@2
|
18
|
+
displayName: 'Setup Consul'
|
19
|
+
inputs:
|
20
|
+
targetType: inline
|
21
|
+
workingDirectory: $(System.DefaultWorkingDirectory)
|
22
|
+
script: |
|
23
|
+
iwr -outf consul.zip https://releases.hashicorp.com/consul/1.4.2/consul_1.4.2_windows_amd64.zip
|
24
|
+
mkdir consul
|
25
|
+
Expand-Archive -Path consul.zip -DestinationPath consul
|
26
|
+
cd consul
|
27
|
+
sc.exe create "Consul" binPath="$(System.DefaultWorkingDirectory)/consul/consul.exe agent -dev"
|
28
|
+
sc.exe start "Consul"
|
29
|
+
- task: PowerShell@2
|
30
|
+
displayName: 'Setup Redis'
|
31
|
+
inputs:
|
32
|
+
targetType: inline
|
33
|
+
workingDirectory: $(System.DefaultWorkingDirectory)
|
34
|
+
script: |
|
35
|
+
iwr -outf redis.zip https://github.com/MicrosoftArchive/redis/releases/download/win-3.0.504/Redis-x64-3.0.504.zip
|
36
|
+
mkdir redis
|
37
|
+
Expand-Archive -Path redis.zip -DestinationPath redis
|
38
|
+
cd redis
|
39
|
+
./redis-server --service-install
|
40
|
+
./redis-server --service-start
|
41
|
+
- task: PowerShell@2
|
42
|
+
displayName: 'Setup SDK and Test'
|
43
|
+
inputs:
|
44
|
+
targetType: inline
|
45
|
+
workingDirectory: $(System.DefaultWorkingDirectory)
|
46
|
+
script: |
|
47
|
+
ruby -v
|
48
|
+
gem install bundler -v 1.17.3
|
49
|
+
bundle install
|
50
|
+
mkdir rspec
|
51
|
+
bundle exec rspec --format progress --format RspecJunitFormatter -o ./rspec/rspec.xml spec
|
data/ldclient-rb.gemspec
CHANGED
@@ -34,10 +34,7 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_development_dependency "listen", "~> 3.0" # see file_data_source.rb
|
35
35
|
|
36
36
|
spec.add_runtime_dependency "json", [">= 1.8", "< 3"]
|
37
|
-
spec.add_runtime_dependency "faraday", [">= 0.9", "< 2"]
|
38
|
-
spec.add_runtime_dependency "faraday-http-cache", [">= 1.3.0", "< 3"]
|
39
37
|
spec.add_runtime_dependency "semantic", "~> 1.6"
|
40
|
-
spec.add_runtime_dependency "net-http-persistent", [">= 2.9", "< 4.0"]
|
41
38
|
spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
|
42
39
|
spec.add_runtime_dependency "ld-eventsource", '~> 1.0'
|
43
40
|
end
|
@@ -2,11 +2,9 @@ require "concurrent/map"
|
|
2
2
|
|
3
3
|
module LaunchDarkly
|
4
4
|
#
|
5
|
-
# A thread-safe in-memory store
|
6
|
-
#
|
5
|
+
# A thread-safe in-memory store that uses the same semantics that Faraday would expect, although we
|
6
|
+
# no longer use Faraday. This is used by Requestor, when we are not in a Rails environment.
|
7
7
|
#
|
8
|
-
# @see https://github.com/plataformatec/faraday-http-cache
|
9
|
-
# @see https://github.com/ruby-concurrency
|
10
8
|
# @private
|
11
9
|
#
|
12
10
|
class ThreadSafeMemoryStore
|
data/lib/ldclient-rb/config.rb
CHANGED
@@ -53,7 +53,6 @@ module LaunchDarkly
|
|
53
53
|
@use_ldd = opts.has_key?(:use_ldd) ? opts[:use_ldd] : Config.default_use_ldd
|
54
54
|
@offline = opts.has_key?(:offline) ? opts[:offline] : Config.default_offline
|
55
55
|
@poll_interval = opts.has_key?(:poll_interval) && opts[:poll_interval] > Config.default_poll_interval ? opts[:poll_interval] : Config.default_poll_interval
|
56
|
-
@proxy = opts[:proxy] || Config.default_proxy
|
57
56
|
@all_attributes_private = opts[:all_attributes_private] || false
|
58
57
|
@private_attribute_names = opts[:private_attribute_names] || []
|
59
58
|
@send_events = opts.has_key?(:send_events) ? opts[:send_events] : Config.default_send_events
|
@@ -153,9 +152,10 @@ module LaunchDarkly
|
|
153
152
|
attr_reader :capacity
|
154
153
|
|
155
154
|
#
|
156
|
-
# A store for HTTP caching. This must support the semantics used by
|
157
|
-
# [`faraday-http-cache`](https://github.com/plataformatec/faraday-http-cache) gem
|
158
|
-
# to the Rails cache in a Rails environment, or a
|
155
|
+
# A store for HTTP caching (used only in polling mode). This must support the semantics used by
|
156
|
+
# the [`faraday-http-cache`](https://github.com/plataformatec/faraday-http-cache) gem, although
|
157
|
+
# the SDK no longer uses Faraday. Defaults to the Rails cache in a Rails environment, or a
|
158
|
+
# thread-safe in-memory store otherwise.
|
159
159
|
# @return [Object]
|
160
160
|
#
|
161
161
|
attr_reader :cache_store
|
@@ -184,12 +184,6 @@ module LaunchDarkly
|
|
184
184
|
#
|
185
185
|
attr_reader :feature_store
|
186
186
|
|
187
|
-
#
|
188
|
-
# The proxy configuration string.
|
189
|
-
# @return [String]
|
190
|
-
#
|
191
|
-
attr_reader :proxy
|
192
|
-
|
193
187
|
#
|
194
188
|
# True if all user attributes (other than the key) should be considered private. This means
|
195
189
|
# that the attribute values will not be sent to LaunchDarkly in analytics events and will not
|
@@ -336,14 +330,6 @@ module LaunchDarkly
|
|
336
330
|
2
|
337
331
|
end
|
338
332
|
|
339
|
-
#
|
340
|
-
# The default value for {#proxy}.
|
341
|
-
# @return [String] nil
|
342
|
-
#
|
343
|
-
def self.default_proxy
|
344
|
-
nil
|
345
|
-
end
|
346
|
-
|
347
333
|
#
|
348
334
|
# The default value for {#logger}.
|
349
335
|
# @return [Logger] the Rails logger if in Rails, or a default Logger at WARN level otherwise
|
data/lib/ldclient-rb/events.rb
CHANGED
@@ -3,7 +3,6 @@ require "concurrent/atomics"
|
|
3
3
|
require "concurrent/executors"
|
4
4
|
require "thread"
|
5
5
|
require "time"
|
6
|
-
require "faraday"
|
7
6
|
|
8
7
|
module LaunchDarkly
|
9
8
|
MAX_FLUSH_WORKERS = 5
|
@@ -115,11 +114,17 @@ module LaunchDarkly
|
|
115
114
|
def initialize(queue, sdk_key, config, client)
|
116
115
|
@sdk_key = sdk_key
|
117
116
|
@config = config
|
118
|
-
|
117
|
+
|
118
|
+
if client
|
119
|
+
@client = client
|
120
|
+
else
|
121
|
+
@client = Util.new_http_client(@config.events_uri, @config)
|
122
|
+
end
|
123
|
+
|
119
124
|
@user_keys = SimpleLRUCacheSet.new(config.user_keys_capacity)
|
120
125
|
@formatter = EventOutputFormatter.new(config)
|
121
126
|
@disabled = Concurrent::AtomicBoolean.new(false)
|
122
|
-
@last_known_past_time = Concurrent::
|
127
|
+
@last_known_past_time = Concurrent::AtomicReference.new(0)
|
123
128
|
|
124
129
|
buffer = EventBuffer.new(config.capacity, config.logger)
|
125
130
|
flush_workers = NonBlockingThreadPool.new(MAX_FLUSH_WORKERS)
|
@@ -162,7 +167,10 @@ module LaunchDarkly
|
|
162
167
|
def do_shutdown(flush_workers)
|
163
168
|
flush_workers.shutdown
|
164
169
|
flush_workers.wait_for_termination
|
165
|
-
|
170
|
+
begin
|
171
|
+
@client.finish
|
172
|
+
rescue
|
173
|
+
end
|
166
174
|
end
|
167
175
|
|
168
176
|
def synchronize_for_testing(flush_workers)
|
@@ -246,16 +254,17 @@ module LaunchDarkly
|
|
246
254
|
end
|
247
255
|
|
248
256
|
def handle_response(res)
|
249
|
-
|
250
|
-
|
257
|
+
status = res.code.to_i
|
258
|
+
if status >= 400
|
259
|
+
message = Util.http_error_message(status, "event delivery", "some events were dropped")
|
251
260
|
@config.logger.error { "[LDClient] #{message}" }
|
252
|
-
if !Util.http_error_recoverable?(
|
261
|
+
if !Util.http_error_recoverable?(status)
|
253
262
|
@disabled.value = true
|
254
263
|
end
|
255
264
|
else
|
256
|
-
if !res.
|
265
|
+
if !res["date"].nil?
|
257
266
|
begin
|
258
|
-
res_time = (Time.httpdate(res
|
267
|
+
res_time = (Time.httpdate(res["date"]).to_f * 1000).to_i
|
259
268
|
@last_known_past_time.value = res_time
|
260
269
|
rescue ArgumentError
|
261
270
|
end
|
@@ -316,22 +325,24 @@ module LaunchDarkly
|
|
316
325
|
sleep(1)
|
317
326
|
end
|
318
327
|
begin
|
328
|
+
client.start if !client.started?
|
319
329
|
config.logger.debug { "[LDClient] sending #{events_out.length} events: #{body}" }
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
330
|
+
uri = URI(config.events_uri + "/bulk")
|
331
|
+
req = Net::HTTP::Post.new(uri)
|
332
|
+
req.content_type = "application/json"
|
333
|
+
req.body = body
|
334
|
+
req["Authorization"] = sdk_key
|
335
|
+
req["User-Agent"] = "RubyClient/" + LaunchDarkly::VERSION
|
336
|
+
req["X-LaunchDarkly-Event-Schema"] = CURRENT_SCHEMA_VERSION.to_s
|
337
|
+
req["Connection"] = "keep-alive"
|
338
|
+
res = client.request(req)
|
329
339
|
rescue StandardError => exn
|
330
340
|
config.logger.warn { "[LDClient] Error flushing events: #{exn.inspect}." }
|
331
341
|
next
|
332
342
|
end
|
333
|
-
|
334
|
-
|
343
|
+
status = res.code.to_i
|
344
|
+
if status < 200 || status >= 300
|
345
|
+
if Util.http_error_recoverable?(status)
|
335
346
|
next
|
336
347
|
end
|
337
348
|
end
|
data/lib/ldclient-rb/polling.rb
CHANGED
@@ -26,7 +26,7 @@ module LaunchDarkly
|
|
26
26
|
|
27
27
|
def stop
|
28
28
|
if @stopped.make_true
|
29
|
-
if @worker && @worker.alive?
|
29
|
+
if @worker && @worker.alive? && @worker != Thread.current
|
30
30
|
@worker.run # causes the thread to wake up if it's currently in a sleep
|
31
31
|
@worker.join
|
32
32
|
end
|
@@ -63,8 +63,7 @@ module LaunchDarkly
|
|
63
63
|
stop
|
64
64
|
end
|
65
65
|
rescue StandardError => exn
|
66
|
-
@config.logger
|
67
|
-
# TODO: log_exception(__method__.to_s, exn)
|
66
|
+
Util.log_exception(@config.logger, "Exception while polling", exn)
|
68
67
|
end
|
69
68
|
delta = @config.poll_interval - (Time.now - started_at)
|
70
69
|
if delta > 0
|
@@ -1,12 +1,13 @@
|
|
1
|
+
require "concurrent/atomics"
|
1
2
|
require "json"
|
2
|
-
require "
|
3
|
-
require "faraday/http_cache"
|
3
|
+
require "uri"
|
4
4
|
|
5
5
|
module LaunchDarkly
|
6
6
|
# @private
|
7
7
|
class UnexpectedResponseError < StandardError
|
8
8
|
def initialize(status)
|
9
9
|
@status = status
|
10
|
+
super("HTTP error #{status}")
|
10
11
|
end
|
11
12
|
|
12
13
|
def status
|
@@ -16,14 +17,13 @@ module LaunchDarkly
|
|
16
17
|
|
17
18
|
# @private
|
18
19
|
class Requestor
|
20
|
+
CacheEntry = Struct.new(:etag, :body)
|
21
|
+
|
19
22
|
def initialize(sdk_key, config)
|
20
23
|
@sdk_key = sdk_key
|
21
24
|
@config = config
|
22
|
-
@client =
|
23
|
-
|
24
|
-
|
25
|
-
builder.adapter :net_http_persistent
|
26
|
-
end
|
25
|
+
@client = Util.new_http_client(@config.base_uri, @config)
|
26
|
+
@cache = @config.cache_store
|
27
27
|
end
|
28
28
|
|
29
29
|
def request_flag(key)
|
@@ -38,27 +38,64 @@ module LaunchDarkly
|
|
38
38
|
make_request("/sdk/latest-all")
|
39
39
|
end
|
40
40
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
req.headers["User-Agent"] = "RubyClient/" + LaunchDarkly::VERSION
|
46
|
-
req.options.timeout = @config.read_timeout
|
47
|
-
req.options.open_timeout = @config.connect_timeout
|
48
|
-
if @config.proxy
|
49
|
-
req.options.proxy = Faraday::ProxyOptions.from @config.proxy
|
50
|
-
end
|
41
|
+
def stop
|
42
|
+
begin
|
43
|
+
@client.finish
|
44
|
+
rescue
|
51
45
|
end
|
46
|
+
end
|
52
47
|
|
53
|
-
|
48
|
+
private
|
54
49
|
|
55
|
-
|
56
|
-
|
50
|
+
def make_request(path)
|
51
|
+
@client.start if !@client.started?
|
52
|
+
uri = URI(@config.base_uri + path)
|
53
|
+
req = Net::HTTP::Get.new(uri)
|
54
|
+
req["Authorization"] = @sdk_key
|
55
|
+
req["User-Agent"] = "RubyClient/" + LaunchDarkly::VERSION
|
56
|
+
req["Connection"] = "keep-alive"
|
57
|
+
cached = @cache.read(uri)
|
58
|
+
if !cached.nil?
|
59
|
+
req["If-None-Match"] = cached.etag
|
57
60
|
end
|
61
|
+
res = @client.request(req)
|
62
|
+
status = res.code.to_i
|
63
|
+
@config.logger.debug { "[LDClient] Got response from uri: #{uri}\n\tstatus code: #{status}\n\theaders: #{res.to_hash}\n\tbody: #{res.body}" }
|
58
64
|
|
59
|
-
|
65
|
+
if status == 304 && !cached.nil?
|
66
|
+
body = cached.body
|
67
|
+
else
|
68
|
+
@cache.delete(uri)
|
69
|
+
if status < 200 || status >= 300
|
70
|
+
raise UnexpectedResponseError.new(status)
|
71
|
+
end
|
72
|
+
body = fix_encoding(res.body, res["content-type"])
|
73
|
+
etag = res["etag"]
|
74
|
+
@cache.write(uri, CacheEntry.new(etag, body)) if !etag.nil?
|
75
|
+
end
|
76
|
+
JSON.parse(body, symbolize_names: true)
|
77
|
+
end
|
78
|
+
|
79
|
+
def fix_encoding(body, content_type)
|
80
|
+
return body if content_type.nil?
|
81
|
+
media_type, charset = parse_content_type(content_type)
|
82
|
+
return body if charset.nil?
|
83
|
+
body.force_encoding(Encoding::find(charset)).encode(Encoding::UTF_8)
|
60
84
|
end
|
61
85
|
|
62
|
-
|
86
|
+
def parse_content_type(value)
|
87
|
+
return [nil, nil] if value.nil? || value == ''
|
88
|
+
parts = value.split(/; */)
|
89
|
+
return [value, nil] if parts.count < 2
|
90
|
+
charset = nil
|
91
|
+
parts.each do |part|
|
92
|
+
fields = part.split('=')
|
93
|
+
if fields.count >= 2 && fields[0] == 'charset'
|
94
|
+
charset = fields[1]
|
95
|
+
break
|
96
|
+
end
|
97
|
+
end
|
98
|
+
return [parts[0], charset]
|
99
|
+
end
|
63
100
|
end
|
64
101
|
end
|
data/lib/ldclient-rb/stream.rb
CHANGED
data/lib/ldclient-rb/util.rb
CHANGED
@@ -1,7 +1,17 @@
|
|
1
|
+
require "uri"
|
1
2
|
|
2
3
|
module LaunchDarkly
|
3
4
|
# @private
|
4
5
|
module Util
|
6
|
+
def self.new_http_client(uri_s, config)
|
7
|
+
uri = URI(uri_s)
|
8
|
+
client = Net::HTTP.new(uri.hostname, uri.port)
|
9
|
+
client.use_ssl = true if uri.scheme == "https"
|
10
|
+
client.open_timeout = config.connect_timeout
|
11
|
+
client.read_timeout = config.read_timeout
|
12
|
+
client
|
13
|
+
end
|
14
|
+
|
5
15
|
def self.log_exception(logger, message, exc)
|
6
16
|
logger.error { "[LDClient] #{message}: #{exc.inspect}" }
|
7
17
|
logger.debug { "[LDClient] Exception trace: #{exc.backtrace}" }
|
data/lib/ldclient-rb/version.rb
CHANGED
data/spec/events_spec.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
+
require "http_util"
|
1
2
|
require "spec_helper"
|
2
|
-
require "faraday"
|
3
3
|
require "time"
|
4
4
|
|
5
5
|
describe LaunchDarkly::EventProcessor do
|
@@ -348,7 +348,7 @@ describe LaunchDarkly::EventProcessor do
|
|
348
348
|
@ep.flush
|
349
349
|
@ep.wait_until_inactive
|
350
350
|
|
351
|
-
expect(hc.get_request
|
351
|
+
expect(hc.get_request["authorization"]).to eq "sdk_key"
|
352
352
|
end
|
353
353
|
|
354
354
|
def verify_unrecoverable_http_error(status)
|
@@ -414,7 +414,7 @@ describe LaunchDarkly::EventProcessor do
|
|
414
414
|
e = { kind: "identify", user: user }
|
415
415
|
@ep.add_event(e)
|
416
416
|
|
417
|
-
hc.set_exception(
|
417
|
+
hc.set_exception(IOError.new("deliberate error"))
|
418
418
|
@ep.flush
|
419
419
|
@ep.wait_until_inactive
|
420
420
|
|
@@ -423,6 +423,46 @@ describe LaunchDarkly::EventProcessor do
|
|
423
423
|
expect(hc.get_request).to be_nil # no 3rd request
|
424
424
|
end
|
425
425
|
|
426
|
+
it "makes actual HTTP request with correct headers" do
|
427
|
+
e = { kind: "identify", key: user[:key], user: user }
|
428
|
+
with_server do |server|
|
429
|
+
server.setup_ok_response("/bulk", "")
|
430
|
+
|
431
|
+
@ep = subject.new("sdk_key", LaunchDarkly::Config.new(events_uri: server.base_uri.to_s))
|
432
|
+
@ep.add_event(e)
|
433
|
+
@ep.flush
|
434
|
+
|
435
|
+
req = server.await_request
|
436
|
+
expect(req.header).to include({
|
437
|
+
"authorization" => [ "sdk_key" ],
|
438
|
+
"content-type" => [ "application/json" ],
|
439
|
+
"user-agent" => [ "RubyClient/" + LaunchDarkly::VERSION ],
|
440
|
+
"x-launchdarkly-event-schema" => [ "3" ]
|
441
|
+
})
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
it "can use a proxy server" do
|
446
|
+
e = { kind: "identify", key: user[:key], user: user }
|
447
|
+
with_server do |server|
|
448
|
+
server.setup_ok_response("/bulk", "")
|
449
|
+
|
450
|
+
with_server(StubProxyServer.new) do |proxy|
|
451
|
+
begin
|
452
|
+
ENV["http_proxy"] = proxy.base_uri.to_s
|
453
|
+
@ep = subject.new("sdk_key", LaunchDarkly::Config.new(events_uri: server.base_uri.to_s))
|
454
|
+
@ep.add_event(e)
|
455
|
+
@ep.flush
|
456
|
+
|
457
|
+
req = server.await_request
|
458
|
+
expect(req["content-type"]).to eq("application/json")
|
459
|
+
ensure
|
460
|
+
ENV["http_proxy"] = nil
|
461
|
+
end
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
426
466
|
def index_event(e, user)
|
427
467
|
{
|
428
468
|
kind: "index",
|
@@ -496,38 +536,42 @@ describe LaunchDarkly::EventProcessor do
|
|
496
536
|
@status = 200
|
497
537
|
end
|
498
538
|
|
499
|
-
def
|
500
|
-
req = Faraday::Request.create("POST")
|
501
|
-
req.headers = {}
|
502
|
-
req.options = Faraday::RequestOptions.new
|
503
|
-
yield req
|
539
|
+
def request(req)
|
504
540
|
@requests.push(req)
|
505
541
|
if @exception
|
506
542
|
raise @exception
|
507
543
|
else
|
508
|
-
resp = Faraday::Response.new
|
509
544
|
headers = {}
|
510
545
|
if @server_time
|
511
546
|
headers["Date"] = @server_time.httpdate
|
512
547
|
end
|
513
|
-
|
514
|
-
status: @status ? @status : 200,
|
515
|
-
response_headers: headers
|
516
|
-
})
|
517
|
-
resp
|
548
|
+
FakeResponse.new(@status ? @status : 200, headers)
|
518
549
|
end
|
519
550
|
end
|
520
551
|
|
552
|
+
def start
|
553
|
+
end
|
554
|
+
|
555
|
+
def started?
|
556
|
+
false
|
557
|
+
end
|
558
|
+
|
559
|
+
def finish
|
560
|
+
end
|
561
|
+
|
521
562
|
def get_request
|
522
563
|
@requests.shift
|
523
564
|
end
|
524
565
|
end
|
525
566
|
|
526
567
|
class FakeResponse
|
527
|
-
|
528
|
-
@status = status
|
529
|
-
end
|
568
|
+
include Net::HTTPHeader
|
530
569
|
|
531
|
-
attr_reader :
|
570
|
+
attr_reader :code
|
571
|
+
|
572
|
+
def initialize(status, headers)
|
573
|
+
@code = status.to_s
|
574
|
+
initialize_http_header(headers)
|
575
|
+
end
|
532
576
|
end
|
533
577
|
end
|
@@ -74,7 +74,7 @@ flagValues:
|
|
74
74
|
segments:
|
75
75
|
seg1:
|
76
76
|
key: seg1
|
77
|
-
include: ["user1"]
|
77
|
+
include: ["user1"]
|
78
78
|
EOF
|
79
79
|
}
|
80
80
|
|
@@ -87,7 +87,7 @@ EOF
|
|
87
87
|
end
|
88
88
|
|
89
89
|
after do
|
90
|
-
FileUtils.
|
90
|
+
FileUtils.rm_rf(@tmp_dir)
|
91
91
|
end
|
92
92
|
|
93
93
|
def make_temp_file(content)
|
@@ -198,10 +198,10 @@ EOF
|
|
198
198
|
event = ds.start
|
199
199
|
expect(event.set?).to eq(true)
|
200
200
|
expect(@store.all(LaunchDarkly::SEGMENTS).keys).to eq([])
|
201
|
-
|
201
|
+
|
202
202
|
sleep(1)
|
203
203
|
IO.write(file, all_properties_json)
|
204
|
-
|
204
|
+
|
205
205
|
max_time = 10
|
206
206
|
ok = wait_for_condition(10) { @store.all(LaunchDarkly::SEGMENTS).keys == all_segment_keys }
|
207
207
|
expect(ok).to eq(true), "Waited #{max_time}s after modifying file and it did not reload"
|
@@ -243,7 +243,7 @@ EOF
|
|
243
243
|
client.close
|
244
244
|
end
|
245
245
|
end
|
246
|
-
|
246
|
+
|
247
247
|
def wait_for_condition(max_time)
|
248
248
|
deadline = Time.now + max_time
|
249
249
|
while Time.now < deadline
|
data/spec/http_util.rb
CHANGED
@@ -23,6 +23,7 @@ class StubHTTPServer
|
|
23
23
|
retry
|
24
24
|
end
|
25
25
|
@requests = []
|
26
|
+
@requests_queue = Queue.new
|
26
27
|
end
|
27
28
|
|
28
29
|
def self.next_port
|
@@ -62,6 +63,11 @@ class StubHTTPServer
|
|
62
63
|
|
63
64
|
def record_request(req, res)
|
64
65
|
@requests.push(req)
|
66
|
+
@requests_queue << req
|
67
|
+
end
|
68
|
+
|
69
|
+
def await_request
|
70
|
+
@requests_queue.pop
|
65
71
|
end
|
66
72
|
end
|
67
73
|
|
data/spec/requestor_spec.rb
CHANGED
@@ -1,57 +1,212 @@
|
|
1
1
|
require "http_util"
|
2
2
|
require "spec_helper"
|
3
3
|
|
4
|
+
$sdk_key = "secret"
|
5
|
+
|
4
6
|
describe LaunchDarkly::Requestor do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
server.
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
7
|
+
def with_requestor(base_uri)
|
8
|
+
r = LaunchDarkly::Requestor.new($sdk_key, LaunchDarkly::Config.new(base_uri: base_uri))
|
9
|
+
yield r
|
10
|
+
r.stop
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "request_all_flags" do
|
14
|
+
it "uses expected URI and headers" do
|
15
|
+
with_server do |server|
|
16
|
+
with_requestor(server.base_uri.to_s) do |requestor|
|
17
|
+
server.setup_ok_response("/", "{}")
|
18
|
+
requestor.request_all_data()
|
19
|
+
expect(server.requests.count).to eq 1
|
20
|
+
expect(server.requests[0].unparsed_uri).to eq "/sdk/latest-all"
|
21
|
+
expect(server.requests[0].header).to include({
|
22
|
+
"authorization" => [ $sdk_key ],
|
23
|
+
"user-agent" => [ "RubyClient/" + LaunchDarkly::VERSION ]
|
24
|
+
})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "parses response" do
|
30
|
+
expected_data = { flags: { x: { key: "x" } } }
|
31
|
+
with_server do |server|
|
32
|
+
with_requestor(server.base_uri.to_s) do |requestor|
|
33
|
+
server.setup_ok_response("/", expected_data.to_json)
|
34
|
+
data = requestor.request_all_data()
|
35
|
+
expect(data).to eq expected_data
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it "sends etag from previous response" do
|
41
|
+
etag = "xyz"
|
42
|
+
with_server do |server|
|
43
|
+
with_requestor(server.base_uri.to_s) do |requestor|
|
44
|
+
server.setup_response("/") do |req, res|
|
45
|
+
res.status = 200
|
46
|
+
res.body = "{}"
|
47
|
+
res["ETag"] = etag
|
48
|
+
end
|
49
|
+
requestor.request_all_data()
|
50
|
+
expect(server.requests.count).to eq 1
|
51
|
+
|
52
|
+
requestor.request_all_data()
|
53
|
+
expect(server.requests.count).to eq 2
|
54
|
+
expect(server.requests[1].header).to include({ "if-none-match" => [ etag ] })
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it "can reuse cached data" do
|
60
|
+
etag = "xyz"
|
61
|
+
expected_data = { flags: { x: { key: "x" } } }
|
62
|
+
with_server do |server|
|
63
|
+
with_requestor(server.base_uri.to_s) do |requestor|
|
64
|
+
server.setup_response("/") do |req, res|
|
65
|
+
res.status = 200
|
66
|
+
res.body = expected_data.to_json
|
67
|
+
res["ETag"] = etag
|
68
|
+
end
|
69
|
+
requestor.request_all_data()
|
70
|
+
expect(server.requests.count).to eq 1
|
71
|
+
|
72
|
+
server.setup_response("/") do |req, res|
|
73
|
+
res.status = 304
|
74
|
+
end
|
75
|
+
data = requestor.request_all_data()
|
76
|
+
expect(server.requests.count).to eq 2
|
77
|
+
expect(server.requests[1].header).to include({ "if-none-match" => [ etag ] })
|
78
|
+
expect(data).to eq expected_data
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it "replaces cached data with new data" do
|
84
|
+
etag1 = "abc"
|
85
|
+
etag2 = "xyz"
|
86
|
+
expected_data1 = { flags: { x: { key: "x" } } }
|
87
|
+
expected_data2 = { flags: { y: { key: "y" } } }
|
88
|
+
with_server do |server|
|
89
|
+
with_requestor(server.base_uri.to_s) do |requestor|
|
90
|
+
server.setup_response("/") do |req, res|
|
91
|
+
res.status = 200
|
92
|
+
res.body = expected_data1.to_json
|
93
|
+
res["ETag"] = etag1
|
94
|
+
end
|
95
|
+
data = requestor.request_all_data()
|
96
|
+
expect(data).to eq expected_data1
|
97
|
+
expect(server.requests.count).to eq 1
|
98
|
+
|
99
|
+
server.setup_response("/") do |req, res|
|
100
|
+
res.status = 304
|
101
|
+
end
|
102
|
+
data = requestor.request_all_data()
|
103
|
+
expect(data).to eq expected_data1
|
104
|
+
expect(server.requests.count).to eq 2
|
105
|
+
expect(server.requests[1].header).to include({ "if-none-match" => [ etag1 ] })
|
106
|
+
|
107
|
+
server.setup_response("/") do |req, res|
|
108
|
+
res.status = 200
|
109
|
+
res.body = expected_data2.to_json
|
110
|
+
res["ETag"] = etag2
|
111
|
+
end
|
112
|
+
data = requestor.request_all_data()
|
113
|
+
expect(data).to eq expected_data2
|
114
|
+
expect(server.requests.count).to eq 3
|
115
|
+
expect(server.requests[2].header).to include({ "if-none-match" => [ etag1 ] })
|
116
|
+
|
117
|
+
server.setup_response("/") do |req, res|
|
118
|
+
res.status = 304
|
119
|
+
end
|
120
|
+
data = requestor.request_all_data()
|
121
|
+
expect(data).to eq expected_data2
|
122
|
+
expect(server.requests.count).to eq 4
|
123
|
+
expect(server.requests[3].header).to include({ "if-none-match" => [ etag2 ] })
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
it "uses UTF-8 encoding by default" do
|
129
|
+
content = '{"flags": {"flagkey": {"key": "flagkey", "variations": ["blue", "grėeń"]}}}'
|
130
|
+
with_server do |server|
|
131
|
+
server.setup_ok_response("/sdk/latest-all", content, "application/json")
|
132
|
+
with_requestor(server.base_uri.to_s) do |requestor|
|
133
|
+
data = requestor.request_all_data
|
134
|
+
expect(data).to eq(JSON.parse(content, symbolize_names: true))
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
it "detects other encodings from Content-Type" do
|
140
|
+
content = '{"flags": {"flagkey": {"key": "flagkey", "variations": ["proszę", "dziękuję"]}}}'
|
141
|
+
with_server do |server|
|
142
|
+
server.setup_ok_response("/sdk/latest-all", content.encode(Encoding::ISO_8859_2),
|
143
|
+
"text/plain; charset=ISO-8859-2")
|
144
|
+
with_requestor(server.base_uri.to_s) do |requestor|
|
145
|
+
data = requestor.request_all_data
|
146
|
+
expect(data).to eq(JSON.parse(content, symbolize_names: true))
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
it "throws exception for error status" do
|
152
|
+
with_server do |server|
|
153
|
+
with_requestor(server.base_uri.to_s) do |requestor|
|
154
|
+
server.setup_response("/") do |req, res|
|
155
|
+
res.status = 400
|
156
|
+
end
|
157
|
+
expect { requestor.request_all_data() }.to raise_error(LaunchDarkly::UnexpectedResponseError)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
it "can use a proxy server" do
|
163
|
+
content = '{"flags": {"flagkey": {"key": "flagkey"}}}'
|
164
|
+
with_server do |server|
|
165
|
+
server.setup_ok_response("/sdk/latest-all", content, "application/json", { "etag" => "x" })
|
166
|
+
with_server(StubProxyServer.new) do |proxy|
|
167
|
+
begin
|
168
|
+
ENV["http_proxy"] = proxy.base_uri.to_s
|
169
|
+
with_requestor(server.base_uri.to_s) do |requestor|
|
170
|
+
data = requestor.request_all_data
|
171
|
+
expect(data).to eq(JSON.parse(content, symbolize_names: true))
|
172
|
+
end
|
173
|
+
ensure
|
174
|
+
ENV["http_proxy"] = nil
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe "request_flag" do
|
182
|
+
it "uses expected URI and headers" do
|
183
|
+
with_server do |server|
|
184
|
+
with_requestor(server.base_uri.to_s) do |requestor|
|
185
|
+
server.setup_ok_response("/", "{}")
|
186
|
+
requestor.request_flag("key")
|
187
|
+
expect(server.requests.count).to eq 1
|
188
|
+
expect(server.requests[0].unparsed_uri).to eq "/sdk/latest-flags/key"
|
189
|
+
expect(server.requests[0].header).to include({
|
190
|
+
"authorization" => [ $sdk_key ],
|
191
|
+
"user-agent" => [ "RubyClient/" + LaunchDarkly::VERSION ]
|
192
|
+
})
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
describe "request_segment" do
|
199
|
+
it "uses expected URI and headers" do
|
200
|
+
with_server do |server|
|
201
|
+
with_requestor(server.base_uri.to_s) do |requestor|
|
202
|
+
server.setup_ok_response("/", "{}")
|
203
|
+
requestor.request_segment("key")
|
204
|
+
expect(server.requests.count).to eq 1
|
205
|
+
expect(server.requests[0].unparsed_uri).to eq "/sdk/latest-segments/key"
|
206
|
+
expect(server.requests[0].header).to include({
|
207
|
+
"authorization" => [ $sdk_key ],
|
208
|
+
"user-agent" => [ "RubyClient/" + LaunchDarkly::VERSION ]
|
209
|
+
})
|
55
210
|
end
|
56
211
|
end
|
57
212
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ldclient-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.5.
|
4
|
+
version: 5.5.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- LaunchDarkly
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-02-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk-dynamodb
|
@@ -184,46 +184,6 @@ dependencies:
|
|
184
184
|
- - "<"
|
185
185
|
- !ruby/object:Gem::Version
|
186
186
|
version: '3'
|
187
|
-
- !ruby/object:Gem::Dependency
|
188
|
-
name: faraday
|
189
|
-
requirement: !ruby/object:Gem::Requirement
|
190
|
-
requirements:
|
191
|
-
- - ">="
|
192
|
-
- !ruby/object:Gem::Version
|
193
|
-
version: '0.9'
|
194
|
-
- - "<"
|
195
|
-
- !ruby/object:Gem::Version
|
196
|
-
version: '2'
|
197
|
-
type: :runtime
|
198
|
-
prerelease: false
|
199
|
-
version_requirements: !ruby/object:Gem::Requirement
|
200
|
-
requirements:
|
201
|
-
- - ">="
|
202
|
-
- !ruby/object:Gem::Version
|
203
|
-
version: '0.9'
|
204
|
-
- - "<"
|
205
|
-
- !ruby/object:Gem::Version
|
206
|
-
version: '2'
|
207
|
-
- !ruby/object:Gem::Dependency
|
208
|
-
name: faraday-http-cache
|
209
|
-
requirement: !ruby/object:Gem::Requirement
|
210
|
-
requirements:
|
211
|
-
- - ">="
|
212
|
-
- !ruby/object:Gem::Version
|
213
|
-
version: 1.3.0
|
214
|
-
- - "<"
|
215
|
-
- !ruby/object:Gem::Version
|
216
|
-
version: '3'
|
217
|
-
type: :runtime
|
218
|
-
prerelease: false
|
219
|
-
version_requirements: !ruby/object:Gem::Requirement
|
220
|
-
requirements:
|
221
|
-
- - ">="
|
222
|
-
- !ruby/object:Gem::Version
|
223
|
-
version: 1.3.0
|
224
|
-
- - "<"
|
225
|
-
- !ruby/object:Gem::Version
|
226
|
-
version: '3'
|
227
187
|
- !ruby/object:Gem::Dependency
|
228
188
|
name: semantic
|
229
189
|
requirement: !ruby/object:Gem::Requirement
|
@@ -238,26 +198,6 @@ dependencies:
|
|
238
198
|
- - "~>"
|
239
199
|
- !ruby/object:Gem::Version
|
240
200
|
version: '1.6'
|
241
|
-
- !ruby/object:Gem::Dependency
|
242
|
-
name: net-http-persistent
|
243
|
-
requirement: !ruby/object:Gem::Requirement
|
244
|
-
requirements:
|
245
|
-
- - ">="
|
246
|
-
- !ruby/object:Gem::Version
|
247
|
-
version: '2.9'
|
248
|
-
- - "<"
|
249
|
-
- !ruby/object:Gem::Version
|
250
|
-
version: '4.0'
|
251
|
-
type: :runtime
|
252
|
-
prerelease: false
|
253
|
-
version_requirements: !ruby/object:Gem::Requirement
|
254
|
-
requirements:
|
255
|
-
- - ">="
|
256
|
-
- !ruby/object:Gem::Version
|
257
|
-
version: '2.9'
|
258
|
-
- - "<"
|
259
|
-
- !ruby/object:Gem::Version
|
260
|
-
version: '4.0'
|
261
201
|
- !ruby/object:Gem::Dependency
|
262
202
|
name: concurrent-ruby
|
263
203
|
requirement: !ruby/object:Gem::Requirement
|
@@ -309,6 +249,7 @@ files:
|
|
309
249
|
- LICENSE.txt
|
310
250
|
- README.md
|
311
251
|
- Rakefile
|
252
|
+
- azure-pipelines.yml
|
312
253
|
- ext/mkrf_conf.rb
|
313
254
|
- ldclient-rb.gemspec
|
314
255
|
- lib/ldclient-rb.rb
|