kameleoon-client-ruby 3.22.1 → 3.22.2
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/lib/kameleoon/kameleoon_client.rb +1 -0
- data/lib/kameleoon/managers/tracking/tracking_manager.rb +46 -16
- data/lib/kameleoon/network/net_provider.rb +92 -10
- data/lib/kameleoon/network/network_manager.rb +6 -0
- data/lib/kameleoon/real_time/sse_request.rb +6 -3
- data/lib/kameleoon/version.rb +1 -1
- metadata +10 -24
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7eceff94d1ab7a39dfa3603c7cfd124bad0c49a7315feb7e0bd938c45feca9f0
|
|
4
|
+
data.tar.gz: f3df43034ca7460e3fe21a47caa920e9ede1e65ac49cb5c04b0196198d4a2391
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7ed0b76309075aac725c827733a454754f6183001a600582f79e116c8774d5ac4e8448dbb7b58fcb006649d1801cc0d86ef0484b0e6ee70201d4eb6cf3aa92fd
|
|
7
|
+
data.tar.gz: 927ca5a73f64293a53b3f4e0083112ed0257de431c04fa80541102cd1fec954d80c122c9138448264090e017d78b02a8b1028ac4ed36d1e1ecb0b489b7822211
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'concurrent/executor/thread_pool_executor'
|
|
3
4
|
require 'kameleoon/data/data'
|
|
4
5
|
require 'kameleoon/logging/kameleoon_logger'
|
|
5
6
|
require 'kameleoon/managers/tracking/tracking_builder'
|
|
@@ -12,9 +13,13 @@ module Kameleoon
|
|
|
12
13
|
LINES_DELIMETER = "\n"
|
|
13
14
|
REQUEST_SIZE_LIMIT = 2560 * 1024 # 2.5 * 1024^2 characters
|
|
14
15
|
PROBE_MAX_VISITORS = 1
|
|
16
|
+
MAX_SEND_THREADS = 8
|
|
17
|
+
MAX_QUEUED_REQUESTS = 32
|
|
18
|
+
SEND_THREAD_IDLE_SECONDS = 60
|
|
19
|
+
STOP_WAIT_SECONDS = 1
|
|
15
20
|
|
|
16
21
|
def initialize(
|
|
17
|
-
data_manager, network_manager, visitor_manager, track_interval_seconds, scheduler
|
|
22
|
+
data_manager, network_manager, visitor_manager, track_interval_seconds, scheduler, executor: nil
|
|
18
23
|
)
|
|
19
24
|
Logging::KameleoonLogger.debug(
|
|
20
25
|
'CALL: TrackingManager.new(data_manager, network_manager. visitor_manager, ' \
|
|
@@ -25,6 +30,10 @@ module Kameleoon
|
|
|
25
30
|
@data_manager = data_manager
|
|
26
31
|
@network_manager = network_manager
|
|
27
32
|
@visitor_manager = visitor_manager
|
|
33
|
+
@executor = executor || Concurrent::ThreadPoolExecutor.new(
|
|
34
|
+
name: 'kameleoon-tracking', min_threads: 0, max_threads: MAX_SEND_THREADS,
|
|
35
|
+
max_queue: MAX_QUEUED_REQUESTS, idletime: SEND_THREAD_IDLE_SECONDS, fallback_policy: :abort
|
|
36
|
+
)
|
|
28
37
|
@track_all_job = scheduler.schedule_every(track_interval_seconds, method(:track_all)) unless scheduler.nil?
|
|
29
38
|
Logging::KameleoonLogger.debug(
|
|
30
39
|
'RETURN: TrackingManager.new(data_manager, network_manager. visitor_manager, ' \
|
|
@@ -36,6 +45,10 @@ module Kameleoon
|
|
|
36
45
|
Logging::KameleoonLogger.debug('CALL: TrackingManager.stop')
|
|
37
46
|
@track_all_job&.unschedule
|
|
38
47
|
@track_all_job = nil
|
|
48
|
+
# Queued requests still run; a later `track` is rejected and keeps its data (see `perform_tracking_request`).
|
|
49
|
+
# The wait lets in-flight requests finish before the client disposal closes the connections.
|
|
50
|
+
@executor.shutdown
|
|
51
|
+
@executor.wait_for_termination(STOP_WAIT_SECONDS)
|
|
39
52
|
Logging::KameleoonLogger.debug('RETURN: TrackingManager.stop')
|
|
40
53
|
end
|
|
41
54
|
|
|
@@ -97,25 +110,42 @@ module Kameleoon
|
|
|
97
110
|
lines = tracking_lines.join(LINES_DELIMETER)
|
|
98
111
|
# mark unsent data as transmitted
|
|
99
112
|
visitor_data.each { |d| d.mark_as_transmitting if d.is_a?(Kameleoon::Data) }
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
Logging::KameleoonLogger.error('Tracking request failed')
|
|
109
|
-
Logging::KameleoonLogger.info('Failed request for tracking visitors: %s, data: %s',
|
|
110
|
-
visitor_codes, visitor_data)
|
|
111
|
-
visitor_data.each { |d| d.mark_as_unsent if d.is_a?(Kameleoon::Data) }
|
|
112
|
-
enter_probe_mode
|
|
113
|
-
@tracking_visitors.add_all(visitor_codes)
|
|
114
|
-
end
|
|
113
|
+
begin
|
|
114
|
+
@executor.post { send_tracking_request(visitor_codes, visitor_data, lines) }
|
|
115
|
+
rescue Concurrent::RejectedExecutionError
|
|
116
|
+
Logging::KameleoonLogger.warning(
|
|
117
|
+
'Tracking request pool is full or stopped: data of %s visitors is kept to be sent later',
|
|
118
|
+
visitor_codes.size
|
|
119
|
+
)
|
|
120
|
+
keep_for_later(visitor_codes, visitor_data)
|
|
115
121
|
end
|
|
116
122
|
true
|
|
117
123
|
end
|
|
118
124
|
|
|
125
|
+
def send_tracking_request(visitor_codes, visitor_data, lines)
|
|
126
|
+
result = @network_manager.send_tracking_data(lines)
|
|
127
|
+
if result != false
|
|
128
|
+
Logging::KameleoonLogger.info('Successful request for tracking visitors: %s, data: %s',
|
|
129
|
+
visitor_codes, visitor_data)
|
|
130
|
+
visitor_data.each { |d| d.mark_as_sent if d.is_a?(Kameleoon::Data) }
|
|
131
|
+
leave_probe_mode
|
|
132
|
+
else
|
|
133
|
+
Logging::KameleoonLogger.error('Tracking request failed')
|
|
134
|
+
Logging::KameleoonLogger.info('Failed request for tracking visitors: %s, data: %s',
|
|
135
|
+
visitor_codes, visitor_data)
|
|
136
|
+
enter_probe_mode
|
|
137
|
+
keep_for_later(visitor_codes, visitor_data)
|
|
138
|
+
end
|
|
139
|
+
rescue StandardError => e
|
|
140
|
+
Logging::KameleoonLogger.error('Error occurred within tracking request: %s', e)
|
|
141
|
+
keep_for_later(visitor_codes, visitor_data)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def keep_for_later(visitor_codes, visitor_data)
|
|
145
|
+
visitor_data.each { |d| d.mark_as_unsent if d.is_a?(Kameleoon::Data) }
|
|
146
|
+
@tracking_visitors.add_all(visitor_codes)
|
|
147
|
+
end
|
|
148
|
+
|
|
119
149
|
def enter_probe_mode
|
|
120
150
|
return if @probe_mode
|
|
121
151
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'net/http'
|
|
4
|
+
require 'net/http/persistent'
|
|
4
5
|
require 'kameleoon/version'
|
|
5
6
|
require 'kameleoon/network/response'
|
|
7
|
+
require 'kameleoon/logging/kameleoon_logger'
|
|
6
8
|
require 'kameleoon/exceptions'
|
|
7
9
|
|
|
8
10
|
module Kameleoon
|
|
@@ -12,6 +14,9 @@ module Kameleoon
|
|
|
12
14
|
raise KameleoonError, 'Call of not implemented method!'
|
|
13
15
|
end
|
|
14
16
|
|
|
17
|
+
# Releases the resources held by the provider (e.g. kept-alive connections); no-op by default.
|
|
18
|
+
def close; end
|
|
19
|
+
|
|
15
20
|
private
|
|
16
21
|
|
|
17
22
|
def collect_headers(request)
|
|
@@ -29,7 +34,27 @@ module Kameleoon
|
|
|
29
34
|
end
|
|
30
35
|
end
|
|
31
36
|
|
|
37
|
+
# Performs requests over kept-alive connections managed by `Net::HTTP::Persistent`.
|
|
38
|
+
#
|
|
39
|
+
# `Net::HTTP::Persistent` timeouts are per client, while the SDK timeout is per request, so one client (with its
|
|
40
|
+
# own connection pool) is kept per distinct timeout value. In practice that is the default timeout plus the few
|
|
41
|
+
# values callers pass explicitly.
|
|
32
42
|
class SyncNetProvider < NetProvider
|
|
43
|
+
# Idle connections are reconnected before use after this long; keeps a request from hitting a connection the
|
|
44
|
+
# server has already dropped.
|
|
45
|
+
IDLE_TIMEOUT = 15 # seconds
|
|
46
|
+
# Guards against a caller passing a different timeout on every request (each value would keep its own idle
|
|
47
|
+
# connections). Requests with further timeout values are sent on a one-off connection with their own timeout.
|
|
48
|
+
MAX_CLIENTS = 16
|
|
49
|
+
|
|
50
|
+
def initialize
|
|
51
|
+
super()
|
|
52
|
+
@clients = {}
|
|
53
|
+
@clients_mutex = Mutex.new
|
|
54
|
+
@overflow_warned = false
|
|
55
|
+
@closed = false
|
|
56
|
+
end
|
|
57
|
+
|
|
33
58
|
def make_request(request)
|
|
34
59
|
for _ in 0..8 do
|
|
35
60
|
response = make_single_request(request)
|
|
@@ -44,10 +69,17 @@ module Kameleoon
|
|
|
44
69
|
response
|
|
45
70
|
end
|
|
46
71
|
|
|
72
|
+
def close
|
|
73
|
+
@clients_mutex.synchronize do
|
|
74
|
+
@closed = true
|
|
75
|
+
@clients.each_value(&:shutdown)
|
|
76
|
+
@clients.clear
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
47
80
|
private
|
|
48
81
|
|
|
49
82
|
def make_single_request(request)
|
|
50
|
-
resp = nil
|
|
51
83
|
case request.method
|
|
52
84
|
when Method::GET
|
|
53
85
|
req = Net::HTTP::Get.new(request.url)
|
|
@@ -57,21 +89,71 @@ module Kameleoon
|
|
|
57
89
|
else
|
|
58
90
|
return unknown_method_response(request.method, request)
|
|
59
91
|
end
|
|
60
|
-
|
|
61
|
-
headers = collect_headers(request)
|
|
62
|
-
headers.each { |k, v| req[k] = v }
|
|
92
|
+
collect_headers(request).each { |k, v| req[k] = v }
|
|
63
93
|
uri = URI(request.url)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
end
|
|
94
|
+
timeout = request.timeout.to_f / 1000.0
|
|
95
|
+
client = client_for(timeout)
|
|
96
|
+
resp = client ? client.request(uri, req) : request_once(uri, req, timeout)
|
|
68
97
|
body = resp.body
|
|
69
98
|
body = nil if body&.empty?
|
|
70
|
-
|
|
71
|
-
Response.new(nil, resp.code.to_i, body, headers, request)
|
|
99
|
+
Response.new(nil, resp.code.to_i, body, resp.to_hash, request)
|
|
72
100
|
rescue StandardError => e
|
|
73
101
|
Response.new(e, nil, nil, nil, request)
|
|
74
102
|
end
|
|
103
|
+
|
|
104
|
+
def client_for(timeout)
|
|
105
|
+
@clients_mutex.synchronize do
|
|
106
|
+
return nil if @closed
|
|
107
|
+
|
|
108
|
+
client = @clients[timeout]
|
|
109
|
+
return client if client
|
|
110
|
+
|
|
111
|
+
if @clients.size >= MAX_CLIENTS
|
|
112
|
+
unless @overflow_warned
|
|
113
|
+
@overflow_warned = true
|
|
114
|
+
Logging::KameleoonLogger.warning(
|
|
115
|
+
'More than %s distinct request timeouts in use; requests with further timeout values ' \
|
|
116
|
+
'are sent without connection reuse', MAX_CLIENTS
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
return nil
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
@clients[timeout] = build_client(timeout)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Sends the request on a connection which is closed afterwards, honouring `timeout` (seconds).
|
|
127
|
+
def request_once(uri, req, timeout)
|
|
128
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https', open_timeout: timeout,
|
|
129
|
+
read_timeout: timeout, write_timeout: timeout) do |http|
|
|
130
|
+
http.request(req)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# The timeout is applied as given, also when it is zero or negative (as `Net::HTTP` did before pooling):
|
|
135
|
+
# validating it is the callers' concern.
|
|
136
|
+
def build_client(timeout)
|
|
137
|
+
http = Net::HTTP::Persistent.new(name: "kameleoon-sdk-#{timeout}", proxy: proxy_setting)
|
|
138
|
+
http.idle_timeout = IDLE_TIMEOUT
|
|
139
|
+
http.open_timeout = timeout
|
|
140
|
+
http.read_timeout = timeout
|
|
141
|
+
http.write_timeout = timeout
|
|
142
|
+
http
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# `:ENV` keeps the `http_proxy`/`no_proxy` support that plain `Net::HTTP` has by default. net-http-persistent
|
|
146
|
+
# 4.0.8 parses `no_proxy` with `Array#filter_map` (Ruby 2.7+), so on older Rubies with `no_proxy` set the proxy
|
|
147
|
+
# URI is passed directly, without the bypass list (Kameleoon hosts are not expected to be in it anyway).
|
|
148
|
+
def proxy_setting
|
|
149
|
+
no_proxy = ENV['no_proxy'] || ENV['NO_PROXY']
|
|
150
|
+
return :ENV if RUBY_VERSION >= '2.7' || no_proxy.nil? || no_proxy.empty?
|
|
151
|
+
|
|
152
|
+
env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
|
|
153
|
+
return nil if env_proxy.nil? || env_proxy.empty? || no_proxy == '*'
|
|
154
|
+
|
|
155
|
+
URI(env_proxy.match?(%r{\Ahttps?://}) ? env_proxy : "http://#{env_proxy}")
|
|
156
|
+
end
|
|
75
157
|
end
|
|
76
158
|
end
|
|
77
159
|
end
|
|
@@ -86,6 +86,12 @@ module Kameleoon
|
|
|
86
86
|
unwrap_response(*make_call(Events::RequestType::ACCESS_TOKEN, request, false))
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
+
# Closes the kept-alive connections. A request still running completes on its connection; a request started
|
|
90
|
+
# afterwards fails with an error response.
|
|
91
|
+
def close
|
|
92
|
+
@sync_net_provider.close
|
|
93
|
+
end
|
|
94
|
+
|
|
89
95
|
private
|
|
90
96
|
|
|
91
97
|
def make_call(request_type, request, try_access_token_auth, retry_limit = 0, retry_delay = 0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'net/http'
|
|
4
4
|
|
|
5
5
|
module Kameleoon
|
|
6
6
|
module RealTime
|
|
@@ -31,8 +31,11 @@ module Kameleoon
|
|
|
31
31
|
##
|
|
32
32
|
# Starts SSE connection and stay in the loop until close.
|
|
33
33
|
def start
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
uri = URI(@url)
|
|
35
|
+
# No read timeout: the stream is expected to stay silent between events.
|
|
36
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', read_timeout: nil) do |http|
|
|
37
|
+
http.request(Net::HTTP::Get.new(uri, @headers)) { |resp| handle_resp(resp) }
|
|
38
|
+
end
|
|
36
39
|
end
|
|
37
40
|
|
|
38
41
|
##
|
data/lib/kameleoon/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,43 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kameleoon-client-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.22.
|
|
4
|
+
version: 3.22.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kameleoon
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: concurrent-ruby
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: 1.1.5
|
|
20
|
-
type: :runtime
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - ">="
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: 1.1.5
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: em-synchrony
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - ">="
|
|
17
|
+
- - "~>"
|
|
32
18
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: 1.
|
|
19
|
+
version: '1.2'
|
|
34
20
|
type: :runtime
|
|
35
21
|
prerelease: false
|
|
36
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
23
|
requirements:
|
|
38
|
-
- - "
|
|
24
|
+
- - "~>"
|
|
39
25
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: 1.
|
|
26
|
+
version: '1.2'
|
|
41
27
|
- !ruby/object:Gem::Dependency
|
|
42
28
|
name: rufus-scheduler
|
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -53,19 +39,19 @@ dependencies:
|
|
|
53
39
|
- !ruby/object:Gem::Version
|
|
54
40
|
version: '3.7'
|
|
55
41
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
42
|
+
name: net-http-persistent
|
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
|
58
44
|
requirements:
|
|
59
45
|
- - "~>"
|
|
60
46
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '
|
|
47
|
+
version: '4.0'
|
|
62
48
|
type: :runtime
|
|
63
49
|
prerelease: false
|
|
64
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
51
|
requirements:
|
|
66
52
|
- - "~>"
|
|
67
53
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '
|
|
54
|
+
version: '4.0'
|
|
69
55
|
description: Kameleoon Client Ruby Software Development Kit
|
|
70
56
|
email:
|
|
71
57
|
- sdk@kameleoon.com
|