http-2 0.11.0 → 0.12.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/README.md +0 -2
- data/lib/http/2/buffer.rb +6 -4
- data/lib/http/2/client.rb +5 -1
- data/lib/http/2/compressor.rb +42 -34
- data/lib/http/2/connection.rb +72 -86
- data/lib/http/2/emitter.rb +4 -1
- data/lib/http/2/error.rb +2 -0
- data/lib/http/2/flow_buffer.rb +8 -3
- data/lib/http/2/framer.rb +83 -94
- data/lib/http/2/huffman.rb +19 -17
- data/lib/http/2/server.rb +9 -7
- data/lib/http/2/stream.rb +48 -48
- data/lib/http/2/version.rb +3 -1
- data/lib/http/2.rb +2 -0
- metadata +7 -60
- data/.autotest +0 -20
- data/.coveralls.yml +0 -1
- data/.gitignore +0 -20
- data/.gitmodules +0 -3
- data/.rspec +0 -5
- data/.rubocop.yml +0 -93
- data/.rubocop_todo.yml +0 -131
- data/.travis.yml +0 -17
- data/Gemfile +0 -16
- data/Guardfile +0 -18
- data/Guardfile.h2spec +0 -12
- data/Rakefile +0 -49
- data/example/Gemfile +0 -3
- data/example/README.md +0 -44
- data/example/client.rb +0 -122
- data/example/helper.rb +0 -19
- data/example/keys/server.crt +0 -20
- data/example/keys/server.key +0 -27
- data/example/server.rb +0 -139
- data/example/upgrade_client.rb +0 -153
- data/example/upgrade_server.rb +0 -203
- data/http-2.gemspec +0 -22
- data/lib/tasks/generate_huffman_table.rb +0 -166
- data/spec/buffer_spec.rb +0 -28
- data/spec/client_spec.rb +0 -188
- data/spec/compressor_spec.rb +0 -666
- data/spec/connection_spec.rb +0 -681
- data/spec/emitter_spec.rb +0 -54
- data/spec/framer_spec.rb +0 -487
- data/spec/h2spec/h2spec.darwin +0 -0
- data/spec/h2spec/output/non_secure.txt +0 -317
- data/spec/helper.rb +0 -147
- data/spec/hpack_test_spec.rb +0 -84
- data/spec/huffman_spec.rb +0 -68
- data/spec/server_spec.rb +0 -52
- data/spec/stream_spec.rb +0 -878
- data/spec/support/deep_dup.rb +0 -55
- data/spec/support/duplicable.rb +0 -98
data/spec/connection_spec.rb
DELETED
@@ -1,681 +0,0 @@
|
|
1
|
-
require 'helper'
|
2
|
-
|
3
|
-
RSpec.describe HTTP2::Connection do
|
4
|
-
include FrameHelpers
|
5
|
-
before(:each) do
|
6
|
-
@conn = Client.new
|
7
|
-
end
|
8
|
-
|
9
|
-
let(:f) { Framer.new }
|
10
|
-
|
11
|
-
context 'initialization and settings' do
|
12
|
-
it 'should raise error if first frame is not settings' do
|
13
|
-
(frame_types - [settings_frame]).each do |frame|
|
14
|
-
expect { @conn << frame }.to raise_error(ProtocolError)
|
15
|
-
expect(@conn).to be_closed
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
it 'should not raise error if first frame is SETTINGS' do
|
20
|
-
expect { @conn << f.generate(settings_frame) }.to_not raise_error
|
21
|
-
expect(@conn.state).to eq :connected
|
22
|
-
expect(@conn).to_not be_closed
|
23
|
-
end
|
24
|
-
|
25
|
-
it 'should raise error if SETTINGS stream != 0' do
|
26
|
-
frame = set_stream_id(f.generate(settings_frame), 0x1)
|
27
|
-
expect { @conn << frame }.to raise_error(ProtocolError)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
context 'settings synchronization' do
|
32
|
-
it 'should reflect outgoing settings when ack is received' do
|
33
|
-
expect(@conn.local_settings[:settings_header_table_size]).to eq 4096
|
34
|
-
@conn.settings(settings_header_table_size: 256)
|
35
|
-
expect(@conn.local_settings[:settings_header_table_size]).to eq 4096
|
36
|
-
|
37
|
-
ack = { type: :settings, stream: 0, payload: [], flags: [:ack] }
|
38
|
-
@conn << f.generate(ack)
|
39
|
-
|
40
|
-
expect(@conn.local_settings[:settings_header_table_size]).to eq 256
|
41
|
-
end
|
42
|
-
|
43
|
-
it 'should reflect incoming settings when SETTINGS is received' do
|
44
|
-
expect(@conn.remote_settings[:settings_header_table_size]).to eq 4096
|
45
|
-
settings = settings_frame
|
46
|
-
settings[:payload] = [[:settings_header_table_size, 256]]
|
47
|
-
|
48
|
-
@conn << f.generate(settings)
|
49
|
-
|
50
|
-
expect(@conn.remote_settings[:settings_header_table_size]).to eq 256
|
51
|
-
end
|
52
|
-
|
53
|
-
it 'should reflect settings_max_frame_size recevied from peer' do
|
54
|
-
settings = settings_frame
|
55
|
-
settings[:payload] = [[:settings_max_frame_size, 16_385]]
|
56
|
-
|
57
|
-
@conn << f.generate(settings)
|
58
|
-
|
59
|
-
frame = {
|
60
|
-
length: 16_385,
|
61
|
-
type: :data,
|
62
|
-
flags: [:end_stream],
|
63
|
-
stream: 1,
|
64
|
-
payload: 'a' * 16_385,
|
65
|
-
}
|
66
|
-
expect { @conn.send(frame) }.not_to raise_error(CompressionError)
|
67
|
-
end
|
68
|
-
|
69
|
-
it 'should send SETTINGS ACK when SETTINGS is received' do
|
70
|
-
settings = settings_frame
|
71
|
-
settings[:payload] = [[:settings_header_table_size, 256]]
|
72
|
-
|
73
|
-
# We should expect two frames here (append .twice) - one for the connection setup, and one for the settings ack.
|
74
|
-
frames = []
|
75
|
-
expect(@conn).to receive(:send).twice do |frame|
|
76
|
-
frames << frame
|
77
|
-
end
|
78
|
-
|
79
|
-
@conn.send_connection_preface
|
80
|
-
@conn << f.generate(settings)
|
81
|
-
|
82
|
-
frame = frames.last
|
83
|
-
expect(frame[:type]).to eq :settings
|
84
|
-
expect(frame[:flags]).to eq [:ack]
|
85
|
-
expect(frame[:payload]).to eq []
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
context 'stream management' do
|
90
|
-
it 'should initialize to default stream limit (100)' do
|
91
|
-
expect(@conn.local_settings[:settings_max_concurrent_streams]).to eq 100
|
92
|
-
end
|
93
|
-
|
94
|
-
it 'should change stream limit to received SETTINGS value' do
|
95
|
-
@conn << f.generate(settings_frame)
|
96
|
-
expect(@conn.remote_settings[:settings_max_concurrent_streams]).to eq 10
|
97
|
-
end
|
98
|
-
|
99
|
-
it 'should count open streams against stream limit' do
|
100
|
-
s = @conn.new_stream
|
101
|
-
expect(@conn.active_stream_count).to eq 0
|
102
|
-
s.receive headers_frame
|
103
|
-
expect(@conn.active_stream_count).to eq 1
|
104
|
-
end
|
105
|
-
|
106
|
-
it 'should not count reserved streams against stream limit' do
|
107
|
-
s1 = @conn.new_stream
|
108
|
-
s1.receive push_promise_frame
|
109
|
-
expect(@conn.active_stream_count).to eq 0
|
110
|
-
|
111
|
-
s2 = @conn.new_stream
|
112
|
-
s2.send push_promise_frame
|
113
|
-
expect(@conn.active_stream_count).to eq 0
|
114
|
-
|
115
|
-
# transition to half closed
|
116
|
-
s1.receive headers_frame
|
117
|
-
s2.send headers_frame
|
118
|
-
expect(@conn.active_stream_count).to eq 2
|
119
|
-
|
120
|
-
# transition to closed
|
121
|
-
s1.receive data_frame
|
122
|
-
s2.send data_frame
|
123
|
-
expect(@conn.active_stream_count).to eq 0
|
124
|
-
|
125
|
-
expect(s1).to be_closed
|
126
|
-
expect(s2).to be_closed
|
127
|
-
end
|
128
|
-
|
129
|
-
it 'should not exceed stream limit set by peer' do
|
130
|
-
@conn << f.generate(settings_frame)
|
131
|
-
|
132
|
-
expect do
|
133
|
-
10.times do
|
134
|
-
s = @conn.new_stream
|
135
|
-
s.send headers_frame
|
136
|
-
end
|
137
|
-
end.to_not raise_error
|
138
|
-
|
139
|
-
expect { @conn.new_stream }.to raise_error(StreamLimitExceeded)
|
140
|
-
end
|
141
|
-
|
142
|
-
it 'should initialize stream with HEADERS priority value' do
|
143
|
-
@conn << f.generate(settings_frame)
|
144
|
-
|
145
|
-
stream, headers = nil, headers_frame
|
146
|
-
headers[:weight] = 20
|
147
|
-
headers[:stream_dependency] = 0
|
148
|
-
headers[:exclusive] = false
|
149
|
-
|
150
|
-
@conn.on(:stream) { |s| stream = s }
|
151
|
-
@conn << f.generate(headers)
|
152
|
-
|
153
|
-
expect(stream.weight).to eq 20
|
154
|
-
end
|
155
|
-
|
156
|
-
it 'should initialize idle stream on PRIORITY frame' do
|
157
|
-
@conn << f.generate(settings_frame)
|
158
|
-
|
159
|
-
stream = nil
|
160
|
-
@conn.on(:stream) { |s| stream = s }
|
161
|
-
@conn << f.generate(priority_frame)
|
162
|
-
|
163
|
-
expect(stream.state).to eq :idle
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
context 'cleanup_recently_closed' do
|
168
|
-
it 'should cleanup old connections' do
|
169
|
-
now_ts = Time.now.to_i
|
170
|
-
stream_ids = Array.new(4) { @conn.new_stream.id }
|
171
|
-
expect(@conn.instance_variable_get('@streams').size).to eq(4)
|
172
|
-
|
173
|
-
# Assume that the first 3 streams were closed in different time
|
174
|
-
recently_closed = stream_ids[0, 3].zip([now_ts - 100, now_ts - 50, now_ts - 5]).to_h
|
175
|
-
@conn.instance_variable_set('@streams_recently_closed', recently_closed)
|
176
|
-
|
177
|
-
# Cleanup should delete streams that were closed earlier than 15s ago
|
178
|
-
@conn.__send__(:cleanup_recently_closed)
|
179
|
-
expect(@conn.instance_variable_get('@streams').size).to eq(2)
|
180
|
-
expect(@conn.instance_variable_get('@streams_recently_closed')).to eq(stream_ids[2] => now_ts - 5)
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
context 'Headers pre/post processing' do
|
185
|
-
it 'should not concatenate multiple occurences of a header field with the same name' do
|
186
|
-
input = [
|
187
|
-
['Content-Type', 'text/html'],
|
188
|
-
['Cache-Control', 'max-age=60, private'],
|
189
|
-
['Cache-Control', 'must-revalidate'],
|
190
|
-
]
|
191
|
-
expected = [
|
192
|
-
['content-type', 'text/html'],
|
193
|
-
['cache-control', 'max-age=60, private'],
|
194
|
-
['cache-control', 'must-revalidate'],
|
195
|
-
]
|
196
|
-
headers = []
|
197
|
-
@conn.on(:frame) do |bytes|
|
198
|
-
headers << f.parse(bytes) if [1, 5, 9].include?(bytes[3].ord)
|
199
|
-
end
|
200
|
-
|
201
|
-
stream = @conn.new_stream
|
202
|
-
stream.headers(input)
|
203
|
-
|
204
|
-
expect(headers.size).to eq 1
|
205
|
-
emitted = Decompressor.new.decode(headers.first[:payload])
|
206
|
-
expect(emitted).to match_array(expected)
|
207
|
-
end
|
208
|
-
|
209
|
-
it 'should not split zero-concatenated header field values' do
|
210
|
-
input = [*RESPONSE_HEADERS,
|
211
|
-
['cache-control', "max-age=60, private\0must-revalidate"],
|
212
|
-
['content-type', 'text/html'],
|
213
|
-
['cookie', "a=b\0c=d; e=f"]]
|
214
|
-
expected = [*RESPONSE_HEADERS,
|
215
|
-
['cache-control', "max-age=60, private\0must-revalidate"],
|
216
|
-
['content-type', 'text/html'],
|
217
|
-
['cookie', "a=b\0c=d; e=f"]]
|
218
|
-
|
219
|
-
result = nil
|
220
|
-
@conn.on(:stream) do |stream|
|
221
|
-
stream.on(:headers) { |h| result = h }
|
222
|
-
end
|
223
|
-
|
224
|
-
srv = Server.new
|
225
|
-
srv.on(:frame) { |bytes| @conn << bytes }
|
226
|
-
stream = srv.new_stream
|
227
|
-
stream.headers(input)
|
228
|
-
|
229
|
-
expect(result).to eq expected
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
context 'flow control' do
|
234
|
-
it 'should initialize to default flow window' do
|
235
|
-
expect(@conn.remote_window).to eq DEFAULT_FLOW_WINDOW
|
236
|
-
end
|
237
|
-
|
238
|
-
it 'should update connection and stream windows on SETTINGS' do
|
239
|
-
settings, data = settings_frame, data_frame
|
240
|
-
settings[:payload] = [[:settings_initial_window_size, 1024]]
|
241
|
-
data[:payload] = 'x' * 2048
|
242
|
-
|
243
|
-
stream = @conn.new_stream
|
244
|
-
|
245
|
-
stream.send headers_frame
|
246
|
-
stream.send data
|
247
|
-
expect(stream.remote_window).to eq(DEFAULT_FLOW_WINDOW - 2048)
|
248
|
-
expect(@conn.remote_window).to eq(DEFAULT_FLOW_WINDOW - 2048)
|
249
|
-
|
250
|
-
@conn << f.generate(settings)
|
251
|
-
expect(@conn.remote_window).to eq(-1024)
|
252
|
-
expect(stream.remote_window).to eq(-1024)
|
253
|
-
end
|
254
|
-
|
255
|
-
it 'should initialize streams with window specified by peer' do
|
256
|
-
settings = settings_frame
|
257
|
-
settings[:payload] = [[:settings_initial_window_size, 1024]]
|
258
|
-
|
259
|
-
@conn << f.generate(settings)
|
260
|
-
expect(@conn.new_stream.remote_window).to eq 1024
|
261
|
-
end
|
262
|
-
|
263
|
-
it 'should observe connection flow control' do
|
264
|
-
settings, data = settings_frame, data_frame
|
265
|
-
settings[:payload] = [[:settings_initial_window_size, 1000]]
|
266
|
-
|
267
|
-
@conn << f.generate(settings)
|
268
|
-
s1 = @conn.new_stream
|
269
|
-
s2 = @conn.new_stream
|
270
|
-
|
271
|
-
s1.send headers_frame
|
272
|
-
s1.send data.merge(payload: 'x' * 900)
|
273
|
-
expect(@conn.remote_window).to eq 100
|
274
|
-
|
275
|
-
s2.send headers_frame
|
276
|
-
s2.send data.merge(payload: 'x' * 200)
|
277
|
-
expect(@conn.remote_window).to eq 0
|
278
|
-
expect(@conn.buffered_amount).to eq 100
|
279
|
-
|
280
|
-
@conn << f.generate(window_update_frame.merge(stream: 0, increment: 1000))
|
281
|
-
expect(@conn.buffered_amount).to eq 0
|
282
|
-
expect(@conn.remote_window).to eq 900
|
283
|
-
end
|
284
|
-
|
285
|
-
it 'should update window when data received is over half of the maximum local window size' do
|
286
|
-
settings, data = settings_frame, data_frame
|
287
|
-
conn = Client.new(settings_initial_window_size: 500)
|
288
|
-
|
289
|
-
conn.receive f.generate(settings)
|
290
|
-
s1 = conn.new_stream
|
291
|
-
s2 = conn.new_stream
|
292
|
-
|
293
|
-
s1.send headers_frame
|
294
|
-
s2.send headers_frame
|
295
|
-
expect(conn).to receive(:send) do |frame|
|
296
|
-
expect(frame[:type]).to eq :window_update
|
297
|
-
expect(frame[:stream]).to eq 0
|
298
|
-
expect(frame[:increment]).to eq 400
|
299
|
-
end
|
300
|
-
conn.receive f.generate(data.merge(payload: 'x' * 200, end_stream: false, stream: s1.id))
|
301
|
-
conn.receive f.generate(data.merge(payload: 'x' * 200, end_stream: false, stream: s2.id))
|
302
|
-
expect(s1.local_window).to eq 300
|
303
|
-
expect(s2.local_window).to eq 300
|
304
|
-
expect(conn.local_window).to eq 500
|
305
|
-
end
|
306
|
-
end
|
307
|
-
|
308
|
-
context 'framing' do
|
309
|
-
it 'should buffer incomplete frames' do
|
310
|
-
settings = settings_frame
|
311
|
-
settings[:payload] = [[:settings_initial_window_size, 1000]]
|
312
|
-
@conn << f.generate(settings)
|
313
|
-
|
314
|
-
frame = f.generate(window_update_frame.merge(stream: 0, increment: 1000))
|
315
|
-
@conn << frame
|
316
|
-
expect(@conn.remote_window).to eq 2000
|
317
|
-
|
318
|
-
@conn << frame.slice!(0, 1)
|
319
|
-
@conn << frame
|
320
|
-
expect(@conn.remote_window).to eq 3000
|
321
|
-
end
|
322
|
-
|
323
|
-
it 'should decompress header blocks regardless of stream state' do
|
324
|
-
req_headers = [
|
325
|
-
['content-length', '20'],
|
326
|
-
['x-my-header', 'first'],
|
327
|
-
]
|
328
|
-
|
329
|
-
cc = Compressor.new
|
330
|
-
headers = headers_frame
|
331
|
-
headers[:payload] = cc.encode(req_headers)
|
332
|
-
|
333
|
-
@conn << f.generate(settings_frame)
|
334
|
-
@conn.on(:stream) do |stream|
|
335
|
-
expect(stream).to receive(:<<) do |frame|
|
336
|
-
expect(frame[:payload]).to eq req_headers
|
337
|
-
end
|
338
|
-
end
|
339
|
-
|
340
|
-
@conn << f.generate(headers)
|
341
|
-
end
|
342
|
-
|
343
|
-
it 'should decode non-contiguous header blocks' do
|
344
|
-
req_headers = [
|
345
|
-
['content-length', '15'],
|
346
|
-
['x-my-header', 'first'],
|
347
|
-
]
|
348
|
-
|
349
|
-
cc = Compressor.new
|
350
|
-
h1, h2 = headers_frame, continuation_frame
|
351
|
-
|
352
|
-
# Header block fragment might not complete for decompression
|
353
|
-
payload = cc.encode(req_headers)
|
354
|
-
h1[:payload] = payload.slice!(0, payload.size / 2) # first half
|
355
|
-
h1[:stream] = 5
|
356
|
-
h1[:flags] = []
|
357
|
-
|
358
|
-
h2[:payload] = payload # the remaining
|
359
|
-
h2[:stream] = 5
|
360
|
-
|
361
|
-
@conn << f.generate(settings_frame)
|
362
|
-
@conn.on(:stream) do |stream|
|
363
|
-
expect(stream).to receive(:<<) do |frame|
|
364
|
-
expect(frame[:payload]).to eq req_headers
|
365
|
-
end
|
366
|
-
end
|
367
|
-
|
368
|
-
@conn << f.generate(h1)
|
369
|
-
@conn << f.generate(h2)
|
370
|
-
end
|
371
|
-
|
372
|
-
it 'should require that split header blocks are a contiguous sequence' do
|
373
|
-
headers = headers_frame
|
374
|
-
headers[:flags] = []
|
375
|
-
|
376
|
-
@conn << f.generate(settings_frame)
|
377
|
-
@conn << f.generate(headers)
|
378
|
-
(frame_types - [continuation_frame]).each do |frame|
|
379
|
-
expect { @conn << f.generate(frame.deep_dup) }.to raise_error(ProtocolError)
|
380
|
-
end
|
381
|
-
end
|
382
|
-
|
383
|
-
it 'should raise compression error on encode of invalid frame' do
|
384
|
-
@conn << f.generate(settings_frame)
|
385
|
-
stream = @conn.new_stream
|
386
|
-
|
387
|
-
expect do
|
388
|
-
stream.headers({ 'name' => Float::INFINITY })
|
389
|
-
end.to raise_error(CompressionError)
|
390
|
-
end
|
391
|
-
|
392
|
-
it 'should raise connection error on decode of invalid frame' do
|
393
|
-
@conn << f.generate(settings_frame)
|
394
|
-
frame = f.generate(data_frame) # Receiving DATA on unopened stream 1 is an error.
|
395
|
-
# Connection errors emit protocol error frames
|
396
|
-
expect { @conn << frame }.to raise_error(ProtocolError)
|
397
|
-
end
|
398
|
-
|
399
|
-
it 'should emit encoded frames via on(:frame)' do
|
400
|
-
bytes = nil
|
401
|
-
@conn.on(:frame) { |d| bytes = d }
|
402
|
-
@conn.settings(settings_max_concurrent_streams: 10,
|
403
|
-
settings_initial_window_size: 0x7fffffff)
|
404
|
-
|
405
|
-
expect(bytes).to eq f.generate(settings_frame)
|
406
|
-
end
|
407
|
-
|
408
|
-
it 'should compress stream headers' do
|
409
|
-
@conn.on(:frame) do |bytes|
|
410
|
-
expect(bytes).not_to include('get')
|
411
|
-
expect(bytes).not_to include('http')
|
412
|
-
expect(bytes).not_to include('www.example.org') # should be huffman encoded
|
413
|
-
end
|
414
|
-
|
415
|
-
stream = @conn.new_stream
|
416
|
-
stream.headers({ ':method' => 'get',
|
417
|
-
':scheme' => 'http',
|
418
|
-
':authority' => 'www.example.org',
|
419
|
-
':path' => '/resource' })
|
420
|
-
end
|
421
|
-
|
422
|
-
it 'should generate CONTINUATION if HEADERS is too long' do
|
423
|
-
headers = []
|
424
|
-
@conn.on(:frame) do |bytes|
|
425
|
-
# bytes[3]: frame's type field
|
426
|
-
headers << f.parse(bytes) if [1, 5, 9].include?(bytes[3].ord)
|
427
|
-
end
|
428
|
-
|
429
|
-
stream = @conn.new_stream
|
430
|
-
stream.headers({
|
431
|
-
':method' => 'get',
|
432
|
-
':scheme' => 'http',
|
433
|
-
':authority' => 'www.example.org',
|
434
|
-
':path' => '/resource',
|
435
|
-
'custom' => 'q' * 44_000,
|
436
|
-
}, end_stream: true)
|
437
|
-
expect(headers.size).to eq 3
|
438
|
-
expect(headers[0][:type]).to eq :headers
|
439
|
-
expect(headers[1][:type]).to eq :continuation
|
440
|
-
expect(headers[2][:type]).to eq :continuation
|
441
|
-
expect(headers[0][:flags]).to eq [:end_stream]
|
442
|
-
expect(headers[1][:flags]).to eq []
|
443
|
-
expect(headers[2][:flags]).to eq [:end_headers]
|
444
|
-
end
|
445
|
-
|
446
|
-
it 'should not generate CONTINUATION if HEADERS fits exactly in a frame' do
|
447
|
-
headers = []
|
448
|
-
@conn.on(:frame) do |bytes|
|
449
|
-
# bytes[3]: frame's type field
|
450
|
-
headers << f.parse(bytes) if [1, 5, 9].include?(bytes[3].ord)
|
451
|
-
end
|
452
|
-
|
453
|
-
stream = @conn.new_stream
|
454
|
-
stream.headers({
|
455
|
-
':method' => 'get',
|
456
|
-
':scheme' => 'http',
|
457
|
-
':authority' => 'www.example.org',
|
458
|
-
':path' => '/resource',
|
459
|
-
'custom' => 'q' * 18_682, # this number should be updated when Huffman table is changed
|
460
|
-
}, end_stream: true)
|
461
|
-
expect(headers[0][:length]).to eq @conn.remote_settings[:settings_max_frame_size]
|
462
|
-
expect(headers.size).to eq 1
|
463
|
-
expect(headers[0][:type]).to eq :headers
|
464
|
-
expect(headers[0][:flags]).to include(:end_headers)
|
465
|
-
expect(headers[0][:flags]).to include(:end_stream)
|
466
|
-
end
|
467
|
-
|
468
|
-
it 'should not generate CONTINUATION if HEADERS fits exactly in a frame' do
|
469
|
-
headers = []
|
470
|
-
@conn.on(:frame) do |bytes|
|
471
|
-
# bytes[3]: frame's type field
|
472
|
-
headers << f.parse(bytes) if [1, 5, 9].include?(bytes[3].ord)
|
473
|
-
end
|
474
|
-
|
475
|
-
stream = @conn.new_stream
|
476
|
-
stream.headers({
|
477
|
-
':method' => 'get',
|
478
|
-
':scheme' => 'http',
|
479
|
-
':authority' => 'www.example.org',
|
480
|
-
':path' => '/resource',
|
481
|
-
'custom' => 'q' * 18_682, # this number should be updated when Huffman table is changed
|
482
|
-
}, end_stream: true)
|
483
|
-
expect(headers[0][:length]).to eq @conn.remote_settings[:settings_max_frame_size]
|
484
|
-
expect(headers.size).to eq 1
|
485
|
-
expect(headers[0][:type]).to eq :headers
|
486
|
-
expect(headers[0][:flags]).to include(:end_headers)
|
487
|
-
expect(headers[0][:flags]).to include(:end_stream)
|
488
|
-
end
|
489
|
-
|
490
|
-
it 'should generate CONTINUATION if HEADERS exceed the max payload by one byte' do
|
491
|
-
headers = []
|
492
|
-
@conn.on(:frame) do |bytes|
|
493
|
-
headers << f.parse(bytes) if [1, 5, 9].include?(bytes[3].ord)
|
494
|
-
end
|
495
|
-
|
496
|
-
stream = @conn.new_stream
|
497
|
-
stream.headers({
|
498
|
-
':method' => 'get',
|
499
|
-
':scheme' => 'http',
|
500
|
-
':authority' => 'www.example.org',
|
501
|
-
':path' => '/resource',
|
502
|
-
'custom' => 'q' * 18_683, # this number should be updated when Huffman table is changed
|
503
|
-
}, end_stream: true)
|
504
|
-
expect(headers[0][:length]).to eq @conn.remote_settings[:settings_max_frame_size]
|
505
|
-
expect(headers[1][:length]).to eq 1
|
506
|
-
expect(headers.size).to eq 2
|
507
|
-
expect(headers[0][:type]).to eq :headers
|
508
|
-
expect(headers[1][:type]).to eq :continuation
|
509
|
-
expect(headers[0][:flags]).to eq [:end_stream]
|
510
|
-
expect(headers[1][:flags]).to eq [:end_headers]
|
511
|
-
end
|
512
|
-
end
|
513
|
-
|
514
|
-
context 'connection management' do
|
515
|
-
it 'should raise error on invalid connection header' do
|
516
|
-
srv = Server.new
|
517
|
-
expect { srv << f.generate(settings_frame) }.to raise_error(HandshakeError)
|
518
|
-
|
519
|
-
srv = Server.new
|
520
|
-
expect do
|
521
|
-
srv << CONNECTION_PREFACE_MAGIC
|
522
|
-
srv << f.generate(settings_frame)
|
523
|
-
end.to_not raise_error
|
524
|
-
end
|
525
|
-
|
526
|
-
it 'should respond to PING frames' do
|
527
|
-
@conn << f.generate(settings_frame)
|
528
|
-
expect(@conn).to receive(:send) do |frame|
|
529
|
-
expect(frame[:type]).to eq :ping
|
530
|
-
expect(frame[:flags]).to eq [:ack]
|
531
|
-
expect(frame[:payload]).to eq '12345678'
|
532
|
-
end
|
533
|
-
|
534
|
-
@conn << f.generate(ping_frame)
|
535
|
-
end
|
536
|
-
|
537
|
-
it 'should fire callback on PONG' do
|
538
|
-
@conn << f.generate(settings_frame)
|
539
|
-
|
540
|
-
pong = nil
|
541
|
-
@conn.ping('12345678') { |d| pong = d }
|
542
|
-
@conn << f.generate(pong_frame)
|
543
|
-
expect(pong).to eq '12345678'
|
544
|
-
end
|
545
|
-
|
546
|
-
it 'should fire callback on receipt of GOAWAY' do
|
547
|
-
last_stream, payload, error = nil
|
548
|
-
@conn << f.generate(settings_frame)
|
549
|
-
@conn.on(:goaway) do |s, e, p|
|
550
|
-
last_stream = s
|
551
|
-
error = e
|
552
|
-
payload = p
|
553
|
-
end
|
554
|
-
@conn << f.generate(goaway_frame.merge(last_stream: 17, payload: 'test'))
|
555
|
-
|
556
|
-
expect(last_stream).to eq 17
|
557
|
-
expect(error).to eq :no_error
|
558
|
-
expect(payload).to eq 'test'
|
559
|
-
|
560
|
-
expect(@conn).to be_closed
|
561
|
-
end
|
562
|
-
|
563
|
-
it 'should raise error when opening new stream after sending GOAWAY' do
|
564
|
-
@conn.goaway
|
565
|
-
expect(@conn).to be_closed
|
566
|
-
|
567
|
-
expect { @conn.new_stream }.to raise_error(ConnectionClosed)
|
568
|
-
end
|
569
|
-
|
570
|
-
it 'should raise error when opening new stream after receiving GOAWAY' do
|
571
|
-
@conn << f.generate(settings_frame)
|
572
|
-
@conn << f.generate(goaway_frame)
|
573
|
-
expect { @conn.new_stream }.to raise_error(ConnectionClosed)
|
574
|
-
end
|
575
|
-
|
576
|
-
it 'should not raise error when receiving connection management frames immediately after emitting goaway' do
|
577
|
-
@conn.goaway
|
578
|
-
expect(@conn).to be_closed
|
579
|
-
|
580
|
-
expect { @conn << f.generate(settings_frame) }.not_to raise_error(ProtocolError)
|
581
|
-
expect { @conn << f.generate(ping_frame) }.not_to raise_error(ProtocolError)
|
582
|
-
expect { @conn << f.generate(goaway_frame) }.not_to raise_error(ProtocolError)
|
583
|
-
end
|
584
|
-
|
585
|
-
it 'should process connection management frames after GOAWAY' do
|
586
|
-
@conn << f.generate(settings_frame)
|
587
|
-
@conn << f.generate(headers_frame)
|
588
|
-
@conn << f.generate(goaway_frame)
|
589
|
-
@conn << f.generate(headers_frame.merge(stream: 7))
|
590
|
-
@conn << f.generate(push_promise_frame)
|
591
|
-
|
592
|
-
expect(@conn.active_stream_count).to eq 1
|
593
|
-
end
|
594
|
-
|
595
|
-
it 'should raise error on frame for invalid stream ID' do
|
596
|
-
@conn << f.generate(settings_frame)
|
597
|
-
|
598
|
-
expect do
|
599
|
-
@conn << f.generate(data_frame.merge(stream: 31))
|
600
|
-
end.to raise_error(ProtocolError)
|
601
|
-
end
|
602
|
-
|
603
|
-
it 'should not raise an error on frame for a closed stream ID' do
|
604
|
-
srv = Server.new
|
605
|
-
srv << CONNECTION_PREFACE_MAGIC
|
606
|
-
|
607
|
-
stream = srv.new_stream
|
608
|
-
stream.send headers_frame
|
609
|
-
stream.send data_frame
|
610
|
-
stream.close
|
611
|
-
|
612
|
-
expect do
|
613
|
-
srv << f.generate(rst_stream_frame.merge(stream: stream.id))
|
614
|
-
end.to_not raise_error
|
615
|
-
end
|
616
|
-
|
617
|
-
it 'should send GOAWAY frame on connection error' do
|
618
|
-
stream = @conn.new_stream
|
619
|
-
|
620
|
-
expect(@conn).to receive(:encode) do |frame|
|
621
|
-
expect(frame[:type]).to eq :settings
|
622
|
-
[frame]
|
623
|
-
end
|
624
|
-
expect(@conn).to receive(:encode) do |frame|
|
625
|
-
expect(frame[:type]).to eq :goaway
|
626
|
-
expect(frame[:last_stream]).to eq stream.id
|
627
|
-
expect(frame[:error]).to eq :protocol_error
|
628
|
-
[frame]
|
629
|
-
end
|
630
|
-
|
631
|
-
expect { @conn << f.generate(data_frame) }.to raise_error(ProtocolError)
|
632
|
-
end
|
633
|
-
end
|
634
|
-
|
635
|
-
context 'API' do
|
636
|
-
it '.settings should emit SETTINGS frames' do
|
637
|
-
expect(@conn).to receive(:send) do |frame|
|
638
|
-
expect(frame[:type]).to eq :settings
|
639
|
-
expect(frame[:payload]).to eq([
|
640
|
-
[:settings_max_concurrent_streams, 10],
|
641
|
-
[:settings_initial_window_size, 0x7fffffff],
|
642
|
-
])
|
643
|
-
expect(frame[:stream]).to eq 0
|
644
|
-
end
|
645
|
-
|
646
|
-
@conn.settings(settings_max_concurrent_streams: 10,
|
647
|
-
settings_initial_window_size: 0x7fffffff)
|
648
|
-
end
|
649
|
-
|
650
|
-
it '.ping should generate PING frames' do
|
651
|
-
expect(@conn).to receive(:send) do |frame|
|
652
|
-
expect(frame[:type]).to eq :ping
|
653
|
-
expect(frame[:payload]).to eq 'somedata'
|
654
|
-
end
|
655
|
-
|
656
|
-
@conn.ping('somedata')
|
657
|
-
end
|
658
|
-
|
659
|
-
it '.goaway should generate GOAWAY frame with last processed stream ID' do
|
660
|
-
@conn << f.generate(settings_frame)
|
661
|
-
@conn << f.generate(headers_frame.merge(stream: 17))
|
662
|
-
|
663
|
-
expect(@conn).to receive(:send) do |frame|
|
664
|
-
expect(frame[:type]).to eq :goaway
|
665
|
-
expect(frame[:last_stream]).to eq 17
|
666
|
-
expect(frame[:error]).to eq :internal_error
|
667
|
-
expect(frame[:payload]).to eq 'payload'
|
668
|
-
end
|
669
|
-
|
670
|
-
@conn.goaway(:internal_error, 'payload')
|
671
|
-
end
|
672
|
-
it '.window_update should emit WINDOW_UPDATE frames' do
|
673
|
-
expect(@conn).to receive(:send) do |frame|
|
674
|
-
expect(frame[:type]).to eq :window_update
|
675
|
-
expect(frame[:increment]).to eq 20
|
676
|
-
expect(frame[:stream]).to eq 0
|
677
|
-
end
|
678
|
-
@conn.window_update(20)
|
679
|
-
end
|
680
|
-
end
|
681
|
-
end
|