http-2 0.11.0 → 0.12.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 +0 -2
- data/lib/http/2/buffer.rb +6 -4
- data/lib/http/2/client.rb +5 -1
- data/lib/http/2/compressor.rb +42 -34
- data/lib/http/2/connection.rb +72 -86
- 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 +7 -60
- 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/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 -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
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
|