http-2 0.9.1 → 0.10.2
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 +5 -5
- data/.rubocop.yml +44 -8
- data/.rubocop_todo.yml +6 -3
- data/.travis.yml +1 -1
- data/Gemfile +1 -1
- data/Guardfile +0 -1
- data/LICENSE +21 -0
- data/README.md +13 -19
- data/example/client.rb +1 -1
- data/example/server.rb +1 -1
- data/example/upgrade_client.rb +5 -6
- data/example/upgrade_server.rb +2 -2
- data/http-2.gemspec +2 -3
- data/lib/http/2/client.rb +6 -1
- data/lib/http/2/compressor.rb +19 -4
- data/lib/http/2/connection.rb +54 -15
- data/lib/http/2/framer.rb +3 -2
- data/lib/http/2/stream.rb +14 -1
- data/lib/http/2/version.rb +1 -1
- data/spec/client_spec.rb +74 -11
- data/spec/compressor_spec.rb +145 -26
- data/spec/connection_spec.rb +123 -82
- data/spec/framer_spec.rb +5 -5
- data/spec/helper.rb +125 -107
- data/spec/server_spec.rb +2 -1
- data/spec/stream_spec.rb +113 -111
- metadata +8 -8
data/lib/http/2/framer.rb
CHANGED
@@ -359,14 +359,14 @@ module HTTP2
|
|
359
359
|
if frame[:flags].include? :priority
|
360
360
|
e_sd = payload.read_uint32
|
361
361
|
frame[:stream_dependency] = e_sd & RBIT
|
362
|
-
frame[:exclusive] = (e_sd & EBIT) != 0
|
362
|
+
frame[:exclusive] = (e_sd & EBIT) != 0
|
363
363
|
frame[:weight] = payload.getbyte + 1
|
364
364
|
end
|
365
365
|
frame[:payload] = payload.read(frame[:length])
|
366
366
|
when :priority
|
367
367
|
e_sd = payload.read_uint32
|
368
368
|
frame[:stream_dependency] = e_sd & RBIT
|
369
|
-
frame[:exclusive] = (e_sd & EBIT) != 0
|
369
|
+
frame[:exclusive] = (e_sd & EBIT) != 0
|
370
370
|
frame[:weight] = payload.getbyte + 1
|
371
371
|
when :rst_stream
|
372
372
|
frame[:error] = unpack_error payload.read_uint32
|
@@ -442,4 +442,5 @@ module HTTP2
|
|
442
442
|
name || error
|
443
443
|
end
|
444
444
|
end
|
445
|
+
# rubocop:enable ClassLength
|
445
446
|
end
|
data/lib/http/2/stream.rb
CHANGED
@@ -91,6 +91,10 @@ module HTTP2
|
|
91
91
|
on(:local_window) { |v| @local_window_max_size = @local_window = v }
|
92
92
|
end
|
93
93
|
|
94
|
+
def closed?
|
95
|
+
@state == :closed
|
96
|
+
end
|
97
|
+
|
94
98
|
# Processes incoming HTTP 2.0 frames. The frames must be decoded upstream.
|
95
99
|
#
|
96
100
|
# @param frame [Hash]
|
@@ -111,7 +115,15 @@ module HTTP2
|
|
111
115
|
process_priority(frame)
|
112
116
|
when :window_update
|
113
117
|
process_window_update(frame)
|
114
|
-
when :altsvc
|
118
|
+
when :altsvc
|
119
|
+
# 4. The ALTSVC HTTP/2 Frame
|
120
|
+
# An ALTSVC frame on a
|
121
|
+
# stream other than stream 0 containing non-empty "Origin" information
|
122
|
+
# is invalid and MUST be ignored.
|
123
|
+
if !frame[:origin] || frame[:origin].empty?
|
124
|
+
emit(frame[:type], frame)
|
125
|
+
end
|
126
|
+
when :blocked
|
115
127
|
emit(frame[:type], frame)
|
116
128
|
end
|
117
129
|
|
@@ -144,6 +156,7 @@ module HTTP2
|
|
144
156
|
end
|
145
157
|
|
146
158
|
# Sends a HEADERS frame containing HTTP response headers.
|
159
|
+
# All pseudo-header fields MUST appear in the header block before regular header fields.
|
147
160
|
#
|
148
161
|
# @param headers [Array or Hash] Array of key-value pairs or Hash
|
149
162
|
# @param end_headers [Boolean] indicates that no more headers will be sent
|
data/lib/http/2/version.rb
CHANGED
data/spec/client_spec.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
RSpec.describe HTTP2::Client do
|
4
|
+
include FrameHelpers
|
4
5
|
before(:each) do
|
5
6
|
@client = Client.new
|
6
7
|
end
|
@@ -32,6 +33,18 @@ RSpec.describe HTTP2::Client do
|
|
32
33
|
expect(frame[:type]).to eq :settings
|
33
34
|
expect(frame[:payload]).to include([:settings_max_concurrent_streams, 200])
|
34
35
|
end
|
36
|
+
|
37
|
+
it 'should initialize client when receiving server settings before sending ack' do
|
38
|
+
frames = []
|
39
|
+
@client.on(:frame) { |bytes| frames << bytes }
|
40
|
+
@client << f.generate(settings_frame)
|
41
|
+
|
42
|
+
expect(frames[0]).to eq CONNECTION_PREFACE_MAGIC
|
43
|
+
expect(f.parse(frames[1])[:type]).to eq :settings
|
44
|
+
ack_frame = f.parse(frames[2])
|
45
|
+
expect(ack_frame[:type]).to eq :settings
|
46
|
+
expect(ack_frame[:flags]).to include(:ack)
|
47
|
+
end
|
35
48
|
end
|
36
49
|
|
37
50
|
context 'upgrade' do
|
@@ -60,33 +73,33 @@ RSpec.describe HTTP2::Client do
|
|
60
73
|
|
61
74
|
it 'should raise error on PUSH_PROMISE against stream 0' do
|
62
75
|
expect do
|
63
|
-
@client << set_stream_id(f.generate(
|
76
|
+
@client << set_stream_id(f.generate(push_promise_frame), 0)
|
64
77
|
end.to raise_error(ProtocolError)
|
65
78
|
end
|
66
79
|
|
67
80
|
it 'should raise error on PUSH_PROMISE against bogus stream' do
|
68
81
|
expect do
|
69
|
-
@client << set_stream_id(f.generate(
|
82
|
+
@client << set_stream_id(f.generate(push_promise_frame), 31_415)
|
70
83
|
end.to raise_error(ProtocolError)
|
71
84
|
end
|
72
85
|
|
73
86
|
it 'should raise error on PUSH_PROMISE against non-idle stream' do
|
74
87
|
expect do
|
75
88
|
s = @client.new_stream
|
76
|
-
s.send
|
89
|
+
s.send headers_frame
|
77
90
|
|
78
|
-
@client << set_stream_id(f.generate(
|
79
|
-
@client << set_stream_id(f.generate(
|
91
|
+
@client << set_stream_id(f.generate(push_promise_frame), s.id)
|
92
|
+
@client << set_stream_id(f.generate(push_promise_frame), s.id)
|
80
93
|
end.to raise_error(ProtocolError)
|
81
94
|
end
|
82
95
|
|
83
96
|
it 'should emit stream object for received PUSH_PROMISE' do
|
84
97
|
s = @client.new_stream
|
85
|
-
s.send
|
98
|
+
s.send headers_frame
|
86
99
|
|
87
100
|
promise = nil
|
88
101
|
@client.on(:promise) { |stream| promise = stream }
|
89
|
-
@client << set_stream_id(f.generate(
|
102
|
+
@client << set_stream_id(f.generate(push_promise_frame), s.id)
|
90
103
|
|
91
104
|
expect(promise.id).to eq 2
|
92
105
|
expect(promise.state).to eq :reserved_remote
|
@@ -95,14 +108,14 @@ RSpec.describe HTTP2::Client do
|
|
95
108
|
it 'should emit promise headers for received PUSH_PROMISE' do
|
96
109
|
header = nil
|
97
110
|
s = @client.new_stream
|
98
|
-
s.send
|
111
|
+
s.send headers_frame
|
99
112
|
|
100
113
|
@client.on(:promise) do |stream|
|
101
114
|
stream.on(:promise_headers) do |h|
|
102
115
|
header = h
|
103
116
|
end
|
104
117
|
end
|
105
|
-
@client << set_stream_id(f.generate(
|
118
|
+
@client << set_stream_id(f.generate(push_promise_frame), s.id)
|
106
119
|
|
107
120
|
expect(header).to be_a(Array)
|
108
121
|
# expect(header).to eq([%w(a b)])
|
@@ -110,7 +123,7 @@ RSpec.describe HTTP2::Client do
|
|
110
123
|
|
111
124
|
it 'should auto RST_STREAM promises against locally-RST stream' do
|
112
125
|
s = @client.new_stream
|
113
|
-
s.send
|
126
|
+
s.send headers_frame
|
114
127
|
s.close
|
115
128
|
|
116
129
|
allow(@client).to receive(:send)
|
@@ -119,7 +132,57 @@ RSpec.describe HTTP2::Client do
|
|
119
132
|
expect(frame[:stream]).to eq 2
|
120
133
|
end
|
121
134
|
|
122
|
-
@client << set_stream_id(f.generate(
|
135
|
+
@client << set_stream_id(f.generate(push_promise_frame), s.id)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'alt-svc' do
|
140
|
+
context 'received in the connection' do
|
141
|
+
it 'should emit :altsvc when receiving one' do
|
142
|
+
@client << f.generate(settings_frame)
|
143
|
+
frame = nil
|
144
|
+
@client.on(:altsvc) do |f|
|
145
|
+
frame = f
|
146
|
+
end
|
147
|
+
@client << f.generate(altsvc_frame)
|
148
|
+
expect(frame).to be_a(Hash)
|
149
|
+
end
|
150
|
+
it 'should not emit :altsvc when the frame when contains no host' do
|
151
|
+
@client << f.generate(settings_frame)
|
152
|
+
frame = nil
|
153
|
+
@client.on(:altsvc) do |f|
|
154
|
+
frame = f
|
155
|
+
end
|
156
|
+
|
157
|
+
@client << f.generate(altsvc_frame.merge(origin: nil))
|
158
|
+
expect(frame).to be_nil
|
159
|
+
end
|
160
|
+
end
|
161
|
+
context 'received in a stream' do
|
162
|
+
it 'should emit :altsvc' do
|
163
|
+
s = @client.new_stream
|
164
|
+
s.send headers_frame
|
165
|
+
s.close
|
166
|
+
|
167
|
+
frame = nil
|
168
|
+
s.on(:altsvc) { |f| frame = f }
|
169
|
+
|
170
|
+
@client << set_stream_id(f.generate(altsvc_frame.merge(origin: nil)), s.id)
|
171
|
+
|
172
|
+
expect(frame).to be_a(Hash)
|
173
|
+
end
|
174
|
+
it 'should not emit :alt_svc when the frame when contains a origin' do
|
175
|
+
s = @client.new_stream
|
176
|
+
s.send headers_frame
|
177
|
+
s.close
|
178
|
+
|
179
|
+
frame = nil
|
180
|
+
s.on(:altsvc) { |f| frame = f }
|
181
|
+
|
182
|
+
@client << set_stream_id(f.generate(altsvc_frame), s.id)
|
183
|
+
|
184
|
+
expect(frame).to be_nil
|
185
|
+
end
|
123
186
|
end
|
124
187
|
end
|
125
188
|
end
|
data/spec/compressor_spec.rb
CHANGED
@@ -236,6 +236,12 @@ RSpec.describe HTTP2::Header do
|
|
236
236
|
expect(cc.table.size).to be 1
|
237
237
|
expect(cc.table.first[0]).to eq 'test2'
|
238
238
|
end
|
239
|
+
|
240
|
+
it 'should reject table size update if exceed limit' do
|
241
|
+
cc = EncodingContext.new(table_size: 4096)
|
242
|
+
|
243
|
+
expect { cc.process(type: :changetablesize, value: 150_000_000) }.to raise_error(CompressionError)
|
244
|
+
end
|
239
245
|
end
|
240
246
|
|
241
247
|
context 'encode' do
|
@@ -351,6 +357,78 @@ RSpec.describe HTTP2::Header do
|
|
351
357
|
},
|
352
358
|
],
|
353
359
|
},
|
360
|
+
{ title: 'D.4.a. Request Examples with Huffman - Client Handling of Improperly Ordered Headers',
|
361
|
+
type: :request,
|
362
|
+
table_size: 4096,
|
363
|
+
huffman: :always,
|
364
|
+
streams: [
|
365
|
+
{ wire: '8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4 ff',
|
366
|
+
emitted: [
|
367
|
+
[':method', 'GET'],
|
368
|
+
[':scheme', 'http'],
|
369
|
+
[':path', '/'],
|
370
|
+
[':authority', 'www.example.com'],
|
371
|
+
],
|
372
|
+
table: [
|
373
|
+
[':authority', 'www.example.com'],
|
374
|
+
],
|
375
|
+
table_size: 57,
|
376
|
+
},
|
377
|
+
{ wire: '8286 84be 5886 a8eb 1064 9cbf',
|
378
|
+
emitted: [
|
379
|
+
[':method', 'GET'],
|
380
|
+
[':scheme', 'http'],
|
381
|
+
['cache-control', 'no-cache'],
|
382
|
+
[':path', '/'],
|
383
|
+
[':authority', 'www.example.com'],
|
384
|
+
],
|
385
|
+
table: [
|
386
|
+
['cache-control', 'no-cache'],
|
387
|
+
[':authority', 'www.example.com'],
|
388
|
+
],
|
389
|
+
table_size: 110,
|
390
|
+
},
|
391
|
+
{ wire: "8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925
|
392
|
+
a849 e95b b8e8 b4bf",
|
393
|
+
emitted: [
|
394
|
+
[':method', 'GET'],
|
395
|
+
[':scheme', 'https'],
|
396
|
+
['custom-key', 'custom-value'],
|
397
|
+
[':path', '/index.html'],
|
398
|
+
[':authority', 'www.example.com'],
|
399
|
+
],
|
400
|
+
table: [
|
401
|
+
['custom-key', 'custom-value'],
|
402
|
+
['cache-control', 'no-cache'],
|
403
|
+
[':authority', 'www.example.com'],
|
404
|
+
],
|
405
|
+
table_size: 164,
|
406
|
+
},
|
407
|
+
],
|
408
|
+
},
|
409
|
+
{ title: 'D.4.b. Request Examples with Huffman - Server Handling of Improperly Ordered Headers',
|
410
|
+
type: :request,
|
411
|
+
bypass_encoder: true,
|
412
|
+
table_size: 4096,
|
413
|
+
huffman: :always,
|
414
|
+
streams: [
|
415
|
+
{ wire: '8286408825a849e95ba97d7f8925a849e95bb8e8b4bf84418cf1e3c2e5f23a6ba0ab90f4ff',
|
416
|
+
emitted: [
|
417
|
+
[':method', 'GET'],
|
418
|
+
[':scheme', 'http'],
|
419
|
+
['custom-key', 'custom-value'],
|
420
|
+
[':path', '/'],
|
421
|
+
[':authority', 'www.example.com'],
|
422
|
+
],
|
423
|
+
table: [
|
424
|
+
['custom-key', 'custom-value'],
|
425
|
+
[':authority', 'www.example.com'],
|
426
|
+
],
|
427
|
+
table_size: 111,
|
428
|
+
has_bad_headers: true,
|
429
|
+
},
|
430
|
+
],
|
431
|
+
},
|
354
432
|
{ title: 'D.5. Response Examples without Huffman',
|
355
433
|
type: :response,
|
356
434
|
table_size: 256,
|
@@ -474,6 +552,22 @@ RSpec.describe HTTP2::Header do
|
|
474
552
|
},
|
475
553
|
],
|
476
554
|
},
|
555
|
+
{ title: 'D.6.a. Response Examples with Huffman - dynamic table size updates should not trigger exceptions',
|
556
|
+
type: :response,
|
557
|
+
table_size: 4096,
|
558
|
+
huffman: :always,
|
559
|
+
bypass_encoder: true,
|
560
|
+
streams: [
|
561
|
+
{ wire: '2088 7689 aa63 55e5 80ae 16d7 17',
|
562
|
+
emitted: [
|
563
|
+
[':status', '200'],
|
564
|
+
['server', 'nginx/1.15.2'],
|
565
|
+
],
|
566
|
+
table: [],
|
567
|
+
table_size: 0,
|
568
|
+
},
|
569
|
+
],
|
570
|
+
},
|
477
571
|
]
|
478
572
|
|
479
573
|
context 'decode' do
|
@@ -485,25 +579,38 @@ RSpec.describe HTTP2::Header do
|
|
485
579
|
before do
|
486
580
|
(0...nth).each do |i|
|
487
581
|
bytes = [ex[:streams][i][:wire].delete(" \n")].pack('H*')
|
488
|
-
|
582
|
+
if ex[:streams][i][:has_bad_headers]
|
583
|
+
expect { @dc.decode(HTTP2::Buffer.new(bytes)) }.to raise_error ProtocolError
|
584
|
+
else
|
585
|
+
@dc.decode(HTTP2::Buffer.new(bytes))
|
586
|
+
end
|
489
587
|
end
|
490
588
|
end
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
589
|
+
if ex[:streams][nth][:has_bad_headers]
|
590
|
+
it 'should raise CompressionError' do
|
591
|
+
bytes = [ex[:streams][nth][:wire].delete(" \n")].pack('H*')
|
592
|
+
expect { @dc.decode(HTTP2::Buffer.new(bytes)) }.to raise_error ProtocolError
|
593
|
+
end
|
594
|
+
else
|
595
|
+
subject do
|
596
|
+
bytes = [ex[:streams][nth][:wire].delete(" \n")].pack('H*')
|
597
|
+
@emitted = @dc.decode(HTTP2::Buffer.new(bytes))
|
598
|
+
end
|
599
|
+
it 'should emit expected headers' do
|
600
|
+
subject
|
601
|
+
# partitioned compare
|
602
|
+
pseudo_headers, headers = ex[:streams][nth][:emitted].partition { |f, _| f.start_with? ':' }
|
603
|
+
partitioned_headers = pseudo_headers + headers
|
604
|
+
expect(@emitted).to eq partitioned_headers
|
605
|
+
end
|
606
|
+
it 'should update header table' do
|
607
|
+
subject
|
608
|
+
expect(@dc.instance_eval { @cc.table }).to eq ex[:streams][nth][:table]
|
609
|
+
end
|
610
|
+
it 'should compute header table size' do
|
611
|
+
subject
|
612
|
+
expect(@dc.instance_eval { @cc.current_table_size }).to eq ex[:streams][nth][:table_size]
|
613
|
+
end
|
507
614
|
end
|
508
615
|
end
|
509
616
|
end
|
@@ -513,6 +620,7 @@ RSpec.describe HTTP2::Header do
|
|
513
620
|
|
514
621
|
context 'encode' do
|
515
622
|
spec_examples.each do |ex|
|
623
|
+
next if ex[:bypass_encoder]
|
516
624
|
context "spec example #{ex[:title]}" do
|
517
625
|
ex[:streams].size.times do |nth|
|
518
626
|
context "request #{nth + 1}" do
|
@@ -522,22 +630,33 @@ RSpec.describe HTTP2::Header do
|
|
522
630
|
end
|
523
631
|
before do
|
524
632
|
(0...nth).each do |i|
|
525
|
-
|
633
|
+
if ex[:streams][i][:has_bad_headers]
|
634
|
+
@cc.encode(ex[:streams][i][:emitted], ensure_proper_ordering: false)
|
635
|
+
else
|
636
|
+
@cc.encode(ex[:streams][i][:emitted])
|
637
|
+
end
|
526
638
|
end
|
527
639
|
end
|
528
640
|
subject do
|
529
|
-
|
641
|
+
if ex[:streams][nth][:has_bad_headers]
|
642
|
+
@cc.encode(ex[:streams][nth][:emitted], ensure_proper_ordering: false)
|
643
|
+
else
|
644
|
+
@cc.encode(ex[:streams][nth][:emitted])
|
645
|
+
end
|
530
646
|
end
|
531
647
|
it 'should emit expected bytes on wire' do
|
648
|
+
puts subject.unpack('H*').first
|
532
649
|
expect(subject.unpack('H*').first).to eq ex[:streams][nth][:wire].delete(" \n")
|
533
650
|
end
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
651
|
+
unless ex[:streams][nth][:has_bad_headers]
|
652
|
+
it 'should update header table' do
|
653
|
+
subject
|
654
|
+
expect(@cc.instance_eval { @cc.table }).to eq ex[:streams][nth][:table]
|
655
|
+
end
|
656
|
+
it 'should compute header table size' do
|
657
|
+
subject
|
658
|
+
expect(@cc.instance_eval { @cc.current_table_size }).to eq ex[:streams][nth][:table_size]
|
659
|
+
end
|
541
660
|
end
|
542
661
|
end
|
543
662
|
end
|
data/spec/connection_spec.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
RSpec.describe HTTP2::Connection do
|
4
|
+
include FrameHelpers
|
4
5
|
before(:each) do
|
5
6
|
@conn = Client.new
|
6
7
|
end
|
@@ -8,18 +9,21 @@ RSpec.describe HTTP2::Connection do
|
|
8
9
|
let(:f) { Framer.new }
|
9
10
|
|
10
11
|
context 'initialization and settings' do
|
11
|
-
it 'should raise error if first frame is not
|
12
|
-
(
|
13
|
-
|
14
|
-
expect
|
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
|
15
16
|
end
|
17
|
+
end
|
16
18
|
|
17
|
-
|
19
|
+
it 'should not raise error if first frame is SETTINGS' do
|
20
|
+
expect { @conn << f.generate(settings_frame) }.to_not raise_error
|
18
21
|
expect(@conn.state).to eq :connected
|
22
|
+
expect(@conn).to_not be_closed
|
19
23
|
end
|
20
24
|
|
21
25
|
it 'should raise error if SETTINGS stream != 0' do
|
22
|
-
frame = set_stream_id(f.generate(
|
26
|
+
frame = set_stream_id(f.generate(settings_frame), 0x1)
|
23
27
|
expect { @conn << frame }.to raise_error(ProtocolError)
|
24
28
|
end
|
25
29
|
end
|
@@ -38,7 +42,7 @@ RSpec.describe HTTP2::Connection do
|
|
38
42
|
|
39
43
|
it 'should reflect incoming settings when SETTINGS is received' do
|
40
44
|
expect(@conn.remote_settings[:settings_header_table_size]).to eq 4096
|
41
|
-
settings =
|
45
|
+
settings = settings_frame
|
42
46
|
settings[:payload] = [[:settings_header_table_size, 256]]
|
43
47
|
|
44
48
|
@conn << f.generate(settings)
|
@@ -47,16 +51,22 @@ RSpec.describe HTTP2::Connection do
|
|
47
51
|
end
|
48
52
|
|
49
53
|
it 'should send SETTINGS ACK when SETTINGS is received' do
|
50
|
-
settings =
|
54
|
+
settings = settings_frame
|
51
55
|
settings[:payload] = [[:settings_header_table_size, 256]]
|
52
56
|
|
53
|
-
expect(
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
+
# We should expect two frames here (append .twice) - one for the connection setup, and one for the settings ack.
|
58
|
+
frames = []
|
59
|
+
expect(@conn).to receive(:send).twice do |frame|
|
60
|
+
frames << frame
|
57
61
|
end
|
58
62
|
|
63
|
+
@conn.send_connection_preface
|
59
64
|
@conn << f.generate(settings)
|
65
|
+
|
66
|
+
frame = frames.last
|
67
|
+
expect(frame[:type]).to eq :settings
|
68
|
+
expect(frame[:flags]).to eq [:ack]
|
69
|
+
expect(frame[:payload]).to eq []
|
60
70
|
end
|
61
71
|
end
|
62
72
|
|
@@ -66,44 +76,47 @@ RSpec.describe HTTP2::Connection do
|
|
66
76
|
end
|
67
77
|
|
68
78
|
it 'should change stream limit to received SETTINGS value' do
|
69
|
-
@conn << f.generate(
|
79
|
+
@conn << f.generate(settings_frame)
|
70
80
|
expect(@conn.remote_settings[:settings_max_concurrent_streams]).to eq 10
|
71
81
|
end
|
72
82
|
|
73
83
|
it 'should count open streams against stream limit' do
|
74
84
|
s = @conn.new_stream
|
75
85
|
expect(@conn.active_stream_count).to eq 0
|
76
|
-
s.receive
|
86
|
+
s.receive headers_frame
|
77
87
|
expect(@conn.active_stream_count).to eq 1
|
78
88
|
end
|
79
89
|
|
80
90
|
it 'should not count reserved streams against stream limit' do
|
81
91
|
s1 = @conn.new_stream
|
82
|
-
s1.receive
|
92
|
+
s1.receive push_promise_frame
|
83
93
|
expect(@conn.active_stream_count).to eq 0
|
84
94
|
|
85
95
|
s2 = @conn.new_stream
|
86
|
-
s2.send
|
96
|
+
s2.send push_promise_frame
|
87
97
|
expect(@conn.active_stream_count).to eq 0
|
88
98
|
|
89
99
|
# transition to half closed
|
90
|
-
s1.receive
|
91
|
-
s2.send
|
100
|
+
s1.receive headers_frame
|
101
|
+
s2.send headers_frame
|
92
102
|
expect(@conn.active_stream_count).to eq 2
|
93
103
|
|
94
104
|
# transition to closed
|
95
|
-
s1.receive
|
96
|
-
s2.send
|
105
|
+
s1.receive data_frame
|
106
|
+
s2.send data_frame
|
97
107
|
expect(@conn.active_stream_count).to eq 0
|
108
|
+
|
109
|
+
expect(s1).to be_closed
|
110
|
+
expect(s2).to be_closed
|
98
111
|
end
|
99
112
|
|
100
113
|
it 'should not exceed stream limit set by peer' do
|
101
|
-
@conn << f.generate(
|
114
|
+
@conn << f.generate(settings_frame)
|
102
115
|
|
103
116
|
expect do
|
104
117
|
10.times do
|
105
118
|
s = @conn.new_stream
|
106
|
-
s.send
|
119
|
+
s.send headers_frame
|
107
120
|
end
|
108
121
|
end.to_not raise_error
|
109
122
|
|
@@ -111,9 +124,9 @@ RSpec.describe HTTP2::Connection do
|
|
111
124
|
end
|
112
125
|
|
113
126
|
it 'should initialize stream with HEADERS priority value' do
|
114
|
-
@conn << f.generate(
|
127
|
+
@conn << f.generate(settings_frame)
|
115
128
|
|
116
|
-
stream, headers = nil,
|
129
|
+
stream, headers = nil, headers_frame
|
117
130
|
headers[:weight] = 20
|
118
131
|
headers[:stream_dependency] = 0
|
119
132
|
headers[:exclusive] = false
|
@@ -125,16 +138,33 @@ RSpec.describe HTTP2::Connection do
|
|
125
138
|
end
|
126
139
|
|
127
140
|
it 'should initialize idle stream on PRIORITY frame' do
|
128
|
-
@conn << f.generate(
|
141
|
+
@conn << f.generate(settings_frame)
|
129
142
|
|
130
143
|
stream = nil
|
131
144
|
@conn.on(:stream) { |s| stream = s }
|
132
|
-
@conn << f.generate(
|
145
|
+
@conn << f.generate(priority_frame)
|
133
146
|
|
134
147
|
expect(stream.state).to eq :idle
|
135
148
|
end
|
136
149
|
end
|
137
150
|
|
151
|
+
context 'cleanup_recently_closed' do
|
152
|
+
it 'should cleanup old connections' do
|
153
|
+
now_ts = Time.now.to_i
|
154
|
+
stream_ids = Array.new(4) { @conn.new_stream.id }
|
155
|
+
expect(@conn.instance_variable_get('@streams').size).to eq(4)
|
156
|
+
|
157
|
+
# Assume that the first 3 streams were closed in different time
|
158
|
+
recently_closed = stream_ids[0, 3].zip([now_ts - 100, now_ts - 50, now_ts - 5]).to_h
|
159
|
+
@conn.instance_variable_set('@streams_recently_closed', recently_closed)
|
160
|
+
|
161
|
+
# Cleanup should delete streams that were closed earlier than 15s ago
|
162
|
+
@conn.__send__(:cleanup_recently_closed)
|
163
|
+
expect(@conn.instance_variable_get('@streams').size).to eq(2)
|
164
|
+
expect(@conn.instance_variable_get('@streams_recently_closed')).to eq(stream_ids[2] => now_ts - 5)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
138
168
|
context 'Headers pre/post processing' do
|
139
169
|
it 'should not concatenate multiple occurences of a header field with the same name' do
|
140
170
|
input = [
|
@@ -161,16 +191,14 @@ RSpec.describe HTTP2::Connection do
|
|
161
191
|
end
|
162
192
|
|
163
193
|
it 'should not split zero-concatenated header field values' do
|
164
|
-
input = [
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
['cookie', "a=b\0c=d; e=f"],
|
173
|
-
]
|
194
|
+
input = [*RESPONSE_HEADERS,
|
195
|
+
['cache-control', "max-age=60, private\0must-revalidate"],
|
196
|
+
['content-type', 'text/html'],
|
197
|
+
['cookie', "a=b\0c=d; e=f"]]
|
198
|
+
expected = [*RESPONSE_HEADERS,
|
199
|
+
['cache-control', "max-age=60, private\0must-revalidate"],
|
200
|
+
['content-type', 'text/html'],
|
201
|
+
['cookie', "a=b\0c=d; e=f"]]
|
174
202
|
|
175
203
|
result = nil
|
176
204
|
@conn.on(:stream) do |stream|
|
@@ -192,13 +220,13 @@ RSpec.describe HTTP2::Connection do
|
|
192
220
|
end
|
193
221
|
|
194
222
|
it 'should update connection and stream windows on SETTINGS' do
|
195
|
-
settings, data =
|
223
|
+
settings, data = settings_frame, data_frame
|
196
224
|
settings[:payload] = [[:settings_initial_window_size, 1024]]
|
197
225
|
data[:payload] = 'x' * 2048
|
198
226
|
|
199
227
|
stream = @conn.new_stream
|
200
228
|
|
201
|
-
stream.send
|
229
|
+
stream.send headers_frame
|
202
230
|
stream.send data
|
203
231
|
expect(stream.remote_window).to eq(DEFAULT_FLOW_WINDOW - 2048)
|
204
232
|
expect(@conn.remote_window).to eq(DEFAULT_FLOW_WINDOW - 2048)
|
@@ -209,7 +237,7 @@ RSpec.describe HTTP2::Connection do
|
|
209
237
|
end
|
210
238
|
|
211
239
|
it 'should initialize streams with window specified by peer' do
|
212
|
-
settings =
|
240
|
+
settings = settings_frame
|
213
241
|
settings[:payload] = [[:settings_initial_window_size, 1024]]
|
214
242
|
|
215
243
|
@conn << f.generate(settings)
|
@@ -217,37 +245,37 @@ RSpec.describe HTTP2::Connection do
|
|
217
245
|
end
|
218
246
|
|
219
247
|
it 'should observe connection flow control' do
|
220
|
-
settings, data =
|
248
|
+
settings, data = settings_frame, data_frame
|
221
249
|
settings[:payload] = [[:settings_initial_window_size, 1000]]
|
222
250
|
|
223
251
|
@conn << f.generate(settings)
|
224
252
|
s1 = @conn.new_stream
|
225
253
|
s2 = @conn.new_stream
|
226
254
|
|
227
|
-
s1.send
|
255
|
+
s1.send headers_frame
|
228
256
|
s1.send data.merge(payload: 'x' * 900)
|
229
257
|
expect(@conn.remote_window).to eq 100
|
230
258
|
|
231
|
-
s2.send
|
259
|
+
s2.send headers_frame
|
232
260
|
s2.send data.merge(payload: 'x' * 200)
|
233
261
|
expect(@conn.remote_window).to eq 0
|
234
262
|
expect(@conn.buffered_amount).to eq 100
|
235
263
|
|
236
|
-
@conn << f.generate(
|
264
|
+
@conn << f.generate(window_update_frame.merge(stream: 0, increment: 1000))
|
237
265
|
expect(@conn.buffered_amount).to eq 0
|
238
266
|
expect(@conn.remote_window).to eq 900
|
239
267
|
end
|
240
268
|
|
241
269
|
it 'should update window when data received is over half of the maximum local window size' do
|
242
|
-
settings, data =
|
270
|
+
settings, data = settings_frame, data_frame
|
243
271
|
conn = Client.new(settings_initial_window_size: 500)
|
244
272
|
|
245
273
|
conn.receive f.generate(settings)
|
246
274
|
s1 = conn.new_stream
|
247
275
|
s2 = conn.new_stream
|
248
276
|
|
249
|
-
s1.send
|
250
|
-
s2.send
|
277
|
+
s1.send headers_frame
|
278
|
+
s2.send headers_frame
|
251
279
|
expect(conn).to receive(:send) do |frame|
|
252
280
|
expect(frame[:type]).to eq :window_update
|
253
281
|
expect(frame[:stream]).to eq 0
|
@@ -263,11 +291,11 @@ RSpec.describe HTTP2::Connection do
|
|
263
291
|
|
264
292
|
context 'framing' do
|
265
293
|
it 'should buffer incomplete frames' do
|
266
|
-
settings =
|
294
|
+
settings = settings_frame
|
267
295
|
settings[:payload] = [[:settings_initial_window_size, 1000]]
|
268
296
|
@conn << f.generate(settings)
|
269
297
|
|
270
|
-
frame = f.generate(
|
298
|
+
frame = f.generate(window_update_frame.merge(stream: 0, increment: 1000))
|
271
299
|
@conn << frame
|
272
300
|
expect(@conn.remote_window).to eq 2000
|
273
301
|
|
@@ -283,10 +311,10 @@ RSpec.describe HTTP2::Connection do
|
|
283
311
|
]
|
284
312
|
|
285
313
|
cc = Compressor.new
|
286
|
-
headers =
|
314
|
+
headers = headers_frame
|
287
315
|
headers[:payload] = cc.encode(req_headers)
|
288
316
|
|
289
|
-
@conn << f.generate(
|
317
|
+
@conn << f.generate(settings_frame)
|
290
318
|
@conn.on(:stream) do |stream|
|
291
319
|
expect(stream).to receive(:<<) do |frame|
|
292
320
|
expect(frame[:payload]).to eq req_headers
|
@@ -303,7 +331,7 @@ RSpec.describe HTTP2::Connection do
|
|
303
331
|
]
|
304
332
|
|
305
333
|
cc = Compressor.new
|
306
|
-
h1, h2 =
|
334
|
+
h1, h2 = headers_frame, continuation_frame
|
307
335
|
|
308
336
|
# Header block fragment might not complete for decompression
|
309
337
|
payload = cc.encode(req_headers)
|
@@ -314,7 +342,7 @@ RSpec.describe HTTP2::Connection do
|
|
314
342
|
h2[:payload] = payload # the remaining
|
315
343
|
h2[:stream] = 5
|
316
344
|
|
317
|
-
@conn << f.generate(
|
345
|
+
@conn << f.generate(settings_frame)
|
318
346
|
@conn.on(:stream) do |stream|
|
319
347
|
expect(stream).to receive(:<<) do |frame|
|
320
348
|
expect(frame[:payload]).to eq req_headers
|
@@ -326,18 +354,18 @@ RSpec.describe HTTP2::Connection do
|
|
326
354
|
end
|
327
355
|
|
328
356
|
it 'should require that split header blocks are a contiguous sequence' do
|
329
|
-
headers =
|
357
|
+
headers = headers_frame
|
330
358
|
headers[:flags] = []
|
331
359
|
|
332
|
-
@conn << f.generate(
|
360
|
+
@conn << f.generate(settings_frame)
|
333
361
|
@conn << f.generate(headers)
|
334
|
-
(
|
362
|
+
(frame_types - [continuation_frame]).each do |frame|
|
335
363
|
expect { @conn << f.generate(frame.deep_dup) }.to raise_error(ProtocolError)
|
336
364
|
end
|
337
365
|
end
|
338
366
|
|
339
367
|
it 'should raise compression error on encode of invalid frame' do
|
340
|
-
@conn << f.generate(
|
368
|
+
@conn << f.generate(settings_frame)
|
341
369
|
stream = @conn.new_stream
|
342
370
|
|
343
371
|
expect do
|
@@ -346,8 +374,8 @@ RSpec.describe HTTP2::Connection do
|
|
346
374
|
end
|
347
375
|
|
348
376
|
it 'should raise connection error on decode of invalid frame' do
|
349
|
-
@conn << f.generate(
|
350
|
-
frame = f.generate(
|
377
|
+
@conn << f.generate(settings_frame)
|
378
|
+
frame = f.generate(data_frame) # Receiving DATA on unopened stream 1 is an error.
|
351
379
|
# Connection errors emit protocol error frames
|
352
380
|
expect { @conn << frame }.to raise_error(ProtocolError)
|
353
381
|
end
|
@@ -358,7 +386,7 @@ RSpec.describe HTTP2::Connection do
|
|
358
386
|
@conn.settings(settings_max_concurrent_streams: 10,
|
359
387
|
settings_initial_window_size: 0x7fffffff)
|
360
388
|
|
361
|
-
expect(bytes).to eq f.generate(
|
389
|
+
expect(bytes).to eq f.generate(settings_frame)
|
362
390
|
end
|
363
391
|
|
364
392
|
it 'should compress stream headers' do
|
@@ -470,76 +498,89 @@ RSpec.describe HTTP2::Connection do
|
|
470
498
|
context 'connection management' do
|
471
499
|
it 'should raise error on invalid connection header' do
|
472
500
|
srv = Server.new
|
473
|
-
expect { srv << f.generate(
|
501
|
+
expect { srv << f.generate(settings_frame) }.to raise_error(HandshakeError)
|
474
502
|
|
475
503
|
srv = Server.new
|
476
504
|
expect do
|
477
505
|
srv << CONNECTION_PREFACE_MAGIC
|
478
|
-
srv << f.generate(
|
506
|
+
srv << f.generate(settings_frame)
|
479
507
|
end.to_not raise_error
|
480
508
|
end
|
481
509
|
|
482
510
|
it 'should respond to PING frames' do
|
483
|
-
@conn << f.generate(
|
511
|
+
@conn << f.generate(settings_frame)
|
484
512
|
expect(@conn).to receive(:send) do |frame|
|
485
513
|
expect(frame[:type]).to eq :ping
|
486
514
|
expect(frame[:flags]).to eq [:ack]
|
487
515
|
expect(frame[:payload]).to eq '12345678'
|
488
516
|
end
|
489
517
|
|
490
|
-
@conn << f.generate(
|
518
|
+
@conn << f.generate(ping_frame)
|
491
519
|
end
|
492
520
|
|
493
521
|
it 'should fire callback on PONG' do
|
494
|
-
@conn << f.generate(
|
522
|
+
@conn << f.generate(settings_frame)
|
495
523
|
|
496
524
|
pong = nil
|
497
525
|
@conn.ping('12345678') { |d| pong = d }
|
498
|
-
@conn << f.generate(
|
526
|
+
@conn << f.generate(pong_frame)
|
499
527
|
expect(pong).to eq '12345678'
|
500
528
|
end
|
501
529
|
|
502
530
|
it 'should fire callback on receipt of GOAWAY' do
|
503
531
|
last_stream, payload, error = nil
|
504
|
-
@conn << f.generate(
|
532
|
+
@conn << f.generate(settings_frame)
|
505
533
|
@conn.on(:goaway) do |s, e, p|
|
506
534
|
last_stream = s
|
507
535
|
error = e
|
508
536
|
payload = p
|
509
537
|
end
|
510
|
-
@conn << f.generate(
|
538
|
+
@conn << f.generate(goaway_frame.merge(last_stream: 17, payload: 'test'))
|
511
539
|
|
512
540
|
expect(last_stream).to eq 17
|
513
541
|
expect(error).to eq :no_error
|
514
542
|
expect(payload).to eq 'test'
|
543
|
+
|
544
|
+
expect(@conn).to be_closed
|
515
545
|
end
|
516
546
|
|
517
547
|
it 'should raise error when opening new stream after sending GOAWAY' do
|
518
548
|
@conn.goaway
|
549
|
+
expect(@conn).to be_closed
|
550
|
+
|
519
551
|
expect { @conn.new_stream }.to raise_error(ConnectionClosed)
|
520
552
|
end
|
521
553
|
|
522
554
|
it 'should raise error when opening new stream after receiving GOAWAY' do
|
523
|
-
@conn << f.generate(
|
524
|
-
@conn << f.generate(
|
555
|
+
@conn << f.generate(settings_frame)
|
556
|
+
@conn << f.generate(goaway_frame)
|
525
557
|
expect { @conn.new_stream }.to raise_error(ConnectionClosed)
|
526
558
|
end
|
527
559
|
|
560
|
+
it 'should not raise error when receiving connection management frames immediately after emitting goaway' do
|
561
|
+
@conn.goaway
|
562
|
+
expect(@conn).to be_closed
|
563
|
+
|
564
|
+
expect { @conn << f.generate(settings_frame) }.not_to raise_error(ProtocolError)
|
565
|
+
expect { @conn << f.generate(ping_frame) }.not_to raise_error(ProtocolError)
|
566
|
+
expect { @conn << f.generate(goaway_frame) }.not_to raise_error(ProtocolError)
|
567
|
+
end
|
568
|
+
|
528
569
|
it 'should process connection management frames after GOAWAY' do
|
529
|
-
@conn << f.generate(
|
530
|
-
@conn << f.generate(
|
531
|
-
@conn << f.generate(
|
532
|
-
@conn << f.generate(
|
533
|
-
@conn << f.generate(
|
570
|
+
@conn << f.generate(settings_frame)
|
571
|
+
@conn << f.generate(headers_frame)
|
572
|
+
@conn << f.generate(goaway_frame)
|
573
|
+
@conn << f.generate(headers_frame.merge(stream: 7))
|
574
|
+
@conn << f.generate(push_promise_frame)
|
534
575
|
|
535
576
|
expect(@conn.active_stream_count).to eq 1
|
536
577
|
end
|
537
578
|
|
538
579
|
it 'should raise error on frame for invalid stream ID' do
|
539
|
-
@conn << f.generate(
|
580
|
+
@conn << f.generate(settings_frame)
|
540
581
|
|
541
582
|
expect do
|
542
|
-
@conn << f.generate(
|
583
|
+
@conn << f.generate(data_frame.merge(stream: 31))
|
543
584
|
end.to raise_error(ProtocolError)
|
544
585
|
end
|
545
586
|
|
@@ -548,12 +589,12 @@ RSpec.describe HTTP2::Connection do
|
|
548
589
|
srv << CONNECTION_PREFACE_MAGIC
|
549
590
|
|
550
591
|
stream = srv.new_stream
|
551
|
-
stream.send
|
552
|
-
stream.send
|
592
|
+
stream.send headers_frame
|
593
|
+
stream.send data_frame
|
553
594
|
stream.close
|
554
595
|
|
555
596
|
expect do
|
556
|
-
srv << f.generate(
|
597
|
+
srv << f.generate(rst_stream_frame.merge(stream: stream.id))
|
557
598
|
end.to_not raise_error
|
558
599
|
end
|
559
600
|
|
@@ -571,7 +612,7 @@ RSpec.describe HTTP2::Connection do
|
|
571
612
|
[frame]
|
572
613
|
end
|
573
614
|
|
574
|
-
expect { @conn << f.generate(
|
615
|
+
expect { @conn << f.generate(data_frame) }.to raise_error(ProtocolError)
|
575
616
|
end
|
576
617
|
end
|
577
618
|
|
@@ -600,8 +641,8 @@ RSpec.describe HTTP2::Connection do
|
|
600
641
|
end
|
601
642
|
|
602
643
|
it '.goaway should generate GOAWAY frame with last processed stream ID' do
|
603
|
-
@conn << f.generate(
|
604
|
-
@conn << f.generate(
|
644
|
+
@conn << f.generate(settings_frame)
|
645
|
+
@conn << f.generate(headers_frame.merge(stream: 17))
|
605
646
|
|
606
647
|
expect(@conn).to receive(:send) do |frame|
|
607
648
|
expect(frame[:type]).to eq :goaway
|