http-2 0.8.2 → 0.8.3
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/.gitignore +3 -0
- data/.rubocop.yml +41 -2
- data/.rubocop_todo.yml +85 -12
- data/Gemfile +1 -2
- data/Guardfile +19 -0
- data/Guardfile.h2spec +12 -0
- data/README.md +18 -9
- data/Rakefile +39 -1
- data/example/Gemfile +3 -0
- data/example/README.md +4 -0
- data/example/client.rb +10 -9
- data/example/helper.rb +1 -1
- data/example/keys/server.crt +20 -0
- data/example/keys/server.key +27 -0
- data/example/server.rb +48 -4
- data/example/upgrade_server.rb +20 -10
- data/lib/http/2/buffer.rb +42 -14
- data/lib/http/2/compressor.rb +2 -2
- data/lib/http/2/connection.rb +38 -16
- data/lib/http/2/emitter.rb +1 -1
- data/lib/http/2/framer.rb +12 -7
- data/lib/http/2/server.rb +2 -1
- data/lib/http/2/stream.rb +25 -11
- data/lib/http/2/version.rb +1 -1
- data/lib/tasks/generate_huffman_table.rb +3 -3
- data/spec/client_spec.rb +3 -3
- data/spec/connection_spec.rb +0 -1
- data/spec/h2spec/h2spec.darwin +0 -0
- data/spec/h2spec/output/non_secure.txt +317 -0
- data/spec/helper.rb +1 -1
- data/spec/hpack_test_spec.rb +1 -1
- data/spec/stream_spec.rb +22 -1
- data/spec/support/deep_dup.rb +55 -0
- data/spec/support/duplicable.rb +98 -0
- metadata +16 -5
- data/example/keys/mycert.pem +0 -23
- data/example/keys/mykey.pem +0 -27
@@ -0,0 +1,27 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIIEpAIBAAKCAQEAoxV61qrUWRDDjv6OgMRm4A2DIIP+7sQhUCTOWeguCCOg/Ffe
|
3
|
+
BQ8gnfidCzitoQfkFHRaqQNUvekVtjLyMHb0bdN0STIG33FBdPsOGSLb9sa1vcGg
|
4
|
+
nwpi0kQ2YHEgE2nBBervadlbPqOCf3B2nIq0sI8uPpveAoJMbVBRrRnCWhagGegn
|
5
|
+
Y+Vr0HSUogCN8d4DQv1BgPuXS7KfPr8Fsfv0zHOpwwZqEVtxh3hvzQKUEodNcN8A
|
6
|
+
79BZlGhUJQOVj8mvybPgsrmIdZol20CRIw5xhmiY56MkQEKT65O9iY/m53zrvZMa
|
7
|
+
c5LiyTrG2qH0RZDPvwK+VPm0M7lNBK0PCIfDDwIDAQABAoIBAQCZShphZsccRJ6c
|
8
|
+
bPdDX9iW5vyG9qsMgPwTGdWAOrXx3pN2PZ0pwjNVaRcsMgU6JHGlLEz/Kmtf6pQG
|
9
|
+
41I0bcuI48Yc+tHs+sadD1IMHHEHP3Yau8KfWyLSI129Pvf4Z2IQjuik5LJYaVbD
|
10
|
+
NNG4iMQYZS0Bmn6Oey0dXu62t0ywYa0qvbIDse/RmjTQSTipuvGg8/QEAeRGABv8
|
11
|
+
Nd4Esya0zuxk6hGaNp3hkjyRkeoC7RsBVJbFSnp6gSubPdXwrJyHfySKe9jvrDG3
|
12
|
+
Q/AzyHUh/6EODd5n66x0p6rq7oo9/PnLvZJY8jIGWG+aEp68RJyEgimrwll0rAWw
|
13
|
+
/buqijGRAoGBANimL8407fFirmct7BceavaeJfXPK5yWiOhVX0XlJ0phAFuaAxK3
|
14
|
+
5HVT7DD+KKV66g1jtS9FUVZGDiYFHlsdsYuHVYcRmr0h5rZr941obrDwNrM9Nf9C
|
15
|
+
0uehN5+n/FaeGoQLR3V4THoP3rlkYTlLpQnI5mKA19JukXnIiJM9ARUZAoGBAMC0
|
16
|
+
mcVsVuSKSFwURtQHHIufxL6SqC2kLTwIQ7exqejNYPCqCiif+ZWOmsTqbVGAGbMK
|
17
|
+
Ohak4oLwN5IGCl4jNQG+vWagREkx6OXSk5NYcfoNBrOm+0UoFRzoEA85s7Dy6PuD
|
18
|
+
tBucNZpt1sGauzkCSx7C8jj4ZlSwkv0XhBFfbTZnAoGBAK2wBjF+U6iq4YFM2rLq
|
19
|
+
KvzOa0Z3MdKXCOmiz//cKDTEMaI+heoyzZCWmIvqpzGLqirT3gUowH23Kk6m2eBY
|
20
|
+
nOdst0/S+Eha7nkfc9bFe8CUxHXMRAcCTs1ufYadCXtzw3RLCp4NtNpC8N+Wry9d
|
21
|
+
CtIeYz1jaCOHi0+kSoIobT65AoGAc6hxWkJp7ITqZQlucTdLdKmRheeztKEC3TMA
|
22
|
+
obGqDqWldww3SKarP431ahZhQjcmNYT/1DNmF7xhPe0OL+3llISMXJn4Ig4ogDdg
|
23
|
+
h2DgF3nV+eFQkfM6qLzHVrwFE0DXgI1NffzFV0hxSoW5tL+honbStkqv8EiCEBEb
|
24
|
+
HOovPCUCgYBpXuPARd2ycInAulVHijJmj2rmK7f41ZhVCWovYjcCWyeJyLIO7j+b
|
25
|
+
MBJZbmwpStJhEjW64nE2zZGWg2HCBbvZz5/SXIr3fp7qVXwpn1TvB/TJDf43t0oF
|
26
|
+
3caLgyQYoQCsVHKT3cU4s3wuog/DyHKh9FtRkcJrEy7h9Rrc+ModbA==
|
27
|
+
-----END RSA PRIVATE KEY-----
|
data/example/server.rb
CHANGED
@@ -11,6 +11,10 @@ OptionParser.new do |opts|
|
|
11
11
|
opts.on('-p', '--port [Integer]', 'listen port') do |v|
|
12
12
|
options[:port] = v
|
13
13
|
end
|
14
|
+
|
15
|
+
opts.on('-u', '--push', 'Push message') do |_v|
|
16
|
+
options[:push] = true
|
17
|
+
end
|
14
18
|
end.parse!
|
15
19
|
|
16
20
|
puts "Starting server on port #{options[:port]}"
|
@@ -18,9 +22,23 @@ server = TCPServer.new(options[:port])
|
|
18
22
|
|
19
23
|
if options[:secure]
|
20
24
|
ctx = OpenSSL::SSL::SSLContext.new
|
21
|
-
ctx.cert = OpenSSL::X509::Certificate.new(File.open('keys/
|
22
|
-
ctx.key = OpenSSL::PKey::RSA.new(File.open('keys/
|
23
|
-
|
25
|
+
ctx.cert = OpenSSL::X509::Certificate.new(File.open('keys/server.crt'))
|
26
|
+
ctx.key = OpenSSL::PKey::RSA.new(File.open('keys/server.key'))
|
27
|
+
|
28
|
+
ctx.ssl_version = :TLSv1_2
|
29
|
+
ctx.options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
|
30
|
+
ctx.ciphers = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:ciphers]
|
31
|
+
|
32
|
+
ctx.alpn_protocols = ['h2']
|
33
|
+
|
34
|
+
ctx.alpn_select_cb = lambda do |protocols|
|
35
|
+
raise "Protocol #{DRAFT} is required" if protocols.index(DRAFT).nil?
|
36
|
+
DRAFT
|
37
|
+
end
|
38
|
+
|
39
|
+
ctx.tmp_ecdh_callback = lambda do |*_args|
|
40
|
+
OpenSSL::PKey::EC.new 'prime256v1'
|
41
|
+
end
|
24
42
|
|
25
43
|
server = OpenSSL::SSL::SSLServer.new(server, ctx)
|
26
44
|
end
|
@@ -32,7 +50,7 @@ loop do
|
|
32
50
|
conn = HTTP2::Server.new
|
33
51
|
conn.on(:frame) do |bytes|
|
34
52
|
# puts "Writing bytes: #{bytes.unpack("H*").first}"
|
35
|
-
sock.
|
53
|
+
sock.is_a?(TCPSocket) ? sock.sendmsg(bytes) : sock.write(bytes)
|
36
54
|
end
|
37
55
|
conn.on(:frame_sent) do |frame|
|
38
56
|
puts "Sent frame: #{frame.inspect}"
|
@@ -76,9 +94,35 @@ loop do
|
|
76
94
|
'content-type' => 'text/plain',
|
77
95
|
}, end_stream: false)
|
78
96
|
|
97
|
+
if options[:push]
|
98
|
+
push_streams = []
|
99
|
+
|
100
|
+
# send 10 promises
|
101
|
+
10.times do |i|
|
102
|
+
puts 'sending push'
|
103
|
+
|
104
|
+
head = { ':method' => 'GET',
|
105
|
+
':authority' => 'localhost',
|
106
|
+
':scheme' => 'https',
|
107
|
+
':path' => "/other_resource/#{i}" }
|
108
|
+
|
109
|
+
stream.promise(head) do |push|
|
110
|
+
push.headers(':status' => '200', 'content-type' => 'text/plain', 'content-length' => '11')
|
111
|
+
push_streams << push
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
79
116
|
# split response into multiple DATA frames
|
80
117
|
stream.data(response.slice!(0, 5), end_stream: false)
|
81
118
|
stream.data(response)
|
119
|
+
|
120
|
+
if options[:push]
|
121
|
+
push_streams.each_with_index do |push, i|
|
122
|
+
sleep 1
|
123
|
+
push.data("push_data #{i}")
|
124
|
+
end
|
125
|
+
end
|
82
126
|
end
|
83
127
|
end
|
84
128
|
|
data/example/upgrade_server.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literals: true
|
2
|
+
|
1
3
|
require_relative 'helper'
|
2
4
|
require 'http_parser'
|
3
|
-
require 'base64'
|
4
5
|
|
5
6
|
options = { port: 8080 }
|
6
7
|
OptionParser.new do |opts|
|
@@ -20,15 +21,24 @@ server = TCPServer.new(options[:port])
|
|
20
21
|
|
21
22
|
if options[:secure]
|
22
23
|
ctx = OpenSSL::SSL::SSLContext.new
|
23
|
-
ctx.cert = OpenSSL::X509::Certificate.new(File.open('keys/
|
24
|
-
ctx.key = OpenSSL::PKey::RSA.new(File.open('keys/
|
24
|
+
ctx.cert = OpenSSL::X509::Certificate.new(File.open('keys/server.crt'))
|
25
|
+
ctx.key = OpenSSL::PKey::RSA.new(File.open('keys/server.key'))
|
25
26
|
ctx.npn_protocols = [DRAFT]
|
26
27
|
|
27
28
|
server = OpenSSL::SSL::SSLServer.new(server, ctx)
|
28
29
|
end
|
29
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
|
+
|
30
40
|
class UpgradeHandler
|
31
|
-
VALID_UPGRADE_METHODS = %w(GET OPTIONS)
|
41
|
+
VALID_UPGRADE_METHODS = %w(GET OPTIONS).freeze
|
32
42
|
UPGRADE_RESPONSE = <<-RESP
|
33
43
|
HTTP/1.1 101 Switching Protocols
|
34
44
|
Connection: Upgrade
|
@@ -41,6 +51,7 @@ RESP
|
|
41
51
|
def initialize(conn, sock)
|
42
52
|
@conn, @sock = conn, sock
|
43
53
|
@complete, @parsing = false, false
|
54
|
+
@headers = request_header_hash
|
44
55
|
@body = ''
|
45
56
|
@parser = ::HTTP::Parser.new(self)
|
46
57
|
end
|
@@ -68,7 +79,7 @@ RESP
|
|
68
79
|
end
|
69
80
|
|
70
81
|
def on_headers_complete(headers)
|
71
|
-
@headers
|
82
|
+
@headers.merge! headers
|
72
83
|
end
|
73
84
|
|
74
85
|
def on_body(chunk)
|
@@ -100,7 +111,8 @@ loop do
|
|
100
111
|
|
101
112
|
conn.on(:stream) do |stream|
|
102
113
|
log = Logger.new(stream.id)
|
103
|
-
req
|
114
|
+
req = request_header_hash
|
115
|
+
buffer = ''
|
104
116
|
|
105
117
|
stream.on(:active) { log.info 'client opened new stream' }
|
106
118
|
stream.on(:close) do
|
@@ -108,7 +120,7 @@ loop do
|
|
108
120
|
end
|
109
121
|
|
110
122
|
stream.on(:headers) do |h|
|
111
|
-
req
|
123
|
+
req.merge! Hash[*h.flatten]
|
112
124
|
log.info "request headers: #{h}"
|
113
125
|
end
|
114
126
|
|
@@ -122,9 +134,7 @@ loop do
|
|
122
134
|
|
123
135
|
if req['Upgrade']
|
124
136
|
log.info "Processing h2c Upgrade request: #{req}"
|
125
|
-
|
126
|
-
# Don't respond to OPTIONS...
|
127
|
-
if req[':method'] != 'OPTIONS'
|
137
|
+
if req[':method'] != 'OPTIONS' # Don't respond to OPTIONS...
|
128
138
|
response = 'Hello h2c world!'
|
129
139
|
stream.headers({
|
130
140
|
':status' => '200',
|
data/lib/http/2/buffer.rb
CHANGED
@@ -1,35 +1,61 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
1
3
|
module HTTP2
|
2
|
-
#
|
3
|
-
#
|
4
|
-
# TODO: Refactor, it would be better if Buffer were not a String subclass,
|
5
|
-
# but rather wrap a string and only expose the mutating API needed so that
|
6
|
-
# the possible surface for things to go wrong stays controllable.
|
7
|
-
# - https://github.com/igrigorik/http-2/pull/46
|
4
|
+
# Binary buffer wraps String.
|
8
5
|
#
|
9
|
-
class Buffer
|
6
|
+
class Buffer
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@buffer, :ord, :encoding, :setbyte, :unpack,
|
10
|
+
:size, :each_byte, :to_str, :to_s, :length, :inspect,
|
11
|
+
:[], :[]=, :empty?, :bytesize, :include?
|
12
|
+
|
10
13
|
UINT32 = 'N'.freeze
|
11
14
|
private_constant :UINT32
|
12
15
|
|
13
16
|
# Forces binary encoding on the string
|
14
|
-
def initialize(
|
15
|
-
|
17
|
+
def initialize(str = '')
|
18
|
+
str = str.dup if str.frozen?
|
19
|
+
@buffer = str.force_encoding(Encoding::BINARY)
|
16
20
|
end
|
17
21
|
|
18
22
|
# Emulate StringIO#read: slice first n bytes from the buffer.
|
19
23
|
#
|
20
24
|
# @param n [Integer] number of bytes to slice from the buffer
|
21
25
|
def read(n)
|
22
|
-
Buffer.new(slice!(0, n))
|
26
|
+
Buffer.new(@buffer.slice!(0, n))
|
23
27
|
end
|
24
28
|
|
25
|
-
# Alias getbyte to readbyte
|
26
|
-
alias_method :readbyte, :getbyte
|
27
|
-
|
28
29
|
# Emulate StringIO#getbyte: slice first byte from buffer.
|
29
30
|
def getbyte
|
30
31
|
read(1).ord
|
31
32
|
end
|
32
33
|
|
34
|
+
def slice!(*args)
|
35
|
+
Buffer.new(@buffer.slice!(*args))
|
36
|
+
end
|
37
|
+
|
38
|
+
def slice(*args)
|
39
|
+
Buffer.new(@buffer.slice(*args))
|
40
|
+
end
|
41
|
+
|
42
|
+
def force_encoding(*args)
|
43
|
+
@buffer = @buffer.force_encoding(*args)
|
44
|
+
end
|
45
|
+
|
46
|
+
def ==(other)
|
47
|
+
@buffer == other
|
48
|
+
end
|
49
|
+
|
50
|
+
def +(other)
|
51
|
+
@buffer += other
|
52
|
+
end
|
53
|
+
|
54
|
+
# Emulate String#getbyte: return nth byte from buffer.
|
55
|
+
def readbyte(n)
|
56
|
+
@buffer[n].ord
|
57
|
+
end
|
58
|
+
|
33
59
|
# Slice unsigned 32-bit integer from buffer.
|
34
60
|
# @return [Integer]
|
35
61
|
def read_uint32
|
@@ -41,7 +67,9 @@ module HTTP2
|
|
41
67
|
[:<<, :prepend].each do |mutating_method|
|
42
68
|
define_method(mutating_method) do |string|
|
43
69
|
string = string.dup if string.frozen?
|
44
|
-
|
70
|
+
@buffer.send mutating_method, string.force_encoding(Encoding::BINARY)
|
71
|
+
|
72
|
+
self
|
45
73
|
end
|
46
74
|
end
|
47
75
|
end
|
data/lib/http/2/compressor.rb
CHANGED
@@ -527,12 +527,12 @@ module HTTP2
|
|
527
527
|
|
528
528
|
case header[:type]
|
529
529
|
when :indexed
|
530
|
-
fail CompressionError if header[:name]
|
530
|
+
fail CompressionError if (header[:name]).zero?
|
531
531
|
header[:name] -= 1
|
532
532
|
when :changetablesize
|
533
533
|
header[:value] = header[:name]
|
534
534
|
else
|
535
|
-
if header[:name]
|
535
|
+
if (header[:name]).zero?
|
536
536
|
header[:name] = string(buf)
|
537
537
|
else
|
538
538
|
header[:name] -= 1
|
data/lib/http/2/connection.rb
CHANGED
@@ -56,7 +56,7 @@ module HTTP2
|
|
56
56
|
# infinity, but is automatically updated on receipt of peer settings).
|
57
57
|
attr_reader :local_window
|
58
58
|
attr_reader :remote_window
|
59
|
-
|
59
|
+
alias window local_window
|
60
60
|
|
61
61
|
# Current settings value for local and peer
|
62
62
|
attr_reader :local_settings
|
@@ -81,6 +81,7 @@ module HTTP2
|
|
81
81
|
|
82
82
|
@active_stream_count = 0
|
83
83
|
@streams = {}
|
84
|
+
@streams_recently_closed = {}
|
84
85
|
@pending_settings = []
|
85
86
|
|
86
87
|
@framer = Framer.new
|
@@ -226,6 +227,10 @@ module HTTP2
|
|
226
227
|
else
|
227
228
|
case frame[:type]
|
228
229
|
when :headers
|
230
|
+
# When server receives even-numbered stream identifier,
|
231
|
+
# the endpoint MUST respond with a connection error of type PROTOCOL_ERROR.
|
232
|
+
connection_error if frame[:stream].even? && self.is_a?(Server)
|
233
|
+
|
229
234
|
# The last frame in a sequence of HEADERS/CONTINUATION
|
230
235
|
# frames MUST have the END_HEADERS flag set.
|
231
236
|
unless frame[:flags].include? :end_headers
|
@@ -278,6 +283,12 @@ module HTTP2
|
|
278
283
|
parent = @streams[frame[:stream]]
|
279
284
|
pid = frame[:promise_stream]
|
280
285
|
|
286
|
+
# if PUSH parent is recently closed, RST_STREAM the push
|
287
|
+
if @streams_recently_closed[frame[:stream]]
|
288
|
+
send(type: :rst_stream, stream: pid, error: :refused_stream)
|
289
|
+
return
|
290
|
+
end
|
291
|
+
|
281
292
|
connection_error(msg: 'missing parent ID') if parent.nil?
|
282
293
|
|
283
294
|
unless parent.state == :open || parent.state == :half_closed_local
|
@@ -330,7 +341,7 @@ module HTTP2
|
|
330
341
|
raise if e.is_a?(Error::Error)
|
331
342
|
connection_error(e: e)
|
332
343
|
end
|
333
|
-
|
344
|
+
alias << receive
|
334
345
|
|
335
346
|
private
|
336
347
|
|
@@ -364,12 +375,10 @@ module HTTP2
|
|
364
375
|
# @param frame [Hash]
|
365
376
|
# @return [Array of Buffer] encoded frame
|
366
377
|
def encode(frame)
|
367
|
-
frames = []
|
368
|
-
|
369
|
-
if frame[:type] == :headers || frame[:type] == :push_promise
|
370
|
-
frames = encode_headers(frame) # HEADERS and PUSH_PROMISE may create more than one frame
|
378
|
+
frames = if frame[:type] == :headers || frame[:type] == :push_promise
|
379
|
+
encode_headers(frame) # HEADERS and PUSH_PROMISE may create more than one frame
|
371
380
|
else
|
372
|
-
|
381
|
+
[frame] # otherwise one frame
|
373
382
|
end
|
374
383
|
|
375
384
|
frames.map { |f| @framer.generate(f) }
|
@@ -381,7 +390,7 @@ module HTTP2
|
|
381
390
|
# @param frame [Hash]
|
382
391
|
# @return [Boolean]
|
383
392
|
def connection_frame?(frame)
|
384
|
-
frame[:stream]
|
393
|
+
(frame[:stream]).zero? ||
|
385
394
|
frame[:type] == :settings ||
|
386
395
|
frame[:type] == :ping ||
|
387
396
|
frame[:type] == :goaway
|
@@ -447,11 +456,11 @@ module HTTP2
|
|
447
456
|
# Clients MUST reject any attempt to change the
|
448
457
|
# SETTINGS_ENABLE_PUSH setting to a value other than 0 by treating the
|
449
458
|
# message as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
|
450
|
-
return ProtocolError.new("invalid #{key} value") unless v
|
459
|
+
return ProtocolError.new("invalid #{key} value") unless v.zero?
|
451
460
|
when :client
|
452
461
|
# Any value other than 0 or 1 MUST be treated as a
|
453
462
|
# connection error (Section 5.4.1) of type PROTOCOL_ERROR.
|
454
|
-
unless v
|
463
|
+
unless v.zero? || v == 1
|
455
464
|
return ProtocolError.new("invalid #{key} value")
|
456
465
|
end
|
457
466
|
end
|
@@ -485,7 +494,7 @@ module HTTP2
|
|
485
494
|
#
|
486
495
|
# @param frame [Hash]
|
487
496
|
def connection_settings(frame)
|
488
|
-
connection_error unless frame[:type] == :settings && frame[:stream]
|
497
|
+
connection_error unless frame[:type] == :settings && (frame[:stream]).zero?
|
489
498
|
|
490
499
|
# Apply settings.
|
491
500
|
# side =
|
@@ -576,7 +585,7 @@ module HTTP2
|
|
576
585
|
#
|
577
586
|
# @param frame [Hash]
|
578
587
|
def decode_headers(frame)
|
579
|
-
if frame[:payload].is_a?
|
588
|
+
if frame[:payload].is_a? Buffer
|
580
589
|
frame[:payload] = @decompressor.decode(frame[:payload])
|
581
590
|
end
|
582
591
|
|
@@ -590,11 +599,11 @@ module HTTP2
|
|
590
599
|
# @return [Array of Frame]
|
591
600
|
def encode_headers(frame)
|
592
601
|
payload = frame[:payload]
|
593
|
-
payload = @compressor.encode(payload) unless payload.is_a?
|
602
|
+
payload = @compressor.encode(payload) unless payload.is_a? Buffer
|
594
603
|
|
595
604
|
frames = []
|
596
605
|
|
597
|
-
while payload.
|
606
|
+
while payload.bytesize > 0
|
598
607
|
cont = frame.dup
|
599
608
|
cont[:type] = :continuation
|
600
609
|
cont[:flags] = []
|
@@ -632,7 +641,18 @@ module HTTP2
|
|
632
641
|
# states count toward the maximum number of streams that an endpoint is
|
633
642
|
# permitted to open.
|
634
643
|
stream.once(:active) { @active_stream_count += 1 }
|
635
|
-
stream.once(:close)
|
644
|
+
stream.once(:close) do
|
645
|
+
@streams.delete id
|
646
|
+
@active_stream_count -= 1
|
647
|
+
|
648
|
+
# Store a reference to the closed stream, such that we can respond
|
649
|
+
# to any in-flight frames while close is registered on both sides.
|
650
|
+
# References to such streams will be purged whenever another stream
|
651
|
+
# is closed, with a minimum of 15s RTT time window.
|
652
|
+
@streams_recently_closed.delete_if { |_, v| (Time.now - v) > 15 }
|
653
|
+
@streams_recently_closed[id] = Time.now
|
654
|
+
end
|
655
|
+
|
636
656
|
stream.on(:promise, &method(:promise)) if self.is_a? Server
|
637
657
|
stream.on(:frame, &method(:send))
|
638
658
|
stream.on(:window_update, &method(:window_update))
|
@@ -656,7 +676,9 @@ module HTTP2
|
|
656
676
|
|
657
677
|
@state, @error = :closed, error
|
658
678
|
klass = error.to_s.split('_').map(&:capitalize).join
|
659
|
-
|
679
|
+
msg ||= e && e.message
|
680
|
+
backtrace = (e && e.backtrace) || []
|
681
|
+
fail Error.const_get(klass), msg, backtrace
|
660
682
|
end
|
661
683
|
end
|
662
684
|
end
|
data/lib/http/2/emitter.rb
CHANGED