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 +5 -5
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +12 -0
- data/.travis.yml +6 -3
- data/LICENSE +21 -0
- data/example/upgrade_client.rb +10 -10
- data/example/upgrade_server.rb +5 -5
- data/http-2.gemspec +1 -1
- data/lib/http/2/compressor.rb +5 -2
- data/lib/http/2/connection.rb +28 -12
- data/lib/http/2/version.rb +1 -1
- data/spec/client_spec.rb +21 -20
- data/spec/compressor_spec.rb +6 -0
- data/spec/connection_spec.rb +116 -84
- data/spec/helper.rb +123 -107
- data/spec/server_spec.rb +3 -2
- data/spec/stream_spec.rb +112 -111
- metadata +11 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5617138c0740b55546b79e29a06978a9fcb9aae208ccb2f695c32939eca5f88a
|
4
|
+
data.tar.gz: 193880583c0a80aa569f433977af1798c98be25a86fb782a410e16eb38d5116c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 46fde2633ab7d074136a32e7ae8cdf90ecd037f3a95bfeabab9557d72a13487a47a3e298339bc74df4d89c6b802537b9c35a1f7709fd6cf1f4fde44eecbe468e
|
7
|
+
data.tar.gz: 6eadeb79f3bbf6fa1911933e2dc399bfe50ae08c598cb72278a37e0c42102bf27ccbed77b7acb5e7f1e85a55bd6967e63a8950fa3184fb7c9eef750c8a9e123c
|
data/.rubocop.yml
CHANGED
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
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.
|
data/example/upgrade_client.rb
CHANGED
@@ -34,16 +34,16 @@ end
|
|
34
34
|
|
35
35
|
# upgrader module
|
36
36
|
class UpgradeHandler
|
37
|
-
UPGRADE_REQUEST =
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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)
|
data/example/upgrade_server.rb
CHANGED
@@ -39,12 +39,12 @@ end
|
|
39
39
|
|
40
40
|
class UpgradeHandler
|
41
41
|
VALID_UPGRADE_METHODS = %w(GET OPTIONS).freeze
|
42
|
-
UPGRADE_RESPONSE =
|
43
|
-
|
44
|
-
|
45
|
-
|
42
|
+
UPGRADE_RESPONSE = <<RESP.freeze
|
43
|
+
HTTP/1.1 101 Switching Protocols
|
44
|
+
Connection: Upgrade
|
45
|
+
Upgrade: h2c
|
46
46
|
|
47
|
-
|
47
|
+
RESP
|
48
48
|
|
49
49
|
attr_reader :complete, :headers, :body, :parsing
|
50
50
|
|
data/http-2.gemspec
CHANGED
data/lib/http/2/compressor.rb
CHANGED
@@ -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
|
data/lib/http/2/connection.rb
CHANGED
@@ -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
|
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
|
-
#
|
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
|
687
|
-
@streams_recently_closed[id] = Time.now
|
688
|
-
|
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
|
#
|
data/lib/http/2/version.rb
CHANGED
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(
|
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(
|
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(
|
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
|
89
|
+
s.send headers_frame
|
89
90
|
|
90
|
-
@client << set_stream_id(f.generate(
|
91
|
-
@client << set_stream_id(f.generate(
|
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
|
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(
|
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
|
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(
|
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
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|
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(
|
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
|
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(
|
182
|
+
@client << set_stream_id(f.generate(altsvc_frame), s.id)
|
182
183
|
|
183
184
|
expect(frame).to be_nil
|
184
185
|
end
|
data/spec/compressor_spec.rb
CHANGED
@@ -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
|
data/spec/connection_spec.rb
CHANGED
@@ -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
|
-
|
12
|
-
|
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(
|
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(
|
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 =
|
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 =
|
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(
|
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
|
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
|
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
|
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
|
100
|
-
s2.send
|
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
|
105
|
-
s2.send
|
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(
|
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
|
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(
|
143
|
+
@conn << f.generate(settings_frame)
|
127
144
|
|
128
|
-
stream, headers = nil,
|
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(
|
157
|
+
@conn << f.generate(settings_frame)
|
141
158
|
|
142
159
|
stream = nil
|
143
160
|
@conn.on(:stream) { |s| stream = s }
|
144
|
-
@conn << f.generate(
|
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
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
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 =
|
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
|
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 =
|
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 =
|
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
|
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
|
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(
|
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 =
|
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
|
262
|
-
s2.send
|
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 =
|
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(
|
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 =
|
330
|
+
headers = headers_frame
|
299
331
|
headers[:payload] = cc.encode(req_headers)
|
300
332
|
|
301
|
-
@conn << f.generate(
|
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 =
|
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(
|
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 =
|
373
|
+
headers = headers_frame
|
342
374
|
headers[:flags] = []
|
343
375
|
|
344
|
-
@conn << f.generate(
|
376
|
+
@conn << f.generate(settings_frame)
|
345
377
|
@conn << f.generate(headers)
|
346
|
-
(
|
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(
|
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(
|
362
|
-
frame = f.generate(
|
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(
|
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
|
-
|
386
|
-
|
387
|
-
|
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(
|
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(
|
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(
|
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(
|
534
|
+
@conn << f.generate(ping_frame)
|
503
535
|
end
|
504
536
|
|
505
537
|
it 'should fire callback on PONG' do
|
506
|
-
@conn << f.generate(
|
538
|
+
@conn << f.generate(settings_frame)
|
507
539
|
|
508
540
|
pong = nil
|
509
541
|
@conn.ping('12345678') { |d| pong = d }
|
510
|
-
@conn << f.generate(
|
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(
|
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(
|
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(
|
540
|
-
@conn << f.generate(
|
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(
|
549
|
-
expect { @conn << f.generate(
|
550
|
-
expect { @conn << f.generate(
|
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(
|
555
|
-
@conn << f.generate(
|
556
|
-
@conn << f.generate(
|
557
|
-
@conn << f.generate(
|
558
|
-
@conn << f.generate(
|
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(
|
596
|
+
@conn << f.generate(settings_frame)
|
565
597
|
|
566
598
|
expect do
|
567
|
-
@conn << f.generate(
|
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
|
577
|
-
stream.send
|
608
|
+
stream.send headers_frame
|
609
|
+
stream.send data_frame
|
578
610
|
stream.close
|
579
611
|
|
580
612
|
expect do
|
581
|
-
srv << f.generate(
|
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(
|
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(
|
629
|
-
@conn << f.generate(
|
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
|