http-2 0.10.1 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +3 -0
- 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 +3 -0
- data/lib/http/2/connection.rb +22 -7
- 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 +95 -79
- data/spec/helper.rb +123 -107
- data/spec/server_spec.rb +2 -1
- data/spec/stream_spec.rb +112 -111
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8dd78ba6a3468b6a3ceda2ebabc37ceb895a90d675d9fecc035b74d2f479a457
|
4
|
+
data.tar.gz: d70542d69e57050ab9ee3c837b6123fea81424a03bf3b99ffdc889a54a2fd20c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f678316f6235ed93ce5a3902ef3bf4c48854605de0875054d1f3e05196fc161bdfe49d149ff930413bef5ca76c61ab2d193092867a383f1f5dbe309cfef0f012
|
7
|
+
data.tar.gz: 52fce71379a9299df1cd8c93d44964d668d09c199564482193513b109a6339f220b4ae1e724c5965892b3e53855820a810e95405b8eb198797ea130611d39a05
|
data/.rubocop.yml
CHANGED
data/.rubocop_todo.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
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.
|
@@ -683,13 +686,9 @@ module HTTP2
|
|
683
686
|
# Store a reference to the closed stream, such that we can respond
|
684
687
|
# to any in-flight frames while close is registered on both sides.
|
685
688
|
# 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
|
689
|
+
# is closed, with a defined RTT time window.
|
690
|
+
@streams_recently_closed[id] = Time.now.to_i
|
691
|
+
cleanup_recently_closed
|
693
692
|
end
|
694
693
|
|
695
694
|
stream.on(:promise, &method(:promise)) if self.is_a? Server
|
@@ -698,6 +697,22 @@ module HTTP2
|
|
698
697
|
@streams[id] = stream
|
699
698
|
end
|
700
699
|
|
700
|
+
# Purge recently streams closed within defined RTT time window.
|
701
|
+
def cleanup_recently_closed
|
702
|
+
now_ts = Time.now.to_i
|
703
|
+
to_delete = []
|
704
|
+
@streams_recently_closed.each do |stream_id, ts|
|
705
|
+
# Ruby Hash enumeration is ordered, so once fresh stream is met we can stop searching.
|
706
|
+
break if now_ts - ts < RECENTLY_CLOSED_STREAMS_TTL
|
707
|
+
to_delete << stream_id
|
708
|
+
end
|
709
|
+
|
710
|
+
to_delete.each do |stream_id|
|
711
|
+
@streams.delete stream_id
|
712
|
+
@streams_recently_closed.delete stream_id
|
713
|
+
end
|
714
|
+
end
|
715
|
+
|
701
716
|
# Emit GOAWAY error indicating to peer that the connection is being
|
702
717
|
# aborted, and once sent, raise a local exception.
|
703
718
|
#
|
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)
|
@@ -50,7 +51,7 @@ RSpec.describe HTTP2::Connection do
|
|
50
51
|
end
|
51
52
|
|
52
53
|
it 'should send SETTINGS ACK when SETTINGS is received' do
|
53
|
-
settings =
|
54
|
+
settings = settings_frame
|
54
55
|
settings[:payload] = [[:settings_header_table_size, 256]]
|
55
56
|
|
56
57
|
# We should expect two frames here (append .twice) - one for the connection setup, and one for the settings ack.
|
@@ -75,34 +76,34 @@ RSpec.describe HTTP2::Connection do
|
|
75
76
|
end
|
76
77
|
|
77
78
|
it 'should change stream limit to received SETTINGS value' do
|
78
|
-
@conn << f.generate(
|
79
|
+
@conn << f.generate(settings_frame)
|
79
80
|
expect(@conn.remote_settings[:settings_max_concurrent_streams]).to eq 10
|
80
81
|
end
|
81
82
|
|
82
83
|
it 'should count open streams against stream limit' do
|
83
84
|
s = @conn.new_stream
|
84
85
|
expect(@conn.active_stream_count).to eq 0
|
85
|
-
s.receive
|
86
|
+
s.receive headers_frame
|
86
87
|
expect(@conn.active_stream_count).to eq 1
|
87
88
|
end
|
88
89
|
|
89
90
|
it 'should not count reserved streams against stream limit' do
|
90
91
|
s1 = @conn.new_stream
|
91
|
-
s1.receive
|
92
|
+
s1.receive push_promise_frame
|
92
93
|
expect(@conn.active_stream_count).to eq 0
|
93
94
|
|
94
95
|
s2 = @conn.new_stream
|
95
|
-
s2.send
|
96
|
+
s2.send push_promise_frame
|
96
97
|
expect(@conn.active_stream_count).to eq 0
|
97
98
|
|
98
99
|
# transition to half closed
|
99
|
-
s1.receive
|
100
|
-
s2.send
|
100
|
+
s1.receive headers_frame
|
101
|
+
s2.send headers_frame
|
101
102
|
expect(@conn.active_stream_count).to eq 2
|
102
103
|
|
103
104
|
# transition to closed
|
104
|
-
s1.receive
|
105
|
-
s2.send
|
105
|
+
s1.receive data_frame
|
106
|
+
s2.send data_frame
|
106
107
|
expect(@conn.active_stream_count).to eq 0
|
107
108
|
|
108
109
|
expect(s1).to be_closed
|
@@ -110,12 +111,12 @@ RSpec.describe HTTP2::Connection do
|
|
110
111
|
end
|
111
112
|
|
112
113
|
it 'should not exceed stream limit set by peer' do
|
113
|
-
@conn << f.generate(
|
114
|
+
@conn << f.generate(settings_frame)
|
114
115
|
|
115
116
|
expect do
|
116
117
|
10.times do
|
117
118
|
s = @conn.new_stream
|
118
|
-
s.send
|
119
|
+
s.send headers_frame
|
119
120
|
end
|
120
121
|
end.to_not raise_error
|
121
122
|
|
@@ -123,9 +124,9 @@ RSpec.describe HTTP2::Connection do
|
|
123
124
|
end
|
124
125
|
|
125
126
|
it 'should initialize stream with HEADERS priority value' do
|
126
|
-
@conn << f.generate(
|
127
|
+
@conn << f.generate(settings_frame)
|
127
128
|
|
128
|
-
stream, headers = nil,
|
129
|
+
stream, headers = nil, headers_frame
|
129
130
|
headers[:weight] = 20
|
130
131
|
headers[:stream_dependency] = 0
|
131
132
|
headers[:exclusive] = false
|
@@ -137,16 +138,33 @@ RSpec.describe HTTP2::Connection do
|
|
137
138
|
end
|
138
139
|
|
139
140
|
it 'should initialize idle stream on PRIORITY frame' do
|
140
|
-
@conn << f.generate(
|
141
|
+
@conn << f.generate(settings_frame)
|
141
142
|
|
142
143
|
stream = nil
|
143
144
|
@conn.on(:stream) { |s| stream = s }
|
144
|
-
@conn << f.generate(
|
145
|
+
@conn << f.generate(priority_frame)
|
145
146
|
|
146
147
|
expect(stream.state).to eq :idle
|
147
148
|
end
|
148
149
|
end
|
149
150
|
|
151
|
+
context 'cleanup_recently_closed' do
|
152
|
+
it 'should cleanup old connections' do
|
153
|
+
now_ts = Time.now.to_i
|
154
|
+
stream_ids = Array.new(4) { @conn.new_stream.id }
|
155
|
+
expect(@conn.instance_variable_get('@streams').size).to eq(4)
|
156
|
+
|
157
|
+
# Assume that the first 3 streams were closed in different time
|
158
|
+
recently_closed = stream_ids[0, 3].zip([now_ts - 100, now_ts - 50, now_ts - 5]).to_h
|
159
|
+
@conn.instance_variable_set('@streams_recently_closed', recently_closed)
|
160
|
+
|
161
|
+
# Cleanup should delete streams that were closed earlier than 15s ago
|
162
|
+
@conn.__send__(:cleanup_recently_closed)
|
163
|
+
expect(@conn.instance_variable_get('@streams').size).to eq(2)
|
164
|
+
expect(@conn.instance_variable_get('@streams_recently_closed')).to eq(stream_ids[2] => now_ts - 5)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
150
168
|
context 'Headers pre/post processing' do
|
151
169
|
it 'should not concatenate multiple occurences of a header field with the same name' do
|
152
170
|
input = [
|
@@ -173,16 +191,14 @@ RSpec.describe HTTP2::Connection do
|
|
173
191
|
end
|
174
192
|
|
175
193
|
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
|
-
]
|
194
|
+
input = [*RESPONSE_HEADERS,
|
195
|
+
['cache-control', "max-age=60, private\0must-revalidate"],
|
196
|
+
['content-type', 'text/html'],
|
197
|
+
['cookie', "a=b\0c=d; e=f"]]
|
198
|
+
expected = [*RESPONSE_HEADERS,
|
199
|
+
['cache-control', "max-age=60, private\0must-revalidate"],
|
200
|
+
['content-type', 'text/html'],
|
201
|
+
['cookie', "a=b\0c=d; e=f"]]
|
186
202
|
|
187
203
|
result = nil
|
188
204
|
@conn.on(:stream) do |stream|
|
@@ -204,13 +220,13 @@ RSpec.describe HTTP2::Connection do
|
|
204
220
|
end
|
205
221
|
|
206
222
|
it 'should update connection and stream windows on SETTINGS' do
|
207
|
-
settings, data =
|
223
|
+
settings, data = settings_frame, data_frame
|
208
224
|
settings[:payload] = [[:settings_initial_window_size, 1024]]
|
209
225
|
data[:payload] = 'x' * 2048
|
210
226
|
|
211
227
|
stream = @conn.new_stream
|
212
228
|
|
213
|
-
stream.send
|
229
|
+
stream.send headers_frame
|
214
230
|
stream.send data
|
215
231
|
expect(stream.remote_window).to eq(DEFAULT_FLOW_WINDOW - 2048)
|
216
232
|
expect(@conn.remote_window).to eq(DEFAULT_FLOW_WINDOW - 2048)
|
@@ -221,7 +237,7 @@ RSpec.describe HTTP2::Connection do
|
|
221
237
|
end
|
222
238
|
|
223
239
|
it 'should initialize streams with window specified by peer' do
|
224
|
-
settings =
|
240
|
+
settings = settings_frame
|
225
241
|
settings[:payload] = [[:settings_initial_window_size, 1024]]
|
226
242
|
|
227
243
|
@conn << f.generate(settings)
|
@@ -229,37 +245,37 @@ RSpec.describe HTTP2::Connection do
|
|
229
245
|
end
|
230
246
|
|
231
247
|
it 'should observe connection flow control' do
|
232
|
-
settings, data =
|
248
|
+
settings, data = settings_frame, data_frame
|
233
249
|
settings[:payload] = [[:settings_initial_window_size, 1000]]
|
234
250
|
|
235
251
|
@conn << f.generate(settings)
|
236
252
|
s1 = @conn.new_stream
|
237
253
|
s2 = @conn.new_stream
|
238
254
|
|
239
|
-
s1.send
|
255
|
+
s1.send headers_frame
|
240
256
|
s1.send data.merge(payload: 'x' * 900)
|
241
257
|
expect(@conn.remote_window).to eq 100
|
242
258
|
|
243
|
-
s2.send
|
259
|
+
s2.send headers_frame
|
244
260
|
s2.send data.merge(payload: 'x' * 200)
|
245
261
|
expect(@conn.remote_window).to eq 0
|
246
262
|
expect(@conn.buffered_amount).to eq 100
|
247
263
|
|
248
|
-
@conn << f.generate(
|
264
|
+
@conn << f.generate(window_update_frame.merge(stream: 0, increment: 1000))
|
249
265
|
expect(@conn.buffered_amount).to eq 0
|
250
266
|
expect(@conn.remote_window).to eq 900
|
251
267
|
end
|
252
268
|
|
253
269
|
it 'should update window when data received is over half of the maximum local window size' do
|
254
|
-
settings, data =
|
270
|
+
settings, data = settings_frame, data_frame
|
255
271
|
conn = Client.new(settings_initial_window_size: 500)
|
256
272
|
|
257
273
|
conn.receive f.generate(settings)
|
258
274
|
s1 = conn.new_stream
|
259
275
|
s2 = conn.new_stream
|
260
276
|
|
261
|
-
s1.send
|
262
|
-
s2.send
|
277
|
+
s1.send headers_frame
|
278
|
+
s2.send headers_frame
|
263
279
|
expect(conn).to receive(:send) do |frame|
|
264
280
|
expect(frame[:type]).to eq :window_update
|
265
281
|
expect(frame[:stream]).to eq 0
|
@@ -275,11 +291,11 @@ RSpec.describe HTTP2::Connection do
|
|
275
291
|
|
276
292
|
context 'framing' do
|
277
293
|
it 'should buffer incomplete frames' do
|
278
|
-
settings =
|
294
|
+
settings = settings_frame
|
279
295
|
settings[:payload] = [[:settings_initial_window_size, 1000]]
|
280
296
|
@conn << f.generate(settings)
|
281
297
|
|
282
|
-
frame = f.generate(
|
298
|
+
frame = f.generate(window_update_frame.merge(stream: 0, increment: 1000))
|
283
299
|
@conn << frame
|
284
300
|
expect(@conn.remote_window).to eq 2000
|
285
301
|
|
@@ -295,10 +311,10 @@ RSpec.describe HTTP2::Connection do
|
|
295
311
|
]
|
296
312
|
|
297
313
|
cc = Compressor.new
|
298
|
-
headers =
|
314
|
+
headers = headers_frame
|
299
315
|
headers[:payload] = cc.encode(req_headers)
|
300
316
|
|
301
|
-
@conn << f.generate(
|
317
|
+
@conn << f.generate(settings_frame)
|
302
318
|
@conn.on(:stream) do |stream|
|
303
319
|
expect(stream).to receive(:<<) do |frame|
|
304
320
|
expect(frame[:payload]).to eq req_headers
|
@@ -315,7 +331,7 @@ RSpec.describe HTTP2::Connection do
|
|
315
331
|
]
|
316
332
|
|
317
333
|
cc = Compressor.new
|
318
|
-
h1, h2 =
|
334
|
+
h1, h2 = headers_frame, continuation_frame
|
319
335
|
|
320
336
|
# Header block fragment might not complete for decompression
|
321
337
|
payload = cc.encode(req_headers)
|
@@ -326,7 +342,7 @@ RSpec.describe HTTP2::Connection do
|
|
326
342
|
h2[:payload] = payload # the remaining
|
327
343
|
h2[:stream] = 5
|
328
344
|
|
329
|
-
@conn << f.generate(
|
345
|
+
@conn << f.generate(settings_frame)
|
330
346
|
@conn.on(:stream) do |stream|
|
331
347
|
expect(stream).to receive(:<<) do |frame|
|
332
348
|
expect(frame[:payload]).to eq req_headers
|
@@ -338,18 +354,18 @@ RSpec.describe HTTP2::Connection do
|
|
338
354
|
end
|
339
355
|
|
340
356
|
it 'should require that split header blocks are a contiguous sequence' do
|
341
|
-
headers =
|
357
|
+
headers = headers_frame
|
342
358
|
headers[:flags] = []
|
343
359
|
|
344
|
-
@conn << f.generate(
|
360
|
+
@conn << f.generate(settings_frame)
|
345
361
|
@conn << f.generate(headers)
|
346
|
-
(
|
362
|
+
(frame_types - [continuation_frame]).each do |frame|
|
347
363
|
expect { @conn << f.generate(frame.deep_dup) }.to raise_error(ProtocolError)
|
348
364
|
end
|
349
365
|
end
|
350
366
|
|
351
367
|
it 'should raise compression error on encode of invalid frame' do
|
352
|
-
@conn << f.generate(
|
368
|
+
@conn << f.generate(settings_frame)
|
353
369
|
stream = @conn.new_stream
|
354
370
|
|
355
371
|
expect do
|
@@ -358,8 +374,8 @@ RSpec.describe HTTP2::Connection do
|
|
358
374
|
end
|
359
375
|
|
360
376
|
it 'should raise connection error on decode of invalid frame' do
|
361
|
-
@conn << f.generate(
|
362
|
-
frame = f.generate(
|
377
|
+
@conn << f.generate(settings_frame)
|
378
|
+
frame = f.generate(data_frame) # Receiving DATA on unopened stream 1 is an error.
|
363
379
|
# Connection errors emit protocol error frames
|
364
380
|
expect { @conn << frame }.to raise_error(ProtocolError)
|
365
381
|
end
|
@@ -370,7 +386,7 @@ RSpec.describe HTTP2::Connection do
|
|
370
386
|
@conn.settings(settings_max_concurrent_streams: 10,
|
371
387
|
settings_initial_window_size: 0x7fffffff)
|
372
388
|
|
373
|
-
expect(bytes).to eq f.generate(
|
389
|
+
expect(bytes).to eq f.generate(settings_frame)
|
374
390
|
end
|
375
391
|
|
376
392
|
it 'should compress stream headers' do
|
@@ -482,44 +498,44 @@ RSpec.describe HTTP2::Connection do
|
|
482
498
|
context 'connection management' do
|
483
499
|
it 'should raise error on invalid connection header' do
|
484
500
|
srv = Server.new
|
485
|
-
expect { srv << f.generate(
|
501
|
+
expect { srv << f.generate(settings_frame) }.to raise_error(HandshakeError)
|
486
502
|
|
487
503
|
srv = Server.new
|
488
504
|
expect do
|
489
505
|
srv << CONNECTION_PREFACE_MAGIC
|
490
|
-
srv << f.generate(
|
506
|
+
srv << f.generate(settings_frame)
|
491
507
|
end.to_not raise_error
|
492
508
|
end
|
493
509
|
|
494
510
|
it 'should respond to PING frames' do
|
495
|
-
@conn << f.generate(
|
511
|
+
@conn << f.generate(settings_frame)
|
496
512
|
expect(@conn).to receive(:send) do |frame|
|
497
513
|
expect(frame[:type]).to eq :ping
|
498
514
|
expect(frame[:flags]).to eq [:ack]
|
499
515
|
expect(frame[:payload]).to eq '12345678'
|
500
516
|
end
|
501
517
|
|
502
|
-
@conn << f.generate(
|
518
|
+
@conn << f.generate(ping_frame)
|
503
519
|
end
|
504
520
|
|
505
521
|
it 'should fire callback on PONG' do
|
506
|
-
@conn << f.generate(
|
522
|
+
@conn << f.generate(settings_frame)
|
507
523
|
|
508
524
|
pong = nil
|
509
525
|
@conn.ping('12345678') { |d| pong = d }
|
510
|
-
@conn << f.generate(
|
526
|
+
@conn << f.generate(pong_frame)
|
511
527
|
expect(pong).to eq '12345678'
|
512
528
|
end
|
513
529
|
|
514
530
|
it 'should fire callback on receipt of GOAWAY' do
|
515
531
|
last_stream, payload, error = nil
|
516
|
-
@conn << f.generate(
|
532
|
+
@conn << f.generate(settings_frame)
|
517
533
|
@conn.on(:goaway) do |s, e, p|
|
518
534
|
last_stream = s
|
519
535
|
error = e
|
520
536
|
payload = p
|
521
537
|
end
|
522
|
-
@conn << f.generate(
|
538
|
+
@conn << f.generate(goaway_frame.merge(last_stream: 17, payload: 'test'))
|
523
539
|
|
524
540
|
expect(last_stream).to eq 17
|
525
541
|
expect(error).to eq :no_error
|
@@ -536,8 +552,8 @@ RSpec.describe HTTP2::Connection do
|
|
536
552
|
end
|
537
553
|
|
538
554
|
it 'should raise error when opening new stream after receiving GOAWAY' do
|
539
|
-
@conn << f.generate(
|
540
|
-
@conn << f.generate(
|
555
|
+
@conn << f.generate(settings_frame)
|
556
|
+
@conn << f.generate(goaway_frame)
|
541
557
|
expect { @conn.new_stream }.to raise_error(ConnectionClosed)
|
542
558
|
end
|
543
559
|
|
@@ -545,26 +561,26 @@ RSpec.describe HTTP2::Connection do
|
|
545
561
|
@conn.goaway
|
546
562
|
expect(@conn).to be_closed
|
547
563
|
|
548
|
-
expect { @conn << f.generate(
|
549
|
-
expect { @conn << f.generate(
|
550
|
-
expect { @conn << f.generate(
|
564
|
+
expect { @conn << f.generate(settings_frame) }.not_to raise_error(ProtocolError)
|
565
|
+
expect { @conn << f.generate(ping_frame) }.not_to raise_error(ProtocolError)
|
566
|
+
expect { @conn << f.generate(goaway_frame) }.not_to raise_error(ProtocolError)
|
551
567
|
end
|
552
568
|
|
553
569
|
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(
|
570
|
+
@conn << f.generate(settings_frame)
|
571
|
+
@conn << f.generate(headers_frame)
|
572
|
+
@conn << f.generate(goaway_frame)
|
573
|
+
@conn << f.generate(headers_frame.merge(stream: 7))
|
574
|
+
@conn << f.generate(push_promise_frame)
|
559
575
|
|
560
576
|
expect(@conn.active_stream_count).to eq 1
|
561
577
|
end
|
562
578
|
|
563
579
|
it 'should raise error on frame for invalid stream ID' do
|
564
|
-
@conn << f.generate(
|
580
|
+
@conn << f.generate(settings_frame)
|
565
581
|
|
566
582
|
expect do
|
567
|
-
@conn << f.generate(
|
583
|
+
@conn << f.generate(data_frame.merge(stream: 31))
|
568
584
|
end.to raise_error(ProtocolError)
|
569
585
|
end
|
570
586
|
|
@@ -573,12 +589,12 @@ RSpec.describe HTTP2::Connection do
|
|
573
589
|
srv << CONNECTION_PREFACE_MAGIC
|
574
590
|
|
575
591
|
stream = srv.new_stream
|
576
|
-
stream.send
|
577
|
-
stream.send
|
592
|
+
stream.send headers_frame
|
593
|
+
stream.send data_frame
|
578
594
|
stream.close
|
579
595
|
|
580
596
|
expect do
|
581
|
-
srv << f.generate(
|
597
|
+
srv << f.generate(rst_stream_frame.merge(stream: stream.id))
|
582
598
|
end.to_not raise_error
|
583
599
|
end
|
584
600
|
|
@@ -596,7 +612,7 @@ RSpec.describe HTTP2::Connection do
|
|
596
612
|
[frame]
|
597
613
|
end
|
598
614
|
|
599
|
-
expect { @conn << f.generate(
|
615
|
+
expect { @conn << f.generate(data_frame) }.to raise_error(ProtocolError)
|
600
616
|
end
|
601
617
|
end
|
602
618
|
|
@@ -625,8 +641,8 @@ RSpec.describe HTTP2::Connection do
|
|
625
641
|
end
|
626
642
|
|
627
643
|
it '.goaway should generate GOAWAY frame with last processed stream ID' do
|
628
|
-
@conn << f.generate(
|
629
|
-
@conn << f.generate(
|
644
|
+
@conn << f.generate(settings_frame)
|
645
|
+
@conn << f.generate(headers_frame.merge(stream: 17))
|
630
646
|
|
631
647
|
expect(@conn).to receive(:send) do |frame|
|
632
648
|
expect(frame[:type]).to eq :goaway
|