ld-eventsource 2.5.1 → 3.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6c00daae667f29637ba82d85e504df0492d94f364b09bf1f2082dbf2eafae697
4
- data.tar.gz: 1fe8ad53f9c4329df7e93f96347f306a5e1f78798910e45206c5378bdd5160a6
3
+ metadata.gz: dcf2b9358b588b57f4bba285ed1a91bd399fe04c74622a0d944fd3fa0c56e9bb
4
+ data.tar.gz: 22514811554b69e1225aebebb18bb531b65daf27c8f0d2284717791f21aeb771
5
5
  SHA512:
6
- metadata.gz: 4aa8fcac3a6f23d04dcd865954af486711c45fcc6dcfe0158458c8729efa9d37ab8bd9e9b6820db68ccf86ea3f4b6bc51eeb29155e568df697a4842c58ab5fdb
7
- data.tar.gz: f55c144c992d34190bfc68e59f3b42bf9af80692a82cf1ce847391eb3ae2adfeb7be7500a0f671a5d9efa62f44ce595cdc3b30a12b6f69ea7db6c06dad35789e
6
+ metadata.gz: b20450c7187809e03706778149944c6268782c0b307b58874f569af2c6eb635ec125c65ed2d1c3077438f63a95e1dc86411743deb432f7da71aa4945bbfc12c1
7
+ data.tar.gz: 06dc4097880991e04e3ad8a8c3c3f0d01488849d84f0cc98444cbb8cb558b7a9949fc56169e3b18e569b2fe18b1900cd25404bf882f114c9816f0a715b1f9480
@@ -52,6 +52,15 @@ module SSE
52
52
  # The default HTTP method for requests.
53
53
  DEFAULT_HTTP_METHOD = "GET"
54
54
 
55
+ # TODO(breaking): Remove this filtering once we have updated to the next major breaking version.
56
+ # HTTP v6 requires keyword arguments instead of an options hash, so we filter to only known valid
57
+ # arguments to avoid passing unsupported options.
58
+ VALID_HTTP_CLIENT_OPTIONS = %i[
59
+ base_uri body encoding features follow form headers json keep_alive_timeout
60
+ nodelay params persistent proxy response retriable socket_class ssl_context
61
+ ssl ssl_socket_class timeout_class timeout_options
62
+ ].freeze
63
+
55
64
  #
56
65
  # Creates a new SSE client.
57
66
  #
@@ -114,19 +123,19 @@ module SSE
114
123
  retry_enabled: true,
115
124
  http_client_options: nil)
116
125
  @uri = URI(uri)
117
- @stopped = Concurrent::AtomicBoolean.new(false)
126
+ @stop_event = Concurrent::Event.new
118
127
  @retry_enabled = retry_enabled
119
128
 
120
129
  @headers = headers.clone
121
- @connect_timeout = connect_timeout
122
- @read_timeout = read_timeout
130
+ @connect_timeout = connect_timeout&.to_f
131
+ @read_timeout = read_timeout&.to_f
123
132
  @method = method.to_s.upcase
124
133
  @payload = payload
125
134
  @logger = logger || default_logger
126
135
 
127
136
  base_http_client_options = {}
128
137
  if socket_factory
129
- base_http_client_options["socket_class"] = socket_factory
138
+ base_http_client_options[:socket_class] = socket_factory
130
139
  end
131
140
 
132
141
  if proxy
@@ -139,22 +148,29 @@ module SSE
139
148
  end
140
149
 
141
150
  if @proxy
142
- base_http_client_options["proxy"] = {
151
+ base_http_client_options[:proxy] = {
143
152
  :proxy_address => @proxy.host,
144
153
  :proxy_port => @proxy.port,
145
154
  }
146
- base_http_client_options["proxy"][:proxy_username] = @proxy.user unless @proxy.user.nil?
147
- base_http_client_options["proxy"][:proxy_password] = @proxy.password unless @proxy.password.nil?
155
+ base_http_client_options[:proxy][:proxy_username] = @proxy.user unless @proxy.user.nil?
156
+ base_http_client_options[:proxy][:proxy_password] = @proxy.password unless @proxy.password.nil?
148
157
  end
149
158
 
150
159
  options = http_client_options.is_a?(Hash) ? base_http_client_options.merge(http_client_options) : base_http_client_options
160
+ options = options.transform_keys(&:to_sym)
161
+ options = options.select do |key, _|
162
+ included = VALID_HTTP_CLIENT_OPTIONS.include?(key)
163
+ @logger.warn { "Ignoring unsupported HTTP client option: #{key}" } unless included
164
+ included
165
+ end
166
+
167
+ timeout_options = {}
168
+ timeout_options[:connect] = @connect_timeout if @connect_timeout
169
+ timeout_options[:read] = @read_timeout if @read_timeout
151
170
 
152
- @http_client = HTTP::Client.new(options)
171
+ @http_client = HTTP::Client.new(**options)
153
172
  .follow
154
- .timeout({
155
- read: read_timeout,
156
- connect: connect_timeout,
157
- })
173
+ @http_client = @http_client.timeout(timeout_options) unless timeout_options.empty?
158
174
  @cxn = nil
159
175
  @lock = Mutex.new
160
176
 
@@ -262,7 +278,7 @@ module SSE
262
278
  # has no effect if called a second time.
263
279
  #
264
280
  def close
265
- if @stopped.make_true
281
+ if @stop_event.try?
266
282
  reset_http
267
283
  end
268
284
  end
@@ -273,7 +289,7 @@ module SSE
273
289
  # @return [Boolean] true if the client has been shut down
274
290
  #
275
291
  def closed?
276
- @stopped.value
292
+ @stop_event.set?
277
293
  end
278
294
 
279
295
  private
@@ -298,7 +314,7 @@ module SSE
298
314
  end
299
315
 
300
316
  def run_stream
301
- until @stopped.value
317
+ until @stop_event.set?
302
318
  close_connection
303
319
  begin
304
320
  resp = connect
@@ -307,12 +323,16 @@ module SSE
307
323
  end
308
324
  # There's a potential race if close was called in the middle of the previous line, i.e. after we
309
325
  # connected but before @cxn was set. Checking the variable again is a bit clunky but avoids that.
310
- return if @stopped.value
326
+ if @stop_event.set?
327
+ # close did not see this connection, so close it here.
328
+ reset_http
329
+ return
330
+ end
311
331
  read_stream(resp) unless resp.nil?
312
332
  rescue => e
313
333
  # When we deliberately close the connection, it will usually trigger an exception. The exact type
314
- # of exception depends on the specific Ruby runtime. But @stopped will always be set in this case.
315
- if @stopped.value
334
+ # of exception depends on the specific Ruby runtime. But the client will always be closed in this case.
335
+ if @stop_event.set?
316
336
  @logger.info { "Stream connection closed" }
317
337
  else
318
338
  log_and_dispatch_error(e, "Unexpected error from event source")
@@ -331,18 +351,20 @@ module SSE
331
351
  # Try to establish a streaming connection. Returns the StreamingHTTPConnection object if successful.
332
352
  def connect
333
353
  loop do
334
- return if @stopped.value
354
+ return if @stop_event.set?
335
355
  interval = @first_attempt ? 0 : @backoff.next_interval
336
356
  @first_attempt = false
337
357
  if interval > 0
338
358
  @logger.info { "Will retry connection after #{'%.3f' % interval} seconds" }
339
- sleep(interval)
359
+ # This wait ends early when close sets the event.
360
+ @stop_event.wait(interval)
361
+ return if @stop_event.set?
340
362
  end
341
363
  cxn = nil
342
364
  begin
343
365
  uri = build_uri_with_query_params
344
366
  @logger.info { "Connecting to event stream at #{uri}" }
345
- cxn = @http_client.request(@method, uri, build_opts)
367
+ cxn = @http_client.request(@method, uri, **build_opts)
346
368
  headers = cxn.headers
347
369
  if cxn.status.code == 200
348
370
  content_type = cxn.content_type.mime_type
@@ -379,7 +401,7 @@ module SSE
379
401
 
380
402
  chunks = Enumerator.new do |gen|
381
403
  loop do
382
- if @stopped.value
404
+ if @stop_event.set?
383
405
  break
384
406
  else
385
407
  begin
@@ -390,8 +412,10 @@ module SSE
390
412
  rescue HTTP::TimeoutError
391
413
  # For historical reasons, we rethrow this as our own type
392
414
  raise Errors::ReadTimeoutError.new(@read_timeout)
415
+ rescue EOFError
416
+ raise Errors::StreamClosedByServerError
393
417
  end
394
- break if data.nil?
418
+ raise Errors::StreamClosedByServerError if data.nil? # keep for v5 compat
395
419
  gen.yield data
396
420
  end
397
421
  end
@@ -399,7 +423,7 @@ module SSE
399
423
  event_parser = Impl::EventParser.new(Impl::BufferedLineReader.lines_from(chunks), @last_id)
400
424
 
401
425
  event_parser.items.each do |item|
402
- return if @stopped.value
426
+ return if @stop_event.set?
403
427
  case item
404
428
  when StreamEvent
405
429
  dispatch_event(item)
@@ -79,5 +79,15 @@ module SSE
79
79
  super("no data received in #{interval} seconds")
80
80
  end
81
81
  end
82
+
83
+ #
84
+ # An exception class indicating that the server ended the stream without an error. This is
85
+ # passed to the error handler specified in {Client#on_error}, and the client then reconnects.
86
+ #
87
+ class StreamClosedByServerError < StandardError
88
+ def initialize
89
+ super("stream closed by server")
90
+ end
91
+ end
82
92
  end
83
93
  end
@@ -1,3 +1,3 @@
1
1
  module SSE
2
- VERSION = "2.5.1" # x-release-please-version
2
+ VERSION = "3.0.0" # x-release-please-version
3
3
  end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ld-eventsource
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - LaunchDarkly
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-02 00:00:00.000000000 Z
11
+ date: 2026-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: logger
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '1.5'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '1.5'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: rspec
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -117,7 +103,7 @@ dependencies:
117
103
  version: 4.4.1
118
104
  - - "<"
119
105
  - !ruby/object:Gem::Version
120
- version: 6.0.0
106
+ version: 7.0.0
121
107
  type: :runtime
122
108
  prerelease: false
123
109
  version_requirements: !ruby/object:Gem::Requirement
@@ -127,7 +113,21 @@ dependencies:
127
113
  version: 4.4.1
128
114
  - - "<"
129
115
  - !ruby/object:Gem::Version
130
- version: 6.0.0
116
+ version: 7.0.0
117
+ - !ruby/object:Gem::Dependency
118
+ name: logger
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '1.5'
124
+ type: :runtime
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '1.5'
131
131
  description: LaunchDarkly SSE client for Ruby
132
132
  email:
133
133
  - team@launchdarkly.com