http-2 0.11.0 → 1.0.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/README.md +10 -9
- data/lib/http/2/base64.rb +45 -0
- data/lib/http/2/client.rb +19 -6
- data/lib/http/2/connection.rb +235 -163
- data/lib/http/2/emitter.rb +7 -5
- data/lib/http/2/error.rb +24 -1
- data/lib/http/2/extensions.rb +53 -0
- data/lib/http/2/flow_buffer.rb +91 -33
- data/lib/http/2/framer.rb +184 -157
- data/lib/http/2/header/compressor.rb +157 -0
- data/lib/http/2/header/decompressor.rb +144 -0
- data/lib/http/2/header/encoding_context.rb +337 -0
- data/lib/http/2/{huffman.rb → header/huffman.rb} +25 -19
- data/lib/http/2/{huffman_statemachine.rb → header/huffman_statemachine.rb} +2 -0
- data/lib/http/2/header.rb +35 -0
- data/lib/http/2/server.rb +47 -20
- data/lib/http/2/stream.rb +130 -61
- data/lib/http/2/version.rb +3 -1
- data/lib/http/2.rb +14 -13
- data/sig/client.rbs +9 -0
- data/sig/connection.rbs +93 -0
- data/sig/emitter.rbs +13 -0
- data/sig/error.rbs +35 -0
- data/sig/extensions.rbs +5 -0
- data/sig/flow_buffer.rbs +21 -0
- data/sig/frame_buffer.rbs +13 -0
- data/sig/framer.rbs +54 -0
- data/sig/header/compressor.rbs +27 -0
- data/sig/header/decompressor.rbs +22 -0
- data/sig/header/encoding_context.rbs +34 -0
- data/sig/header/huffman.rbs +9 -0
- data/sig/header.rbs +27 -0
- data/sig/next.rbs +101 -0
- data/sig/server.rbs +12 -0
- data/sig/stream.rbs +91 -0
- metadata +38 -79
- data/.autotest +0 -20
- data/.coveralls.yml +0 -1
- data/.gitignore +0 -20
- data/.gitmodules +0 -3
- data/.rspec +0 -5
- data/.rubocop.yml +0 -93
- data/.rubocop_todo.yml +0 -131
- data/.travis.yml +0 -17
- data/Gemfile +0 -16
- data/Guardfile +0 -18
- data/Guardfile.h2spec +0 -12
- data/LICENSE +0 -21
- data/Rakefile +0 -49
- data/example/Gemfile +0 -3
- data/example/README.md +0 -44
- data/example/client.rb +0 -122
- data/example/helper.rb +0 -19
- data/example/keys/server.crt +0 -20
- data/example/keys/server.key +0 -27
- data/example/server.rb +0 -139
- data/example/upgrade_client.rb +0 -153
- data/example/upgrade_server.rb +0 -203
- data/http-2.gemspec +0 -22
- data/lib/http/2/buffer.rb +0 -76
- data/lib/http/2/compressor.rb +0 -572
- data/lib/tasks/generate_huffman_table.rb +0 -166
- data/spec/buffer_spec.rb +0 -28
- data/spec/client_spec.rb +0 -188
- data/spec/compressor_spec.rb +0 -666
- data/spec/connection_spec.rb +0 -681
- data/spec/emitter_spec.rb +0 -54
- data/spec/framer_spec.rb +0 -487
- data/spec/h2spec/h2spec.darwin +0 -0
- data/spec/h2spec/output/non_secure.txt +0 -317
- data/spec/helper.rb +0 -147
- data/spec/hpack_test_spec.rb +0 -84
- data/spec/huffman_spec.rb +0 -68
- data/spec/server_spec.rb +0 -52
- data/spec/stream_spec.rb +0 -878
- data/spec/support/deep_dup.rb +0 -55
- data/spec/support/duplicable.rb +0 -98
@@ -1,166 +0,0 @@
|
|
1
|
-
desc 'Generate Huffman precompiled table in huffman_statemachine.rb'
|
2
|
-
task :generate_table do
|
3
|
-
HuffmanTable::Node.generate_state_table
|
4
|
-
end
|
5
|
-
|
6
|
-
require_relative '../http/2/huffman'
|
7
|
-
|
8
|
-
# @private
|
9
|
-
module HuffmanTable
|
10
|
-
BITS_AT_ONCE = HTTP2::Header::Huffman::BITS_AT_ONCE
|
11
|
-
EOS = 256
|
12
|
-
|
13
|
-
class Node
|
14
|
-
attr_accessor :next, :emit, :final, :depth
|
15
|
-
attr_accessor :transitions
|
16
|
-
attr_accessor :id
|
17
|
-
@@id = 0 # rubocop:disable Style/ClassVars
|
18
|
-
def initialize(depth)
|
19
|
-
@next = [nil, nil]
|
20
|
-
@id = @@id
|
21
|
-
@@id += 1 # rubocop:disable Style/ClassVars
|
22
|
-
@final = false
|
23
|
-
@depth = depth
|
24
|
-
end
|
25
|
-
|
26
|
-
def add(code, len, chr)
|
27
|
-
self.final = true if chr == EOS && @depth <= 7
|
28
|
-
if len.zero?
|
29
|
-
@emit = chr
|
30
|
-
else
|
31
|
-
bit = (code & (1 << (len - 1))).zero? ? 0 : 1
|
32
|
-
node = @next[bit] ||= Node.new(@depth + 1)
|
33
|
-
node.add(code, len - 1, chr)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
class Transition
|
38
|
-
attr_accessor :emit, :node
|
39
|
-
def initialize(emit, node)
|
40
|
-
@emit = emit
|
41
|
-
@node = node
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def self.generate_tree
|
46
|
-
@root = new(0)
|
47
|
-
HTTP2::Header::Huffman::CODES.each_with_index do |c, chr|
|
48
|
-
code, len = c
|
49
|
-
@root.add(code, len, chr)
|
50
|
-
end
|
51
|
-
puts "#{@@id} nodes"
|
52
|
-
@root
|
53
|
-
end
|
54
|
-
|
55
|
-
def self.generate_machine
|
56
|
-
generate_tree
|
57
|
-
togo = Set[@root]
|
58
|
-
@states = Set[@root]
|
59
|
-
|
60
|
-
until togo.empty?
|
61
|
-
node = togo.first
|
62
|
-
togo.delete(node)
|
63
|
-
|
64
|
-
next if node.transitions
|
65
|
-
node.transitions = Array[1 << BITS_AT_ONCE]
|
66
|
-
|
67
|
-
(1 << BITS_AT_ONCE).times do |input|
|
68
|
-
n = node
|
69
|
-
emit = ''
|
70
|
-
(BITS_AT_ONCE - 1).downto(0) do |i|
|
71
|
-
bit = (input & (1 << i)).zero? ? 0 : 1
|
72
|
-
n = n.next[bit]
|
73
|
-
next unless n.emit
|
74
|
-
if n.emit == EOS
|
75
|
-
emit = EOS # cause error on decoding
|
76
|
-
else
|
77
|
-
emit << n.emit.chr(Encoding::BINARY) unless emit == EOS
|
78
|
-
end
|
79
|
-
n = @root
|
80
|
-
end
|
81
|
-
node.transitions[input] = Transition.new(emit, n)
|
82
|
-
togo << n
|
83
|
-
@states << n
|
84
|
-
end
|
85
|
-
end
|
86
|
-
puts "#{@states.size} states"
|
87
|
-
@root
|
88
|
-
end
|
89
|
-
|
90
|
-
def self.generate_state_table
|
91
|
-
generate_machine
|
92
|
-
state_id = {}
|
93
|
-
id_state = {}
|
94
|
-
state_id[@root] = 0
|
95
|
-
id_state[0] = @root
|
96
|
-
max_final = 0
|
97
|
-
id = 1
|
98
|
-
(@states - [@root]).sort_by { |s| s.final ? 0 : 1 }.each do |s|
|
99
|
-
state_id[s] = id
|
100
|
-
id_state[id] = s
|
101
|
-
max_final = id if s.final
|
102
|
-
id += 1
|
103
|
-
end
|
104
|
-
|
105
|
-
File.open(File.expand_path('../http/2/huffman_statemachine.rb', File.dirname(__FILE__)), 'w') do |f|
|
106
|
-
f.print <<HEADER
|
107
|
-
# Machine generated Huffman decoder state machine.
|
108
|
-
# DO NOT EDIT THIS FILE.
|
109
|
-
|
110
|
-
# The following task generates this file.
|
111
|
-
# rake generate_huffman_table
|
112
|
-
|
113
|
-
module HTTP2
|
114
|
-
module Header
|
115
|
-
class Huffman
|
116
|
-
# :nodoc:
|
117
|
-
MAX_FINAL_STATE = #{max_final}
|
118
|
-
MACHINE = [
|
119
|
-
HEADER
|
120
|
-
id.times do |i|
|
121
|
-
n = id_state[i]
|
122
|
-
f.print ' ['
|
123
|
-
string = (1 << BITS_AT_ONCE).times.map do |t|
|
124
|
-
transition = n.transitions.fetch(t)
|
125
|
-
emit = transition.emit
|
126
|
-
unless emit == EOS
|
127
|
-
bytes = emit.bytes
|
128
|
-
fail ArgumentError if bytes.size > 1
|
129
|
-
emit = bytes.first
|
130
|
-
end
|
131
|
-
"[#{emit.inspect}, #{state_id.fetch(transition.node)}]"
|
132
|
-
end.join(', ')
|
133
|
-
f.print(string)
|
134
|
-
f.print "],\n"
|
135
|
-
end
|
136
|
-
f.print <<TAILER
|
137
|
-
].each { |arr| arr.each { |subarr| subarr.each(&:freeze) }.freeze }.freeze
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
141
|
-
TAILER
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
class << self
|
146
|
-
attr_reader :root
|
147
|
-
end
|
148
|
-
|
149
|
-
# Test decoder
|
150
|
-
def self.decode(input)
|
151
|
-
emit = ''
|
152
|
-
n = root
|
153
|
-
nibbles = input.unpack('C*').flat_map { |b| [((b & 0xf0) >> 4), b & 0xf] }
|
154
|
-
until nibbles.empty?
|
155
|
-
nb = nibbles.shift
|
156
|
-
t = n.transitions[nb]
|
157
|
-
emit << t.emit
|
158
|
-
n = t.node
|
159
|
-
end
|
160
|
-
unless n.final && nibbles.all? { |x| x == 0xf }
|
161
|
-
puts "len = #{emit.size} n.final = #{n.final} nibbles = #{nibbles}"
|
162
|
-
end
|
163
|
-
emit
|
164
|
-
end
|
165
|
-
end
|
166
|
-
end
|
data/spec/buffer_spec.rb
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
require 'helper'
|
2
|
-
|
3
|
-
RSpec.describe HTTP2::Buffer do
|
4
|
-
let(:b) { Buffer.new('émalgré') }
|
5
|
-
|
6
|
-
it 'should force 8-bit encoding' do
|
7
|
-
expect(b.encoding.to_s).to eq 'ASCII-8BIT'
|
8
|
-
end
|
9
|
-
|
10
|
-
it 'should force 8-bit encoding when adding data' do
|
11
|
-
b << 'émalgré'
|
12
|
-
expect(b.encoding.to_s).to eq 'ASCII-8BIT'
|
13
|
-
b.prepend('émalgré')
|
14
|
-
expect(b.encoding.to_s).to eq 'ASCII-8BIT'
|
15
|
-
end
|
16
|
-
|
17
|
-
it 'should return bytesize of the buffer' do
|
18
|
-
expect(b.size).to eq 9
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'should read single byte at a time' do
|
22
|
-
9.times { expect(b.read(1)).not_to be_nil }
|
23
|
-
end
|
24
|
-
|
25
|
-
it 'should unpack an unsigned 32-bit int' do
|
26
|
-
expect(Buffer.new([256].pack('N')).read_uint32).to eq 256
|
27
|
-
end
|
28
|
-
end
|
data/spec/client_spec.rb
DELETED
@@ -1,188 +0,0 @@
|
|
1
|
-
require 'helper'
|
2
|
-
|
3
|
-
RSpec.describe HTTP2::Client do
|
4
|
-
include FrameHelpers
|
5
|
-
before(:each) do
|
6
|
-
@client = Client.new
|
7
|
-
end
|
8
|
-
|
9
|
-
let(:f) { Framer.new }
|
10
|
-
|
11
|
-
context 'initialization and settings' do
|
12
|
-
it 'should return odd stream IDs' do
|
13
|
-
expect(@client.new_stream.id).not_to be_even
|
14
|
-
end
|
15
|
-
|
16
|
-
it 'should emit connection header and SETTINGS on new client connection' do
|
17
|
-
frames = []
|
18
|
-
@client.on(:frame) { |bytes| frames << bytes }
|
19
|
-
@client.ping('12345678')
|
20
|
-
|
21
|
-
expect(frames[0]).to eq CONNECTION_PREFACE_MAGIC
|
22
|
-
expect(f.parse(frames[1])[:type]).to eq :settings
|
23
|
-
end
|
24
|
-
|
25
|
-
it 'should initialize client with custom connection settings' do
|
26
|
-
frames = []
|
27
|
-
|
28
|
-
@client = Client.new(settings_max_concurrent_streams: 200)
|
29
|
-
@client.on(:frame) { |bytes| frames << bytes }
|
30
|
-
@client.ping('12345678')
|
31
|
-
|
32
|
-
frame = f.parse(frames[1])
|
33
|
-
expect(frame[:type]).to eq :settings
|
34
|
-
expect(frame[:payload]).to include([:settings_max_concurrent_streams, 200])
|
35
|
-
end
|
36
|
-
|
37
|
-
it 'should initialize client when receiving server settings before sending ack' do
|
38
|
-
frames = []
|
39
|
-
@client.on(:frame) { |bytes| frames << bytes }
|
40
|
-
@client << f.generate(settings_frame)
|
41
|
-
|
42
|
-
expect(frames[0]).to eq CONNECTION_PREFACE_MAGIC
|
43
|
-
expect(f.parse(frames[1])[:type]).to eq :settings
|
44
|
-
ack_frame = f.parse(frames[2])
|
45
|
-
expect(ack_frame[:type]).to eq :settings
|
46
|
-
expect(ack_frame[:flags]).to include(:ack)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
context 'upgrade' do
|
51
|
-
it 'fails when client has already created streams' do
|
52
|
-
@client.new_stream
|
53
|
-
expect { @client.upgrade }.to raise_error(HTTP2::Error::ProtocolError)
|
54
|
-
end
|
55
|
-
|
56
|
-
it 'sends the preface' do
|
57
|
-
expect(@client).to receive(:send_connection_preface)
|
58
|
-
@client.upgrade
|
59
|
-
end
|
60
|
-
|
61
|
-
it 'initializes the first stream in the half-closed state' do
|
62
|
-
stream = @client.upgrade
|
63
|
-
expect(stream.state).to be(:half_closed_local)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
context 'push' do
|
68
|
-
it 'should disallow client initiated push' do
|
69
|
-
expect do
|
70
|
-
@client.promise({}) {}
|
71
|
-
end.to raise_error(NoMethodError)
|
72
|
-
end
|
73
|
-
|
74
|
-
it 'should raise error on PUSH_PROMISE against stream 0' do
|
75
|
-
expect do
|
76
|
-
@client << set_stream_id(f.generate(push_promise_frame), 0)
|
77
|
-
end.to raise_error(ProtocolError)
|
78
|
-
end
|
79
|
-
|
80
|
-
it 'should raise error on PUSH_PROMISE against bogus stream' do
|
81
|
-
expect do
|
82
|
-
@client << set_stream_id(f.generate(push_promise_frame), 31_415)
|
83
|
-
end.to raise_error(ProtocolError)
|
84
|
-
end
|
85
|
-
|
86
|
-
it 'should raise error on PUSH_PROMISE against non-idle stream' do
|
87
|
-
expect do
|
88
|
-
s = @client.new_stream
|
89
|
-
s.send headers_frame
|
90
|
-
|
91
|
-
@client << set_stream_id(f.generate(push_promise_frame), s.id)
|
92
|
-
@client << set_stream_id(f.generate(push_promise_frame), s.id)
|
93
|
-
end.to raise_error(ProtocolError)
|
94
|
-
end
|
95
|
-
|
96
|
-
it 'should emit stream object for received PUSH_PROMISE' do
|
97
|
-
s = @client.new_stream
|
98
|
-
s.send headers_frame
|
99
|
-
|
100
|
-
promise = nil
|
101
|
-
@client.on(:promise) { |stream| promise = stream }
|
102
|
-
@client << set_stream_id(f.generate(push_promise_frame), s.id)
|
103
|
-
|
104
|
-
expect(promise.id).to eq 2
|
105
|
-
expect(promise.state).to eq :reserved_remote
|
106
|
-
end
|
107
|
-
|
108
|
-
it 'should emit promise headers for received PUSH_PROMISE' do
|
109
|
-
header = nil
|
110
|
-
s = @client.new_stream
|
111
|
-
s.send headers_frame
|
112
|
-
|
113
|
-
@client.on(:promise) do |stream|
|
114
|
-
stream.on(:promise_headers) do |h|
|
115
|
-
header = h
|
116
|
-
end
|
117
|
-
end
|
118
|
-
@client << set_stream_id(f.generate(push_promise_frame), s.id)
|
119
|
-
|
120
|
-
expect(header).to be_a(Array)
|
121
|
-
# expect(header).to eq([%w(a b)])
|
122
|
-
end
|
123
|
-
|
124
|
-
it 'should auto RST_STREAM promises against locally-RST stream' do
|
125
|
-
s = @client.new_stream
|
126
|
-
s.send headers_frame
|
127
|
-
s.close
|
128
|
-
|
129
|
-
allow(@client).to receive(:send)
|
130
|
-
expect(@client).to receive(:send) do |frame|
|
131
|
-
expect(frame[:type]).to eq :rst_stream
|
132
|
-
expect(frame[:stream]).to eq 2
|
133
|
-
end
|
134
|
-
|
135
|
-
@client << set_stream_id(f.generate(push_promise_frame), s.id)
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
context 'alt-svc' do
|
140
|
-
context 'received in the connection' do
|
141
|
-
it 'should emit :altsvc when receiving one' do
|
142
|
-
@client << f.generate(settings_frame)
|
143
|
-
frame = nil
|
144
|
-
@client.on(:altsvc) do |f|
|
145
|
-
frame = f
|
146
|
-
end
|
147
|
-
@client << f.generate(altsvc_frame)
|
148
|
-
expect(frame).to be_a(Hash)
|
149
|
-
end
|
150
|
-
it 'should not emit :altsvc when the frame when contains no host' do
|
151
|
-
@client << f.generate(settings_frame)
|
152
|
-
frame = nil
|
153
|
-
@client.on(:altsvc) do |f|
|
154
|
-
frame = f
|
155
|
-
end
|
156
|
-
|
157
|
-
@client << f.generate(altsvc_frame.merge(origin: nil))
|
158
|
-
expect(frame).to be_nil
|
159
|
-
end
|
160
|
-
end
|
161
|
-
context 'received in a stream' do
|
162
|
-
it 'should emit :altsvc' do
|
163
|
-
s = @client.new_stream
|
164
|
-
s.send headers_frame
|
165
|
-
s.close
|
166
|
-
|
167
|
-
frame = nil
|
168
|
-
s.on(:altsvc) { |f| frame = f }
|
169
|
-
|
170
|
-
@client << set_stream_id(f.generate(altsvc_frame.merge(origin: nil)), s.id)
|
171
|
-
|
172
|
-
expect(frame).to be_a(Hash)
|
173
|
-
end
|
174
|
-
it 'should not emit :alt_svc when the frame when contains a origin' do
|
175
|
-
s = @client.new_stream
|
176
|
-
s.send headers_frame
|
177
|
-
s.close
|
178
|
-
|
179
|
-
frame = nil
|
180
|
-
s.on(:altsvc) { |f| frame = f }
|
181
|
-
|
182
|
-
@client << set_stream_id(f.generate(altsvc_frame), s.id)
|
183
|
-
|
184
|
-
expect(frame).to be_nil
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|