splitclient-rb 7.0.3.pre.rc6-java → 7.0.4.pre.rc1-java
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 +9 -1
- data/CHANGES.txt +2 -2
- data/LICENSE +1 -1
- data/lib/splitclient-rb.rb +11 -2
- data/lib/splitclient-rb/cache/{stores/segment_store.rb → fetchers/segment_fetcher.rb} +20 -14
- data/lib/splitclient-rb/cache/{stores/split_store.rb → fetchers/split_fetcher.rb} +17 -19
- data/lib/splitclient-rb/cache/repositories/splits_repository.rb +12 -0
- data/lib/splitclient-rb/clients/split_client.rb +4 -1
- data/lib/splitclient-rb/engine/api/segments.rb +1 -1
- data/lib/splitclient-rb/engine/parser/split_adapter.rb +10 -7
- data/lib/splitclient-rb/split_config.rb +4 -1
- data/lib/splitclient-rb/split_factory.rb +14 -1
- data/lib/splitclient-rb/sse/event_source/client.rb +162 -0
- data/lib/splitclient-rb/sse/event_source/event_types.rb +14 -0
- data/lib/splitclient-rb/sse/event_source/status.rb +13 -0
- data/lib/splitclient-rb/sse/sse_handler.rb +83 -0
- data/lib/splitclient-rb/sse/workers/control_worker.rb +34 -0
- data/lib/splitclient-rb/sse/workers/segments_worker.rb +47 -0
- data/lib/splitclient-rb/sse/workers/splits_worker.rb +48 -0
- data/lib/splitclient-rb/version.rb +1 -1
- data/splitclient-rb.gemspec +1 -0
- metadata +25 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 011bc30b94b1b3d235bf7c2d549fbe0f3e9d4399
|
4
|
+
data.tar.gz: 7673857e19e18b0967a29f7594b60b101139d818
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 91ebecabb11b4555f8e469ecacfb5959e19cf9811a1504f8f2bd60c482531346064abf022b91919c374bb612da824d125a50c585201e774f48141b8575236778
|
7
|
+
data.tar.gz: ddfcdf53cc0d4ff4965578a01a600a444d514598c07afaad3d67001b5da88dc36564653229d88e2a356277c51211ab2fdb5d182e653e754c68589c790b976194
|
data/.rubocop.yml
CHANGED
@@ -1,11 +1,19 @@
|
|
1
1
|
Documentation:
|
2
2
|
Enabled: false
|
3
3
|
|
4
|
+
Metrics/AbcSize:
|
5
|
+
Max: 25
|
6
|
+
|
4
7
|
Metrics/MethodLength:
|
5
|
-
Max:
|
8
|
+
Max: 20
|
9
|
+
|
10
|
+
Metrics/ClassLength:
|
11
|
+
Max: 120
|
6
12
|
|
7
13
|
Metrics/LineLength:
|
8
14
|
Max: 121
|
15
|
+
Exclude:
|
16
|
+
- spec/sse/**/*
|
9
17
|
|
10
18
|
Metrics/BlockLength:
|
11
19
|
Exclude:
|
data/CHANGES.txt
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
7.0.3 (
|
1
|
+
7.0.3 (Jan 20, 2020)
|
2
2
|
- Added integration tests.
|
3
3
|
- Fixed impressions labels.
|
4
4
|
|
@@ -40,7 +40,7 @@
|
|
40
40
|
define configurations for your treatments and also whitelisted keys. Read more in our docs!
|
41
41
|
|
42
42
|
6.2.0 (Mar 7th, 2019)
|
43
|
-
- Reworked SplitClient#destroy to ensure events, impressions and metrics are sent to Split backend when called
|
43
|
+
- Reworked SplitClient#destroy to ensure events, impressions and metrics are sent to Split backend when called.
|
44
44
|
- Ensured destroy is called when keyboard interrupts are sent to the application
|
45
45
|
- Changed SDK blocker (and block_until_ready) to have no effect in consumer mode
|
46
46
|
- Added support for applications tied to Faraday < 0.13 and net-http-persistent 3 using a patched Faraday adapter
|
data/LICENSE
CHANGED
data/lib/splitclient-rb.rb
CHANGED
@@ -9,6 +9,8 @@ require 'splitclient-rb/cache/adapters/memory_adapters/queue_adapter'
|
|
9
9
|
require 'splitclient-rb/cache/adapters/cache_adapter'
|
10
10
|
require 'splitclient-rb/cache/adapters/memory_adapter'
|
11
11
|
require 'splitclient-rb/cache/adapters/redis_adapter'
|
12
|
+
require 'splitclient-rb/cache/fetchers/segment_fetcher'
|
13
|
+
require 'splitclient-rb/cache/fetchers/split_fetcher'
|
12
14
|
require 'splitclient-rb/cache/repositories/repository'
|
13
15
|
require 'splitclient-rb/cache/repositories/segments_repository'
|
14
16
|
require 'splitclient-rb/cache/repositories/splits_repository'
|
@@ -29,8 +31,6 @@ require 'splitclient-rb/cache/senders/localhost_repo_cleaner'
|
|
29
31
|
require 'splitclient-rb/cache/stores/store_utils'
|
30
32
|
require 'splitclient-rb/cache/stores/localhost_split_builder'
|
31
33
|
require 'splitclient-rb/cache/stores/sdk_blocker'
|
32
|
-
require 'splitclient-rb/cache/stores/segment_store'
|
33
|
-
require 'splitclient-rb/cache/stores/split_store'
|
34
34
|
require 'splitclient-rb/cache/stores/localhost_split_store'
|
35
35
|
|
36
36
|
require 'splitclient-rb/clients/split_client'
|
@@ -88,6 +88,15 @@ require 'splitclient-rb/utilitites'
|
|
88
88
|
# redis metrics fixer
|
89
89
|
require 'splitclient-rb/redis_metrics_fixer'
|
90
90
|
|
91
|
+
# SSE
|
92
|
+
require 'splitclient-rb/sse/event_source/client'
|
93
|
+
require 'splitclient-rb/sse/event_source/event_types'
|
94
|
+
require 'splitclient-rb/sse/event_source/status'
|
95
|
+
require 'splitclient-rb/sse/workers/control_worker'
|
96
|
+
require 'splitclient-rb/sse/workers/segments_worker'
|
97
|
+
require 'splitclient-rb/sse/workers/splits_worker'
|
98
|
+
require 'splitclient-rb/sse/sse_handler'
|
99
|
+
|
91
100
|
# C extension
|
92
101
|
require 'murmurhash/murmurhash_mri'
|
93
102
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module SplitIoClient
|
2
2
|
module Cache
|
3
|
-
module
|
4
|
-
class
|
3
|
+
module Fetchers
|
4
|
+
class SegmentFetcher
|
5
5
|
attr_reader :segments_repository
|
6
6
|
|
7
7
|
def initialize(segments_repository, api_key, metrics, config, sdk_blocker = nil)
|
@@ -14,7 +14,7 @@ module SplitIoClient
|
|
14
14
|
|
15
15
|
def call
|
16
16
|
if ENV['SPLITCLIENT_ENV'] == 'test'
|
17
|
-
|
17
|
+
fetch_segments
|
18
18
|
else
|
19
19
|
segments_thread
|
20
20
|
|
@@ -26,16 +26,30 @@ module SplitIoClient
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
+
def fetch_segment(name)
|
30
|
+
segments_api.fetch_segments_by_names([name])
|
31
|
+
rescue StandardError => error
|
32
|
+
@config.log_found_exception(__method__.to_s, error)
|
33
|
+
end
|
34
|
+
|
35
|
+
def fetch_segments
|
36
|
+
segments_api.fetch_segments_by_names(@segments_repository.used_segment_names)
|
37
|
+
|
38
|
+
@sdk_blocker.segments_ready!
|
39
|
+
rescue StandardError => error
|
40
|
+
@config.log_found_exception(__method__.to_s, error)
|
41
|
+
end
|
42
|
+
|
29
43
|
private
|
30
44
|
|
31
45
|
def segments_thread
|
32
|
-
@config.threads[:
|
46
|
+
@config.threads[:segment_fetcher] = Thread.new do
|
33
47
|
@config.logger.info('Starting segments fetcher service')
|
34
48
|
|
35
49
|
loop do
|
36
50
|
next unless @sdk_blocker.splits_repository.ready?
|
37
51
|
|
38
|
-
|
52
|
+
fetch_segments
|
39
53
|
@config.logger.debug("Segment names: #{@segments_repository.used_segment_names.to_a}") if @config.debug_enabled
|
40
54
|
|
41
55
|
sleep_for = StoreUtils.random_interval(@config.segments_refresh_rate)
|
@@ -43,15 +57,7 @@ module SplitIoClient
|
|
43
57
|
sleep(sleep_for)
|
44
58
|
end
|
45
59
|
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def store_segments
|
49
|
-
segments_api.store_segments_by_names(@segments_repository.used_segment_names)
|
50
|
-
|
51
|
-
@sdk_blocker.segments_ready!
|
52
|
-
rescue StandardError => error
|
53
|
-
@config.log_found_exception(__method__.to_s, error)
|
54
|
-
end
|
60
|
+
end
|
55
61
|
|
56
62
|
def segments_api
|
57
63
|
@segments_api ||= SplitIoClient::Api::Segments.new(@api_key, @metrics, @segments_repository, @config)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module SplitIoClient
|
2
2
|
module Cache
|
3
|
-
module
|
4
|
-
class
|
3
|
+
module Fetchers
|
4
|
+
class SplitFetcher
|
5
5
|
attr_reader :splits_repository
|
6
6
|
|
7
7
|
def initialize(splits_repository, api_key, metrics, config, sdk_blocker = nil)
|
@@ -14,7 +14,7 @@ module SplitIoClient
|
|
14
14
|
|
15
15
|
def call
|
16
16
|
if ENV['SPLITCLIENT_ENV'] == 'test'
|
17
|
-
|
17
|
+
fetch_splits
|
18
18
|
else
|
19
19
|
splits_thread
|
20
20
|
|
@@ -26,20 +26,7 @@ module SplitIoClient
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
def splits_thread
|
32
|
-
@config.threads[:split_store] = Thread.new do
|
33
|
-
@config.logger.info('Starting splits fetcher service')
|
34
|
-
loop do
|
35
|
-
store_splits
|
36
|
-
|
37
|
-
sleep(StoreUtils.random_interval(@config.features_refresh_rate))
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def store_splits
|
29
|
+
def fetch_splits
|
43
30
|
data = splits_since(@splits_repository.get_change_number)
|
44
31
|
|
45
32
|
data[:splits] && data[:splits].each do |split|
|
@@ -57,6 +44,19 @@ module SplitIoClient
|
|
57
44
|
@config.log_found_exception(__method__.to_s, error)
|
58
45
|
end
|
59
46
|
|
47
|
+
private
|
48
|
+
|
49
|
+
def splits_thread
|
50
|
+
@config.threads[:split_fetcher] = Thread.new do
|
51
|
+
@config.logger.info('Starting splits fetcher service')
|
52
|
+
loop do
|
53
|
+
fetch_splits
|
54
|
+
|
55
|
+
sleep(StoreUtils.random_interval(@config.features_refresh_rate))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
60
|
def splits_since(since)
|
61
61
|
splits_api.since(since)
|
62
62
|
end
|
@@ -83,8 +83,6 @@ module SplitIoClient
|
|
83
83
|
@splits_repository.add_split(split)
|
84
84
|
end
|
85
85
|
|
86
|
-
private
|
87
|
-
|
88
86
|
def splits_api
|
89
87
|
@splits_api ||= SplitIoClient::Api::Splits.new(@api_key, @metrics, @config)
|
90
88
|
end
|
@@ -128,6 +128,18 @@ module SplitIoClient
|
|
128
128
|
@adapter.clear(namespace_key)
|
129
129
|
end
|
130
130
|
|
131
|
+
def kill(change_number, split_name, default_treatment)
|
132
|
+
split = get_split(split_name)
|
133
|
+
|
134
|
+
return if split.nil?
|
135
|
+
|
136
|
+
split[:label] = Engine::Models::Label::KILLED
|
137
|
+
split[:defaultTreatment] = default_treatment
|
138
|
+
split[:changeNumber] = change_number
|
139
|
+
|
140
|
+
@adapter.set_string(namespace_key(".split.#{split_name}"), split.to_json)
|
141
|
+
end
|
142
|
+
|
131
143
|
private
|
132
144
|
|
133
145
|
def increase_tt_name_count(tt_name)
|
@@ -9,7 +9,7 @@ module SplitIoClient
|
|
9
9
|
# @param api_key [String] the API key for your split account
|
10
10
|
#
|
11
11
|
# @return [SplitIoClient] split.io client instance
|
12
|
-
def initialize(api_key, adapter = nil, splits_repository, segments_repository, impressions_repository, metrics_repository, events_repository, sdk_blocker, config)
|
12
|
+
def initialize(api_key, adapter = nil, splits_repository, segments_repository, impressions_repository, metrics_repository, events_repository, sdk_blocker, config, sse_handler)
|
13
13
|
@api_key = api_key
|
14
14
|
@splits_repository = splits_repository
|
15
15
|
@segments_repository = segments_repository
|
@@ -20,6 +20,7 @@ module SplitIoClient
|
|
20
20
|
@destroyed = false
|
21
21
|
@config = config
|
22
22
|
@adapter = adapter
|
23
|
+
@sse_handler = sse_handler
|
23
24
|
end
|
24
25
|
|
25
26
|
def get_treatment(
|
@@ -61,6 +62,8 @@ module SplitIoClient
|
|
61
62
|
thread.join
|
62
63
|
end
|
63
64
|
|
65
|
+
@sse_handler.sse_client.close if @config.push_notification_enabled
|
66
|
+
|
64
67
|
@config.threads.values.each { |thread| Thread.kill(thread) }
|
65
68
|
|
66
69
|
@splits_repository.clear
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'thread'
|
3
3
|
|
4
|
+
include SplitIoClient::Cache::Fetchers
|
4
5
|
include SplitIoClient::Cache::Stores
|
5
6
|
include SplitIoClient::Cache::Senders
|
6
7
|
|
@@ -11,7 +12,7 @@ module SplitIoClient
|
|
11
12
|
# also, uses safe threads to execute fetches and post give the time execution values from the config
|
12
13
|
#
|
13
14
|
class SplitAdapter < NoMethodError
|
14
|
-
attr_reader :splits_repository, :segments_repository, :impressions_repository, :metrics
|
15
|
+
attr_reader :splits_repository, :segments_repository, :impressions_repository, :metrics, :split_fetcher, :segment_fetcher
|
15
16
|
|
16
17
|
#
|
17
18
|
# Creates a new split api adapter instance that consumes split api endpoints
|
@@ -50,8 +51,8 @@ module SplitIoClient
|
|
50
51
|
end
|
51
52
|
|
52
53
|
def start_standalone_components
|
53
|
-
|
54
|
-
|
54
|
+
split_fetch
|
55
|
+
segment_fetch
|
55
56
|
metrics_sender
|
56
57
|
impressions_sender
|
57
58
|
events_sender
|
@@ -73,13 +74,15 @@ module SplitIoClient
|
|
73
74
|
end
|
74
75
|
|
75
76
|
# Starts thread which loops constantly and stores splits in the splits_repository of choice
|
76
|
-
def
|
77
|
-
|
77
|
+
def split_fetch
|
78
|
+
@split_fetcher = SplitFetcher.new(@splits_repository, @api_key, @metrics, @config, @sdk_blocker)
|
79
|
+
@split_fetcher.fetch_splits
|
78
80
|
end
|
79
81
|
|
80
82
|
# Starts thread which loops constantly and stores segments in the segments_repository of choice
|
81
|
-
def
|
82
|
-
|
83
|
+
def segment_fetch
|
84
|
+
@segment_fetcher = SegmentFetcher.new(@segments_repository, @api_key, @metrics, @config, @sdk_blocker)
|
85
|
+
@segment_fetcher.fetch_segments
|
83
86
|
end
|
84
87
|
|
85
88
|
# Starts thread which loops constantly and sends impressions to the Split API
|
@@ -102,6 +102,8 @@ module SplitIoClient
|
|
102
102
|
@split_validator = SplitIoClient::Validators.new(self)
|
103
103
|
@localhost_mode = opts[:localhost_mode]
|
104
104
|
|
105
|
+
@push_notification_enabled = opts[:push_notification_enabled].nil? ? true : false
|
106
|
+
|
105
107
|
startup_log
|
106
108
|
end
|
107
109
|
|
@@ -172,7 +174,6 @@ module SplitIoClient
|
|
172
174
|
# @return [SplitLogger] The configured logger
|
173
175
|
attr_accessor :split_logger
|
174
176
|
|
175
|
-
|
176
177
|
#
|
177
178
|
# The split validator. The client library uses the split validator
|
178
179
|
# to validate inputs accross the sdk
|
@@ -255,6 +256,8 @@ module SplitIoClient
|
|
255
256
|
|
256
257
|
attr_accessor :ip_addresses_enabled
|
257
258
|
|
259
|
+
attr_accessor :push_notification_enabled
|
260
|
+
|
258
261
|
#
|
259
262
|
# The default split client configuration
|
260
263
|
#
|
@@ -36,7 +36,9 @@ module SplitIoClient
|
|
36
36
|
|
37
37
|
@adapter = start!
|
38
38
|
|
39
|
-
|
39
|
+
start_sse!
|
40
|
+
|
41
|
+
@client = SplitClient.new(@api_key, @adapter, @splits_repository, @segments_repository, @impressions_repository, @metrics_repository, @events_repository, @sdk_blocker, @config, @sse_handler)
|
40
42
|
@manager = SplitManager.new(@splits_repository, @sdk_blocker, @config)
|
41
43
|
|
42
44
|
validate_api_key
|
@@ -115,5 +117,16 @@ module SplitIoClient
|
|
115
117
|
@config.valid_mode = false
|
116
118
|
end
|
117
119
|
end
|
120
|
+
|
121
|
+
def start_sse!
|
122
|
+
if @config.push_notification_enabled
|
123
|
+
@splits_worker = SSE::Workers::SplitsWorker.new(@adapter, @config, @splits_repository)
|
124
|
+
@segments_worker = SSE::Workers::SegmentsWorker.new(@adapter, @config, @segments_repository)
|
125
|
+
@control_worker = SSE::Workers::ControlWorker.new(@adapter, @config)
|
126
|
+
|
127
|
+
options = { channels: 'mauro-c', key: 'fake_key', url_host: 'fake-url' }
|
128
|
+
@sse_handler = SSE::SSEHandler.new(@config, options, @splits_worker, @segments_worker, @control_worker)
|
129
|
+
end
|
130
|
+
end
|
118
131
|
end
|
119
132
|
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
require 'concurrent/atomics'
|
4
|
+
require 'socketry'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
module SplitIoClient
|
8
|
+
module SSE
|
9
|
+
module EventSource
|
10
|
+
class Client
|
11
|
+
DEFAULT_READ_TIMEOUT = 200
|
12
|
+
KEEP_ALIVE_RESPONSE = "c\r\n:keepalive\n\n\r\n".freeze
|
13
|
+
|
14
|
+
def initialize(url, config, read_timeout: DEFAULT_READ_TIMEOUT)
|
15
|
+
@uri = URI(url)
|
16
|
+
@config = config
|
17
|
+
@read_timeout = read_timeout
|
18
|
+
@connected = Concurrent::AtomicBoolean.new(false)
|
19
|
+
@socket = nil
|
20
|
+
|
21
|
+
@on = { event: ->(_) {}, error: ->(_) {} }
|
22
|
+
|
23
|
+
yield self if block_given?
|
24
|
+
|
25
|
+
connect_thread
|
26
|
+
|
27
|
+
connect_passenger_forked if defined?(PhusionPassenger)
|
28
|
+
end
|
29
|
+
|
30
|
+
def on_event(&action)
|
31
|
+
@on[:event] = action
|
32
|
+
end
|
33
|
+
|
34
|
+
def on_error(&action)
|
35
|
+
@on[:error] = action
|
36
|
+
end
|
37
|
+
|
38
|
+
def close
|
39
|
+
@connected.make_false
|
40
|
+
@socket&.close
|
41
|
+
@socket = nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def status
|
45
|
+
return Status::CONNECTED if @connected.value
|
46
|
+
|
47
|
+
Status::DISCONNECTED
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def connect_thread
|
53
|
+
@config.threads[:connect_stream] = Thread.new do
|
54
|
+
connect_stream
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def connect_passenger_forked
|
59
|
+
PhusionPassenger.on_event(:starting_worker_process) { |forked| connect_thread if forked }
|
60
|
+
end
|
61
|
+
|
62
|
+
def connect_stream
|
63
|
+
@config.logger.info("Connecting to #{@uri.host}...")
|
64
|
+
|
65
|
+
begin
|
66
|
+
@socket = socket_connect
|
67
|
+
|
68
|
+
@socket.write(build_request(@uri))
|
69
|
+
@connected.make_true
|
70
|
+
rescue StandardError => e
|
71
|
+
dispatch_error(e.inspect)
|
72
|
+
end
|
73
|
+
|
74
|
+
while @connected.value
|
75
|
+
begin
|
76
|
+
partial_data = @socket.readpartial(2048, timeout: @read_timeout)
|
77
|
+
rescue Socketry::TimeoutError
|
78
|
+
@config.logger.error("Socket read time out in #{@read_timeout}")
|
79
|
+
@connected.make_false
|
80
|
+
connect_stream
|
81
|
+
end
|
82
|
+
|
83
|
+
proccess_data(partial_data) unless partial_data == KEEP_ALIVE_RESPONSE
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def socket_connect
|
88
|
+
return Socketry::SSL::Socket.connect(@uri.host, @uri.port) if @uri.scheme.casecmp('https').zero?
|
89
|
+
|
90
|
+
Socketry::TCP::Socket.connect(@uri.host, @uri.port)
|
91
|
+
end
|
92
|
+
|
93
|
+
def proccess_data(partial_data)
|
94
|
+
unless partial_data.nil?
|
95
|
+
@config.logger.debug("Event partial data: #{partial_data}")
|
96
|
+
buffer = read_partial_data(partial_data)
|
97
|
+
event = parse_event(buffer)
|
98
|
+
|
99
|
+
dispatch_event(event)
|
100
|
+
end
|
101
|
+
rescue StandardError => e
|
102
|
+
dispatch_error(e.inspect)
|
103
|
+
end
|
104
|
+
|
105
|
+
def build_request(uri)
|
106
|
+
req = "GET #{uri.request_uri} HTTP/1.1\r\n"
|
107
|
+
req << "Host: #{uri.host}\r\n"
|
108
|
+
req << "Accept: text/event-stream\r\n"
|
109
|
+
req << "Cache-Control: no-cache\r\n"
|
110
|
+
req << "\r\n"
|
111
|
+
@config.logger.debug("Request info: #{req}")
|
112
|
+
req
|
113
|
+
end
|
114
|
+
|
115
|
+
def read_partial_data(data)
|
116
|
+
buffer = ''
|
117
|
+
buffer << data
|
118
|
+
buffer.chomp!
|
119
|
+
buffer.split("\n")
|
120
|
+
end
|
121
|
+
|
122
|
+
def parse_event(buffer)
|
123
|
+
event_type = nil
|
124
|
+
parsed_data = nil
|
125
|
+
client_id = nil
|
126
|
+
|
127
|
+
buffer.each do |d|
|
128
|
+
splited_data = d.split(':')
|
129
|
+
|
130
|
+
case splited_data[0]
|
131
|
+
when 'event'
|
132
|
+
event_type = splited_data[1].strip
|
133
|
+
when 'data'
|
134
|
+
event_data = JSON.parse(d.sub('data: ', ''))
|
135
|
+
client_id = event_data['clientId']&.strip
|
136
|
+
parsed_data = JSON.parse(event_data['data'])
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
return StreamData.new(event_type, client_id, parsed_data) unless event_type.nil? || parsed_data.nil?
|
141
|
+
|
142
|
+
raise 'Invalid event format.'
|
143
|
+
rescue StandardError => e
|
144
|
+
dispatch_error(e.inspect)
|
145
|
+
nil
|
146
|
+
end
|
147
|
+
|
148
|
+
def dispatch_event(event)
|
149
|
+
@config.logger.debug("Dispatching event: #{event}") unless event.nil?
|
150
|
+
@on[:event].call(event) unless event.nil?
|
151
|
+
end
|
152
|
+
|
153
|
+
def dispatch_error(error)
|
154
|
+
@config.logger.debug("Dispatching error: #{error}")
|
155
|
+
@on[:error].call(error)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
StreamData = Struct.new(:event_type, :client_id, :data)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SplitIoClient
|
4
|
+
module SSE
|
5
|
+
module EventSource
|
6
|
+
class EventTypes
|
7
|
+
SPLIT_UPDATE = 'SPLIT_UPDATE'
|
8
|
+
SPLIT_KILL = 'SPLIT_KILL'
|
9
|
+
SEGMENT_UPDATE = 'SEGMENT_UPDATE'
|
10
|
+
CONTROL = 'CONTROL'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SplitIoClient
|
4
|
+
module SSE
|
5
|
+
class SSEHandler
|
6
|
+
attr_reader :sse_client
|
7
|
+
|
8
|
+
def initialize(config, options, splits_worker, segments_worker, control_worker)
|
9
|
+
@config = config
|
10
|
+
@options = options
|
11
|
+
@splits_worker = splits_worker
|
12
|
+
@segments_worker = segments_worker
|
13
|
+
@control_worker = control_worker
|
14
|
+
|
15
|
+
@sse_client = start_sse_client
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def start_sse_client
|
21
|
+
url = "#{@options[:url_host]}/event-stream?channels=#{@options[:channels]}&v=1.1&key=#{@options[:key]}"
|
22
|
+
|
23
|
+
sse_client = SSE::EventSource::Client.new(url, @config) do |client|
|
24
|
+
client.on_event do |event|
|
25
|
+
process_event(event)
|
26
|
+
end
|
27
|
+
|
28
|
+
client.on_error do |error|
|
29
|
+
process_error(error)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
sse_client
|
34
|
+
end
|
35
|
+
|
36
|
+
def process_event(event)
|
37
|
+
case event.data['type']
|
38
|
+
when SSE::EventSource::EventTypes::SPLIT_UPDATE
|
39
|
+
split_update_notification(event)
|
40
|
+
when SSE::EventSource::EventTypes::SPLIT_KILL
|
41
|
+
split_kill_notification(event)
|
42
|
+
when SSE::EventSource::EventTypes::SEGMENT_UPDATE
|
43
|
+
segment_update_notification(event)
|
44
|
+
when SSE::EventSource::EventTypes::CONTROL
|
45
|
+
control_notification(event)
|
46
|
+
else
|
47
|
+
@config.logger.error("Incorrect event type: #{event}")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def process_error(error)
|
52
|
+
@config.logger.error("SSE::EventSource::Client error: #{error}")
|
53
|
+
end
|
54
|
+
|
55
|
+
def split_update_notification(event)
|
56
|
+
@config.logger.debug("SPLIT UPDATE notification received: #{event}")
|
57
|
+
@splits_worker.add_to_queue(event.data['changeNumber'])
|
58
|
+
end
|
59
|
+
|
60
|
+
def split_kill_notification(event)
|
61
|
+
@config.logger.debug("SPLIT KILL notification received: #{event}")
|
62
|
+
|
63
|
+
change_number = event.data['changeNumber']
|
64
|
+
default_treatment = event.data['defaultTreatment']
|
65
|
+
split_name = event.data['splitName']
|
66
|
+
|
67
|
+
@splits_worker.kill_split(change_number, split_name, default_treatment)
|
68
|
+
end
|
69
|
+
|
70
|
+
def segment_update_notification(event)
|
71
|
+
@config.logger.debug("SEGMENT UPDATE notification received: #{event}")
|
72
|
+
change_number = event.data['changeNumber']
|
73
|
+
segment_name = event.data['segmentName']
|
74
|
+
|
75
|
+
@segments_worker.add_to_queue(change_number, segment_name)
|
76
|
+
end
|
77
|
+
|
78
|
+
def control_notification(event)
|
79
|
+
@config.logger.debug("CONTROL notification received: #{event}")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SplitIoClient
|
4
|
+
module SSE
|
5
|
+
module Workers
|
6
|
+
class ControlWorker
|
7
|
+
def initialize(adapter, config)
|
8
|
+
@adapter = adapter
|
9
|
+
@config = config
|
10
|
+
|
11
|
+
perform_thread
|
12
|
+
|
13
|
+
perform_passenger_forked if defined?(PhusionPassenger)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def perform
|
19
|
+
# TODO: IMPLEMENT THIS METHOD
|
20
|
+
end
|
21
|
+
|
22
|
+
def perform_thread
|
23
|
+
@config.threads[:segment_update_worker] = Thread.new do
|
24
|
+
perform
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def perform_passenger_forked
|
29
|
+
PhusionPassenger.on_event(:starting_worker_process) { |forked| perform_thread if forked }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SplitIoClient
|
4
|
+
module SSE
|
5
|
+
module Workers
|
6
|
+
class SegmentsWorker
|
7
|
+
def initialize(adapter, config, segments_repository)
|
8
|
+
@adapter = adapter
|
9
|
+
@config = config
|
10
|
+
@segments_repository = segments_repository
|
11
|
+
@queue = Queue.new
|
12
|
+
|
13
|
+
perform_thread
|
14
|
+
|
15
|
+
perform_passenger_forked if defined?(PhusionPassenger)
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_to_queue(change_number, segment_name)
|
19
|
+
item = { change_number: change_number, segment_name: segment_name }
|
20
|
+
@queue.push(item)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def perform
|
26
|
+
while (item = @queue.pop)
|
27
|
+
segment_name = item[:segment_name]
|
28
|
+
change_number = item[:change_number]
|
29
|
+
since = @segments_repository.get_change_number(segment_name)
|
30
|
+
|
31
|
+
@adapter.segment_fetcher.fetch_segment(segment_name) unless since >= change_number
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def perform_thread
|
36
|
+
@config.threads[:segment_update_worker] = Thread.new do
|
37
|
+
perform
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def perform_passenger_forked
|
42
|
+
PhusionPassenger.on_event(:starting_worker_process) { |forked| perform_thread if forked }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SplitIoClient
|
4
|
+
module SSE
|
5
|
+
module Workers
|
6
|
+
class SplitsWorker
|
7
|
+
def initialize(adapter, config, splits_repository)
|
8
|
+
@adapter = adapter
|
9
|
+
@config = config
|
10
|
+
@splits_repository = splits_repository
|
11
|
+
@queue = Queue.new
|
12
|
+
|
13
|
+
perform_thread
|
14
|
+
|
15
|
+
perform_passenger_forked if defined?(PhusionPassenger)
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_to_queue(change_number)
|
19
|
+
@queue.push(change_number)
|
20
|
+
end
|
21
|
+
|
22
|
+
def kill_split(change_number, split_name, default_treatment)
|
23
|
+
@splits_repository.kill(change_number, split_name, default_treatment)
|
24
|
+
add_to_queue(change_number)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def perform
|
30
|
+
while (change_number = @queue.pop)
|
31
|
+
since = @splits_repository.get_change_number
|
32
|
+
@adapter.split_fetcher.fetch_splits unless since >= change_number
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def perform_thread
|
37
|
+
@config.threads[:split_update_worker] = Thread.new do
|
38
|
+
perform
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def perform_passenger_forked
|
43
|
+
PhusionPassenger.on_event(:starting_worker_process) { |forked| perform_thread if forked }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/splitclient-rb.gemspec
CHANGED
@@ -54,5 +54,6 @@ Gem::Specification.new do |spec|
|
|
54
54
|
spec.add_runtime_dependency 'lru_redux'
|
55
55
|
spec.add_runtime_dependency 'net-http-persistent', '>= 2.9'
|
56
56
|
spec.add_runtime_dependency 'redis', '>= 3.2'
|
57
|
+
spec.add_runtime_dependency 'socketry', '~> 0.5.1'
|
57
58
|
spec.add_runtime_dependency 'thread_safe', '>= 0.3'
|
58
59
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: splitclient-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 7.0.
|
4
|
+
version: 7.0.4.pre.rc1
|
5
5
|
platform: java
|
6
6
|
authors:
|
7
7
|
- Split Software
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-03-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -262,6 +262,20 @@ dependencies:
|
|
262
262
|
- - ">="
|
263
263
|
- !ruby/object:Gem::Version
|
264
264
|
version: '3.2'
|
265
|
+
- !ruby/object:Gem::Dependency
|
266
|
+
requirement: !ruby/object:Gem::Requirement
|
267
|
+
requirements:
|
268
|
+
- - "~>"
|
269
|
+
- !ruby/object:Gem::Version
|
270
|
+
version: 0.5.1
|
271
|
+
name: socketry
|
272
|
+
prerelease: false
|
273
|
+
type: :runtime
|
274
|
+
version_requirements: !ruby/object:Gem::Requirement
|
275
|
+
requirements:
|
276
|
+
- - "~>"
|
277
|
+
- !ruby/object:Gem::Version
|
278
|
+
version: 0.5.1
|
265
279
|
- !ruby/object:Gem::Dependency
|
266
280
|
requirement: !ruby/object:Gem::Requirement
|
267
281
|
requirements:
|
@@ -307,6 +321,8 @@ files:
|
|
307
321
|
- lib/splitclient-rb/cache/adapters/memory_adapters/map_adapter.rb
|
308
322
|
- lib/splitclient-rb/cache/adapters/memory_adapters/queue_adapter.rb
|
309
323
|
- lib/splitclient-rb/cache/adapters/redis_adapter.rb
|
324
|
+
- lib/splitclient-rb/cache/fetchers/segment_fetcher.rb
|
325
|
+
- lib/splitclient-rb/cache/fetchers/split_fetcher.rb
|
310
326
|
- lib/splitclient-rb/cache/repositories/events/memory_repository.rb
|
311
327
|
- lib/splitclient-rb/cache/repositories/events/redis_repository.rb
|
312
328
|
- lib/splitclient-rb/cache/repositories/events_repository.rb
|
@@ -328,8 +344,6 @@ files:
|
|
328
344
|
- lib/splitclient-rb/cache/stores/localhost_split_builder.rb
|
329
345
|
- lib/splitclient-rb/cache/stores/localhost_split_store.rb
|
330
346
|
- lib/splitclient-rb/cache/stores/sdk_blocker.rb
|
331
|
-
- lib/splitclient-rb/cache/stores/segment_store.rb
|
332
|
-
- lib/splitclient-rb/cache/stores/split_store.rb
|
333
347
|
- lib/splitclient-rb/cache/stores/store_utils.rb
|
334
348
|
- lib/splitclient-rb/clients/split_client.rb
|
335
349
|
- lib/splitclient-rb/engine/api/client.rb
|
@@ -380,6 +394,13 @@ files:
|
|
380
394
|
- lib/splitclient-rb/split_factory_builder.rb
|
381
395
|
- lib/splitclient-rb/split_factory_registry.rb
|
382
396
|
- lib/splitclient-rb/split_logger.rb
|
397
|
+
- lib/splitclient-rb/sse/event_source/client.rb
|
398
|
+
- lib/splitclient-rb/sse/event_source/event_types.rb
|
399
|
+
- lib/splitclient-rb/sse/event_source/status.rb
|
400
|
+
- lib/splitclient-rb/sse/sse_handler.rb
|
401
|
+
- lib/splitclient-rb/sse/workers/control_worker.rb
|
402
|
+
- lib/splitclient-rb/sse/workers/segments_worker.rb
|
403
|
+
- lib/splitclient-rb/sse/workers/splits_worker.rb
|
383
404
|
- lib/splitclient-rb/utilitites.rb
|
384
405
|
- lib/splitclient-rb/validators.rb
|
385
406
|
- lib/splitclient-rb/version.rb
|