splitclient-rb 7.0.3.pre.rc6-java → 7.0.4.pre.rc1-java
Sign up to get free protection for your applications and to get access to all the features.
- 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
|