ld-eventsource 1.0.3 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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