http-2 0.10.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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