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