featurehub-sdk 1.1.0 → 1.2.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/.rubocop.yml +3 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile.lock +1 -1
- data/lib/feature_hub/sdk/feature_hub_config.rb +8 -2
- data/lib/feature_hub/sdk/poll_edge_service.rb +65 -15
- data/lib/feature_hub/sdk/streaming_edge_service.rb +44 -8
- data/lib/feature_hub/sdk/version.rb +1 -1
- metadata +2 -3
- data/featurehub-sdk.gemspec +0 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 033fbbd756fd2a748d584634724881aae1a2d49feec0a2f7cec4aa7245dc8440
|
4
|
+
data.tar.gz: 042ff7ee449a8b80a5f120d203c791e7e42c896dce377a5c7af4367c94048829
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0fe881017cbb21138eed0663f0df11d18df61e63be666c5e2e26b68ca478792e8bb51f123ea415d86c8ce6ede3e087b2c7e8b35991c90d6939bac7672f0f7de4
|
7
|
+
data.tar.gz: 944146e27443caad934f35b1efb30179ec3c1a8b8c78d2def46e68250b338b156184e79b861be2ae3b929cf4b38a7514978a1757bd7e24a65ede50930010c9a7
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,16 @@
|
|
1
|
+
## [1.2.0] - 2022-10-14
|
2
|
+
|
3
|
+
- Fixing polling client for server eval
|
4
|
+
- Added in tests for polling client
|
5
|
+
- Added in server eval for streaming client
|
6
|
+
- Added expired environment support for polling and streaming
|
7
|
+
- Added cache busting for server eval polling client
|
8
|
+
-
|
9
|
+
|
1
10
|
## [1.1.0] - 2022-10-12
|
2
11
|
|
3
12
|
- Adds support for array values in flag evaluation contexts via [this PR](https://github.com/featurehub-io/featurehub-ruby-sdk/pull/12)
|
13
|
+
|
4
14
|
## [1.0.0] - 2022-06-06
|
5
15
|
|
6
16
|
- Initial release, feature complete
|
data/Gemfile.lock
CHANGED
@@ -74,7 +74,10 @@ module FeatureHub
|
|
74
74
|
edge_provider
|
75
75
|
end
|
76
76
|
|
77
|
-
def use_polling_edge_service(interval = ENV.fetch("FEATUREHUB_POLL_INTERVAL", "30").to_i)
|
77
|
+
def use_polling_edge_service(interval = ENV.fetch("FEATUREHUB_POLL_INTERVAL", "30").to_i)
|
78
|
+
@interval = interval
|
79
|
+
@edge_service_provider = method(:create_polling_edge_provider)
|
80
|
+
end
|
78
81
|
|
79
82
|
def new_context
|
80
83
|
get_or_create_edge_service
|
@@ -99,8 +102,11 @@ module FeatureHub
|
|
99
102
|
@edge_service_provider.call(@repository, @api_keys, @edge_url, @logger)
|
100
103
|
end
|
101
104
|
|
105
|
+
def create_polling_edge_provider(repo, api_keys, edge_url, logger)
|
106
|
+
FeatureHub::Sdk::PollingEdgeService.new(repo, api_keys, edge_url, @interval, logger)
|
107
|
+
end
|
108
|
+
|
102
109
|
def create_default_provider(repo, api_keys, edge_url, logger)
|
103
|
-
# FeatureHub::Sdk::PollingEdgeService.new(repo, api_keys, edge_url, 10, logger)
|
104
110
|
FeatureHub::Sdk::StreamingEdgeService.new(repo, api_keys, edge_url, logger)
|
105
111
|
end
|
106
112
|
|
@@ -4,12 +4,13 @@ require "faraday"
|
|
4
4
|
require "faraday/net_http"
|
5
5
|
require "json"
|
6
6
|
require "concurrent-ruby"
|
7
|
+
require "digest/sha2"
|
7
8
|
|
8
9
|
module FeatureHub
|
9
10
|
module Sdk
|
10
11
|
# uses a periodic polling mechanism to get updates
|
11
12
|
class PollingEdgeService < EdgeService
|
12
|
-
attr_reader :repository, :api_keys, :edge_url, :interval
|
13
|
+
attr_reader :repository, :api_keys, :edge_url, :interval, :stopped, :etag, :cancel, :sha_context
|
13
14
|
|
14
15
|
def initialize(repository, api_keys, edge_url, interval, logger = nil)
|
15
16
|
super(repository, api_keys, edge_url)
|
@@ -25,6 +26,8 @@ module FeatureHub
|
|
25
26
|
@cancel = false
|
26
27
|
@context = nil
|
27
28
|
@etag = nil
|
29
|
+
@stopped = false
|
30
|
+
@sha_context = nil
|
28
31
|
|
29
32
|
generate_url
|
30
33
|
end
|
@@ -48,34 +51,53 @@ module FeatureHub
|
|
48
51
|
return if new_header == @context
|
49
52
|
|
50
53
|
@context = new_header
|
54
|
+
@sha_context = Digest::SHA256.hexdigest(@context)
|
51
55
|
|
52
|
-
|
56
|
+
if active
|
57
|
+
get_updates
|
58
|
+
else
|
59
|
+
poll
|
60
|
+
end
|
53
61
|
end
|
54
62
|
|
55
63
|
def close
|
56
64
|
cancel_task
|
57
65
|
end
|
58
66
|
|
67
|
+
def active
|
68
|
+
!@task.nil?
|
69
|
+
end
|
70
|
+
|
59
71
|
private
|
60
72
|
|
61
73
|
def poll_with_interval
|
62
|
-
return if @cancel || !@task.nil?
|
63
|
-
|
64
|
-
get_updates
|
74
|
+
return if @cancel || !@task.nil? || @stopped
|
65
75
|
|
66
76
|
@logger.info("starting polling for #{determine_request_url}")
|
67
|
-
@task = Concurrent::TimerTask.new(execution_interval: @interval) do
|
77
|
+
@task = Concurrent::TimerTask.new(execution_interval: @interval, run_now: false) do
|
68
78
|
get_updates
|
69
79
|
end
|
70
|
-
|
80
|
+
|
81
|
+
get_updates
|
82
|
+
|
83
|
+
@task&.execute # could have been shutdown
|
71
84
|
end
|
72
85
|
|
73
86
|
def cancel_task
|
87
|
+
@cancel = true
|
88
|
+
shutdown_task
|
89
|
+
end
|
90
|
+
|
91
|
+
def stopped_task
|
92
|
+
@stopped = true
|
93
|
+
shutdown_task
|
94
|
+
end
|
95
|
+
|
96
|
+
def shutdown_task
|
74
97
|
return if @task.nil?
|
75
98
|
|
76
99
|
@task.shutdown
|
77
100
|
@task = nil
|
78
|
-
@cancel = true
|
79
101
|
end
|
80
102
|
|
81
103
|
# rubocop:disable Naming/AccessorMethodName
|
@@ -86,36 +108,63 @@ module FeatureHub
|
|
86
108
|
"X-SDK": "Ruby",
|
87
109
|
"X-SDK-Version": FeatureHub::Sdk::VERSION
|
88
110
|
}
|
111
|
+
|
112
|
+
headers["x-featurehub"] = @context unless @context.nil?
|
89
113
|
headers["if-none-match"] = @etag unless @etag.nil?
|
114
|
+
|
90
115
|
@logger.debug("polling for #{url}")
|
91
|
-
resp = @conn.get
|
116
|
+
resp = @conn.get url, {}, headers
|
92
117
|
case resp.status
|
93
118
|
when 200
|
94
|
-
|
95
|
-
|
119
|
+
success(resp)
|
120
|
+
when 236
|
121
|
+
stopped_task
|
122
|
+
success(resp)
|
96
123
|
when 404 # no such key
|
97
124
|
@repository.notify("failed", nil)
|
98
|
-
|
125
|
+
cancel_task
|
99
126
|
@logger.error("featurehub: key does not exist, stopping polling")
|
100
127
|
when 503 # dacha busy
|
101
|
-
@logger.debug("featurehub: dacha is busy, trying
|
128
|
+
@logger.debug("featurehub: dacha is busy, trying again")
|
102
129
|
else
|
103
130
|
@logger.debug("featurehub: unknown error #{resp.status}")
|
104
131
|
end
|
105
132
|
end
|
133
|
+
|
106
134
|
# rubocop:enable Naming/AccessorMethodName
|
107
135
|
|
136
|
+
def success(resp)
|
137
|
+
@etag = resp.headers["etag"]
|
138
|
+
|
139
|
+
check_interval_change(resp.headers["cache-control"]) if resp.headers["cache-control"]
|
140
|
+
|
141
|
+
process_results(JSON.parse(resp.body))
|
142
|
+
end
|
143
|
+
|
108
144
|
def process_results(data)
|
109
145
|
data.each do |environment|
|
110
146
|
@repository.notify("features", environment["features"]) if environment
|
111
147
|
end
|
112
148
|
end
|
113
149
|
|
150
|
+
def check_interval_change(cache_control_header)
|
151
|
+
found = cache_control_header.scan(/max-age=(\d+)/)
|
152
|
+
|
153
|
+
return if @task.nil? || found.empty? || found[0].empty?
|
154
|
+
|
155
|
+
new_interval = found[0][0].to_i
|
156
|
+
|
157
|
+
return unless new_interval.positive? && new_interval != @interval
|
158
|
+
|
159
|
+
@interval = new_interval
|
160
|
+
@task.execution_interval = @interval
|
161
|
+
end
|
162
|
+
|
114
163
|
def determine_request_url
|
115
164
|
if @context.nil?
|
116
|
-
@url
|
165
|
+
"#{@url}&contextSha=0"
|
117
166
|
else
|
118
|
-
"#{@url}
|
167
|
+
"#{@url}&contextSha=#{@sha_context}"
|
119
168
|
end
|
120
169
|
end
|
121
170
|
|
@@ -125,6 +174,7 @@ module FeatureHub
|
|
125
174
|
@timeout = ENV.fetch("FEATUREHUB_POLL_HTTP_TIMEOUT", "12").to_i
|
126
175
|
@conn = Faraday.new(url: @edge_url) do |f|
|
127
176
|
f.adapter :net_http
|
177
|
+
f.options.timeout = @timeout
|
128
178
|
end
|
129
179
|
end
|
130
180
|
end
|
@@ -7,7 +7,7 @@ module FeatureHub
|
|
7
7
|
module Sdk
|
8
8
|
# provides a streaming service
|
9
9
|
class StreamingEdgeService < FeatureHub::Sdk::EdgeService
|
10
|
-
attr_reader :repository, :sse_client, :url, :
|
10
|
+
attr_reader :repository, :sse_client, :url, :stopped
|
11
11
|
|
12
12
|
def initialize(repository, api_keys, edge_url, logger = nil)
|
13
13
|
super(repository, api_keys, edge_url)
|
@@ -15,42 +15,78 @@ module FeatureHub
|
|
15
15
|
@url = "#{edge_url}features/#{api_keys[0]}"
|
16
16
|
@repository = repository
|
17
17
|
@sse_client = nil
|
18
|
-
@
|
18
|
+
@context = nil
|
19
19
|
@logger = logger || FeatureHub::Sdk.default_logger
|
20
20
|
end
|
21
21
|
|
22
|
+
def closed
|
23
|
+
@sse_client.nil?
|
24
|
+
end
|
25
|
+
|
22
26
|
def poll
|
23
|
-
start_streaming unless @sse_client
|
27
|
+
start_streaming unless @sse_client || @stopped
|
24
28
|
end
|
25
29
|
|
26
30
|
def active
|
27
|
-
!@
|
31
|
+
!@sse_client.nil?
|
28
32
|
end
|
29
33
|
|
30
34
|
def close
|
31
|
-
|
35
|
+
close_connection
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def close_connection
|
32
41
|
return if @sse_client.nil?
|
33
42
|
|
34
43
|
@sse_client.close
|
35
44
|
@sse_client = nil
|
36
45
|
end
|
37
46
|
|
38
|
-
|
47
|
+
def stop
|
48
|
+
@stopped = true
|
49
|
+
close_connection
|
50
|
+
end
|
51
|
+
|
52
|
+
def context_change(new_header)
|
53
|
+
return if new_header == @context
|
54
|
+
|
55
|
+
@context = new_header
|
56
|
+
close
|
57
|
+
poll
|
58
|
+
end
|
39
59
|
|
40
60
|
def start_streaming
|
41
|
-
@closed = false
|
42
61
|
@logger.info("streaming from #{@url}")
|
62
|
+
# we can get an error before returning the new() function and get a race condition on the close
|
63
|
+
must_close = false
|
43
64
|
@sse_client = SSE::Client.new(@url) do |client|
|
44
65
|
client.on_event do |event|
|
45
|
-
|
66
|
+
json_data = JSON.parse(event.data)
|
67
|
+
|
68
|
+
if event.type == "config"
|
69
|
+
process_config(json_data)
|
70
|
+
else
|
71
|
+
@repository.notify(event.type, json_data)
|
72
|
+
end
|
46
73
|
end
|
47
74
|
client.on_error do |error|
|
48
75
|
if error.is_a?(SSE::Errors::HTTPStatusError) && (error.status == 404)
|
49
76
|
@repository.notify("failure", nil)
|
50
77
|
close
|
78
|
+
must_close = true
|
51
79
|
end
|
52
80
|
end
|
53
81
|
end
|
82
|
+
|
83
|
+
return unless must_close
|
84
|
+
|
85
|
+
close # try again
|
86
|
+
end
|
87
|
+
|
88
|
+
def process_config(json_data)
|
89
|
+
stop if json_data["edge.stale"]
|
54
90
|
end
|
55
91
|
end
|
56
92
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: featurehub-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Vowles
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2022-
|
12
|
+
date: 2022-11-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: concurrent-ruby
|
@@ -97,7 +97,6 @@ files:
|
|
97
97
|
- Gemfile.lock
|
98
98
|
- LICENSE.txt
|
99
99
|
- Rakefile
|
100
|
-
- featurehub-sdk.gemspec
|
101
100
|
- featurehub-sdk.iml
|
102
101
|
- lib/feature_hub/sdk/context.rb
|
103
102
|
- lib/feature_hub/sdk/feature_hub_config.rb
|
data/featurehub-sdk.gemspec
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
lib = File.expand_path("lib", __dir__)
|
4
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
-
require "feature_hub/sdk/version"
|
6
|
-
|
7
|
-
Gem::Specification.new do |spec|
|
8
|
-
spec.name = "featurehub-sdk"
|
9
|
-
spec.version = FeatureHub::Sdk::VERSION
|
10
|
-
spec.authors = ["Richard Vowles", "Irina Southwell"]
|
11
|
-
spec.email = ["richard@bluetrainsoftware.com"]
|
12
|
-
|
13
|
-
spec.summary = "FeatureHub Ruby SDK"
|
14
|
-
spec.description = "FeatureHub Ruby SDK"
|
15
|
-
spec.homepage = "https://www.featurehub.io"
|
16
|
-
spec.license = "MIT"
|
17
|
-
spec.required_ruby_version = ">= 2.6.0"
|
18
|
-
|
19
|
-
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
20
|
-
|
21
|
-
spec.metadata["homepage_uri"] = spec.homepage
|
22
|
-
spec.metadata["source_code_uri"] = "https://github.com/featurehub-io/featurehub-ruby-sdk"
|
23
|
-
spec.metadata["changelog_uri"] = "https://github.com/featurehub-io/featurehub-ruby-sdk/featurehub-sdk/CHANGELOG.md"
|
24
|
-
spec.metadata["rubygems_mfa_required"] = "true"
|
25
|
-
|
26
|
-
# Specify which files should be added to the gem when it is released.
|
27
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
28
|
-
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
29
|
-
`git ls-files -z`.split("\x0").reject do |f|
|
30
|
-
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
31
|
-
end
|
32
|
-
end
|
33
|
-
spec.bindir = "exe"
|
34
|
-
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
35
|
-
spec.require_paths = ["lib"]
|
36
|
-
|
37
|
-
spec.add_dependency "concurrent-ruby", "~> 1.1.10"
|
38
|
-
spec.add_dependency "faraday", "~> 2.3"
|
39
|
-
spec.add_dependency "ld-eventsource", "~> 2.2.0"
|
40
|
-
spec.add_dependency "murmurhash3", "~> 0.1.6"
|
41
|
-
spec.add_dependency "sem_version", "~> 2.0.0"
|
42
|
-
|
43
|
-
# For more information and examples about making a new gem, check out our
|
44
|
-
# guide at: https://bundler.io/guides/creating_gem.html
|
45
|
-
end
|