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