ldclient-rb 4.0.0 → 5.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 +4 -4
- data/.circleci/config.yml +11 -4
- data/CHANGELOG.md +9 -0
- data/README.md +7 -3
- data/ldclient-rb.gemspec +6 -24
- data/lib/ldclient-rb.rb +1 -0
- data/lib/ldclient-rb/events.rb +13 -7
- data/lib/ldclient-rb/ldclient.rb +29 -12
- data/lib/ldclient-rb/polling.rb +15 -8
- data/lib/ldclient-rb/requestor.rb +10 -14
- data/lib/ldclient-rb/stream.rb +26 -27
- data/lib/ldclient-rb/user_filter.rb +1 -0
- data/lib/ldclient-rb/util.rb +18 -0
- data/lib/ldclient-rb/version.rb +1 -1
- data/lib/sse_client.rb +4 -0
- data/lib/sse_client/backoff.rb +38 -0
- data/lib/sse_client/sse_client.rb +162 -0
- data/lib/sse_client/sse_events.rb +67 -0
- data/lib/sse_client/streaming_http.rb +195 -0
- data/spec/events_spec.rb +30 -3
- data/spec/ldclient_spec.rb +1 -10
- data/spec/polling_spec.rb +89 -0
- data/spec/sse_client/sse_client_spec.rb +139 -0
- data/spec/sse_client/sse_events_spec.rb +100 -0
- data/spec/sse_client/sse_shared.rb +82 -0
- data/spec/sse_client/streaming_http_spec.rb +263 -0
- metadata +24 -36
@@ -0,0 +1,263 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "socketry"
|
3
|
+
require "sse_client/sse_shared"
|
4
|
+
|
5
|
+
#
|
6
|
+
# End-to-end tests of HTTP requests against a real server
|
7
|
+
#
|
8
|
+
describe SSE::StreamingHTTPConnection do
|
9
|
+
subject { SSE::StreamingHTTPConnection }
|
10
|
+
|
11
|
+
def with_connection(cxn)
|
12
|
+
begin
|
13
|
+
yield cxn
|
14
|
+
ensure
|
15
|
+
cxn.close
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "makes HTTP connection and sends request" do
|
20
|
+
with_server do |server|
|
21
|
+
requests = Queue.new
|
22
|
+
server.setup_response("/foo") do |req,res|
|
23
|
+
requests << req
|
24
|
+
res.status = 200
|
25
|
+
end
|
26
|
+
headers = {
|
27
|
+
"Accept" => "text/plain"
|
28
|
+
}
|
29
|
+
with_connection(subject.new(server.base_uri.merge("/foo?bar"), nil, headers, 30, 30)) do
|
30
|
+
received_req = requests.pop
|
31
|
+
expect(received_req.unparsed_uri).to eq("/foo?bar")
|
32
|
+
expect(received_req.header).to eq({
|
33
|
+
"accept" => ["text/plain"],
|
34
|
+
"host" => [server.base_uri.host]
|
35
|
+
})
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it "receives response status" do
|
41
|
+
with_server do |server|
|
42
|
+
server.setup_response("/foo") do |req,res|
|
43
|
+
res.status = 204
|
44
|
+
end
|
45
|
+
with_connection(subject.new(server.base_uri.merge("/foo"), nil, {}, 30, 30)) do |cxn|
|
46
|
+
expect(cxn.status).to eq(204)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it "receives response headers" do
|
52
|
+
with_server do |server|
|
53
|
+
server.setup_response("/foo") do |req,res|
|
54
|
+
res["Content-Type"] = "application/json"
|
55
|
+
end
|
56
|
+
with_connection(subject.new(server.base_uri.merge("/foo"), nil, {}, 30, 30)) do |cxn|
|
57
|
+
expect(cxn.headers["content-type"]).to eq("application/json")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
it "can read response as lines" do
|
63
|
+
body = <<-EOT
|
64
|
+
This is
|
65
|
+
a response
|
66
|
+
EOT
|
67
|
+
with_server do |server|
|
68
|
+
server.setup_response("/foo") do |req,res|
|
69
|
+
res.body = body
|
70
|
+
end
|
71
|
+
with_connection(subject.new(server.base_uri.merge("/foo"), nil, {}, 30, 30)) do |cxn|
|
72
|
+
lines = cxn.read_lines
|
73
|
+
expect(lines.next).to eq("This is\n")
|
74
|
+
expect(lines.next).to eq("a response\n")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "can read entire response body" do
|
80
|
+
body = <<-EOT
|
81
|
+
This is
|
82
|
+
a response
|
83
|
+
EOT
|
84
|
+
with_server do |server|
|
85
|
+
server.setup_response("/foo") do |req,res|
|
86
|
+
res.body = body
|
87
|
+
end
|
88
|
+
with_connection(subject.new(server.base_uri.merge("/foo"), nil, {}, 30, 30)) do |cxn|
|
89
|
+
read_body = cxn.read_all
|
90
|
+
expect(read_body).to eq("This is\na response\n")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
it "enforces read timeout" do
|
96
|
+
with_server do |server|
|
97
|
+
server.setup_response("/") do |req,res|
|
98
|
+
sleep(2)
|
99
|
+
res.status = 200
|
100
|
+
end
|
101
|
+
expect { subject.new(server.base_uri, nil, {}, 30, 0.25) }.to raise_error(Socketry::TimeoutError)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
it "connects to HTTP server through proxy" do
|
106
|
+
body = "hi"
|
107
|
+
with_server do |server|
|
108
|
+
server.setup_response("/") do |req,res|
|
109
|
+
res.body = body
|
110
|
+
end
|
111
|
+
with_server(StubProxyServer.new) do |proxy|
|
112
|
+
with_connection(subject.new(server.base_uri, proxy.base_uri, {}, 30, 30)) do |cxn|
|
113
|
+
read_body = cxn.read_all
|
114
|
+
expect(read_body).to eq("hi")
|
115
|
+
expect(proxy.request_count).to eq(1)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
it "throws error if proxy responds with error status" do
|
122
|
+
with_server do |server|
|
123
|
+
server.setup_response("/") do |req,res|
|
124
|
+
res.body = body
|
125
|
+
end
|
126
|
+
with_server(StubProxyServer.new) do |proxy|
|
127
|
+
proxy.connect_status = 403
|
128
|
+
expect { subject.new(server.base_uri, proxy.base_uri, {}, 30, 30) }.to raise_error(SSE::ProxyError)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# The following 2 tests were originally written to connect to an embedded HTTPS server made with
|
134
|
+
# WEBrick. Unfortunately, some unknown problem prevents WEBrick's self-signed certificate feature
|
135
|
+
# from working in JRuby 9.1 (but not in any other Ruby version). Therefore these tests currently
|
136
|
+
# hit an external URL.
|
137
|
+
|
138
|
+
it "connects to HTTPS server" do
|
139
|
+
with_connection(subject.new(URI("https://app.launchdarkly.com"), nil, {}, 30, 30)) do |cxn|
|
140
|
+
expect(cxn.status).to eq 200
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
it "connects to HTTPS server through proxy" do
|
145
|
+
with_server(StubProxyServer.new) do |proxy|
|
146
|
+
with_connection(subject.new(URI("https://app.launchdarkly.com"), proxy.base_uri, {}, 30, 30)) do |cxn|
|
147
|
+
expect(cxn.status).to eq 200
|
148
|
+
expect(proxy.request_count).to eq(1)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
#
|
155
|
+
# Tests of response parsing functionality without a real HTTP request
|
156
|
+
#
|
157
|
+
describe SSE::HTTPResponseReader do
|
158
|
+
subject { SSE::HTTPResponseReader }
|
159
|
+
|
160
|
+
let(:simple_response) { <<-EOT
|
161
|
+
HTTP/1.1 200 OK
|
162
|
+
Cache-Control: no-cache
|
163
|
+
Content-Type: text/event-stream
|
164
|
+
|
165
|
+
line1\r
|
166
|
+
line2
|
167
|
+
\r
|
168
|
+
EOT
|
169
|
+
}
|
170
|
+
|
171
|
+
def make_chunks(str)
|
172
|
+
# arbitrarily split content into 5-character blocks
|
173
|
+
str.scan(/.{1,5}/m).to_enum
|
174
|
+
end
|
175
|
+
|
176
|
+
def mock_socket_without_timeout(chunks)
|
177
|
+
mock_socket(chunks) { :eof }
|
178
|
+
end
|
179
|
+
|
180
|
+
def mock_socket_with_timeout(chunks)
|
181
|
+
mock_socket(chunks) { raise Socketry::TimeoutError }
|
182
|
+
end
|
183
|
+
|
184
|
+
def mock_socket(chunks)
|
185
|
+
sock = double
|
186
|
+
allow(sock).to receive(:readpartial) do
|
187
|
+
begin
|
188
|
+
chunks.next
|
189
|
+
rescue StopIteration
|
190
|
+
yield
|
191
|
+
end
|
192
|
+
end
|
193
|
+
sock
|
194
|
+
end
|
195
|
+
|
196
|
+
it "parses status code" do
|
197
|
+
socket = mock_socket_without_timeout(make_chunks(simple_response))
|
198
|
+
reader = subject.new(socket, 0)
|
199
|
+
expect(reader.status).to eq(200)
|
200
|
+
end
|
201
|
+
|
202
|
+
it "parses headers" do
|
203
|
+
socket = mock_socket_without_timeout(make_chunks(simple_response))
|
204
|
+
reader = subject.new(socket, 0)
|
205
|
+
expect(reader.headers).to eq({
|
206
|
+
'cache-control' => 'no-cache',
|
207
|
+
'content-type' => 'text/event-stream'
|
208
|
+
})
|
209
|
+
end
|
210
|
+
|
211
|
+
it "can read entire response body" do
|
212
|
+
socket = mock_socket_without_timeout(make_chunks(simple_response))
|
213
|
+
reader = subject.new(socket, 0)
|
214
|
+
expect(reader.read_all).to eq("line1\r\nline2\n\r\n")
|
215
|
+
end
|
216
|
+
|
217
|
+
it "can read response body as lines" do
|
218
|
+
socket = mock_socket_without_timeout(make_chunks(simple_response))
|
219
|
+
reader = subject.new(socket, 0)
|
220
|
+
expect(reader.read_lines.to_a).to eq([
|
221
|
+
"line1\r\n",
|
222
|
+
"line2\n",
|
223
|
+
"\r\n"
|
224
|
+
])
|
225
|
+
end
|
226
|
+
|
227
|
+
it "handles chunked encoding" do
|
228
|
+
chunked_response = <<-EOT
|
229
|
+
HTTP/1.1 200 OK
|
230
|
+
Content-Type: text/plain
|
231
|
+
Transfer-Encoding: chunked
|
232
|
+
|
233
|
+
6\r
|
234
|
+
things\r
|
235
|
+
A\r
|
236
|
+
and stuff\r
|
237
|
+
0\r
|
238
|
+
\r
|
239
|
+
EOT
|
240
|
+
socket = mock_socket_without_timeout(make_chunks(chunked_response))
|
241
|
+
reader = subject.new(socket, 0)
|
242
|
+
expect(reader.read_all).to eq("things and stuff")
|
243
|
+
end
|
244
|
+
|
245
|
+
it "raises error if response ends without complete headers" do
|
246
|
+
malformed_response = <<-EOT
|
247
|
+
HTTP/1.1 200 OK
|
248
|
+
Cache-Control: no-cache
|
249
|
+
EOT
|
250
|
+
socket = mock_socket_without_timeout(make_chunks(malformed_response))
|
251
|
+
expect { subject.new(socket, 0) }.to raise_error(EOFError)
|
252
|
+
end
|
253
|
+
|
254
|
+
it "throws timeout if thrown by socket read" do
|
255
|
+
socket = mock_socket_with_timeout(make_chunks(simple_response))
|
256
|
+
reader = subject.new(socket, 0)
|
257
|
+
lines = reader.read_lines
|
258
|
+
lines.next
|
259
|
+
lines.next
|
260
|
+
lines.next
|
261
|
+
expect { lines.next }.to raise_error(Socketry::TimeoutError)
|
262
|
+
end
|
263
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ldclient-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.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: 2018-
|
11
|
+
date: 2018-06-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -253,61 +253,33 @@ dependencies:
|
|
253
253
|
- !ruby/object:Gem::Version
|
254
254
|
version: '0.2'
|
255
255
|
- !ruby/object:Gem::Dependency
|
256
|
-
name:
|
256
|
+
name: http_tools
|
257
257
|
requirement: !ruby/object:Gem::Requirement
|
258
258
|
requirements:
|
259
259
|
- - "~>"
|
260
260
|
- !ruby/object:Gem::Version
|
261
|
-
version: 0.
|
261
|
+
version: 0.4.5
|
262
262
|
type: :runtime
|
263
263
|
prerelease: false
|
264
264
|
version_requirements: !ruby/object:Gem::Requirement
|
265
265
|
requirements:
|
266
266
|
- - "~>"
|
267
267
|
- !ruby/object:Gem::Version
|
268
|
-
version: 0.
|
268
|
+
version: 0.4.5
|
269
269
|
- !ruby/object:Gem::Dependency
|
270
|
-
name:
|
270
|
+
name: socketry
|
271
271
|
requirement: !ruby/object:Gem::Requirement
|
272
272
|
requirements:
|
273
273
|
- - "~>"
|
274
274
|
- !ruby/object:Gem::Version
|
275
|
-
version: 0.
|
275
|
+
version: 0.5.1
|
276
276
|
type: :runtime
|
277
277
|
prerelease: false
|
278
278
|
version_requirements: !ruby/object:Gem::Requirement
|
279
279
|
requirements:
|
280
280
|
- - "~>"
|
281
281
|
- !ruby/object:Gem::Version
|
282
|
-
version: 0.
|
283
|
-
- !ruby/object:Gem::Dependency
|
284
|
-
name: nio4r
|
285
|
-
requirement: !ruby/object:Gem::Requirement
|
286
|
-
requirements:
|
287
|
-
- - "<"
|
288
|
-
- !ruby/object:Gem::Version
|
289
|
-
version: '3'
|
290
|
-
type: :runtime
|
291
|
-
prerelease: false
|
292
|
-
version_requirements: !ruby/object:Gem::Requirement
|
293
|
-
requirements:
|
294
|
-
- - "<"
|
295
|
-
- !ruby/object:Gem::Version
|
296
|
-
version: '3'
|
297
|
-
- !ruby/object:Gem::Dependency
|
298
|
-
name: waitutil
|
299
|
-
requirement: !ruby/object:Gem::Requirement
|
300
|
-
requirements:
|
301
|
-
- - '='
|
302
|
-
- !ruby/object:Gem::Version
|
303
|
-
version: '0.2'
|
304
|
-
type: :runtime
|
305
|
-
prerelease: false
|
306
|
-
version_requirements: !ruby/object:Gem::Requirement
|
307
|
-
requirements:
|
308
|
-
- - '='
|
309
|
-
- !ruby/object:Gem::Version
|
310
|
-
version: '0.2'
|
282
|
+
version: 0.5.1
|
311
283
|
description: Official LaunchDarkly SDK for Ruby
|
312
284
|
email:
|
313
285
|
- team@launchdarkly.com
|
@@ -349,7 +321,13 @@ files:
|
|
349
321
|
- lib/ldclient-rb/simple_lru_cache.rb
|
350
322
|
- lib/ldclient-rb/stream.rb
|
351
323
|
- lib/ldclient-rb/user_filter.rb
|
324
|
+
- lib/ldclient-rb/util.rb
|
352
325
|
- lib/ldclient-rb/version.rb
|
326
|
+
- lib/sse_client.rb
|
327
|
+
- lib/sse_client/backoff.rb
|
328
|
+
- lib/sse_client/sse_client.rb
|
329
|
+
- lib/sse_client/sse_events.rb
|
330
|
+
- lib/sse_client/streaming_http.rb
|
353
331
|
- scripts/release.sh
|
354
332
|
- spec/config_spec.rb
|
355
333
|
- spec/evaluation_spec.rb
|
@@ -365,11 +343,16 @@ files:
|
|
365
343
|
- spec/in_memory_feature_store_spec.rb
|
366
344
|
- spec/ldclient_spec.rb
|
367
345
|
- spec/newrelic_spec.rb
|
346
|
+
- spec/polling_spec.rb
|
368
347
|
- spec/redis_feature_store_spec.rb
|
369
348
|
- spec/requestor_spec.rb
|
370
349
|
- spec/segment_store_spec_base.rb
|
371
350
|
- spec/simple_lru_cache_spec.rb
|
372
351
|
- spec/spec_helper.rb
|
352
|
+
- spec/sse_client/sse_client_spec.rb
|
353
|
+
- spec/sse_client/sse_events_spec.rb
|
354
|
+
- spec/sse_client/sse_shared.rb
|
355
|
+
- spec/sse_client/streaming_http_spec.rb
|
373
356
|
- spec/store_spec.rb
|
374
357
|
- spec/stream_spec.rb
|
375
358
|
- spec/user_filter_spec.rb
|
@@ -413,11 +396,16 @@ test_files:
|
|
413
396
|
- spec/in_memory_feature_store_spec.rb
|
414
397
|
- spec/ldclient_spec.rb
|
415
398
|
- spec/newrelic_spec.rb
|
399
|
+
- spec/polling_spec.rb
|
416
400
|
- spec/redis_feature_store_spec.rb
|
417
401
|
- spec/requestor_spec.rb
|
418
402
|
- spec/segment_store_spec_base.rb
|
419
403
|
- spec/simple_lru_cache_spec.rb
|
420
404
|
- spec/spec_helper.rb
|
405
|
+
- spec/sse_client/sse_client_spec.rb
|
406
|
+
- spec/sse_client/sse_events_spec.rb
|
407
|
+
- spec/sse_client/sse_shared.rb
|
408
|
+
- spec/sse_client/streaming_http_spec.rb
|
421
409
|
- spec/store_spec.rb
|
422
410
|
- spec/stream_spec.rb
|
423
411
|
- spec/user_filter_spec.rb
|