http-2 0.8.4 → 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.
@@ -1,3 +1,3 @@
1
1
  module HTTP2
2
- VERSION = '0.8.4'.freeze
2
+ VERSION = '0.10.2'.freeze
3
3
  end
@@ -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,35 @@ 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
48
+ end
49
+
50
+ context 'upgrade' do
51
+ it 'fails when client has already created streams' do
52
+ @client.new_stream
53
+ expect { @client.upgrade }.to raise_error(HTTP2::Error::ProtocolError)
54
+ end
55
+
56
+ it 'sends the preface' do
57
+ expect(@client).to receive(:send_connection_preface)
58
+ @client.upgrade
59
+ end
60
+
61
+ it 'initializes the first stream in the half-closed state' do
62
+ stream = @client.upgrade
63
+ expect(stream.state).to be(:half_closed_local)
64
+ end
35
65
  end
36
66
 
37
67
  context 'push' do
@@ -43,41 +73,57 @@ RSpec.describe HTTP2::Client do
43
73
 
44
74
  it 'should raise error on PUSH_PROMISE against stream 0' do
45
75
  expect do
46
- @client << set_stream_id(f.generate(PUSH_PROMISE.dup), 0)
76
+ @client << set_stream_id(f.generate(push_promise_frame), 0)
47
77
  end.to raise_error(ProtocolError)
48
78
  end
49
79
 
50
80
  it 'should raise error on PUSH_PROMISE against bogus stream' do
51
81
  expect do
52
- @client << set_stream_id(f.generate(PUSH_PROMISE.dup), 31_415)
82
+ @client << set_stream_id(f.generate(push_promise_frame), 31_415)
53
83
  end.to raise_error(ProtocolError)
54
84
  end
55
85
 
56
86
  it 'should raise error on PUSH_PROMISE against non-idle stream' do
57
87
  expect do
58
88
  s = @client.new_stream
59
- s.send HEADERS.dup
89
+ s.send headers_frame
60
90
 
61
- @client << set_stream_id(f.generate(PUSH_PROMISE.dup), s.id)
62
- @client << set_stream_id(f.generate(PUSH_PROMISE.dup), s.id)
91
+ @client << set_stream_id(f.generate(push_promise_frame), s.id)
92
+ @client << set_stream_id(f.generate(push_promise_frame), s.id)
63
93
  end.to raise_error(ProtocolError)
64
94
  end
65
95
 
66
96
  it 'should emit stream object for received PUSH_PROMISE' do
67
97
  s = @client.new_stream
68
- s.send HEADERS.deep_dup
98
+ s.send headers_frame
69
99
 
70
100
  promise = nil
71
101
  @client.on(:promise) { |stream| promise = stream }
72
- @client << set_stream_id(f.generate(PUSH_PROMISE.dup), s.id)
102
+ @client << set_stream_id(f.generate(push_promise_frame), s.id)
73
103
 
74
104
  expect(promise.id).to eq 2
75
105
  expect(promise.state).to eq :reserved_remote
76
106
  end
77
107
 
108
+ it 'should emit promise headers for received PUSH_PROMISE' do
109
+ header = nil
110
+ s = @client.new_stream
111
+ s.send headers_frame
112
+
113
+ @client.on(:promise) do |stream|
114
+ stream.on(:promise_headers) do |h|
115
+ header = h
116
+ end
117
+ end
118
+ @client << set_stream_id(f.generate(push_promise_frame), s.id)
119
+
120
+ expect(header).to be_a(Array)
121
+ # expect(header).to eq([%w(a b)])
122
+ end
123
+
78
124
  it 'should auto RST_STREAM promises against locally-RST stream' do
79
125
  s = @client.new_stream
80
- s.send HEADERS.deep_dup
126
+ s.send headers_frame
81
127
  s.close
82
128
 
83
129
  allow(@client).to receive(:send)
@@ -86,7 +132,57 @@ RSpec.describe HTTP2::Client do
86
132
  expect(frame[:stream]).to eq 2
87
133
  end
88
134
 
89
- @client << set_stream_id(f.generate(PUSH_PROMISE.dup), s.id)
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
90
186
  end
91
187
  end
92
188
  end
@@ -236,6 +236,24 @@ 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
245
+ end
246
+
247
+ context 'encode' do
248
+ it 'downcases the field' do
249
+ expect(EncodingContext.new.encode([['Content-Length', '5']]))
250
+ .to eq(EncodingContext.new.encode([['content-length', '5']]))
251
+ end
252
+
253
+ it 'fills :path if empty' do
254
+ expect(EncodingContext.new.encode([[':path', '']]))
255
+ .to eq(EncodingContext.new.encode([[':path', '/']]))
256
+ end
239
257
  end
240
258
  end
241
259
 
@@ -339,6 +357,78 @@ RSpec.describe HTTP2::Header do
339
357
  },
340
358
  ],
341
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
+ },
342
432
  { title: 'D.5. Response Examples without Huffman',
343
433
  type: :response,
344
434
  table_size: 256,
@@ -462,6 +552,22 @@ RSpec.describe HTTP2::Header do
462
552
  },
463
553
  ],
464
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
+ },
465
571
  ]
466
572
 
467
573
  context 'decode' do
@@ -473,25 +579,38 @@ RSpec.describe HTTP2::Header do
473
579
  before do
474
580
  (0...nth).each do |i|
475
581
  bytes = [ex[:streams][i][:wire].delete(" \n")].pack('H*')
476
- @dc.decode(HTTP2::Buffer.new(bytes))
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
477
587
  end
478
588
  end
479
- subject do
480
- bytes = [ex[:streams][nth][:wire].delete(" \n")].pack('H*')
481
- @emitted = @dc.decode(HTTP2::Buffer.new(bytes))
482
- end
483
- it 'should emit expected headers' do
484
- subject
485
- # order-perserving compare
486
- expect(@emitted).to eq ex[:streams][nth][:emitted]
487
- end
488
- it 'should update header table' do
489
- subject
490
- expect(@dc.instance_eval { @cc.table }).to eq ex[:streams][nth][:table]
491
- end
492
- it 'should compute header table size' do
493
- subject
494
- expect(@dc.instance_eval { @cc.current_table_size }).to eq ex[:streams][nth][:table_size]
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
495
614
  end
496
615
  end
497
616
  end
@@ -501,6 +620,7 @@ RSpec.describe HTTP2::Header do
501
620
 
502
621
  context 'encode' do
503
622
  spec_examples.each do |ex|
623
+ next if ex[:bypass_encoder]
504
624
  context "spec example #{ex[:title]}" do
505
625
  ex[:streams].size.times do |nth|
506
626
  context "request #{nth + 1}" do
@@ -510,22 +630,33 @@ RSpec.describe HTTP2::Header do
510
630
  end
511
631
  before do
512
632
  (0...nth).each do |i|
513
- @cc.encode(ex[:streams][i][:emitted])
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
514
638
  end
515
639
  end
516
640
  subject do
517
- @cc.encode(ex[:streams][nth][:emitted])
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
518
646
  end
519
647
  it 'should emit expected bytes on wire' do
648
+ puts subject.unpack('H*').first
520
649
  expect(subject.unpack('H*').first).to eq ex[:streams][nth][:wire].delete(" \n")
521
650
  end
522
- it 'should update header table' do
523
- subject
524
- expect(@cc.instance_eval { @cc.table }).to eq ex[:streams][nth][:table]
525
- end
526
- it 'should compute header table size' do
527
- subject
528
- expect(@cc.instance_eval { @cc.current_table_size }).to eq ex[:streams][nth][:table_size]
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
529
660
  end
530
661
  end
531
662
  end
@@ -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 SETTINGS' do
12
- (FRAME_TYPES - [SETTINGS]).each do |frame|
13
- frame = set_stream_id(f.generate(frame.deep_dup), 0x0)
14
- expect { @conn.dup << frame }.to raise_error(ProtocolError)
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
- expect { @conn << f.generate(SETTINGS.dup) }.to_not raise_error
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(SETTINGS.dup), 0x1)
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 = SETTINGS.dup
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 = SETTINGS.dup
54
+ settings = settings_frame
51
55
  settings[:payload] = [[:settings_header_table_size, 256]]
52
56
 
53
- expect(@conn).to receive(:send) do |frame|
54
- expect(frame[:type]).to eq :settings
55
- expect(frame[:flags]).to eq [:ack]
56
- expect(frame[:payload]).to eq []
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(SETTINGS.dup)
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 HEADERS
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 PUSH_PROMISE
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 PUSH_PROMISE.deep_dup
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 HEADERS
91
- s2.send HEADERS.deep_dup
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 DATA
96
- s2.send DATA.dup
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(SETTINGS.dup)
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 HEADERS.deep_dup
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(SETTINGS.dup)
127
+ @conn << f.generate(settings_frame)
115
128
 
116
- stream, headers = nil, HEADERS.dup
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(SETTINGS.dup)
141
+ @conn << f.generate(settings_frame)
129
142
 
130
143
  stream = nil
131
144
  @conn.on(:stream) { |s| stream = s }
132
- @conn << f.generate(PRIORITY.dup)
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
- ['cache-control', "max-age=60, private\0must-revalidate"],
166
- ['content-type', 'text/html'],
167
- ['cookie', "a=b\0c=d; e=f"],
168
- ]
169
- expected = [
170
- ['cache-control', "max-age=60, private\0must-revalidate"],
171
- ['content-type', 'text/html'],
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 = SETTINGS.dup, DATA.dup
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 HEADERS.deep_dup
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 = SETTINGS.dup
240
+ settings = settings_frame
213
241
  settings[:payload] = [[:settings_initial_window_size, 1024]]
214
242
 
215
243
  @conn << f.generate(settings)
@@ -217,35 +245,57 @@ RSpec.describe HTTP2::Connection do
217
245
  end
218
246
 
219
247
  it 'should observe connection flow control' do
220
- settings, data = SETTINGS.dup, DATA.dup
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 HEADERS.deep_dup
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 HEADERS.deep_dup
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(WINDOW_UPDATE.merge(stream: 0, increment: 1000))
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
268
+
269
+ it 'should update window when data received is over half of the maximum local window size' do
270
+ settings, data = settings_frame, data_frame
271
+ conn = Client.new(settings_initial_window_size: 500)
272
+
273
+ conn.receive f.generate(settings)
274
+ s1 = conn.new_stream
275
+ s2 = conn.new_stream
276
+
277
+ s1.send headers_frame
278
+ s2.send headers_frame
279
+ expect(conn).to receive(:send) do |frame|
280
+ expect(frame[:type]).to eq :window_update
281
+ expect(frame[:stream]).to eq 0
282
+ expect(frame[:increment]).to eq 400
283
+ end
284
+ conn.receive f.generate(data.merge(payload: 'x' * 200, end_stream: false, stream: s1.id))
285
+ conn.receive f.generate(data.merge(payload: 'x' * 200, end_stream: false, stream: s2.id))
286
+ expect(s1.local_window).to eq 300
287
+ expect(s2.local_window).to eq 300
288
+ expect(conn.local_window).to eq 500
289
+ end
240
290
  end
241
291
 
242
292
  context 'framing' do
243
293
  it 'should buffer incomplete frames' do
244
- settings = SETTINGS.dup
294
+ settings = settings_frame
245
295
  settings[:payload] = [[:settings_initial_window_size, 1000]]
246
296
  @conn << f.generate(settings)
247
297
 
248
- frame = f.generate(WINDOW_UPDATE.merge(stream: 0, increment: 1000))
298
+ frame = f.generate(window_update_frame.merge(stream: 0, increment: 1000))
249
299
  @conn << frame
250
300
  expect(@conn.remote_window).to eq 2000
251
301
 
@@ -261,10 +311,10 @@ RSpec.describe HTTP2::Connection do
261
311
  ]
262
312
 
263
313
  cc = Compressor.new
264
- headers = HEADERS.dup
314
+ headers = headers_frame
265
315
  headers[:payload] = cc.encode(req_headers)
266
316
 
267
- @conn << f.generate(SETTINGS.dup)
317
+ @conn << f.generate(settings_frame)
268
318
  @conn.on(:stream) do |stream|
269
319
  expect(stream).to receive(:<<) do |frame|
270
320
  expect(frame[:payload]).to eq req_headers
@@ -281,7 +331,7 @@ RSpec.describe HTTP2::Connection do
281
331
  ]
282
332
 
283
333
  cc = Compressor.new
284
- h1, h2 = HEADERS.dup, CONTINUATION.dup
334
+ h1, h2 = headers_frame, continuation_frame
285
335
 
286
336
  # Header block fragment might not complete for decompression
287
337
  payload = cc.encode(req_headers)
@@ -292,7 +342,7 @@ RSpec.describe HTTP2::Connection do
292
342
  h2[:payload] = payload # the remaining
293
343
  h2[:stream] = 5
294
344
 
295
- @conn << f.generate(SETTINGS.dup)
345
+ @conn << f.generate(settings_frame)
296
346
  @conn.on(:stream) do |stream|
297
347
  expect(stream).to receive(:<<) do |frame|
298
348
  expect(frame[:payload]).to eq req_headers
@@ -304,18 +354,18 @@ RSpec.describe HTTP2::Connection do
304
354
  end
305
355
 
306
356
  it 'should require that split header blocks are a contiguous sequence' do
307
- headers = HEADERS.dup
357
+ headers = headers_frame
308
358
  headers[:flags] = []
309
359
 
310
- @conn << f.generate(SETTINGS.dup)
360
+ @conn << f.generate(settings_frame)
311
361
  @conn << f.generate(headers)
312
- (FRAME_TYPES - [CONTINUATION]).each do |frame|
362
+ (frame_types - [continuation_frame]).each do |frame|
313
363
  expect { @conn << f.generate(frame.deep_dup) }.to raise_error(ProtocolError)
314
364
  end
315
365
  end
316
366
 
317
367
  it 'should raise compression error on encode of invalid frame' do
318
- @conn << f.generate(SETTINGS.dup)
368
+ @conn << f.generate(settings_frame)
319
369
  stream = @conn.new_stream
320
370
 
321
371
  expect do
@@ -324,8 +374,8 @@ RSpec.describe HTTP2::Connection do
324
374
  end
325
375
 
326
376
  it 'should raise connection error on decode of invalid frame' do
327
- @conn << f.generate(SETTINGS.dup)
328
- frame = f.generate(DATA.dup) # Receiving DATA on unopened stream 1 is an error.
377
+ @conn << f.generate(settings_frame)
378
+ frame = f.generate(data_frame) # Receiving DATA on unopened stream 1 is an error.
329
379
  # Connection errors emit protocol error frames
330
380
  expect { @conn << frame }.to raise_error(ProtocolError)
331
381
  end
@@ -336,7 +386,7 @@ RSpec.describe HTTP2::Connection do
336
386
  @conn.settings(settings_max_concurrent_streams: 10,
337
387
  settings_initial_window_size: 0x7fffffff)
338
388
 
339
- expect(bytes).to eq f.generate(SETTINGS.dup)
389
+ expect(bytes).to eq f.generate(settings_frame)
340
390
  end
341
391
 
342
392
  it 'should compress stream headers' do
@@ -448,79 +498,106 @@ RSpec.describe HTTP2::Connection do
448
498
  context 'connection management' do
449
499
  it 'should raise error on invalid connection header' do
450
500
  srv = Server.new
451
- expect { srv << f.generate(SETTINGS.dup) }.to raise_error(HandshakeError)
501
+ expect { srv << f.generate(settings_frame) }.to raise_error(HandshakeError)
452
502
 
453
503
  srv = Server.new
454
504
  expect do
455
505
  srv << CONNECTION_PREFACE_MAGIC
456
- srv << f.generate(SETTINGS.dup)
506
+ srv << f.generate(settings_frame)
457
507
  end.to_not raise_error
458
508
  end
459
509
 
460
510
  it 'should respond to PING frames' do
461
- @conn << f.generate(SETTINGS.dup)
511
+ @conn << f.generate(settings_frame)
462
512
  expect(@conn).to receive(:send) do |frame|
463
513
  expect(frame[:type]).to eq :ping
464
514
  expect(frame[:flags]).to eq [:ack]
465
515
  expect(frame[:payload]).to eq '12345678'
466
516
  end
467
517
 
468
- @conn << f.generate(PING.dup)
518
+ @conn << f.generate(ping_frame)
469
519
  end
470
520
 
471
521
  it 'should fire callback on PONG' do
472
- @conn << f.generate(SETTINGS.dup)
522
+ @conn << f.generate(settings_frame)
473
523
 
474
524
  pong = nil
475
525
  @conn.ping('12345678') { |d| pong = d }
476
- @conn << f.generate(PONG.dup)
526
+ @conn << f.generate(pong_frame)
477
527
  expect(pong).to eq '12345678'
478
528
  end
479
529
 
480
530
  it 'should fire callback on receipt of GOAWAY' do
481
531
  last_stream, payload, error = nil
482
- @conn << f.generate(SETTINGS.dup)
532
+ @conn << f.generate(settings_frame)
483
533
  @conn.on(:goaway) do |s, e, p|
484
534
  last_stream = s
485
535
  error = e
486
536
  payload = p
487
537
  end
488
- @conn << f.generate(GOAWAY.merge(last_stream: 17, payload: 'test'))
538
+ @conn << f.generate(goaway_frame.merge(last_stream: 17, payload: 'test'))
489
539
 
490
540
  expect(last_stream).to eq 17
491
541
  expect(error).to eq :no_error
492
542
  expect(payload).to eq 'test'
543
+
544
+ expect(@conn).to be_closed
493
545
  end
494
546
 
495
547
  it 'should raise error when opening new stream after sending GOAWAY' do
496
548
  @conn.goaway
549
+ expect(@conn).to be_closed
550
+
497
551
  expect { @conn.new_stream }.to raise_error(ConnectionClosed)
498
552
  end
499
553
 
500
554
  it 'should raise error when opening new stream after receiving GOAWAY' do
501
- @conn << f.generate(SETTINGS.dup)
502
- @conn << f.generate(GOAWAY.dup)
555
+ @conn << f.generate(settings_frame)
556
+ @conn << f.generate(goaway_frame)
503
557
  expect { @conn.new_stream }.to raise_error(ConnectionClosed)
504
558
  end
505
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
+
506
569
  it 'should process connection management frames after GOAWAY' do
507
- @conn << f.generate(SETTINGS.dup)
508
- @conn << f.generate(HEADERS.dup)
509
- @conn << f.generate(GOAWAY.dup)
510
- @conn << f.generate(HEADERS.merge(stream: 7))
511
- @conn << f.generate(PUSH_PROMISE.dup)
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)
512
575
 
513
576
  expect(@conn.active_stream_count).to eq 1
514
577
  end
515
578
 
516
579
  it 'should raise error on frame for invalid stream ID' do
517
- @conn << f.generate(SETTINGS.dup)
580
+ @conn << f.generate(settings_frame)
518
581
 
519
582
  expect do
520
- @conn << f.generate(DATA.dup.merge(stream: 31))
583
+ @conn << f.generate(data_frame.merge(stream: 31))
521
584
  end.to raise_error(ProtocolError)
522
585
  end
523
586
 
587
+ it 'should not raise an error on frame for a closed stream ID' do
588
+ srv = Server.new
589
+ srv << CONNECTION_PREFACE_MAGIC
590
+
591
+ stream = srv.new_stream
592
+ stream.send headers_frame
593
+ stream.send data_frame
594
+ stream.close
595
+
596
+ expect do
597
+ srv << f.generate(rst_stream_frame.merge(stream: stream.id))
598
+ end.to_not raise_error
599
+ end
600
+
524
601
  it 'should send GOAWAY frame on connection error' do
525
602
  stream = @conn.new_stream
526
603
 
@@ -535,7 +612,7 @@ RSpec.describe HTTP2::Connection do
535
612
  [frame]
536
613
  end
537
614
 
538
- expect { @conn << f.generate(DATA.dup) }.to raise_error(ProtocolError)
615
+ expect { @conn << f.generate(data_frame) }.to raise_error(ProtocolError)
539
616
  end
540
617
  end
541
618
 
@@ -564,8 +641,8 @@ RSpec.describe HTTP2::Connection do
564
641
  end
565
642
 
566
643
  it '.goaway should generate GOAWAY frame with last processed stream ID' do
567
- @conn << f.generate(SETTINGS.dup)
568
- @conn << f.generate(HEADERS.merge(stream: 17))
644
+ @conn << f.generate(settings_frame)
645
+ @conn << f.generate(headers_frame.merge(stream: 17))
569
646
 
570
647
  expect(@conn).to receive(:send) do |frame|
571
648
  expect(frame[:type]).to eq :goaway