http-2 0.10.2 → 0.12.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 +0 -2
- data/lib/http/2/buffer.rb +6 -4
- data/lib/http/2/client.rb +5 -1
- data/lib/http/2/compressor.rb +44 -36
- data/lib/http/2/connection.rb +76 -89
- data/lib/http/2/emitter.rb +4 -1
- data/lib/http/2/error.rb +2 -0
- data/lib/http/2/flow_buffer.rb +8 -3
- data/lib/http/2/framer.rb +83 -94
- data/lib/http/2/huffman.rb +19 -17
- data/lib/http/2/server.rb +9 -7
- data/lib/http/2/stream.rb +48 -48
- data/lib/http/2/version.rb +3 -1
- data/lib/http/2.rb +2 -0
- metadata +10 -63
- 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 -122
- data/.travis.yml +0 -14
- data/Gemfile +0 -16
- data/Guardfile +0 -18
- data/Guardfile.h2spec +0 -12
- 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/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 -665
- 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
data/example/upgrade_server.rb
DELETED
@@ -1,203 +0,0 @@
|
|
1
|
-
# frozen_string_literals: true
|
2
|
-
|
3
|
-
require_relative 'helper'
|
4
|
-
require 'http_parser'
|
5
|
-
|
6
|
-
options = { port: 8080 }
|
7
|
-
OptionParser.new do |opts|
|
8
|
-
opts.banner = 'Usage: server.rb [options]'
|
9
|
-
|
10
|
-
opts.on('-s', '--secure', 'HTTPS mode') do |v|
|
11
|
-
options[:secure] = v
|
12
|
-
end
|
13
|
-
|
14
|
-
opts.on('-p', '--port [Integer]', 'listen port') do |v|
|
15
|
-
options[:port] = v
|
16
|
-
end
|
17
|
-
end.parse!
|
18
|
-
|
19
|
-
puts "Starting server on port #{options[:port]}"
|
20
|
-
server = TCPServer.new(options[:port])
|
21
|
-
|
22
|
-
if options[:secure]
|
23
|
-
ctx = OpenSSL::SSL::SSLContext.new
|
24
|
-
ctx.cert = OpenSSL::X509::Certificate.new(File.open('keys/server.crt'))
|
25
|
-
ctx.key = OpenSSL::PKey::RSA.new(File.open('keys/server.key'))
|
26
|
-
ctx.npn_protocols = [DRAFT]
|
27
|
-
|
28
|
-
server = OpenSSL::SSL::SSLServer.new(server, ctx)
|
29
|
-
end
|
30
|
-
|
31
|
-
def request_header_hash
|
32
|
-
Hash.new do |hash, key|
|
33
|
-
k = key.to_s.downcase
|
34
|
-
k.tr! '_', '-'
|
35
|
-
_, value = hash.find { |header_key, _| header_key.downcase == k }
|
36
|
-
hash[key] = value if value
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
class UpgradeHandler
|
41
|
-
VALID_UPGRADE_METHODS = %w(GET OPTIONS).freeze
|
42
|
-
UPGRADE_RESPONSE = <<RESP.freeze
|
43
|
-
HTTP/1.1 101 Switching Protocols
|
44
|
-
Connection: Upgrade
|
45
|
-
Upgrade: h2c
|
46
|
-
|
47
|
-
RESP
|
48
|
-
|
49
|
-
attr_reader :complete, :headers, :body, :parsing
|
50
|
-
|
51
|
-
def initialize(conn, sock)
|
52
|
-
@conn, @sock = conn, sock
|
53
|
-
@complete, @parsing = false, false
|
54
|
-
@headers = request_header_hash
|
55
|
-
@body = ''
|
56
|
-
@parser = ::HTTP::Parser.new(self)
|
57
|
-
end
|
58
|
-
|
59
|
-
def <<(data)
|
60
|
-
@parsing ||= true
|
61
|
-
@parser << data
|
62
|
-
return unless complete
|
63
|
-
|
64
|
-
@sock.write UPGRADE_RESPONSE
|
65
|
-
|
66
|
-
settings = headers['http2-settings']
|
67
|
-
request = {
|
68
|
-
':scheme' => 'http',
|
69
|
-
':method' => @parser.http_method,
|
70
|
-
':authority' => headers['Host'],
|
71
|
-
':path' => @parser.request_url,
|
72
|
-
}.merge(headers)
|
73
|
-
|
74
|
-
@conn.upgrade(settings, request, @body)
|
75
|
-
end
|
76
|
-
|
77
|
-
def complete!
|
78
|
-
@complete = true
|
79
|
-
end
|
80
|
-
|
81
|
-
def on_headers_complete(headers)
|
82
|
-
@headers.merge! headers
|
83
|
-
end
|
84
|
-
|
85
|
-
def on_body(chunk)
|
86
|
-
@body << chunk
|
87
|
-
end
|
88
|
-
|
89
|
-
def on_message_complete
|
90
|
-
fail unless VALID_UPGRADE_METHODS.include?(@parser.http_method)
|
91
|
-
@parsing = false
|
92
|
-
complete!
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
loop do
|
97
|
-
sock = server.accept
|
98
|
-
puts 'New TCP connection!'
|
99
|
-
|
100
|
-
conn = HTTP2::Server.new
|
101
|
-
conn.on(:frame) do |bytes|
|
102
|
-
# puts "Writing bytes: #{bytes.unpack("H*").first}"
|
103
|
-
sock.write bytes
|
104
|
-
end
|
105
|
-
conn.on(:frame_sent) do |frame|
|
106
|
-
puts "Sent frame: #{frame.inspect}"
|
107
|
-
end
|
108
|
-
conn.on(:frame_received) do |frame|
|
109
|
-
puts "Received frame: #{frame.inspect}"
|
110
|
-
end
|
111
|
-
|
112
|
-
conn.on(:stream) do |stream|
|
113
|
-
log = Logger.new(stream.id)
|
114
|
-
req = request_header_hash
|
115
|
-
buffer = ''
|
116
|
-
|
117
|
-
stream.on(:active) { log.info 'client opened new stream' }
|
118
|
-
stream.on(:close) do
|
119
|
-
log.info 'stream closed'
|
120
|
-
end
|
121
|
-
|
122
|
-
stream.on(:headers) do |h|
|
123
|
-
req.merge! Hash[*h.flatten]
|
124
|
-
log.info "request headers: #{h}"
|
125
|
-
end
|
126
|
-
|
127
|
-
stream.on(:data) do |d|
|
128
|
-
log.info "payload chunk: <<#{d}>>"
|
129
|
-
buffer << d
|
130
|
-
end
|
131
|
-
|
132
|
-
stream.on(:half_close) do
|
133
|
-
log.info 'client closed its end of the stream'
|
134
|
-
|
135
|
-
if req['Upgrade']
|
136
|
-
log.info "Processing h2c Upgrade request: #{req}"
|
137
|
-
if req[':method'] != 'OPTIONS' # Don't respond to OPTIONS...
|
138
|
-
response = 'Hello h2c world!'
|
139
|
-
stream.headers({
|
140
|
-
':status' => '200',
|
141
|
-
'content-length' => response.bytesize.to_s,
|
142
|
-
'content-type' => 'text/plain',
|
143
|
-
}, end_stream: false)
|
144
|
-
stream.data(response)
|
145
|
-
end
|
146
|
-
else
|
147
|
-
|
148
|
-
response = nil
|
149
|
-
if req[':method'] == 'POST'
|
150
|
-
log.info "Received POST request, payload: #{buffer}"
|
151
|
-
response = "Hello HTTP 2.0! POST payload: #{buffer}"
|
152
|
-
else
|
153
|
-
log.info 'Received GET request'
|
154
|
-
response = 'Hello HTTP 2.0! GET request'
|
155
|
-
end
|
156
|
-
|
157
|
-
stream.headers({
|
158
|
-
':status' => '200',
|
159
|
-
'content-length' => response.bytesize.to_s,
|
160
|
-
'content-type' => 'text/plain',
|
161
|
-
}, end_stream: false)
|
162
|
-
|
163
|
-
# split response into multiple DATA frames
|
164
|
-
stream.data(response.slice!(0, 5), end_stream: false)
|
165
|
-
stream.data(response)
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
uh = UpgradeHandler.new(conn, sock)
|
171
|
-
|
172
|
-
while !sock.closed? && !(sock.eof? rescue true) # rubocop:disable Style/RescueModifier
|
173
|
-
data = sock.readpartial(1024)
|
174
|
-
# puts "Received bytes: #{data.unpack("H*").first}"
|
175
|
-
|
176
|
-
begin
|
177
|
-
case
|
178
|
-
when !uh.parsing && !uh.complete
|
179
|
-
|
180
|
-
if data.start_with?(*UpgradeHandler::VALID_UPGRADE_METHODS)
|
181
|
-
uh << data
|
182
|
-
else
|
183
|
-
uh.complete!
|
184
|
-
conn << data
|
185
|
-
end
|
186
|
-
|
187
|
-
when uh.parsing && !uh.complete
|
188
|
-
uh << data
|
189
|
-
|
190
|
-
when uh.complete
|
191
|
-
conn << data
|
192
|
-
end
|
193
|
-
|
194
|
-
rescue StandardError => e
|
195
|
-
puts "Exception: #{e}, #{e.message} - closing socket."
|
196
|
-
puts e.backtrace.last(10).join("\n")
|
197
|
-
sock.close
|
198
|
-
end
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
# echo foo=bar | nghttp -d - -t 0 -vu http://127.0.0.1:8080/
|
203
|
-
# nghttp -vu http://127.0.0.1:8080/
|
data/http-2.gemspec
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
lib = File.expand_path('./lib', __dir__)
|
2
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
-
require 'http/2/version'
|
4
|
-
|
5
|
-
Gem::Specification.new do |spec|
|
6
|
-
spec.name = 'http-2'
|
7
|
-
spec.version = HTTP2::VERSION
|
8
|
-
spec.authors = ['Ilya Grigorik', 'Kaoru Maeda']
|
9
|
-
spec.email = ['ilya@igvita.com']
|
10
|
-
spec.description = 'Pure-ruby HTTP 2.0 protocol implementation'
|
11
|
-
spec.summary = spec.description
|
12
|
-
spec.homepage = 'https://github.com/igrigorik/http-2'
|
13
|
-
spec.license = 'MIT'
|
14
|
-
spec.required_ruby_version = '>=2.1.0'
|
15
|
-
|
16
|
-
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
17
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = ['lib']
|
20
|
-
|
21
|
-
spec.add_development_dependency 'bundler'
|
22
|
-
end
|
@@ -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
|