http-2 0.10.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: f52820ad290d538c293b8d008baa622bc788c07c
4
- data.tar.gz: 93aa6df1ade7720059e84c8ffd69d1fb2385028d
2
+ SHA256:
3
+ metadata.gz: 5617138c0740b55546b79e29a06978a9fcb9aae208ccb2f695c32939eca5f88a
4
+ data.tar.gz: 193880583c0a80aa569f433977af1798c98be25a86fb782a410e16eb38d5116c
5
5
  SHA512:
6
- metadata.gz: d8fb28dd5124a1dc9995f250ca82a0a257d5a1c2a4b802fadcf0d7fe30a054c1e1e312fa81f6bb69888103783d23916c3bdb464c5eb5819e2ef68609ddeae303
7
- data.tar.gz: 82f8859ce4752d46be361deb76e1b37b076472b0b07262ca5aa9d7b2daa2c50d6d28b44aa8811031b58a294424c1e8fc056f22ffd9c92301a48b2369503784ae
6
+ metadata.gz: 46fde2633ab7d074136a32e7ae8cdf90ecd037f3a95bfeabab9557d72a13487a47a3e298339bc74df4d89c6b802537b9c35a1f7709fd6cf1f4fde44eecbe468e
7
+ data.tar.gz: 6eadeb79f3bbf6fa1911933e2dc399bfe50ae08c598cb72278a37e0c42102bf27ccbed77b7acb5e7f1e85a55bd6967e63a8950fa3184fb7c9eef750c8a9e123c
data/.rubocop.yml CHANGED
@@ -11,6 +11,7 @@ AllCops:
11
11
  Layout/IndentHeredoc:
12
12
  Exclude:
13
13
  - 'lib/tasks/generate_huffman_table.rb'
14
+ - 'example/*'
14
15
 
15
16
  Metrics/LineLength:
16
17
  Max: 120
data/.rubocop_todo.yml CHANGED
@@ -19,6 +19,9 @@ Metrics/BlockNesting:
19
19
  Metrics/ClassLength:
20
20
  Max: 325
21
21
 
22
+ Metrics/ModuleLength:
23
+ Max: 120
24
+
22
25
  # Offense count: 12
23
26
  Metrics/CyclomaticComplexity:
24
27
  Max: 60
@@ -59,6 +62,15 @@ Style/Documentation:
59
62
  - 'example/upgrade_server.rb'
60
63
  - 'lib/tasks/generate_huffman_table.rb'
61
64
 
65
+ # Offense count: 3
66
+ # Cop supports --auto-correct.
67
+ # Configuration parameters: EnforcedStyle.
68
+ # SupportedStyles: braces, no_braces, context_dependent
69
+ Style/BracesAroundHashParameters:
70
+ Exclude:
71
+ - 'spec/connection_spec.rb'
72
+ - 'spec/server_spec.rb'
73
+
62
74
  # Offense count: 1
63
75
  # Cop supports --auto-correct.
64
76
  Style/EmptyCaseCondition:
data/.travis.yml CHANGED
@@ -1,10 +1,13 @@
1
- sudo: false
2
1
  language: ruby
2
+ cache: bundler
3
3
  rvm:
4
4
  - 2.1
5
5
  - 2.2
6
- - 2.3.0
7
- - 2.4.0
6
+ - 2.3
7
+ - 2.4
8
+ - 2.5
9
+ - 2.6
10
+ - 2.7
8
11
  - jruby-9.2.0.0 # latest stable
9
12
  - jruby-head
10
13
  - rbx-2
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2013 Ilya Grigorik
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -34,16 +34,16 @@ end
34
34
 
35
35
  # upgrader module
36
36
  class UpgradeHandler
37
- UPGRADE_REQUEST = <<-RESP.strip_heredoc.freeze
38
- GET %s HTTP/1.1
39
- Connection: Upgrade, HTTP2-Settings
40
- HTTP2-Settings: #{HTTP2::Client.settings_header(settings_max_concurrent_streams: 100)}
41
- Upgrade: h2c
42
- Host: %s
43
- User-Agent: http-2 upgrade
44
- Accept: */*
45
-
46
- RESP
37
+ UPGRADE_REQUEST = <<RESP.freeze
38
+ GET %s HTTP/1.1
39
+ Connection: Upgrade, HTTP2-Settings
40
+ HTTP2-Settings: #{HTTP2::Client.settings_header(settings_max_concurrent_streams: 100)}
41
+ Upgrade: h2c
42
+ Host: %s
43
+ User-Agent: http-2 upgrade
44
+ Accept: */*
45
+
46
+ RESP
47
47
 
48
48
  attr_reader :complete, :parsing
49
49
  def initialize(conn, sock)
@@ -39,12 +39,12 @@ end
39
39
 
40
40
  class UpgradeHandler
41
41
  VALID_UPGRADE_METHODS = %w(GET OPTIONS).freeze
42
- UPGRADE_RESPONSE = <<-RESP.strip_heredoc.freeze
43
- HTTP/1.1 101 Switching Protocols
44
- Connection: Upgrade
45
- Upgrade: h2c
42
+ UPGRADE_RESPONSE = <<RESP.freeze
43
+ HTTP/1.1 101 Switching Protocols
44
+ Connection: Upgrade
45
+ Upgrade: h2c
46
46
 
47
- RESP
47
+ RESP
48
48
 
49
49
  attr_reader :complete, :headers, :body, :parsing
50
50
 
data/http-2.gemspec CHANGED
@@ -18,5 +18,5 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ['lib']
20
20
 
21
- spec.add_development_dependency 'bundler', '~> 1.3'
21
+ spec.add_development_dependency 'bundler'
22
22
  end
@@ -146,6 +146,9 @@ module HTTP2
146
146
 
147
147
  case cmd[:type]
148
148
  when :changetablesize
149
+ if cmd[:value] > @limit
150
+ fail CompressionError, 'dynamic table size update exceed limit'
151
+ end
149
152
  self.table_size = cmd[:value]
150
153
 
151
154
  when :indexed
@@ -328,7 +331,7 @@ module HTTP2
328
331
  class Compressor
329
332
  # @param options [Hash] encoding options
330
333
  def initialize(**options)
331
- @cc = EncodingContext.new(options)
334
+ @cc = EncodingContext.new(**options)
332
335
  end
333
336
 
334
337
  # Set dynamic table size in EncodingContext
@@ -467,7 +470,7 @@ module HTTP2
467
470
  class Decompressor
468
471
  # @param options [Hash] decoding options. Only :table_size is effective.
469
472
  def initialize(**options)
470
- @cc = EncodingContext.new(options)
473
+ @cc = EncodingContext.new(**options)
471
474
  end
472
475
 
473
476
  # Set dynamic table size in EncodingContext
@@ -33,6 +33,9 @@ module HTTP2
33
33
  # Default connection "fast-fail" preamble string as defined by the spec.
34
34
  CONNECTION_PREFACE_MAGIC = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".freeze
35
35
 
36
+ # Time to hold recently closed streams until purge (seconds)
37
+ RECENTLY_CLOSED_STREAMS_TTL = 15
38
+
36
39
  # Connection encapsulates all of the connection, stream, flow-control,
37
40
  # error management, and other processing logic required for a well-behaved
38
41
  # HTTP 2.0 endpoint.
@@ -73,8 +76,8 @@ module HTTP2
73
76
  @local_settings = DEFAULT_CONNECTION_SETTINGS.merge(settings)
74
77
  @remote_settings = SPEC_DEFAULT_CONNECTION_SETTINGS.dup
75
78
 
76
- @compressor = Header::Compressor.new(settings)
77
- @decompressor = Header::Decompressor.new(settings)
79
+ @compressor = Header::Compressor.new(**settings)
80
+ @decompressor = Header::Decompressor.new(**settings)
78
81
 
79
82
  @active_stream_count = 0
80
83
  @streams = {}
@@ -536,7 +539,7 @@ module HTTP2
536
539
  # Process pending settings we have sent.
537
540
  [@pending_settings.shift, :local]
538
541
  else
539
- connection_error(check) if validate_settings(@remote_role, frame[:payload])
542
+ connection_error if validate_settings(@remote_role, frame[:payload])
540
543
  [frame[:payload], :remote]
541
544
  end
542
545
 
@@ -590,7 +593,8 @@ module HTTP2
590
593
  # nothing to do
591
594
 
592
595
  when :settings_max_frame_size
593
- # nothing to do
596
+ # update framer max_frame_size
597
+ @framer.max_frame_size = v
594
598
 
595
599
  # else # ignore unknown settings
596
600
  end
@@ -671,7 +675,7 @@ module HTTP2
671
675
  def activate_stream(id: nil, **args)
672
676
  connection_error(msg: 'Stream ID already exists') if @streams.key?(id)
673
677
 
674
- stream = Stream.new({ connection: self, id: id }.merge(args))
678
+ stream = Stream.new(**{ connection: self, id: id }.merge(args))
675
679
 
676
680
  # Streams that are in the "open" state, or either of the "half closed"
677
681
  # states count toward the maximum number of streams that an endpoint is
@@ -683,13 +687,9 @@ module HTTP2
683
687
  # Store a reference to the closed stream, such that we can respond
684
688
  # to any in-flight frames while close is registered on both sides.
685
689
  # References to such streams will be purged whenever another stream
686
- # is closed, with a minimum of 15s RTT time window.
687
- @streams_recently_closed[id] = Time.now
688
- to_delete = @streams_recently_closed.select { |_, v| (Time.now - v) > 15 }
689
- to_delete.each do |stream_id|
690
- @streams.delete stream_id
691
- @streams_recently_closed.delete stream_id
692
- end
690
+ # is closed, with a defined RTT time window.
691
+ @streams_recently_closed[id] = Time.now.to_i
692
+ cleanup_recently_closed
693
693
  end
694
694
 
695
695
  stream.on(:promise, &method(:promise)) if self.is_a? Server
@@ -698,6 +698,22 @@ module HTTP2
698
698
  @streams[id] = stream
699
699
  end
700
700
 
701
+ # Purge recently streams closed within defined RTT time window.
702
+ def cleanup_recently_closed
703
+ now_ts = Time.now.to_i
704
+ to_delete = []
705
+ @streams_recently_closed.each do |stream_id, ts|
706
+ # Ruby Hash enumeration is ordered, so once fresh stream is met we can stop searching.
707
+ break if now_ts - ts < RECENTLY_CLOSED_STREAMS_TTL
708
+ to_delete << stream_id
709
+ end
710
+
711
+ to_delete.each do |stream_id|
712
+ @streams.delete stream_id
713
+ @streams_recently_closed.delete stream_id
714
+ end
715
+ end
716
+
701
717
  # Emit GOAWAY error indicating to peer that the connection is being
702
718
  # aborted, and once sent, raise a local exception.
703
719
  #
@@ -1,3 +1,3 @@
1
1
  module HTTP2
2
- VERSION = '0.10.1'.freeze
2
+ VERSION = '0.11.0'.freeze
3
3
  end
data/spec/client_spec.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'helper'
2
2
 
3
3
  RSpec.describe HTTP2::Client do
4
+ include FrameHelpers
4
5
  before(:each) do
5
6
  @client = Client.new
6
7
  end
@@ -36,7 +37,7 @@ RSpec.describe HTTP2::Client do
36
37
  it 'should initialize client when receiving server settings before sending ack' do
37
38
  frames = []
38
39
  @client.on(:frame) { |bytes| frames << bytes }
39
- @client << f.generate(SETTINGS.dup)
40
+ @client << f.generate(settings_frame)
40
41
 
41
42
  expect(frames[0]).to eq CONNECTION_PREFACE_MAGIC
42
43
  expect(f.parse(frames[1])[:type]).to eq :settings
@@ -72,33 +73,33 @@ RSpec.describe HTTP2::Client do
72
73
 
73
74
  it 'should raise error on PUSH_PROMISE against stream 0' do
74
75
  expect do
75
- @client << set_stream_id(f.generate(PUSH_PROMISE.dup), 0)
76
+ @client << set_stream_id(f.generate(push_promise_frame), 0)
76
77
  end.to raise_error(ProtocolError)
77
78
  end
78
79
 
79
80
  it 'should raise error on PUSH_PROMISE against bogus stream' do
80
81
  expect do
81
- @client << set_stream_id(f.generate(PUSH_PROMISE.dup), 31_415)
82
+ @client << set_stream_id(f.generate(push_promise_frame), 31_415)
82
83
  end.to raise_error(ProtocolError)
83
84
  end
84
85
 
85
86
  it 'should raise error on PUSH_PROMISE against non-idle stream' do
86
87
  expect do
87
88
  s = @client.new_stream
88
- s.send HEADERS.dup
89
+ s.send headers_frame
89
90
 
90
- @client << set_stream_id(f.generate(PUSH_PROMISE.dup), s.id)
91
- @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)
92
93
  end.to raise_error(ProtocolError)
93
94
  end
94
95
 
95
96
  it 'should emit stream object for received PUSH_PROMISE' do
96
97
  s = @client.new_stream
97
- s.send HEADERS.deep_dup
98
+ s.send headers_frame
98
99
 
99
100
  promise = nil
100
101
  @client.on(:promise) { |stream| promise = stream }
101
- @client << set_stream_id(f.generate(PUSH_PROMISE.deep_dup), s.id)
102
+ @client << set_stream_id(f.generate(push_promise_frame), s.id)
102
103
 
103
104
  expect(promise.id).to eq 2
104
105
  expect(promise.state).to eq :reserved_remote
@@ -107,14 +108,14 @@ RSpec.describe HTTP2::Client do
107
108
  it 'should emit promise headers for received PUSH_PROMISE' do
108
109
  header = nil
109
110
  s = @client.new_stream
110
- s.send HEADERS.deep_dup
111
+ s.send headers_frame
111
112
 
112
113
  @client.on(:promise) do |stream|
113
114
  stream.on(:promise_headers) do |h|
114
115
  header = h
115
116
  end
116
117
  end
117
- @client << set_stream_id(f.generate(PUSH_PROMISE.deep_dup), s.id)
118
+ @client << set_stream_id(f.generate(push_promise_frame), s.id)
118
119
 
119
120
  expect(header).to be_a(Array)
120
121
  # expect(header).to eq([%w(a b)])
@@ -122,7 +123,7 @@ RSpec.describe HTTP2::Client do
122
123
 
123
124
  it 'should auto RST_STREAM promises against locally-RST stream' do
124
125
  s = @client.new_stream
125
- s.send HEADERS.deep_dup
126
+ s.send headers_frame
126
127
  s.close
127
128
 
128
129
  allow(@client).to receive(:send)
@@ -131,54 +132,54 @@ RSpec.describe HTTP2::Client do
131
132
  expect(frame[:stream]).to eq 2
132
133
  end
133
134
 
134
- @client << set_stream_id(f.generate(PUSH_PROMISE.dup), s.id)
135
+ @client << set_stream_id(f.generate(push_promise_frame), s.id)
135
136
  end
136
137
  end
137
138
 
138
139
  context 'alt-svc' do
139
140
  context 'received in the connection' do
140
141
  it 'should emit :altsvc when receiving one' do
141
- @client << f.generate(SETTINGS.dup)
142
+ @client << f.generate(settings_frame)
142
143
  frame = nil
143
144
  @client.on(:altsvc) do |f|
144
145
  frame = f
145
146
  end
146
- @client << f.generate(ALTSVC.deep_dup)
147
+ @client << f.generate(altsvc_frame)
147
148
  expect(frame).to be_a(Hash)
148
149
  end
149
150
  it 'should not emit :altsvc when the frame when contains no host' do
150
- @client << f.generate(SETTINGS.dup)
151
+ @client << f.generate(settings_frame)
151
152
  frame = nil
152
153
  @client.on(:altsvc) do |f|
153
154
  frame = f
154
155
  end
155
156
 
156
- @client << f.generate(ALTSVC.deep_dup.merge(origin: nil))
157
+ @client << f.generate(altsvc_frame.merge(origin: nil))
157
158
  expect(frame).to be_nil
158
159
  end
159
160
  end
160
161
  context 'received in a stream' do
161
162
  it 'should emit :altsvc' do
162
163
  s = @client.new_stream
163
- s.send HEADERS.deep_dup
164
+ s.send headers_frame
164
165
  s.close
165
166
 
166
167
  frame = nil
167
168
  s.on(:altsvc) { |f| frame = f }
168
169
 
169
- @client << set_stream_id(f.generate(ALTSVC.deep_dup.merge(origin: nil)), s.id)
170
+ @client << set_stream_id(f.generate(altsvc_frame.merge(origin: nil)), s.id)
170
171
 
171
172
  expect(frame).to be_a(Hash)
172
173
  end
173
174
  it 'should not emit :alt_svc when the frame when contains a origin' do
174
175
  s = @client.new_stream
175
- s.send HEADERS.deep_dup
176
+ s.send headers_frame
176
177
  s.close
177
178
 
178
179
  frame = nil
179
180
  s.on(:altsvc) { |f| frame = f }
180
181
 
181
- @client << set_stream_id(f.generate(ALTSVC.deep_dup), s.id)
182
+ @client << set_stream_id(f.generate(altsvc_frame), s.id)
182
183
 
183
184
  expect(frame).to be_nil
184
185
  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
@@ -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,21 +9,21 @@ RSpec.describe HTTP2::Connection do
8
9
  let(:f) { Framer.new }
9
10
 
10
11
  context 'initialization and settings' do
11
- (FRAME_TYPES - [SETTINGS]).each do |frame|
12
- it "should raise error if first frame is #{frame[:type]}" do
12
+ it 'should raise error if first frame is not settings' do
13
+ (frame_types - [settings_frame]).each do |frame|
13
14
  expect { @conn << frame }.to raise_error(ProtocolError)
14
15
  expect(@conn).to be_closed
15
16
  end
16
17
  end
17
18
 
18
19
  it 'should not raise error if first frame is SETTINGS' do
19
- expect { @conn << f.generate(SETTINGS.dup) }.to_not raise_error
20
+ expect { @conn << f.generate(settings_frame) }.to_not raise_error
20
21
  expect(@conn.state).to eq :connected
21
22
  expect(@conn).to_not be_closed
22
23
  end
23
24
 
24
25
  it 'should raise error if SETTINGS stream != 0' do
25
- frame = set_stream_id(f.generate(SETTINGS.dup), 0x1)
26
+ frame = set_stream_id(f.generate(settings_frame), 0x1)
26
27
  expect { @conn << frame }.to raise_error(ProtocolError)
27
28
  end
28
29
  end
@@ -41,7 +42,7 @@ RSpec.describe HTTP2::Connection do
41
42
 
42
43
  it 'should reflect incoming settings when SETTINGS is received' do
43
44
  expect(@conn.remote_settings[:settings_header_table_size]).to eq 4096
44
- settings = SETTINGS.dup
45
+ settings = settings_frame
45
46
  settings[:payload] = [[:settings_header_table_size, 256]]
46
47
 
47
48
  @conn << f.generate(settings)
@@ -49,8 +50,24 @@ RSpec.describe HTTP2::Connection do
49
50
  expect(@conn.remote_settings[:settings_header_table_size]).to eq 256
50
51
  end
51
52
 
53
+ it 'should reflect settings_max_frame_size recevied from peer' do
54
+ settings = settings_frame
55
+ settings[:payload] = [[:settings_max_frame_size, 16_385]]
56
+
57
+ @conn << f.generate(settings)
58
+
59
+ frame = {
60
+ length: 16_385,
61
+ type: :data,
62
+ flags: [:end_stream],
63
+ stream: 1,
64
+ payload: 'a' * 16_385,
65
+ }
66
+ expect { @conn.send(frame) }.not_to raise_error(CompressionError)
67
+ end
68
+
52
69
  it 'should send SETTINGS ACK when SETTINGS is received' do
53
- settings = SETTINGS.dup
70
+ settings = settings_frame
54
71
  settings[:payload] = [[:settings_header_table_size, 256]]
55
72
 
56
73
  # We should expect two frames here (append .twice) - one for the connection setup, and one for the settings ack.
@@ -75,34 +92,34 @@ RSpec.describe HTTP2::Connection do
75
92
  end
76
93
 
77
94
  it 'should change stream limit to received SETTINGS value' do
78
- @conn << f.generate(SETTINGS.dup)
95
+ @conn << f.generate(settings_frame)
79
96
  expect(@conn.remote_settings[:settings_max_concurrent_streams]).to eq 10
80
97
  end
81
98
 
82
99
  it 'should count open streams against stream limit' do
83
100
  s = @conn.new_stream
84
101
  expect(@conn.active_stream_count).to eq 0
85
- s.receive HEADERS
102
+ s.receive headers_frame
86
103
  expect(@conn.active_stream_count).to eq 1
87
104
  end
88
105
 
89
106
  it 'should not count reserved streams against stream limit' do
90
107
  s1 = @conn.new_stream
91
- s1.receive PUSH_PROMISE
108
+ s1.receive push_promise_frame
92
109
  expect(@conn.active_stream_count).to eq 0
93
110
 
94
111
  s2 = @conn.new_stream
95
- s2.send PUSH_PROMISE.deep_dup
112
+ s2.send push_promise_frame
96
113
  expect(@conn.active_stream_count).to eq 0
97
114
 
98
115
  # transition to half closed
99
- s1.receive HEADERS
100
- s2.send HEADERS.deep_dup
116
+ s1.receive headers_frame
117
+ s2.send headers_frame
101
118
  expect(@conn.active_stream_count).to eq 2
102
119
 
103
120
  # transition to closed
104
- s1.receive DATA
105
- s2.send DATA.dup
121
+ s1.receive data_frame
122
+ s2.send data_frame
106
123
  expect(@conn.active_stream_count).to eq 0
107
124
 
108
125
  expect(s1).to be_closed
@@ -110,12 +127,12 @@ RSpec.describe HTTP2::Connection do
110
127
  end
111
128
 
112
129
  it 'should not exceed stream limit set by peer' do
113
- @conn << f.generate(SETTINGS.dup)
130
+ @conn << f.generate(settings_frame)
114
131
 
115
132
  expect do
116
133
  10.times do
117
134
  s = @conn.new_stream
118
- s.send HEADERS.deep_dup
135
+ s.send headers_frame
119
136
  end
120
137
  end.to_not raise_error
121
138
 
@@ -123,9 +140,9 @@ RSpec.describe HTTP2::Connection do
123
140
  end
124
141
 
125
142
  it 'should initialize stream with HEADERS priority value' do
126
- @conn << f.generate(SETTINGS.dup)
143
+ @conn << f.generate(settings_frame)
127
144
 
128
- stream, headers = nil, HEADERS.dup
145
+ stream, headers = nil, headers_frame
129
146
  headers[:weight] = 20
130
147
  headers[:stream_dependency] = 0
131
148
  headers[:exclusive] = false
@@ -137,16 +154,33 @@ RSpec.describe HTTP2::Connection do
137
154
  end
138
155
 
139
156
  it 'should initialize idle stream on PRIORITY frame' do
140
- @conn << f.generate(SETTINGS.dup)
157
+ @conn << f.generate(settings_frame)
141
158
 
142
159
  stream = nil
143
160
  @conn.on(:stream) { |s| stream = s }
144
- @conn << f.generate(PRIORITY.dup)
161
+ @conn << f.generate(priority_frame)
145
162
 
146
163
  expect(stream.state).to eq :idle
147
164
  end
148
165
  end
149
166
 
167
+ context 'cleanup_recently_closed' do
168
+ it 'should cleanup old connections' do
169
+ now_ts = Time.now.to_i
170
+ stream_ids = Array.new(4) { @conn.new_stream.id }
171
+ expect(@conn.instance_variable_get('@streams').size).to eq(4)
172
+
173
+ # Assume that the first 3 streams were closed in different time
174
+ recently_closed = stream_ids[0, 3].zip([now_ts - 100, now_ts - 50, now_ts - 5]).to_h
175
+ @conn.instance_variable_set('@streams_recently_closed', recently_closed)
176
+
177
+ # Cleanup should delete streams that were closed earlier than 15s ago
178
+ @conn.__send__(:cleanup_recently_closed)
179
+ expect(@conn.instance_variable_get('@streams').size).to eq(2)
180
+ expect(@conn.instance_variable_get('@streams_recently_closed')).to eq(stream_ids[2] => now_ts - 5)
181
+ end
182
+ end
183
+
150
184
  context 'Headers pre/post processing' do
151
185
  it 'should not concatenate multiple occurences of a header field with the same name' do
152
186
  input = [
@@ -173,16 +207,14 @@ RSpec.describe HTTP2::Connection do
173
207
  end
174
208
 
175
209
  it 'should not split zero-concatenated header field values' do
176
- input = [
177
- ['cache-control', "max-age=60, private\0must-revalidate"],
178
- ['content-type', 'text/html'],
179
- ['cookie', "a=b\0c=d; e=f"],
180
- ]
181
- expected = [
182
- ['cache-control', "max-age=60, private\0must-revalidate"],
183
- ['content-type', 'text/html'],
184
- ['cookie', "a=b\0c=d; e=f"],
185
- ]
210
+ input = [*RESPONSE_HEADERS,
211
+ ['cache-control', "max-age=60, private\0must-revalidate"],
212
+ ['content-type', 'text/html'],
213
+ ['cookie', "a=b\0c=d; e=f"]]
214
+ expected = [*RESPONSE_HEADERS,
215
+ ['cache-control', "max-age=60, private\0must-revalidate"],
216
+ ['content-type', 'text/html'],
217
+ ['cookie', "a=b\0c=d; e=f"]]
186
218
 
187
219
  result = nil
188
220
  @conn.on(:stream) do |stream|
@@ -204,13 +236,13 @@ RSpec.describe HTTP2::Connection do
204
236
  end
205
237
 
206
238
  it 'should update connection and stream windows on SETTINGS' do
207
- settings, data = SETTINGS.dup, DATA.dup
239
+ settings, data = settings_frame, data_frame
208
240
  settings[:payload] = [[:settings_initial_window_size, 1024]]
209
241
  data[:payload] = 'x' * 2048
210
242
 
211
243
  stream = @conn.new_stream
212
244
 
213
- stream.send HEADERS.deep_dup
245
+ stream.send headers_frame
214
246
  stream.send data
215
247
  expect(stream.remote_window).to eq(DEFAULT_FLOW_WINDOW - 2048)
216
248
  expect(@conn.remote_window).to eq(DEFAULT_FLOW_WINDOW - 2048)
@@ -221,7 +253,7 @@ RSpec.describe HTTP2::Connection do
221
253
  end
222
254
 
223
255
  it 'should initialize streams with window specified by peer' do
224
- settings = SETTINGS.dup
256
+ settings = settings_frame
225
257
  settings[:payload] = [[:settings_initial_window_size, 1024]]
226
258
 
227
259
  @conn << f.generate(settings)
@@ -229,37 +261,37 @@ RSpec.describe HTTP2::Connection do
229
261
  end
230
262
 
231
263
  it 'should observe connection flow control' do
232
- settings, data = SETTINGS.dup, DATA.dup
264
+ settings, data = settings_frame, data_frame
233
265
  settings[:payload] = [[:settings_initial_window_size, 1000]]
234
266
 
235
267
  @conn << f.generate(settings)
236
268
  s1 = @conn.new_stream
237
269
  s2 = @conn.new_stream
238
270
 
239
- s1.send HEADERS.deep_dup
271
+ s1.send headers_frame
240
272
  s1.send data.merge(payload: 'x' * 900)
241
273
  expect(@conn.remote_window).to eq 100
242
274
 
243
- s2.send HEADERS.deep_dup
275
+ s2.send headers_frame
244
276
  s2.send data.merge(payload: 'x' * 200)
245
277
  expect(@conn.remote_window).to eq 0
246
278
  expect(@conn.buffered_amount).to eq 100
247
279
 
248
- @conn << f.generate(WINDOW_UPDATE.merge(stream: 0, increment: 1000))
280
+ @conn << f.generate(window_update_frame.merge(stream: 0, increment: 1000))
249
281
  expect(@conn.buffered_amount).to eq 0
250
282
  expect(@conn.remote_window).to eq 900
251
283
  end
252
284
 
253
285
  it 'should update window when data received is over half of the maximum local window size' do
254
- settings, data = SETTINGS.dup, DATA.dup
286
+ settings, data = settings_frame, data_frame
255
287
  conn = Client.new(settings_initial_window_size: 500)
256
288
 
257
289
  conn.receive f.generate(settings)
258
290
  s1 = conn.new_stream
259
291
  s2 = conn.new_stream
260
292
 
261
- s1.send HEADERS.deep_dup
262
- s2.send HEADERS.deep_dup
293
+ s1.send headers_frame
294
+ s2.send headers_frame
263
295
  expect(conn).to receive(:send) do |frame|
264
296
  expect(frame[:type]).to eq :window_update
265
297
  expect(frame[:stream]).to eq 0
@@ -275,11 +307,11 @@ RSpec.describe HTTP2::Connection do
275
307
 
276
308
  context 'framing' do
277
309
  it 'should buffer incomplete frames' do
278
- settings = SETTINGS.dup
310
+ settings = settings_frame
279
311
  settings[:payload] = [[:settings_initial_window_size, 1000]]
280
312
  @conn << f.generate(settings)
281
313
 
282
- frame = f.generate(WINDOW_UPDATE.merge(stream: 0, increment: 1000))
314
+ frame = f.generate(window_update_frame.merge(stream: 0, increment: 1000))
283
315
  @conn << frame
284
316
  expect(@conn.remote_window).to eq 2000
285
317
 
@@ -295,10 +327,10 @@ RSpec.describe HTTP2::Connection do
295
327
  ]
296
328
 
297
329
  cc = Compressor.new
298
- headers = HEADERS.dup
330
+ headers = headers_frame
299
331
  headers[:payload] = cc.encode(req_headers)
300
332
 
301
- @conn << f.generate(SETTINGS.dup)
333
+ @conn << f.generate(settings_frame)
302
334
  @conn.on(:stream) do |stream|
303
335
  expect(stream).to receive(:<<) do |frame|
304
336
  expect(frame[:payload]).to eq req_headers
@@ -315,7 +347,7 @@ RSpec.describe HTTP2::Connection do
315
347
  ]
316
348
 
317
349
  cc = Compressor.new
318
- h1, h2 = HEADERS.dup, CONTINUATION.dup
350
+ h1, h2 = headers_frame, continuation_frame
319
351
 
320
352
  # Header block fragment might not complete for decompression
321
353
  payload = cc.encode(req_headers)
@@ -326,7 +358,7 @@ RSpec.describe HTTP2::Connection do
326
358
  h2[:payload] = payload # the remaining
327
359
  h2[:stream] = 5
328
360
 
329
- @conn << f.generate(SETTINGS.dup)
361
+ @conn << f.generate(settings_frame)
330
362
  @conn.on(:stream) do |stream|
331
363
  expect(stream).to receive(:<<) do |frame|
332
364
  expect(frame[:payload]).to eq req_headers
@@ -338,28 +370,28 @@ RSpec.describe HTTP2::Connection do
338
370
  end
339
371
 
340
372
  it 'should require that split header blocks are a contiguous sequence' do
341
- headers = HEADERS.dup
373
+ headers = headers_frame
342
374
  headers[:flags] = []
343
375
 
344
- @conn << f.generate(SETTINGS.dup)
376
+ @conn << f.generate(settings_frame)
345
377
  @conn << f.generate(headers)
346
- (FRAME_TYPES - [CONTINUATION]).each do |frame|
378
+ (frame_types - [continuation_frame]).each do |frame|
347
379
  expect { @conn << f.generate(frame.deep_dup) }.to raise_error(ProtocolError)
348
380
  end
349
381
  end
350
382
 
351
383
  it 'should raise compression error on encode of invalid frame' do
352
- @conn << f.generate(SETTINGS.dup)
384
+ @conn << f.generate(settings_frame)
353
385
  stream = @conn.new_stream
354
386
 
355
387
  expect do
356
- stream.headers('name' => Float::INFINITY)
388
+ stream.headers({ 'name' => Float::INFINITY })
357
389
  end.to raise_error(CompressionError)
358
390
  end
359
391
 
360
392
  it 'should raise connection error on decode of invalid frame' do
361
- @conn << f.generate(SETTINGS.dup)
362
- frame = f.generate(DATA.dup) # Receiving DATA on unopened stream 1 is an error.
393
+ @conn << f.generate(settings_frame)
394
+ frame = f.generate(data_frame) # Receiving DATA on unopened stream 1 is an error.
363
395
  # Connection errors emit protocol error frames
364
396
  expect { @conn << frame }.to raise_error(ProtocolError)
365
397
  end
@@ -370,7 +402,7 @@ RSpec.describe HTTP2::Connection do
370
402
  @conn.settings(settings_max_concurrent_streams: 10,
371
403
  settings_initial_window_size: 0x7fffffff)
372
404
 
373
- expect(bytes).to eq f.generate(SETTINGS.dup)
405
+ expect(bytes).to eq f.generate(settings_frame)
374
406
  end
375
407
 
376
408
  it 'should compress stream headers' do
@@ -381,10 +413,10 @@ RSpec.describe HTTP2::Connection do
381
413
  end
382
414
 
383
415
  stream = @conn.new_stream
384
- stream.headers(':method' => 'get',
385
- ':scheme' => 'http',
386
- ':authority' => 'www.example.org',
387
- ':path' => '/resource')
416
+ stream.headers({ ':method' => 'get',
417
+ ':scheme' => 'http',
418
+ ':authority' => 'www.example.org',
419
+ ':path' => '/resource' })
388
420
  end
389
421
 
390
422
  it 'should generate CONTINUATION if HEADERS is too long' do
@@ -482,44 +514,44 @@ RSpec.describe HTTP2::Connection do
482
514
  context 'connection management' do
483
515
  it 'should raise error on invalid connection header' do
484
516
  srv = Server.new
485
- expect { srv << f.generate(SETTINGS.dup) }.to raise_error(HandshakeError)
517
+ expect { srv << f.generate(settings_frame) }.to raise_error(HandshakeError)
486
518
 
487
519
  srv = Server.new
488
520
  expect do
489
521
  srv << CONNECTION_PREFACE_MAGIC
490
- srv << f.generate(SETTINGS.dup)
522
+ srv << f.generate(settings_frame)
491
523
  end.to_not raise_error
492
524
  end
493
525
 
494
526
  it 'should respond to PING frames' do
495
- @conn << f.generate(SETTINGS.dup)
527
+ @conn << f.generate(settings_frame)
496
528
  expect(@conn).to receive(:send) do |frame|
497
529
  expect(frame[:type]).to eq :ping
498
530
  expect(frame[:flags]).to eq [:ack]
499
531
  expect(frame[:payload]).to eq '12345678'
500
532
  end
501
533
 
502
- @conn << f.generate(PING.dup)
534
+ @conn << f.generate(ping_frame)
503
535
  end
504
536
 
505
537
  it 'should fire callback on PONG' do
506
- @conn << f.generate(SETTINGS.dup)
538
+ @conn << f.generate(settings_frame)
507
539
 
508
540
  pong = nil
509
541
  @conn.ping('12345678') { |d| pong = d }
510
- @conn << f.generate(PONG.dup)
542
+ @conn << f.generate(pong_frame)
511
543
  expect(pong).to eq '12345678'
512
544
  end
513
545
 
514
546
  it 'should fire callback on receipt of GOAWAY' do
515
547
  last_stream, payload, error = nil
516
- @conn << f.generate(SETTINGS.dup)
548
+ @conn << f.generate(settings_frame)
517
549
  @conn.on(:goaway) do |s, e, p|
518
550
  last_stream = s
519
551
  error = e
520
552
  payload = p
521
553
  end
522
- @conn << f.generate(GOAWAY.merge(last_stream: 17, payload: 'test'))
554
+ @conn << f.generate(goaway_frame.merge(last_stream: 17, payload: 'test'))
523
555
 
524
556
  expect(last_stream).to eq 17
525
557
  expect(error).to eq :no_error
@@ -536,8 +568,8 @@ RSpec.describe HTTP2::Connection do
536
568
  end
537
569
 
538
570
  it 'should raise error when opening new stream after receiving GOAWAY' do
539
- @conn << f.generate(SETTINGS.dup)
540
- @conn << f.generate(GOAWAY.dup)
571
+ @conn << f.generate(settings_frame)
572
+ @conn << f.generate(goaway_frame)
541
573
  expect { @conn.new_stream }.to raise_error(ConnectionClosed)
542
574
  end
543
575
 
@@ -545,26 +577,26 @@ RSpec.describe HTTP2::Connection do
545
577
  @conn.goaway
546
578
  expect(@conn).to be_closed
547
579
 
548
- expect { @conn << f.generate(SETTINGS.dup) }.not_to raise_error(ProtocolError)
549
- expect { @conn << f.generate(PING.dup) }.not_to raise_error(ProtocolError)
550
- expect { @conn << f.generate(GOAWAY.dup) }.not_to raise_error(ProtocolError)
580
+ expect { @conn << f.generate(settings_frame) }.not_to raise_error(ProtocolError)
581
+ expect { @conn << f.generate(ping_frame) }.not_to raise_error(ProtocolError)
582
+ expect { @conn << f.generate(goaway_frame) }.not_to raise_error(ProtocolError)
551
583
  end
552
584
 
553
585
  it 'should process connection management frames after GOAWAY' do
554
- @conn << f.generate(SETTINGS.dup)
555
- @conn << f.generate(HEADERS.dup)
556
- @conn << f.generate(GOAWAY.dup)
557
- @conn << f.generate(HEADERS.merge(stream: 7))
558
- @conn << f.generate(PUSH_PROMISE.dup)
586
+ @conn << f.generate(settings_frame)
587
+ @conn << f.generate(headers_frame)
588
+ @conn << f.generate(goaway_frame)
589
+ @conn << f.generate(headers_frame.merge(stream: 7))
590
+ @conn << f.generate(push_promise_frame)
559
591
 
560
592
  expect(@conn.active_stream_count).to eq 1
561
593
  end
562
594
 
563
595
  it 'should raise error on frame for invalid stream ID' do
564
- @conn << f.generate(SETTINGS.dup)
596
+ @conn << f.generate(settings_frame)
565
597
 
566
598
  expect do
567
- @conn << f.generate(DATA.dup.merge(stream: 31))
599
+ @conn << f.generate(data_frame.merge(stream: 31))
568
600
  end.to raise_error(ProtocolError)
569
601
  end
570
602
 
@@ -573,12 +605,12 @@ RSpec.describe HTTP2::Connection do
573
605
  srv << CONNECTION_PREFACE_MAGIC
574
606
 
575
607
  stream = srv.new_stream
576
- stream.send HEADERS.dup
577
- stream.send DATA.dup
608
+ stream.send headers_frame
609
+ stream.send data_frame
578
610
  stream.close
579
611
 
580
612
  expect do
581
- srv << f.generate(RST_STREAM.dup.merge(stream: stream.id))
613
+ srv << f.generate(rst_stream_frame.merge(stream: stream.id))
582
614
  end.to_not raise_error
583
615
  end
584
616
 
@@ -596,7 +628,7 @@ RSpec.describe HTTP2::Connection do
596
628
  [frame]
597
629
  end
598
630
 
599
- expect { @conn << f.generate(DATA.dup) }.to raise_error(ProtocolError)
631
+ expect { @conn << f.generate(data_frame) }.to raise_error(ProtocolError)
600
632
  end
601
633
  end
602
634
 
@@ -625,8 +657,8 @@ RSpec.describe HTTP2::Connection do
625
657
  end
626
658
 
627
659
  it '.goaway should generate GOAWAY frame with last processed stream ID' do
628
- @conn << f.generate(SETTINGS.dup)
629
- @conn << f.generate(HEADERS.merge(stream: 17))
660
+ @conn << f.generate(settings_frame)
661
+ @conn << f.generate(headers_frame.merge(stream: 17))
630
662
 
631
663
  expect(@conn).to receive(:send) do |frame|
632
664
  expect(frame[:type]).to eq :goaway