http-2 0.7.0 → 0.8.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 +4 -4
- data/.rspec +1 -0
- data/.rubocop.yml +18 -0
- data/.rubocop_todo.yml +46 -0
- data/.travis.yml +10 -2
- data/Gemfile +7 -5
- data/README.md +35 -37
- data/Rakefile +9 -10
- data/example/README.md +0 -13
- data/example/client.rb +12 -15
- data/example/helper.rb +2 -2
- data/example/server.rb +19 -19
- data/example/upgrade_server.rb +191 -0
- data/http-2.gemspec +10 -9
- data/lib/http/2.rb +13 -13
- data/lib/http/2/buffer.rb +6 -10
- data/lib/http/2/client.rb +5 -10
- data/lib/http/2/compressor.rb +134 -146
- data/lib/http/2/connection.rb +104 -100
- data/lib/http/2/emitter.rb +2 -4
- data/lib/http/2/error.rb +7 -7
- data/lib/http/2/flow_buffer.rb +11 -10
- data/lib/http/2/framer.rb +78 -87
- data/lib/http/2/huffman.rb +265 -274
- data/lib/http/2/huffman_statemachine.rb +257 -257
- data/lib/http/2/server.rb +81 -6
- data/lib/http/2/stream.rb +195 -130
- data/lib/http/2/version.rb +1 -1
- data/lib/tasks/generate_huffman_table.rb +30 -24
- data/spec/buffer_spec.rb +11 -13
- data/spec/client_spec.rb +41 -42
- data/spec/compressor_spec.rb +243 -242
- data/spec/connection_spec.rb +252 -248
- data/spec/emitter_spec.rb +12 -12
- data/spec/framer_spec.rb +177 -179
- data/spec/helper.rb +56 -57
- data/spec/huffman_spec.rb +33 -33
- data/spec/server_spec.rb +15 -15
- data/spec/stream_spec.rb +356 -265
- metadata +7 -6
- data/spec/hpack_test_spec.rb +0 -83
data/spec/helper.rb
CHANGED
@@ -1,22 +1,11 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
RSpec.configure do |config|
|
5
|
-
config.expect_with :rspec do |c|
|
6
|
-
c.syntax = [:should, :expect]
|
7
|
-
end
|
8
|
-
config.mock_with :rspec do |c|
|
9
|
-
c.syntax = [:should, :expect]
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
rescue Exception
|
14
|
-
end
|
1
|
+
require 'active_support/core_ext/object/deep_dup'
|
2
|
+
|
3
|
+
RSpec.configure(&:disable_monkey_patching!)
|
15
4
|
|
16
5
|
require 'json'
|
17
6
|
require 'coveralls'
|
18
7
|
|
19
|
-
Coveralls.wear! if ENV[
|
8
|
+
Coveralls.wear! if ENV['CI']
|
20
9
|
|
21
10
|
require 'http/2'
|
22
11
|
|
@@ -26,24 +15,24 @@ include HTTP2::Error
|
|
26
15
|
|
27
16
|
DATA = {
|
28
17
|
type: :data,
|
29
|
-
flags: [:end_stream],
|
18
|
+
flags: [:end_stream].freeze,
|
30
19
|
stream: 1,
|
31
|
-
payload: 'text'
|
32
|
-
}
|
20
|
+
payload: 'text'.freeze,
|
21
|
+
}.freeze
|
33
22
|
|
34
23
|
HEADERS = {
|
35
24
|
type: :headers,
|
36
|
-
flags: [:end_headers],
|
25
|
+
flags: [:end_headers].freeze,
|
37
26
|
stream: 1,
|
38
|
-
payload: Compressor.new.encode([
|
39
|
-
}
|
27
|
+
payload: Compressor.new.encode([%w(a b)]).freeze,
|
28
|
+
}.freeze
|
40
29
|
|
41
30
|
HEADERS_END_STREAM = {
|
42
31
|
type: :headers,
|
43
|
-
flags: [:end_headers, :end_stream],
|
32
|
+
flags: [:end_headers, :end_stream].freeze,
|
44
33
|
stream: 1,
|
45
|
-
payload: Compressor.new.encode([
|
46
|
-
}
|
34
|
+
payload: Compressor.new.encode([%w(a b)]).freeze,
|
35
|
+
}.freeze
|
47
36
|
|
48
37
|
PRIORITY = {
|
49
38
|
type: :priority,
|
@@ -51,79 +40,89 @@ PRIORITY = {
|
|
51
40
|
exclusive: false,
|
52
41
|
stream_dependency: 0,
|
53
42
|
weight: 20,
|
54
|
-
}
|
43
|
+
}.freeze
|
55
44
|
|
56
45
|
RST_STREAM = {
|
57
46
|
type: :rst_stream,
|
58
47
|
stream: 1,
|
59
|
-
error: :stream_closed
|
60
|
-
}
|
48
|
+
error: :stream_closed,
|
49
|
+
}.freeze
|
61
50
|
|
62
51
|
SETTINGS = {
|
63
52
|
type: :settings,
|
64
53
|
stream: 0,
|
65
54
|
payload: [
|
66
|
-
[:settings_max_concurrent_streams, 10],
|
67
|
-
[:settings_initial_window_size, 0x7fffffff],
|
68
|
-
]
|
69
|
-
}
|
55
|
+
[:settings_max_concurrent_streams, 10].freeze,
|
56
|
+
[:settings_initial_window_size, 0x7fffffff].freeze,
|
57
|
+
].freeze,
|
58
|
+
}.freeze
|
70
59
|
|
71
60
|
PUSH_PROMISE = {
|
72
61
|
type: :push_promise,
|
73
|
-
flags: [:end_headers],
|
62
|
+
flags: [:end_headers].freeze,
|
74
63
|
stream: 1,
|
75
64
|
promise_stream: 2,
|
76
|
-
payload: Compressor.new.encode([
|
77
|
-
}
|
65
|
+
payload: Compressor.new.encode([%w(a b)]).freeze,
|
66
|
+
}.freeze
|
78
67
|
|
79
68
|
PING = {
|
80
69
|
stream: 0,
|
81
70
|
type: :ping,
|
82
|
-
payload: '12345678'
|
83
|
-
}
|
71
|
+
payload: '12345678'.freeze,
|
72
|
+
}.freeze
|
84
73
|
|
85
74
|
PONG = {
|
86
75
|
stream: 0,
|
87
76
|
type: :ping,
|
88
|
-
flags: [:ack],
|
89
|
-
payload: '12345678'
|
90
|
-
}
|
77
|
+
flags: [:ack].freeze,
|
78
|
+
payload: '12345678'.freeze,
|
79
|
+
}.freeze
|
91
80
|
|
92
81
|
GOAWAY = {
|
93
82
|
type: :goaway,
|
94
83
|
last_stream: 2,
|
95
84
|
error: :no_error,
|
96
|
-
payload: 'debug'
|
97
|
-
}
|
85
|
+
payload: 'debug'.freeze,
|
86
|
+
}.freeze
|
98
87
|
|
99
88
|
WINDOW_UPDATE = {
|
100
89
|
type: :window_update,
|
101
|
-
increment: 10
|
102
|
-
}
|
90
|
+
increment: 10,
|
91
|
+
}.freeze
|
103
92
|
|
104
93
|
CONTINUATION = {
|
105
94
|
type: :continuation,
|
106
|
-
flags: [:end_headers],
|
107
|
-
payload: '-second-block'
|
108
|
-
}
|
95
|
+
flags: [:end_headers].freeze,
|
96
|
+
payload: '-second-block'.freeze,
|
97
|
+
}.freeze
|
109
98
|
|
110
99
|
ALTSVC = {
|
111
100
|
type: :altsvc,
|
112
|
-
max_age:
|
113
|
-
port: 8080,
|
114
|
-
proto: 'h2-12',
|
115
|
-
host: 'www.example.com',
|
116
|
-
origin: 'www.example.com',
|
117
|
-
}
|
101
|
+
max_age: 1_402_290_402, # 4
|
102
|
+
port: 8080, # 2 reserved 1
|
103
|
+
proto: 'h2-12'.freeze, # 1 + 5
|
104
|
+
host: 'www.example.com'.freeze, # 1 + 15
|
105
|
+
origin: 'www.example.com'.freeze, # 15
|
106
|
+
}.freeze
|
118
107
|
|
119
108
|
FRAME_TYPES = [
|
120
|
-
DATA,
|
121
|
-
|
122
|
-
|
109
|
+
DATA,
|
110
|
+
HEADERS,
|
111
|
+
PRIORITY,
|
112
|
+
RST_STREAM,
|
113
|
+
SETTINGS,
|
114
|
+
PUSH_PROMISE,
|
115
|
+
PING,
|
116
|
+
GOAWAY,
|
117
|
+
WINDOW_UPDATE,
|
118
|
+
CONTINUATION,
|
119
|
+
ALTSVC,
|
120
|
+
].freeze
|
123
121
|
|
124
122
|
def set_stream_id(bytes, id)
|
125
|
-
|
123
|
+
scheme = 'CnCCN'.freeze
|
124
|
+
head = bytes.slice!(0, 9).unpack(scheme)
|
126
125
|
head[4] = id
|
127
126
|
|
128
|
-
head.pack(
|
127
|
+
head.pack(scheme) + bytes
|
129
128
|
end
|
data/spec/huffman_spec.rb
CHANGED
@@ -1,67 +1,67 @@
|
|
1
|
-
require
|
1
|
+
require 'helper'
|
2
2
|
|
3
|
-
describe HTTP2::Header::Huffman do
|
4
|
-
huffman_examples = [# plain, encoded
|
5
|
-
[
|
6
|
-
[
|
7
|
-
[
|
3
|
+
RSpec.describe HTTP2::Header::Huffman do
|
4
|
+
huffman_examples = [ # plain, encoded
|
5
|
+
['www.example.com', 'f1e3c2e5f23a6ba0ab90f4ff'],
|
6
|
+
['no-cache', 'a8eb10649cbf'],
|
7
|
+
['Mon, 21 Oct 2013 20:13:21 GMT', 'd07abe941054d444a8200595040b8166e082a62d1bff'],
|
8
8
|
]
|
9
|
-
context
|
9
|
+
context 'encode' do
|
10
10
|
before(:all) { @encoder = HTTP2::Header::Huffman.new }
|
11
11
|
huffman_examples.each do |plain, encoded|
|
12
12
|
it "should encode #{plain} into #{encoded}" do
|
13
|
-
@encoder.encode(plain).unpack(
|
13
|
+
expect(@encoder.encode(plain).unpack('H*').first).to eq encoded
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
17
|
-
context
|
17
|
+
context 'decode' do
|
18
18
|
before(:all) { @encoder = HTTP2::Header::Huffman.new }
|
19
19
|
huffman_examples.each do |plain, encoded|
|
20
20
|
it "should decode #{encoded} into #{plain}" do
|
21
|
-
@encoder.decode(HTTP2::Buffer.new([encoded].pack(
|
21
|
+
expect(@encoder.decode(HTTP2::Buffer.new([encoded].pack('H*')))).to eq plain
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
25
|
[
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
26
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0',
|
27
|
+
'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
28
|
+
'http://www.craigslist.org/about/sites/',
|
29
|
+
'cl_b=AB2BKbsl4hGM7M4nH5PYWghTM5A; cl_def_lang=en; cl_def_hp=shoals',
|
30
|
+
'image/png,image/*;q=0.8,*/*;q=0.5',
|
31
|
+
'BX=c99r6jp89a7no&b=3&s=q4; localization=en-us%3Bus%3Bus',
|
32
|
+
'UTF-8でエンコードした日本語文字列',
|
33
33
|
].each do |string|
|
34
34
|
it "should encode then decode '#{string}' into the same" do
|
35
|
-
s = string.dup.force_encoding(
|
35
|
+
s = string.dup.force_encoding(Encoding::BINARY)
|
36
36
|
encoded = @encoder.encode(s)
|
37
|
-
@encoder.decode(HTTP2::Buffer.new(encoded)).
|
37
|
+
expect(@encoder.decode(HTTP2::Buffer.new(encoded))).to eq s
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
it
|
41
|
+
it 'should encode/decode all_possible 2-byte sequences' do
|
42
42
|
(2**16).times do |n|
|
43
|
-
str = [n].pack(
|
44
|
-
@encoder.decode(HTTP2::Buffer.new(@encoder.encode(str))).
|
43
|
+
str = [n].pack('V')[0, 2].force_encoding(Encoding::BINARY)
|
44
|
+
expect(@encoder.decode(HTTP2::Buffer.new(@encoder.encode(str)))).to eq str
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
-
it
|
49
|
-
|
50
|
-
encoded = [encoded].pack(
|
48
|
+
it 'should raise when input is shorter than expected' do
|
49
|
+
encoded = huffman_examples.first.last
|
50
|
+
encoded = [encoded].pack('H*')
|
51
51
|
expect { @encoder.decode(HTTP2::Buffer.new(encoded[0...-1])) }.to raise_error(/EOS invalid/)
|
52
52
|
end
|
53
|
-
it
|
54
|
-
|
55
|
-
encoded = [encoded].pack(
|
53
|
+
it 'should raise when input is not padded by 1s' do
|
54
|
+
encoded = 'f1e3c2e5f23a6ba0ab90f4fe' # note the fe at end
|
55
|
+
encoded = [encoded].pack('H*')
|
56
56
|
expect { @encoder.decode(HTTP2::Buffer.new(encoded)) }.to raise_error(/EOS invalid/)
|
57
57
|
end
|
58
|
-
it
|
59
|
-
|
60
|
-
encoded = [encoded].pack(
|
58
|
+
it 'should raise when exceedingly padded' do
|
59
|
+
encoded = 'e7cf9bebe89b6fb16fa9b6ffff' # note the extra ff
|
60
|
+
encoded = [encoded].pack('H*')
|
61
61
|
expect { @encoder.decode(HTTP2::Buffer.new(encoded)) }.to raise_error(/EOS invalid/)
|
62
62
|
end
|
63
|
-
it
|
64
|
-
encoded = [
|
63
|
+
it 'should raise when EOS is explicitly encoded' do
|
64
|
+
encoded = ['1c7fffffffff'].pack('H*') # a b EOS
|
65
65
|
expect { @encoder.decode(HTTP2::Buffer.new(encoded)) }.to raise_error(/EOS found/)
|
66
66
|
end
|
67
67
|
end
|
data/spec/server_spec.rb
CHANGED
@@ -1,26 +1,26 @@
|
|
1
|
-
require
|
1
|
+
require 'helper'
|
2
2
|
|
3
|
-
describe HTTP2::Server do
|
3
|
+
RSpec.describe HTTP2::Server do
|
4
4
|
before(:each) do
|
5
5
|
@srv = Server.new
|
6
6
|
end
|
7
7
|
|
8
8
|
let(:f) { Framer.new }
|
9
9
|
|
10
|
-
context
|
11
|
-
it
|
12
|
-
@srv.new_stream.id.
|
10
|
+
context 'initialization and settings' do
|
11
|
+
it 'should return even stream IDs' do
|
12
|
+
expect(@srv.new_stream.id).to be_even
|
13
13
|
end
|
14
14
|
|
15
|
-
it
|
15
|
+
it 'should emit SETTINGS on new connection' do
|
16
16
|
frames = []
|
17
17
|
@srv.on(:frame) { |recv| frames << recv }
|
18
18
|
@srv << CONNECTION_PREFACE_MAGIC
|
19
19
|
|
20
|
-
f.parse(frames[0])[:type].
|
20
|
+
expect(f.parse(frames[0])[:type]).to eq :settings
|
21
21
|
end
|
22
22
|
|
23
|
-
it
|
23
|
+
it 'should initialize client with custom connection settings' do
|
24
24
|
frames = []
|
25
25
|
|
26
26
|
@srv = Server.new(settings_max_concurrent_streams: 200,
|
@@ -29,23 +29,23 @@ describe HTTP2::Server do
|
|
29
29
|
@srv << CONNECTION_PREFACE_MAGIC
|
30
30
|
|
31
31
|
frame = f.parse(frames[0])
|
32
|
-
frame[:type].
|
33
|
-
frame[:payload].
|
34
|
-
frame[:payload].
|
32
|
+
expect(frame[:type]).to eq :settings
|
33
|
+
expect(frame[:payload]).to include([:settings_max_concurrent_streams, 200])
|
34
|
+
expect(frame[:payload]).to include([:settings_initial_window_size, 2**10])
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
it
|
38
|
+
it 'should allow server push' do
|
39
39
|
client = Client.new
|
40
40
|
client.on(:frame) { |bytes| @srv << bytes }
|
41
41
|
|
42
42
|
@srv.on(:stream) do |stream|
|
43
|
-
expect
|
43
|
+
expect do
|
44
44
|
stream.promise(':method' => 'GET') {}
|
45
|
-
|
45
|
+
end.to_not raise_error
|
46
46
|
end
|
47
47
|
|
48
48
|
client.new_stream
|
49
|
-
client.send HEADERS
|
49
|
+
client.send HEADERS.deep_dup
|
50
50
|
end
|
51
51
|
end
|
data/spec/stream_spec.rb
CHANGED
@@ -1,169 +1,207 @@
|
|
1
|
-
require
|
1
|
+
require 'helper'
|
2
2
|
|
3
|
-
describe HTTP2::Stream do
|
3
|
+
RSpec.describe HTTP2::Stream do
|
4
4
|
before(:each) do
|
5
5
|
@client = Client.new
|
6
6
|
@stream = @client.new_stream
|
7
7
|
end
|
8
8
|
|
9
|
-
context
|
10
|
-
it
|
11
|
-
@stream.state.
|
9
|
+
context 'stream states' do
|
10
|
+
it 'should initiliaze all streams to IDLE' do
|
11
|
+
expect(@stream.state).to eq :idle
|
12
12
|
end
|
13
13
|
|
14
|
-
it
|
14
|
+
it 'should set custom stream priority' do
|
15
15
|
stream = @client.new_stream(weight: 3, dependency: 2, exclusive: true)
|
16
|
-
stream.weight.
|
16
|
+
expect(stream.weight).to eq 3
|
17
17
|
end
|
18
18
|
|
19
|
-
context
|
20
|
-
|
19
|
+
context 'idle' do
|
20
|
+
it 'should transition to open on sent HEADERS' do
|
21
|
+
@stream.send HEADERS.deep_dup
|
22
|
+
expect(@stream.state).to eq :open
|
23
|
+
end
|
24
|
+
it 'should transition to open on received HEADERS' do
|
25
|
+
@stream.receive HEADERS
|
26
|
+
expect(@stream.state).to eq :open
|
27
|
+
end
|
28
|
+
it 'should transition to reserved (local) on sent PUSH_PROMISE' do
|
29
|
+
@stream.send PUSH_PROMISE.deep_dup
|
30
|
+
expect(@stream.state).to eq :reserved_local
|
31
|
+
end
|
32
|
+
it 'should transition to reserved (remote) on received PUSH_PROMISE' do
|
33
|
+
@stream.receive PUSH_PROMISE
|
34
|
+
expect(@stream.state).to eq :reserved_remote
|
35
|
+
end
|
36
|
+
it 'should reprioritize stream on sent PRIORITY' do
|
37
|
+
expect { @stream.send PRIORITY.dup }.to_not raise_error
|
38
|
+
expect(@stream.weight).to eq 20
|
39
|
+
end
|
40
|
+
it 'should reprioritize stream on received PRIORITY' do
|
41
|
+
expect { @stream.send PRIORITY.dup }.to_not raise_error
|
42
|
+
expect(@stream.weight).to eq 20
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'reserved (local)' do
|
47
|
+
before(:each) { @stream.send PUSH_PROMISE.deep_dup }
|
21
48
|
|
22
|
-
it
|
23
|
-
@stream.state.
|
49
|
+
it 'should transition on sent PUSH_PROMISE' do
|
50
|
+
expect(@stream.state).to eq :reserved_local
|
24
51
|
end
|
25
52
|
|
26
|
-
it
|
27
|
-
expect { @stream.send HEADERS }.to_not raise_error
|
53
|
+
it 'should allow HEADERS to be sent' do
|
54
|
+
expect { @stream.send HEADERS.deep_dup }.to_not raise_error
|
28
55
|
end
|
29
56
|
|
30
|
-
it
|
57
|
+
it 'should raise error if sending invalid frames' do
|
31
58
|
(FRAME_TYPES - [HEADERS, RST_STREAM]).each do |type|
|
32
|
-
expect { @stream.dup.send type }.to raise_error
|
59
|
+
expect { @stream.dup.send type }.to raise_error InternalError
|
33
60
|
end
|
34
61
|
end
|
35
62
|
|
36
|
-
it
|
37
|
-
(FRAME_TYPES - [PRIORITY, RST_STREAM])
|
38
|
-
|
63
|
+
it 'should raise error on receipt of invalid frames' do
|
64
|
+
what_types = (FRAME_TYPES - [PRIORITY, RST_STREAM, WINDOW_UPDATE])
|
65
|
+
what_types.each do |type|
|
66
|
+
expect { @stream.dup.receive type }.to raise_error InternalError
|
39
67
|
end
|
40
68
|
end
|
41
69
|
|
42
|
-
it
|
43
|
-
@stream.send HEADERS
|
44
|
-
@stream.state.
|
70
|
+
it 'should transition to half closed (remote) on sent HEADERS' do
|
71
|
+
@stream.send HEADERS.deep_dup
|
72
|
+
expect(@stream.state).to eq :half_closed_remote
|
45
73
|
end
|
46
74
|
|
47
|
-
it
|
75
|
+
it 'should transition to closed on sent RST_STREAM' do
|
48
76
|
@stream.close
|
49
|
-
@stream.state.
|
77
|
+
expect(@stream.state).to eq :closed
|
50
78
|
end
|
51
79
|
|
52
|
-
it
|
80
|
+
it 'should transition to closed on received RST_STREAM' do
|
53
81
|
@stream.receive RST_STREAM
|
54
|
-
@stream.state.
|
82
|
+
expect(@stream.state).to eq :closed
|
55
83
|
end
|
56
84
|
|
57
|
-
it
|
85
|
+
it 'should reprioritize stream on PRIORITY' do
|
58
86
|
expect { @stream.receive PRIORITY }.to_not raise_error
|
59
|
-
@stream.weight.
|
87
|
+
expect(@stream.weight).to eq 20
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should increment remote_window on received WINDOW_UPDATE' do
|
91
|
+
expect { @stream.receive WINDOW_UPDATE }.to_not raise_error
|
92
|
+
expect(@stream.remote_window).to eq DEFAULT_FLOW_WINDOW + WINDOW_UPDATE[:increment]
|
60
93
|
end
|
61
94
|
end
|
62
95
|
|
63
|
-
context
|
96
|
+
context 'reserved (remote)' do
|
64
97
|
before(:each) { @stream.receive PUSH_PROMISE }
|
65
98
|
|
66
|
-
it
|
67
|
-
@stream.state.
|
99
|
+
it 'should transition on received PUSH_PROMISE' do
|
100
|
+
expect(@stream.state).to eq :reserved_remote
|
68
101
|
end
|
69
102
|
|
70
|
-
it
|
71
|
-
(FRAME_TYPES - [PRIORITY, RST_STREAM]).each do |type|
|
72
|
-
expect { @stream.dup.send type }.to raise_error
|
103
|
+
it 'should raise error if sending invalid frames' do
|
104
|
+
(FRAME_TYPES - [PRIORITY, RST_STREAM, WINDOW_UPDATE]).each do |type|
|
105
|
+
expect { @stream.dup.send type }.to raise_error InternalError
|
73
106
|
end
|
74
107
|
end
|
75
108
|
|
76
|
-
it
|
109
|
+
it 'should raise error on receipt of invalid frames' do
|
77
110
|
(FRAME_TYPES - [HEADERS, RST_STREAM]).each do |type|
|
78
|
-
expect { @stream.dup.receive type }.to raise_error
|
111
|
+
expect { @stream.dup.receive type }.to raise_error InternalError
|
79
112
|
end
|
80
113
|
end
|
81
114
|
|
82
|
-
it
|
115
|
+
it 'should transition to half closed (local) on received HEADERS' do
|
83
116
|
@stream.receive HEADERS
|
84
|
-
@stream.state.
|
117
|
+
expect(@stream.state).to eq :half_closed_local
|
85
118
|
end
|
86
119
|
|
87
|
-
it
|
120
|
+
it 'should transition to closed on sent RST_STREAM' do
|
88
121
|
@stream.close
|
89
|
-
@stream.state.
|
122
|
+
expect(@stream.state).to eq :closed
|
90
123
|
end
|
91
124
|
|
92
|
-
it
|
125
|
+
it 'should transition to closed on received RST_STREAM' do
|
93
126
|
@stream.receive RST_STREAM
|
94
|
-
@stream.state.
|
127
|
+
expect(@stream.state).to eq :closed
|
95
128
|
end
|
96
129
|
|
97
|
-
it
|
98
|
-
expect { @stream.send PRIORITY }.to_not raise_error
|
99
|
-
@stream.weight.
|
130
|
+
it 'should reprioritize stream on PRIORITY' do
|
131
|
+
expect { @stream.send PRIORITY.dup }.to_not raise_error
|
132
|
+
expect(@stream.weight).to eq 20
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'should increment local_window on sent WINDOW_UPDATE' do
|
136
|
+
expect { @stream.send WINDOW_UPDATE.dup }.to_not raise_error
|
137
|
+
expect(@stream.local_window).to eq DEFAULT_FLOW_WINDOW + WINDOW_UPDATE[:increment]
|
100
138
|
end
|
101
139
|
end
|
102
140
|
|
103
|
-
context
|
141
|
+
context 'open' do
|
104
142
|
before(:each) { @stream.receive HEADERS }
|
105
143
|
|
106
|
-
it
|
144
|
+
it 'should allow any valid frames types to be sent' do
|
107
145
|
(FRAME_TYPES - [PING, GOAWAY, SETTINGS]).each do |type|
|
108
|
-
expect { @stream.dup.send type }.to_not raise_error
|
146
|
+
expect { @stream.dup.send type.deep_dup }.to_not raise_error
|
109
147
|
end
|
110
148
|
end
|
111
149
|
|
112
|
-
it
|
150
|
+
it 'should allow frames of any type to be received' do
|
113
151
|
FRAME_TYPES.each do |type|
|
114
152
|
expect { @stream.dup.receive type }.to_not raise_error
|
115
153
|
end
|
116
154
|
end
|
117
155
|
|
118
|
-
it
|
156
|
+
it 'should transition to half closed (local) if sending END_STREAM' do
|
119
157
|
[DATA, HEADERS].each do |frame|
|
120
|
-
s, f = @stream.dup, frame.
|
158
|
+
s, f = @stream.dup, frame.deep_dup
|
121
159
|
f[:flags] = [:end_stream]
|
122
160
|
|
123
161
|
s.send f
|
124
|
-
s.state.
|
162
|
+
expect(s.state).to eq :half_closed_local
|
125
163
|
end
|
126
164
|
end
|
127
165
|
|
128
|
-
it
|
166
|
+
it 'should transition to half closed (remote) if receiving END_STREAM' do
|
129
167
|
[DATA, HEADERS].each do |frame|
|
130
168
|
s, f = @stream.dup, frame.dup
|
131
169
|
f[:flags] = [:end_stream]
|
132
170
|
|
133
171
|
s.receive f
|
134
|
-
s.state.
|
172
|
+
expect(s.state).to eq :half_closed_remote
|
135
173
|
end
|
136
174
|
end
|
137
175
|
|
138
|
-
it
|
176
|
+
it 'should transition to half closed if remote opened with END_STREAM' do
|
139
177
|
s = @client.new_stream
|
140
178
|
hclose = HEADERS.dup
|
141
179
|
hclose[:flags] = [:end_stream]
|
142
180
|
|
143
181
|
s.receive hclose
|
144
|
-
s.state.
|
182
|
+
expect(s.state).to eq :half_closed_remote
|
145
183
|
end
|
146
184
|
|
147
|
-
it
|
185
|
+
it 'should transition to half closed if local opened with END_STREAM' do
|
148
186
|
s = @client.new_stream
|
149
|
-
hclose = HEADERS.
|
187
|
+
hclose = HEADERS.deep_dup
|
150
188
|
hclose[:flags] = [:end_stream]
|
151
189
|
|
152
190
|
s.send hclose
|
153
|
-
s.state.
|
191
|
+
expect(s.state).to eq :half_closed_local
|
154
192
|
end
|
155
193
|
|
156
|
-
it
|
194
|
+
it 'should transition to closed if sending RST_STREAM' do
|
157
195
|
@stream.close
|
158
|
-
@stream.state.
|
196
|
+
expect(@stream.state).to eq :closed
|
159
197
|
end
|
160
198
|
|
161
|
-
it
|
199
|
+
it 'should transition to closed if receiving RST_STREAM' do
|
162
200
|
@stream.receive RST_STREAM
|
163
|
-
@stream.state.
|
201
|
+
expect(@stream.state).to eq :closed
|
164
202
|
end
|
165
203
|
|
166
|
-
it
|
204
|
+
it 'should emit :active on open transition' do
|
167
205
|
openp, openr = false, false
|
168
206
|
sp = @client.new_stream
|
169
207
|
sr = @client.new_stream
|
@@ -171,28 +209,28 @@ describe HTTP2::Stream do
|
|
171
209
|
sr.on(:active) { openr = true }
|
172
210
|
|
173
211
|
sp.receive HEADERS
|
174
|
-
sr.send HEADERS
|
212
|
+
sr.send HEADERS.deep_dup
|
175
213
|
|
176
|
-
openp.
|
177
|
-
openr.
|
214
|
+
expect(openp).to be_truthy
|
215
|
+
expect(openr).to be_truthy
|
178
216
|
end
|
179
217
|
|
180
|
-
it
|
218
|
+
it 'should not emit :active on transition from open' do
|
181
219
|
order, stream = [], @client.new_stream
|
182
220
|
|
183
221
|
stream.on(:active) { order << :active }
|
184
222
|
stream.on(:half_close) { order << :half_close }
|
185
223
|
stream.on(:close) { order << :close }
|
186
224
|
|
187
|
-
req = HEADERS.
|
225
|
+
req = HEADERS.deep_dup
|
188
226
|
req[:flags] = [:end_headers]
|
189
227
|
|
190
228
|
stream.send req
|
191
|
-
stream.send DATA
|
192
|
-
order.
|
229
|
+
stream.send DATA.dup
|
230
|
+
expect(order).to eq [:active, :half_close]
|
193
231
|
end
|
194
232
|
|
195
|
-
it
|
233
|
+
it 'should emit :close on close transition' do
|
196
234
|
closep, closer = false, false
|
197
235
|
sp, sr = @stream.dup, @stream.dup
|
198
236
|
|
@@ -202,11 +240,11 @@ describe HTTP2::Stream do
|
|
202
240
|
sp.receive RST_STREAM
|
203
241
|
sr.close
|
204
242
|
|
205
|
-
closep.
|
206
|
-
closer.
|
243
|
+
expect(closep).to be_truthy
|
244
|
+
expect(closer).to be_truthy
|
207
245
|
end
|
208
246
|
|
209
|
-
it
|
247
|
+
it 'should emit :close after frame is processed' do
|
210
248
|
order, stream = [], @client.new_stream
|
211
249
|
|
212
250
|
stream.on(:active) { order << :active }
|
@@ -214,130 +252,162 @@ describe HTTP2::Stream do
|
|
214
252
|
stream.on(:half_close) { order << :half_close }
|
215
253
|
stream.on(:close) { order << :close }
|
216
254
|
|
217
|
-
req = HEADERS.
|
255
|
+
req = HEADERS.deep_dup
|
218
256
|
req[:flags] = [:end_stream, :end_headers]
|
219
257
|
|
220
258
|
stream.send req
|
221
259
|
stream.receive HEADERS
|
222
260
|
stream.receive DATA
|
223
261
|
|
224
|
-
order.
|
262
|
+
expect(order).to eq [:active, :half_close, :data, :close]
|
225
263
|
end
|
226
264
|
|
227
|
-
it
|
265
|
+
it 'should emit :close with reason' do
|
228
266
|
reason = nil
|
229
|
-
@stream.on(:close) {|r| reason = r }
|
267
|
+
@stream.on(:close) { |r| reason = r }
|
230
268
|
@stream.receive RST_STREAM
|
231
|
-
reason.
|
269
|
+
expect(reason).not_to be_nil
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'should reprioritize stream on sent PRIORITY' do
|
273
|
+
expect { @stream.send PRIORITY.dup }.to_not raise_error
|
274
|
+
expect(@stream.weight).to eq 20
|
275
|
+
end
|
276
|
+
it 'should reprioritize stream on received PRIORITY' do
|
277
|
+
expect { @stream.receive PRIORITY }.to_not raise_error
|
278
|
+
expect(@stream.weight).to eq 20
|
232
279
|
end
|
233
280
|
end
|
234
281
|
|
235
|
-
context
|
236
|
-
before(:each) { @stream.send HEADERS_END_STREAM }
|
282
|
+
context 'half closed (local)' do
|
283
|
+
before(:each) { @stream.send HEADERS_END_STREAM.deep_dup }
|
237
284
|
|
238
|
-
it
|
239
|
-
(FRAME_TYPES - [PRIORITY, RST_STREAM]).each do |frame|
|
240
|
-
expect { @stream.dup.send frame }.to raise_error
|
285
|
+
it 'should raise error on attempt to send invalid frames' do
|
286
|
+
(FRAME_TYPES - [PRIORITY, RST_STREAM, WINDOW_UPDATE]).each do |frame|
|
287
|
+
expect { @stream.dup.send frame }.to raise_error InternalError
|
241
288
|
end
|
242
289
|
end
|
243
290
|
|
244
|
-
it
|
291
|
+
it 'should transition to closed on receipt of END_STREAM flag' do
|
245
292
|
[DATA, HEADERS, CONTINUATION].each do |frame|
|
246
293
|
s, f = @stream.dup, frame.dup
|
247
294
|
f[:flags] = [:end_stream]
|
248
295
|
|
249
296
|
s.receive f
|
250
|
-
s.state.
|
297
|
+
expect(s.state).to eq :closed
|
251
298
|
end
|
252
299
|
end
|
253
300
|
|
254
|
-
it
|
301
|
+
it 'should transition to closed on receipt of RST_STREAM frame' do
|
255
302
|
@stream.receive RST_STREAM
|
256
|
-
@stream.state.
|
303
|
+
expect(@stream.state).to eq :closed
|
257
304
|
end
|
258
305
|
|
259
|
-
it
|
260
|
-
@stream.send RST_STREAM
|
261
|
-
@stream.state.
|
306
|
+
it 'should transition to closed if RST_STREAM frame is sent' do
|
307
|
+
@stream.send RST_STREAM.deep_dup
|
308
|
+
expect(@stream.state).to eq :closed
|
262
309
|
end
|
263
310
|
|
264
|
-
it
|
311
|
+
it 'should ignore received WINDOW_UPDATE frames' do
|
265
312
|
expect { @stream.receive WINDOW_UPDATE }.to_not raise_error
|
313
|
+
expect(@stream.state).to eq :half_closed_local
|
314
|
+
end
|
315
|
+
|
316
|
+
it 'should ignore received PRIORITY frames' do
|
266
317
|
expect { @stream.receive PRIORITY }.to_not raise_error
|
267
|
-
@stream.state.
|
318
|
+
expect(@stream.state).to eq :half_closed_local
|
319
|
+
end
|
320
|
+
|
321
|
+
it 'should reprioritize stream on sent PRIORITY' do
|
322
|
+
expect { @stream.send PRIORITY.dup }.to_not raise_error
|
323
|
+
expect(@stream.weight).to eq 20
|
268
324
|
end
|
269
325
|
|
270
|
-
it
|
271
|
-
expect { @stream.
|
272
|
-
@stream.weight.
|
326
|
+
it 'should reprioritize stream (and decendants) on received PRIORITY' do
|
327
|
+
expect { @stream.receive PRIORITY }.to_not raise_error
|
328
|
+
expect(@stream.weight).to eq 20
|
273
329
|
end
|
274
330
|
|
275
|
-
it
|
331
|
+
it 'should increment local_window on sent WINDOW_UPDATE' do
|
332
|
+
expect { @stream.send WINDOW_UPDATE.dup }.to_not raise_error
|
333
|
+
expect(@stream.local_window).to eq DEFAULT_FLOW_WINDOW + WINDOW_UPDATE[:increment]
|
334
|
+
end
|
335
|
+
|
336
|
+
it 'should emit :half_close event on transition' do
|
276
337
|
order = []
|
277
338
|
stream = @client.new_stream
|
278
339
|
stream.on(:active) { order << :active }
|
279
340
|
stream.on(:half_close) { order << :half_close }
|
280
341
|
|
281
|
-
req = HEADERS.
|
342
|
+
req = HEADERS.deep_dup
|
282
343
|
req[:flags] = [:end_stream, :end_headers]
|
283
344
|
|
284
345
|
stream.send req
|
285
|
-
order.
|
346
|
+
expect(order).to eq [:active, :half_close]
|
286
347
|
end
|
287
348
|
|
288
|
-
it
|
349
|
+
it 'should emit :close event on transition to closed' do
|
289
350
|
closed = false
|
290
351
|
@stream.on(:close) { closed = true }
|
291
352
|
@stream.receive RST_STREAM
|
292
353
|
|
293
|
-
@stream.state.
|
294
|
-
closed.
|
354
|
+
expect(@stream.state).to eq :closed
|
355
|
+
expect(closed).to be_truthy
|
295
356
|
end
|
296
357
|
end
|
297
358
|
|
298
|
-
context
|
359
|
+
context 'half closed (remote)' do
|
299
360
|
before(:each) { @stream.receive HEADERS_END_STREAM }
|
300
361
|
|
301
|
-
it
|
362
|
+
it 'should raise STREAM_CLOSED error on reciept of frames' do
|
302
363
|
(FRAME_TYPES - [PRIORITY, RST_STREAM, WINDOW_UPDATE]).each do |frame|
|
303
|
-
expect
|
364
|
+
expect do
|
304
365
|
@stream.dup.receive frame
|
305
|
-
|
366
|
+
end.to raise_error(StreamClosed)
|
306
367
|
end
|
307
368
|
end
|
308
369
|
|
309
|
-
it
|
370
|
+
it 'should transition to closed if END_STREAM flag is sent' do
|
310
371
|
[DATA, HEADERS].each do |frame|
|
311
|
-
s, f = @stream.dup, frame.
|
372
|
+
s, f = @stream.dup, frame.deep_dup
|
312
373
|
f[:flags] = [:end_stream]
|
313
374
|
|
314
|
-
s.on(:close) { s.state.
|
375
|
+
s.on(:close) { expect(s.state).to eq :closed }
|
315
376
|
s.send f
|
316
|
-
s.state.
|
377
|
+
expect(s.state).to eq :closed
|
317
378
|
end
|
318
379
|
end
|
319
380
|
|
320
|
-
it
|
381
|
+
it 'should transition to closed if RST_STREAM is sent' do
|
321
382
|
@stream.close
|
322
|
-
@stream.state.
|
383
|
+
expect(@stream.state).to eq :closed
|
323
384
|
end
|
324
385
|
|
325
|
-
it
|
386
|
+
it 'should transition to closed on reciept of RST_STREAM frame' do
|
326
387
|
@stream.receive RST_STREAM
|
327
|
-
@stream.state.
|
388
|
+
expect(@stream.state).to eq :closed
|
389
|
+
end
|
390
|
+
|
391
|
+
it 'should ignore sent WINDOW_UPDATE frames' do
|
392
|
+
expect { @stream.send WINDOW_UPDATE.dup }.to_not raise_error
|
393
|
+
expect(@stream.state).to eq :half_closed_remote
|
328
394
|
end
|
329
395
|
|
330
|
-
it
|
396
|
+
it 'should increment remote_window on received WINDOW_UPDATE' do
|
331
397
|
expect { @stream.receive WINDOW_UPDATE }.to_not raise_error
|
332
|
-
@stream.
|
398
|
+
expect(@stream.remote_window).to eq DEFAULT_FLOW_WINDOW + WINDOW_UPDATE[:increment]
|
333
399
|
end
|
334
400
|
|
335
|
-
it
|
401
|
+
it 'should reprioritize stream on sent PRIORITY' do
|
402
|
+
expect { @stream.send PRIORITY.dup }.to_not raise_error
|
403
|
+
expect(@stream.weight).to eq 20
|
404
|
+
end
|
405
|
+
it 'should reprioritize stream on received PRIORITY' do
|
336
406
|
expect { @stream.receive PRIORITY }.to_not raise_error
|
337
|
-
@stream.weight.
|
407
|
+
expect(@stream.weight).to eq 20
|
338
408
|
end
|
339
409
|
|
340
|
-
it
|
410
|
+
it 'should emit :half_close event on transition' do
|
341
411
|
order = []
|
342
412
|
stream = @client.new_stream
|
343
413
|
stream.on(:active) { order << :active }
|
@@ -347,85 +417,92 @@ describe HTTP2::Stream do
|
|
347
417
|
req[:flags] = [:end_stream, :end_headers]
|
348
418
|
|
349
419
|
stream.receive req
|
350
|
-
order.
|
420
|
+
expect(order).to eq [:active, :half_close]
|
351
421
|
end
|
352
422
|
|
353
|
-
it
|
423
|
+
it 'should emit :close event on close transition' do
|
354
424
|
closed = false
|
355
425
|
@stream.on(:close) { closed = true }
|
356
426
|
@stream.close
|
357
427
|
|
358
|
-
@stream.state.
|
359
|
-
closed.
|
428
|
+
expect(@stream.state).to eq :closed
|
429
|
+
expect(closed).to be_truthy
|
360
430
|
end
|
361
431
|
end
|
362
432
|
|
363
|
-
context
|
364
|
-
context
|
433
|
+
context 'closed' do
|
434
|
+
context 'remote closed stream' do
|
365
435
|
before(:each) do
|
366
|
-
@stream.send HEADERS_END_STREAM # half closed local
|
436
|
+
@stream.send HEADERS_END_STREAM.deep_dup # half closed local
|
367
437
|
@stream.receive HEADERS_END_STREAM # closed by remote
|
368
438
|
end
|
369
439
|
|
370
|
-
it
|
440
|
+
it 'should raise STREAM_CLOSED on attempt to send frames' do
|
371
441
|
(FRAME_TYPES - [PRIORITY, RST_STREAM]).each do |frame|
|
372
|
-
expect
|
442
|
+
expect do
|
373
443
|
@stream.dup.send frame
|
374
|
-
|
444
|
+
end.to raise_error(StreamClosed)
|
375
445
|
end
|
376
446
|
end
|
377
447
|
|
378
|
-
it
|
448
|
+
it 'should raise STREAM_CLOSED on receipt of frame' do
|
379
449
|
(FRAME_TYPES - [PRIORITY, RST_STREAM, WINDOW_UPDATE]).each do |frame|
|
380
|
-
expect
|
450
|
+
expect do
|
381
451
|
@stream.dup.receive frame
|
382
|
-
|
452
|
+
end.to raise_error(StreamClosed)
|
383
453
|
end
|
384
454
|
end
|
385
455
|
|
386
|
-
it
|
387
|
-
expect { @stream.send PRIORITY }.to_not raise_error
|
388
|
-
expect { @stream.send RST_STREAM }.to_not raise_error
|
456
|
+
it 'should allow PRIORITY, RST_STREAM to be sent' do
|
457
|
+
expect { @stream.send PRIORITY.dup }.to_not raise_error
|
458
|
+
expect { @stream.send RST_STREAM.dup }.to_not raise_error
|
389
459
|
end
|
390
460
|
|
391
|
-
it
|
461
|
+
it 'should allow PRIORITY, RST_STREAM to be received' do
|
392
462
|
expect { @stream.receive PRIORITY }.to_not raise_error
|
393
463
|
expect { @stream.receive RST_STREAM }.to_not raise_error
|
394
464
|
end
|
395
465
|
|
396
|
-
it
|
466
|
+
it 'should reprioritize stream on sent PRIORITY' do
|
467
|
+
expect { @stream.send PRIORITY.dup }.to_not raise_error
|
468
|
+
expect(@stream.weight).to eq 20
|
469
|
+
end
|
470
|
+
it 'should reprioritize stream on received PRIORITY' do
|
397
471
|
expect { @stream.receive PRIORITY }.to_not raise_error
|
398
|
-
@stream.weight.
|
472
|
+
expect(@stream.weight).to eq 20
|
399
473
|
end
|
400
474
|
|
475
|
+
it 'should ignore received WINDOW_UPDATE frames' do
|
476
|
+
expect { @stream.receive WINDOW_UPDATE }.to_not raise_error
|
477
|
+
expect(@stream.state).to eq :closed
|
478
|
+
end
|
401
479
|
end
|
402
480
|
|
403
|
-
context
|
481
|
+
context 'local closed via RST_STREAM frame' do
|
404
482
|
before(:each) do
|
405
|
-
@stream.send HEADERS # open
|
406
|
-
@stream.send RST_STREAM # closed by local
|
483
|
+
@stream.send HEADERS.deep_dup # open
|
484
|
+
@stream.send RST_STREAM.deep_dup # closed by local
|
407
485
|
end
|
408
486
|
|
409
|
-
it
|
487
|
+
it 'should ignore received frames' do
|
410
488
|
(FRAME_TYPES - [PUSH_PROMISE]).each do |frame|
|
411
|
-
expect
|
489
|
+
expect do
|
412
490
|
cb = []
|
413
491
|
@stream.on(:data) { cb << :data }
|
414
|
-
@stream.on(:headers) { cb << :headers}
|
415
|
-
@stream.dup.receive frame
|
416
|
-
cb.
|
417
|
-
|
492
|
+
@stream.on(:headers) { cb << :headers }
|
493
|
+
@stream.dup.receive frame.dup
|
494
|
+
expect(cb).to be_empty
|
495
|
+
end.to_not raise_error
|
418
496
|
end
|
419
497
|
end
|
420
498
|
|
421
|
-
#it "should transition to reserved remote on PUSH_PROMISE" do
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
#end
|
428
|
-
|
499
|
+
# it "should transition to reserved remote on PUSH_PROMISE" do
|
500
|
+
# An endpoint might receive a PUSH_PROMISE frame after it sends
|
501
|
+
# RST_STREAM. PUSH_PROMISE causes a stream to become "reserved".
|
502
|
+
# ...
|
503
|
+
# We're auto RST'ing PUSH streams in connection class, hence
|
504
|
+
# skipping this transition for now.
|
505
|
+
# end
|
429
506
|
end
|
430
507
|
|
431
508
|
# FIXME: Isn't this test same as "half closed (local)"?
|
@@ -441,42 +518,41 @@ describe HTTP2::Stream do
|
|
441
518
|
# end
|
442
519
|
# end
|
443
520
|
# end
|
444
|
-
|
445
521
|
end
|
446
522
|
end # end stream states
|
447
523
|
|
448
524
|
# TODO: add test cases to ensure on(:priority) emitted after close
|
449
525
|
|
450
|
-
context
|
451
|
-
it
|
452
|
-
@stream.remote_window.
|
526
|
+
context 'flow control' do
|
527
|
+
it 'should initialize to default flow control window' do
|
528
|
+
expect(@stream.remote_window).to eq DEFAULT_FLOW_WINDOW
|
453
529
|
end
|
454
530
|
|
455
|
-
it
|
456
|
-
@stream.send HEADERS # go to open
|
457
|
-
@stream.remote_window.
|
531
|
+
it 'should update window size on DATA frames only' do
|
532
|
+
@stream.send HEADERS.deep_dup # go to open
|
533
|
+
expect(@stream.remote_window).to eq DEFAULT_FLOW_WINDOW
|
458
534
|
|
459
|
-
(FRAME_TYPES - [DATA,PING,GOAWAY,SETTINGS]).each do |frame|
|
535
|
+
(FRAME_TYPES - [DATA, PING, GOAWAY, SETTINGS]).each do |frame|
|
460
536
|
s = @stream.dup
|
461
|
-
s.send frame
|
462
|
-
s.remote_window.
|
537
|
+
s.send frame.deep_dup
|
538
|
+
expect(s.remote_window).to eq DEFAULT_FLOW_WINDOW
|
463
539
|
end
|
464
540
|
|
465
|
-
@stream.send DATA
|
466
|
-
@stream.remote_window.
|
541
|
+
@stream.send DATA.dup
|
542
|
+
expect(@stream.remote_window).to eq DEFAULT_FLOW_WINDOW - DATA[:payload].bytesize
|
467
543
|
end
|
468
544
|
|
469
|
-
it
|
470
|
-
@stream.send HEADERS
|
471
|
-
@stream.send DATA
|
545
|
+
it 'should update window size on receipt of WINDOW_UPDATE' do
|
546
|
+
@stream.send HEADERS.deep_dup
|
547
|
+
@stream.send DATA.dup
|
472
548
|
@stream.receive WINDOW_UPDATE
|
473
549
|
|
474
|
-
@stream.remote_window.
|
475
|
-
DEFAULT_FLOW_WINDOW - DATA[:payload].bytesize + WINDOW_UPDATE[:increment]
|
550
|
+
expect(@stream.remote_window).to eq(
|
551
|
+
DEFAULT_FLOW_WINDOW - DATA[:payload].bytesize + WINDOW_UPDATE[:increment],
|
476
552
|
)
|
477
553
|
end
|
478
554
|
|
479
|
-
it
|
555
|
+
it 'should observe session flow control' do
|
480
556
|
settings, data = SETTINGS.dup, DATA.dup
|
481
557
|
settings[:payload] = [[:settings_initial_window_size, 1000]]
|
482
558
|
settings[:stream] = 0
|
@@ -485,198 +561,207 @@ describe HTTP2::Stream do
|
|
485
561
|
@client << framer.generate(settings)
|
486
562
|
|
487
563
|
s1 = @client.new_stream
|
488
|
-
s1.send HEADERS
|
489
|
-
s1.send data.merge(
|
490
|
-
s1.remote_window.
|
491
|
-
|
492
|
-
s1.send data.merge(
|
493
|
-
s1.remote_window.
|
494
|
-
s1.buffered_amount.
|
495
|
-
|
496
|
-
@client << framer.generate(WINDOW_UPDATE.merge(
|
497
|
-
|
498
|
-
|
499
|
-
s1.buffered_amount.should eq 0
|
500
|
-
s1.remote_window.should eq 900
|
564
|
+
s1.send HEADERS.deep_dup
|
565
|
+
s1.send data.merge(payload: 'x' * 900, flags: [])
|
566
|
+
expect(s1.remote_window).to eq 100
|
567
|
+
|
568
|
+
s1.send data.merge(payload: 'x' * 200)
|
569
|
+
expect(s1.remote_window).to eq 0
|
570
|
+
expect(s1.buffered_amount).to eq 100
|
571
|
+
|
572
|
+
@client << framer.generate(WINDOW_UPDATE.merge(stream: s1.id, increment: 1000))
|
573
|
+
expect(s1.buffered_amount).to eq 0
|
574
|
+
expect(s1.remote_window).to eq 900
|
501
575
|
end
|
502
576
|
end
|
503
577
|
|
504
|
-
context
|
505
|
-
it
|
506
|
-
@stream.
|
507
|
-
frame[:type].
|
508
|
-
frame[:weight].
|
578
|
+
context 'client API' do
|
579
|
+
it '.reprioritize should emit PRIORITY frame' do
|
580
|
+
expect(@stream).to receive(:send) do |frame|
|
581
|
+
expect(frame[:type]).to eq :priority
|
582
|
+
expect(frame[:weight]).to eq 30
|
509
583
|
end
|
510
584
|
|
511
585
|
@stream.reprioritize weight: 30
|
512
586
|
end
|
513
587
|
|
514
|
-
it
|
588
|
+
it '.reprioritize should raise error if invoked by server' do
|
515
589
|
srv = Server.new
|
516
590
|
stream = srv.new_stream
|
517
591
|
|
518
|
-
expect { stream.reprioritize(weight: 10) }.to raise_error(
|
592
|
+
expect { stream.reprioritize(weight: 10) }.to raise_error(InternalError)
|
519
593
|
end
|
520
594
|
|
521
|
-
it
|
595
|
+
it '.headers should emit HEADERS frames' do
|
522
596
|
payload = {
|
523
597
|
':method' => 'GET',
|
524
598
|
':scheme' => 'http',
|
525
599
|
':host' => 'www.example.org',
|
526
600
|
':path' => '/resource',
|
527
|
-
'custom' => 'value'
|
601
|
+
'custom' => 'value',
|
528
602
|
}
|
529
603
|
|
530
|
-
@stream.
|
531
|
-
frame[:type].
|
532
|
-
frame[:payload].
|
533
|
-
frame[:flags].
|
604
|
+
expect(@stream).to receive(:send) do |frame|
|
605
|
+
expect(frame[:type]).to eq :headers
|
606
|
+
expect(frame[:payload]).to eq payload.to_a
|
607
|
+
expect(frame[:flags]).to eq [:end_headers]
|
534
608
|
end
|
535
609
|
|
536
610
|
@stream.headers(payload, end_stream: false, end_headers: true)
|
537
611
|
end
|
538
612
|
|
539
|
-
it
|
540
|
-
@stream.
|
541
|
-
frame[:type].
|
542
|
-
frame[:payload].
|
543
|
-
frame[:flags].
|
613
|
+
it '.data should emit DATA frames' do
|
614
|
+
expect(@stream).to receive(:send) do |frame|
|
615
|
+
expect(frame[:type]).to eq :data
|
616
|
+
expect(frame[:payload]).to eq 'text'
|
617
|
+
expect(frame[:flags]).to be_empty
|
544
618
|
end
|
545
|
-
@stream.data(
|
619
|
+
@stream.data('text', end_stream: false)
|
546
620
|
|
547
|
-
@stream.
|
548
|
-
frame[:flags].
|
621
|
+
expect(@stream).to receive(:send) do |frame|
|
622
|
+
expect(frame[:flags]).to eq [:end_stream]
|
549
623
|
end
|
550
|
-
@stream.data(
|
624
|
+
@stream.data('text')
|
551
625
|
end
|
552
626
|
|
553
|
-
it
|
554
|
-
data =
|
627
|
+
it '.data should split large DATA frames' do
|
628
|
+
data = 'x' * 16_384 * 2
|
629
|
+
|
630
|
+
want = [
|
631
|
+
{ type: :data, flags: [], length: 16_384 },
|
632
|
+
{ type: :data, flags: [], length: 16_384 },
|
633
|
+
{ type: :data, flags: [:end_stream], length: 1 },
|
634
|
+
]
|
635
|
+
want.each do |w|
|
636
|
+
expect(@stream).to receive(:send) do |frame|
|
637
|
+
expect(frame[:type]).to eq w[:type]
|
638
|
+
expect(frame[:flags]).to eq w[:flags]
|
639
|
+
expect(frame[:payload].length).to eq w[:length]
|
640
|
+
end
|
641
|
+
end
|
555
642
|
|
556
|
-
@stream.
|
557
|
-
@stream.should_receive(:send).exactly(3).times
|
558
|
-
@stream.data(data + "x")
|
643
|
+
@stream.data(data + 'x')
|
559
644
|
end
|
560
645
|
|
561
|
-
it
|
562
|
-
@stream.
|
563
|
-
frame[:type].
|
564
|
-
frame[:error].
|
646
|
+
it '.cancel should reset stream with cancel error code' do
|
647
|
+
expect(@stream).to receive(:send) do |frame|
|
648
|
+
expect(frame[:type]).to eq :rst_stream
|
649
|
+
expect(frame[:error]).to eq :cancel
|
565
650
|
end
|
566
651
|
|
567
652
|
@stream.cancel
|
568
653
|
end
|
569
654
|
|
570
|
-
it
|
571
|
-
@stream.
|
572
|
-
frame[:type].
|
573
|
-
frame[:error].
|
655
|
+
it '.refuse should reset stream with refused stream error code' do
|
656
|
+
expect(@stream).to receive(:send) do |frame|
|
657
|
+
expect(frame[:type]).to eq :rst_stream
|
658
|
+
expect(frame[:error]).to eq :refused_stream
|
574
659
|
end
|
575
660
|
|
576
661
|
@stream.refuse
|
577
662
|
end
|
578
663
|
end
|
579
664
|
|
580
|
-
context
|
665
|
+
context 'server API' do
|
581
666
|
before(:each) do
|
582
667
|
@srv = Server.new
|
583
668
|
@frm = Framer.new
|
584
669
|
|
585
|
-
@client.on(:frame) {|bytes| @srv << bytes }
|
670
|
+
@client.on(:frame) { |bytes| @srv << bytes }
|
586
671
|
@client_stream = @client.new_stream
|
587
672
|
end
|
588
673
|
|
589
|
-
it
|
590
|
-
headers, recv = [
|
674
|
+
it 'should emit received headers via on(:headers)' do
|
675
|
+
headers, recv = [%w(header value)], nil
|
591
676
|
@srv.on(:stream) do |stream|
|
592
|
-
stream.on(:headers) {|h| recv = h}
|
677
|
+
stream.on(:headers) { |h| recv = h }
|
593
678
|
end
|
594
679
|
|
595
680
|
@client_stream.headers(headers)
|
596
|
-
recv.
|
681
|
+
expect(recv).to eq headers
|
597
682
|
end
|
598
683
|
|
599
|
-
it
|
600
|
-
payload
|
684
|
+
it 'should emit received payload via on(:data)' do
|
685
|
+
payload = 'some-payload'
|
601
686
|
@srv.on(:stream) do |stream|
|
602
687
|
stream.on(:data) do |recv|
|
603
|
-
recv.
|
688
|
+
expect(recv).to eq payload
|
604
689
|
end
|
605
690
|
end
|
606
691
|
|
607
|
-
@client_stream.headers(
|
692
|
+
@client_stream.headers('key' => 'value')
|
608
693
|
@client_stream.data(payload)
|
609
694
|
end
|
610
695
|
|
611
|
-
it
|
696
|
+
it 'should emit received priority parameters via on(:priority)' do
|
612
697
|
new_weight, new_dependency = 15, @client_stream.id + 2
|
613
698
|
callback_called = false
|
614
699
|
@srv.on(:stream) do |stream|
|
615
700
|
stream.on(:priority) do |pri|
|
616
701
|
callback_called = true
|
617
|
-
pri.is_a?(Hash).
|
618
|
-
pri[:weight].
|
619
|
-
pri[:dependency].
|
702
|
+
expect(pri.is_a?(Hash)).to be
|
703
|
+
expect(pri[:weight]).to eq new_weight
|
704
|
+
expect(pri[:dependency]).to eq new_dependency
|
620
705
|
end
|
621
706
|
end
|
622
707
|
|
623
|
-
@client_stream.headers(
|
708
|
+
@client_stream.headers('key' => 'value')
|
624
709
|
@client_stream.reprioritize(weight: new_weight, dependency: new_dependency)
|
625
|
-
callback_called.
|
710
|
+
expect(callback_called).to be
|
626
711
|
end
|
627
712
|
|
628
|
-
context
|
713
|
+
context 'push' do
|
629
714
|
before(:each) do
|
630
|
-
@srv.on(:frame) {|bytes| @client << bytes }
|
715
|
+
@srv.on(:frame) { |bytes| @client << bytes }
|
631
716
|
@srv.on(:stream) do |stream|
|
632
717
|
@server_stream = stream
|
633
718
|
end
|
634
719
|
|
635
|
-
@client_stream.headers(
|
720
|
+
@client_stream.headers('key' => 'value')
|
636
721
|
end
|
637
722
|
|
638
|
-
it
|
723
|
+
it '.promise should emit server initiated stream' do
|
639
724
|
push = nil
|
640
|
-
@server_stream.promise(
|
641
|
-
push.id.
|
725
|
+
@server_stream.promise('key' => 'val') { |pstream| push = pstream }
|
726
|
+
expect(push.id).to eq 2
|
642
727
|
end
|
643
728
|
|
644
|
-
it
|
729
|
+
it '.promise push stream should have parent stream' do
|
645
730
|
push = nil
|
646
|
-
@server_stream.promise(
|
731
|
+
@server_stream.promise('key' => 'val') { |pstream| push = pstream }
|
647
732
|
|
648
|
-
push.state.
|
649
|
-
push.parent.id.
|
733
|
+
expect(push.state).to eq :reserved_local
|
734
|
+
expect(push.parent.id).to eq @server_stream.id
|
650
735
|
end
|
651
736
|
|
652
|
-
context
|
653
|
-
it
|
737
|
+
context 'stream states' do
|
738
|
+
it 'server: active > half close > close' do
|
654
739
|
order = []
|
655
|
-
@server_stream.promise(
|
740
|
+
@server_stream.promise('key' => 'val') do |push|
|
656
741
|
stream = push
|
657
742
|
|
658
|
-
push.state.
|
743
|
+
expect(push.state).to eq :reserved_local
|
659
744
|
order << :reserved
|
660
745
|
|
661
746
|
push.on(:active) { order << :active }
|
662
|
-
push.on(:half_close){ order << :half_close }
|
747
|
+
push.on(:half_close) { order << :half_close }
|
663
748
|
push.on(:close) { order << :close }
|
664
749
|
|
665
|
-
push.headers(
|
666
|
-
push.send DATA.merge(
|
750
|
+
push.headers('key2' => 'val2')
|
751
|
+
push.send DATA.merge(stream: stream.id)
|
667
752
|
end
|
668
753
|
|
669
|
-
order.
|
754
|
+
expect(order).to eq [:reserved, :active, :half_close, :close]
|
670
755
|
end
|
671
756
|
|
672
|
-
it
|
757
|
+
it 'client: headers > active > headers > .. > data > close' do
|
673
758
|
order, headers = [], []
|
674
759
|
@client.on(:promise) do |push|
|
675
760
|
order << :reserved
|
676
761
|
|
677
762
|
push.on(:active) { order << :active }
|
678
763
|
push.on(:data) { order << :data }
|
679
|
-
push.on(:half_close){ order << :half_close }
|
764
|
+
push.on(:half_close) { order << :half_close }
|
680
765
|
push.on(:close) { order << :close }
|
681
766
|
|
682
767
|
push.on(:headers) do |h|
|
@@ -684,20 +769,26 @@ describe HTTP2::Stream do
|
|
684
769
|
headers += h
|
685
770
|
end
|
686
771
|
|
687
|
-
push.id.
|
772
|
+
expect(push.id).to be_even
|
688
773
|
end
|
689
774
|
|
690
|
-
@server_stream.promise(
|
691
|
-
push.headers(
|
692
|
-
push.data(
|
775
|
+
@server_stream.promise('key' => 'val') do |push|
|
776
|
+
push.headers('key2' => 'val2')
|
777
|
+
push.data('somedata')
|
693
778
|
end
|
694
779
|
|
695
|
-
headers.
|
696
|
-
order.
|
697
|
-
|
780
|
+
expect(headers).to eq([%w(key val), %w(key2 val2)])
|
781
|
+
expect(order).to eq [
|
782
|
+
:reserved,
|
783
|
+
:headers,
|
784
|
+
:active,
|
785
|
+
:headers,
|
786
|
+
:half_close,
|
787
|
+
:data,
|
788
|
+
:close,
|
789
|
+
]
|
698
790
|
end
|
699
791
|
end
|
700
|
-
|
701
792
|
end
|
702
793
|
end
|
703
794
|
end
|