ld-eventsource 1.0.3 → 2.1.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.
@@ -1,3 +1,3 @@
1
1
  module SSE
2
- VERSION = "1.0.3"
2
+ VERSION = "2.1.0"
3
3
  end
data/spec/backoff_spec.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require "ld-eventsource"
2
2
 
3
+ require "http_stub"
4
+
3
5
  module SSE
4
6
  module Impl
5
7
  describe Backoff do
data/spec/client_spec.rb CHANGED
@@ -1,6 +1,4 @@
1
1
  require "ld-eventsource"
2
- require "socketry"
3
- require "http_stub"
4
2
 
5
3
  #
6
4
  # End-to-end tests of the SSE client against a real server
@@ -62,8 +60,11 @@ EOT
62
60
  expect(received_req.header).to eq({
63
61
  "accept" => ["text/event-stream"],
64
62
  "cache-control" => ["no-cache"],
65
- "host" => ["127.0.0.1"],
66
- "authorization" => ["secret"]
63
+ "host" => ["127.0.0.1:" + server.port.to_s],
64
+ "authorization" => ["secret"],
65
+ "user-agent" => ["ruby-eventsource"],
66
+ "content-length" => ["0"],
67
+ "connection" => ["close"]
67
68
  })
68
69
  end
69
70
  end
@@ -85,9 +86,12 @@ EOT
85
86
  expect(received_req.header).to eq({
86
87
  "accept" => ["text/event-stream"],
87
88
  "cache-control" => ["no-cache"],
88
- "host" => ["127.0.0.1"],
89
+ "host" => ["127.0.0.1:" + server.port.to_s],
89
90
  "authorization" => ["secret"],
90
- "last-event-id" => [id]
91
+ "last-event-id" => [id],
92
+ "user-agent" => ["ruby-eventsource"],
93
+ "content-length" => ["0"],
94
+ "connection" => ["close"]
91
95
  })
92
96
  end
93
97
  end
@@ -366,4 +370,75 @@ EOT
366
370
  end
367
371
  end
368
372
  end
373
+
374
+ it "connects to HTTP server through proxy" do
375
+ events_body = simple_event_1_text
376
+ with_server do |server|
377
+ server.setup_response("/") do |req,res|
378
+ send_stream_content(res, events_body, keep_open: false)
379
+ end
380
+ with_server(StubProxyServer.new) do |proxy|
381
+ event_sink = Queue.new
382
+ client = subject.new(server.base_uri, proxy: proxy.base_uri) do |c|
383
+ c.on_event { |event| event_sink << event }
384
+ end
385
+
386
+ with_client(client) do |client|
387
+ expect(event_sink.pop).to eq(simple_event_1)
388
+ expect(proxy.request_count).to eq(1)
389
+ end
390
+ end
391
+ end
392
+ end
393
+
394
+ it "resets read timeout between events" do
395
+ event_body = simple_event_1_text
396
+ with_server do |server|
397
+ attempt = 0
398
+ server.setup_response("/") do |req,res|
399
+ attempt += 1
400
+ if attempt == 1
401
+ stream = send_stream_content(res, event_body, keep_open: true)
402
+ Thread.new do
403
+ 2.times {
404
+ # write within timeout interval
405
+ sleep(0.75)
406
+ stream.write(event_body)
407
+ }
408
+ # cause timeout
409
+ sleep(1.25)
410
+ end
411
+ elsif attempt == 2
412
+ send_stream_content(res, event_body, keep_open: false)
413
+ end
414
+ end
415
+
416
+ event_sink = Queue.new
417
+ client = subject.new(server.base_uri, reconnect_time: reconnect_asap, read_timeout: 1) do |c|
418
+ c.on_event { |event| event_sink << event }
419
+ end
420
+
421
+ with_client(client) do |client|
422
+ 4.times {
423
+ expect(event_sink.pop).to eq(simple_event_1)
424
+ }
425
+ expect(attempt).to eq 2
426
+ end
427
+ end
428
+ end
429
+
430
+ it "returns true from closed? when closed" do
431
+ with_server do |server|
432
+ server.setup_response("/") do |req,res|
433
+ send_stream_content(res, "", keep_open: true)
434
+ end
435
+
436
+ with_client(subject.new(server.base_uri)) do |client|
437
+ expect(client.closed?).to be(false)
438
+
439
+ client.close
440
+ expect(client.closed?).to be(true)
441
+ end
442
+ end
443
+ end
369
444
  end
data/spec/http_stub.rb CHANGED
@@ -3,6 +3,8 @@ require "webrick/httpproxy"
3
3
  require "webrick/https"
4
4
 
5
5
  class StubHTTPServer
6
+ attr_reader :port
7
+
6
8
  def initialize
7
9
  @port = 50000
8
10
  begin
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ld-eventsource
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - LaunchDarkly
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-18 00:00:00.000000000 Z
11
+ date: 2021-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: '1.7'
19
+ version: 2.2.10
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: '1.7'
26
+ version: 2.2.10
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -53,47 +53,53 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: 0.3.0
55
55
  - !ruby/object:Gem::Dependency
56
- name: concurrent-ruby
56
+ name: webrick
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '1.0'
62
- type: :runtime
61
+ version: '1.7'
62
+ type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '1.0'
68
+ version: '1.7'
69
69
  - !ruby/object:Gem::Dependency
70
- name: http_tools
70
+ name: concurrent-ruby
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 0.4.5
75
+ version: '1.0'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 0.4.5
82
+ version: '1.0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: socketry
84
+ name: http
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 4.4.1
90
+ - - "<"
88
91
  - !ruby/object:Gem::Version
89
- version: 0.5.1
92
+ version: 6.0.0
90
93
  type: :runtime
91
94
  prerelease: false
92
95
  version_requirements: !ruby/object:Gem::Requirement
93
96
  requirements:
94
- - - "~>"
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: 4.4.1
100
+ - - "<"
95
101
  - !ruby/object:Gem::Version
96
- version: 0.5.1
102
+ version: 6.0.0
97
103
  description: LaunchDarkly SSE client for Ruby
98
104
  email:
99
105
  - team@launchdarkly.com
@@ -103,10 +109,18 @@ extra_rdoc_files: []
103
109
  files:
104
110
  - ".circleci/config.yml"
105
111
  - ".gitignore"
112
+ - ".ldrelease/circleci/linux/execute.sh"
113
+ - ".ldrelease/circleci/mac/execute.sh"
114
+ - ".ldrelease/circleci/template/build.sh"
115
+ - ".ldrelease/circleci/template/gems-setup.sh"
116
+ - ".ldrelease/circleci/template/prepare.sh"
117
+ - ".ldrelease/circleci/template/publish.sh"
118
+ - ".ldrelease/circleci/template/test.sh"
119
+ - ".ldrelease/circleci/template/update-version.sh"
120
+ - ".ldrelease/circleci/windows/execute.ps1"
106
121
  - ".ldrelease/config.yml"
107
122
  - CHANGELOG.md
108
123
  - Gemfile
109
- - Gemfile.lock
110
124
  - LICENSE
111
125
  - README.md
112
126
  - ld-eventsource.gemspec
@@ -116,7 +130,6 @@ files:
116
130
  - lib/ld-eventsource/events.rb
117
131
  - lib/ld-eventsource/impl/backoff.rb
118
132
  - lib/ld-eventsource/impl/event_parser.rb
119
- - lib/ld-eventsource/impl/streaming_http.rb
120
133
  - lib/ld-eventsource/version.rb
121
134
  - scripts/gendocs.sh
122
135
  - scripts/release.sh
@@ -124,7 +137,6 @@ files:
124
137
  - spec/client_spec.rb
125
138
  - spec/event_parser_spec.rb
126
139
  - spec/http_stub.rb
127
- - spec/streaming_http_spec.rb
128
140
  homepage: https://github.com/launchdarkly/ruby-eventsource
129
141
  licenses:
130
142
  - Apache-2.0
@@ -144,8 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
156
  - !ruby/object:Gem::Version
145
157
  version: '0'
146
158
  requirements: []
147
- rubyforge_project:
148
- rubygems_version: 2.5.2.3
159
+ rubygems_version: 3.2.15
149
160
  signing_key:
150
161
  specification_version: 4
151
162
  summary: LaunchDarkly SSE client
@@ -154,4 +165,3 @@ test_files:
154
165
  - spec/client_spec.rb
155
166
  - spec/event_parser_spec.rb
156
167
  - spec/http_stub.rb
157
- - spec/streaming_http_spec.rb
data/Gemfile.lock DELETED
@@ -1,46 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- ld-eventsource (1.0.3)
5
- concurrent-ruby (~> 1.0)
6
- http_tools (~> 0.4.5)
7
- socketry (~> 0.5.1)
8
-
9
- GEM
10
- remote: https://rubygems.org/
11
- specs:
12
- concurrent-ruby (1.1.6)
13
- diff-lcs (1.3)
14
- hitimes (1.3.1)
15
- hitimes (1.3.1-java)
16
- http_tools (0.4.5)
17
- rspec (3.7.0)
18
- rspec-core (~> 3.7.0)
19
- rspec-expectations (~> 3.7.0)
20
- rspec-mocks (~> 3.7.0)
21
- rspec-core (3.7.1)
22
- rspec-support (~> 3.7.0)
23
- rspec-expectations (3.7.0)
24
- diff-lcs (>= 1.2.0, < 2.0)
25
- rspec-support (~> 3.7.0)
26
- rspec-mocks (3.7.0)
27
- diff-lcs (>= 1.2.0, < 2.0)
28
- rspec-support (~> 3.7.0)
29
- rspec-support (3.7.0)
30
- rspec_junit_formatter (0.3.0)
31
- rspec-core (>= 2, < 4, != 2.12.0)
32
- socketry (0.5.1)
33
- hitimes (~> 1.2)
34
-
35
- PLATFORMS
36
- java
37
- ruby
38
-
39
- DEPENDENCIES
40
- bundler (~> 1.7)
41
- ld-eventsource!
42
- rspec (~> 3.2)
43
- rspec_junit_formatter (~> 0.3.0)
44
-
45
- BUNDLED WITH
46
- 1.17.3
@@ -1,222 +0,0 @@
1
- require "ld-eventsource/errors"
2
-
3
- require "concurrent/atomics"
4
- require "http_tools"
5
- require "socketry"
6
-
7
- module SSE
8
- module Impl
9
- #
10
- # Wrapper around a socket providing a simplified HTTP request-response cycle including streaming.
11
- # The socket is created and managed by Socketry, which we use so that we can have a read timeout.
12
- #
13
- class StreamingHTTPConnection
14
- attr_reader :status, :headers
15
-
16
- #
17
- # Opens a new connection.
18
- #
19
- # @param [String] uri the URI to connect o
20
- # @param [String] proxy the proxy server URI, if any
21
- # @param [Hash] headers request headers
22
- # @param [Float] connect_timeout connection timeout
23
- # @param [Float] read_timeout read timeout
24
- #
25
- def initialize(uri, proxy: nil, headers: {}, connect_timeout: nil, read_timeout: nil)
26
- @socket = HTTPConnectionFactory.connect(uri, proxy, connect_timeout, read_timeout)
27
- @socket.write(build_request(uri, headers))
28
- @reader = HTTPResponseReader.new(@socket, read_timeout)
29
- @status = @reader.status
30
- @headers = @reader.headers
31
- @closed = Concurrent::AtomicBoolean.new(false)
32
- end
33
-
34
- #
35
- # Closes the connection.
36
- #
37
- def close
38
- if @closed.make_true
39
- @socket.close if @socket
40
- @socket = nil
41
- end
42
- end
43
-
44
- #
45
- # Generator that returns one line of the response body at a time (delimited by \r, \n,
46
- # or \r\n) until the response is fully consumed or the socket is closed.
47
- #
48
- def read_lines
49
- @reader.read_lines
50
- end
51
-
52
- #
53
- # Consumes the entire response body and returns it.
54
- #
55
- # @return [String] the response body
56
- #
57
- def read_all
58
- @reader.read_all
59
- end
60
-
61
- private
62
-
63
- # Build an HTTP request line and headers.
64
- def build_request(uri, headers)
65
- ret = "GET #{uri.request_uri} HTTP/1.1\r\n"
66
- ret << "Host: #{uri.host}\r\n"
67
- headers.each { |k, v|
68
- ret << "#{k}: #{v}\r\n"
69
- }
70
- ret + "\r\n"
71
- end
72
- end
73
-
74
- #
75
- # Used internally to send the HTTP request, including the proxy dialogue if necessary.
76
- # @private
77
- #
78
- class HTTPConnectionFactory
79
- def self.connect(uri, proxy, connect_timeout, read_timeout)
80
- if !proxy
81
- return open_socket(uri, connect_timeout)
82
- end
83
-
84
- socket = open_socket(proxy, connect_timeout)
85
- socket.write(build_proxy_request(uri, proxy))
86
-
87
- # temporarily create a reader just for the proxy connect response
88
- proxy_reader = HTTPResponseReader.new(socket, read_timeout)
89
- if proxy_reader.status != 200
90
- raise Errors::HTTPProxyError.new(proxy_reader.status)
91
- end
92
-
93
- # start using TLS at this point if appropriate
94
- if uri.scheme.downcase == 'https'
95
- wrap_socket_in_ssl_socket(socket)
96
- else
97
- socket
98
- end
99
- end
100
-
101
- private
102
-
103
- def self.open_socket(uri, connect_timeout)
104
- if uri.scheme.downcase == 'https'
105
- Socketry::SSL::Socket.connect(uri.host, uri.port, timeout: connect_timeout)
106
- else
107
- Socketry::TCP::Socket.connect(uri.host, uri.port, timeout: connect_timeout)
108
- end
109
- end
110
-
111
- # Build a proxy connection header.
112
- def self.build_proxy_request(uri, proxy)
113
- ret = "CONNECT #{uri.host}:#{uri.port} HTTP/1.1\r\n"
114
- ret << "Host: #{uri.host}:#{uri.port}\r\n"
115
- if proxy.user || proxy.password
116
- encoded_credentials = Base64.strict_encode64([proxy.user || '', proxy.password || ''].join(":"))
117
- ret << "Proxy-Authorization: Basic #{encoded_credentials}\r\n"
118
- end
119
- ret << "\r\n"
120
- ret
121
- end
122
-
123
- def self.wrap_socket_in_ssl_socket(socket)
124
- io = IO.try_convert(socket)
125
- ssl_sock = OpenSSL::SSL::SSLSocket.new(io, OpenSSL::SSL::SSLContext.new)
126
- ssl_sock.connect
127
- Socketry::SSL::Socket.new.from_socket(ssl_sock)
128
- end
129
- end
130
-
131
- #
132
- # Used internally to read the HTTP response, either all at once or as a stream of text lines.
133
- # Incoming data is fed into an instance of HTTPTools::Parser, which gives us the header and
134
- # chunks of the body via callbacks.
135
- # @private
136
- #
137
- class HTTPResponseReader
138
- DEFAULT_CHUNK_SIZE = 10000
139
-
140
- attr_reader :status, :headers
141
-
142
- def initialize(socket, read_timeout)
143
- @socket = socket
144
- @read_timeout = read_timeout
145
- @parser = HTTPTools::Parser.new
146
- @buffer = ""
147
- @done = false
148
- @lock = Mutex.new
149
-
150
- # Provide callbacks for the Parser to give us the headers and body. This has to be done
151
- # before we start piping any data into the parser.
152
- have_headers = false
153
- @parser.on(:header) do
154
- have_headers = true
155
- end
156
- @parser.on(:stream) do |data|
157
- @lock.synchronize { @buffer << data } # synchronize because we're called from another thread in Socketry
158
- end
159
- @parser.on(:finish) do
160
- @lock.synchronize { @done = true }
161
- end
162
-
163
- # Block until the status code and headers have been successfully read.
164
- while !have_headers
165
- raise EOFError if !read_chunk_into_buffer
166
- end
167
- @headers = Hash[@parser.header.map { |k,v| [k.downcase, v] }]
168
- @status = @parser.status_code
169
- end
170
-
171
- def read_lines
172
- Enumerator.new do |gen|
173
- loop do
174
- line = read_line
175
- break if line.nil?
176
- gen.yield line
177
- end
178
- end
179
- end
180
-
181
- def read_all
182
- while read_chunk_into_buffer
183
- end
184
- @buffer
185
- end
186
-
187
- private
188
-
189
- # Attempt to read some more data from the socket. Return true if successful, false if EOF.
190
- # A read timeout will result in an exception from Socketry's readpartial method.
191
- def read_chunk_into_buffer
192
- # If @done is set, it means the Parser has signaled end of response body
193
- @lock.synchronize { return false if @done }
194
- begin
195
- data = @socket.readpartial(DEFAULT_CHUNK_SIZE, timeout: @read_timeout)
196
- rescue Socketry::TimeoutError
197
- # We rethrow this as our own type so the caller doesn't have to know the Socketry API
198
- raise Errors::ReadTimeoutError.new(@read_timeout)
199
- end
200
- return false if data == :eof
201
- @parser << data
202
- # We are piping the content through the parser so that it can handle things like chunked
203
- # encoding for us. The content ends up being appended to @buffer via our callback.
204
- true
205
- end
206
-
207
- # Extract the next line of text from the read buffer, refilling the buffer as needed.
208
- def read_line
209
- loop do
210
- @lock.synchronize do
211
- i = @buffer.index(/[\r\n]/)
212
- if !i.nil?
213
- i += 1 if (@buffer[i] == "\r" && i < @buffer.length - 1 && @buffer[i + 1] == "\n")
214
- return @buffer.slice!(0, i + 1).force_encoding(Encoding::UTF_8)
215
- end
216
- end
217
- return nil if !read_chunk_into_buffer
218
- end
219
- end
220
- end
221
- end
222
- end