ldclient-rb 5.5.2 → 5.5.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|