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.
@@ -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