http-2 0.9.1 → 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 # rubocop:disable Style/NumericPredicate
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 # rubocop:disable Style/NumericPredicate
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
@@ -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, :blocked
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
@@ -1,3 +1,3 @@
1
1
  module HTTP2
2
- VERSION = '0.9.1'.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,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(PUSH_PROMISE.dup), 0)
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(PUSH_PROMISE.dup), 31_415)
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 HEADERS.dup
89
+ s.send headers_frame
77
90
 
78
- @client << set_stream_id(f.generate(PUSH_PROMISE.dup), s.id)
79
- @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)
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 HEADERS.deep_dup
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(PUSH_PROMISE.deep_dup), s.id)
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 HEADERS.deep_dup
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(PUSH_PROMISE.deep_dup), s.id)
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 HEADERS.deep_dup
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(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
123
186
  end
124
187
  end
125
188
  end
@@ -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
- @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
489
587
  end
490
588
  end
491
- subject do
492
- bytes = [ex[:streams][nth][:wire].delete(" \n")].pack('H*')
493
- @emitted = @dc.decode(HTTP2::Buffer.new(bytes))
494
- end
495
- it 'should emit expected headers' do
496
- subject
497
- # order-perserving compare
498
- expect(@emitted).to eq ex[:streams][nth][:emitted]
499
- end
500
- it 'should update header table' do
501
- subject
502
- expect(@dc.instance_eval { @cc.table }).to eq ex[:streams][nth][:table]
503
- end
504
- it 'should compute header table size' do
505
- subject
506
- 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
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
- @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
526
638
  end
527
639
  end
528
640
  subject do
529
- @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
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
- it 'should update header table' do
535
- subject
536
- expect(@cc.instance_eval { @cc.table }).to eq ex[:streams][nth][:table]
537
- end
538
- it 'should compute header table size' do
539
- subject
540
- 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
541
660
  end
542
661
  end
543
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,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 = 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
240
268
 
241
269
  it 'should update window when data received is over half of the maximum local window size' do
242
- settings, data = SETTINGS.dup, DATA.dup
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 HEADERS.deep_dup
250
- s2.send HEADERS.deep_dup
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 = SETTINGS.dup
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(WINDOW_UPDATE.merge(stream: 0, increment: 1000))
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 = HEADERS.dup
314
+ headers = headers_frame
287
315
  headers[:payload] = cc.encode(req_headers)
288
316
 
289
- @conn << f.generate(SETTINGS.dup)
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 = HEADERS.dup, CONTINUATION.dup
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(SETTINGS.dup)
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 = HEADERS.dup
357
+ headers = headers_frame
330
358
  headers[:flags] = []
331
359
 
332
- @conn << f.generate(SETTINGS.dup)
360
+ @conn << f.generate(settings_frame)
333
361
  @conn << f.generate(headers)
334
- (FRAME_TYPES - [CONTINUATION]).each do |frame|
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(SETTINGS.dup)
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(SETTINGS.dup)
350
- 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.
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(SETTINGS.dup)
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(SETTINGS.dup) }.to raise_error(HandshakeError)
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(SETTINGS.dup)
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(SETTINGS.dup)
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(PING.dup)
518
+ @conn << f.generate(ping_frame)
491
519
  end
492
520
 
493
521
  it 'should fire callback on PONG' do
494
- @conn << f.generate(SETTINGS.dup)
522
+ @conn << f.generate(settings_frame)
495
523
 
496
524
  pong = nil
497
525
  @conn.ping('12345678') { |d| pong = d }
498
- @conn << f.generate(PONG.dup)
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(SETTINGS.dup)
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(GOAWAY.merge(last_stream: 17, payload: 'test'))
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(SETTINGS.dup)
524
- @conn << f.generate(GOAWAY.dup)
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(SETTINGS.dup)
530
- @conn << f.generate(HEADERS.dup)
531
- @conn << f.generate(GOAWAY.dup)
532
- @conn << f.generate(HEADERS.merge(stream: 7))
533
- @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)
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(SETTINGS.dup)
580
+ @conn << f.generate(settings_frame)
540
581
 
541
582
  expect do
542
- @conn << f.generate(DATA.dup.merge(stream: 31))
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 HEADERS.dup
552
- stream.send DATA.dup
592
+ stream.send headers_frame
593
+ stream.send data_frame
553
594
  stream.close
554
595
 
555
596
  expect do
556
- srv << f.generate(RST_STREAM.dup.merge(stream: stream.id))
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(DATA.dup) }.to raise_error(ProtocolError)
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(SETTINGS.dup)
604
- @conn << f.generate(HEADERS.merge(stream: 17))
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