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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 04efd3ab470f352395de3161c329086e07893f9a29d2e8d55085cc4ab00788c3
4
- data.tar.gz: 62d498e4f19ec285358b29672d622e0476e70ae31cbf39bd480bf33dbe5b335b
3
+ metadata.gz: 7eceff94d1ab7a39dfa3603c7cfd124bad0c49a7315feb7e0bd938c45feca9f0
4
+ data.tar.gz: f3df43034ca7460e3fe21a47caa920e9ede1e65ac49cb5c04b0196198d4a2391
5
5
  SHA512:
6
- metadata.gz: 0431f162cc71fb10db0c3920bef2a9d04c35713dfdfd467fc750ac16345c3cfd5965b8abb17c335ad8a7e7226d85c776a14d5d76030c7173969b00a61b309fd1
7
- data.tar.gz: 683b9a9e65838df67344bc3b5ff05d8b04eb37fe5328ec4532f4d18b141d9957ca6709b3373adc22ce4f52791d3d0e90be8927693acfe159add2ba991812dc16
6
+ metadata.gz: 7ed0b76309075aac725c827733a454754f6183001a600582f79e116c8774d5ac4e8448dbb7b58fcb006649d1801cc0d86ef0484b0e6ee70201d4eb6cf3aa92fd
7
+ data.tar.gz: 927ca5a73f64293a53b3f4e0083112ed0257de431c04fa80541102cd1fec954d80c122c9138448264090e017d78b02a8b1028ac4ed36d1e1ecb0b489b7822211
@@ -1033,6 +1033,7 @@ module Kameleoon
1033
1033
  @visitor_manager.stop
1034
1034
  @tracking_manager.stop
1035
1035
  @scheduler.shutdown
1036
+ @network_manager.close
1036
1037
  Logging::KameleoonLogger.debug('RETURN: KameleoonClient.dispose')
1037
1038
  end
1038
1039
 
@@ -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
- Thread.new do
101
- result = @network_manager.send_tracking_data(lines)
102
- if result != false
103
- Logging::KameleoonLogger.info('Successful request for tracking visitors: %s, data: %s',
104
- visitor_codes, visitor_data)
105
- visitor_data.each { |d| d.mark_as_sent if d.is_a?(Kameleoon::Data) }
106
- leave_probe_mode
107
- else
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
- timeout = request.timeout.to_f / 1000.0
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
- Net::HTTP.start(uri.hostname, uri.port, use_ssl: true, open_timeout: timeout,
65
- read_timeout: timeout, ssl_timeout: timeout) do |http|
66
- resp = http.request(req)
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
- headers = resp.to_hash
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 'restclient'
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
- RestClient::Request.execute(method: :get, url: @url, headers: @headers, read_timeout: nil,
35
- block_response: method(:handle_resp))
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
  ##
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kameleoon
4
- SDK_VERSION = '3.22.1'
4
+ SDK_VERSION = '3.22.2'
5
5
  SDK_NAME = 'RUBY'
6
6
  end
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.1
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 00:00:00.000000000 Z
11
+ date: 2026-09-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: em-http-request
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.0.6
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.0.6
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: rest-client
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: '2.1'
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: '2.1'
54
+ version: '4.0'
69
55
  description: Kameleoon Client Ruby Software Development Kit
70
56
  email:
71
57
  - sdk@kameleoon.com