http-2 0.8.4 → 0.10.2
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 +5 -5
- data/.rspec +1 -0
- data/.rubocop.yml +44 -8
- data/.rubocop_todo.yml +7 -4
- data/.travis.yml +1 -1
- data/Gemfile +1 -1
- data/Guardfile +0 -1
- data/LICENSE +21 -0
- data/README.md +13 -19
- data/example/client.rb +5 -1
- data/example/server.rb +2 -4
- data/example/upgrade_client.rb +153 -0
- data/example/upgrade_server.rb +2 -2
- data/http-2.gemspec +2 -3
- data/lib/http/2/client.rb +18 -1
- data/lib/http/2/compressor.rb +26 -11
- data/lib/http/2/connection.rb +62 -16
- data/lib/http/2/flow_buffer.rb +31 -0
- data/lib/http/2/framer.rb +3 -2
- data/lib/http/2/stream.rb +27 -15
- data/lib/http/2/version.rb +1 -1
- data/spec/client_spec.rb +105 -9
- data/spec/compressor_spec.rb +157 -26
- data/spec/connection_spec.rb +153 -76
- data/spec/framer_spec.rb +5 -5
- data/spec/helper.rb +125 -107
- data/spec/server_spec.rb +2 -1
- data/spec/stream_spec.rb +131 -115
- metadata +9 -8
data/example/upgrade_server.rb
CHANGED
@@ -39,7 +39,7 @@ end
|
|
39
39
|
|
40
40
|
class UpgradeHandler
|
41
41
|
VALID_UPGRADE_METHODS = %w(GET OPTIONS).freeze
|
42
|
-
UPGRADE_RESPONSE =
|
42
|
+
UPGRADE_RESPONSE = <<RESP.freeze
|
43
43
|
HTTP/1.1 101 Switching Protocols
|
44
44
|
Connection: Upgrade
|
45
45
|
Upgrade: h2c
|
@@ -191,7 +191,7 @@ loop do
|
|
191
191
|
conn << data
|
192
192
|
end
|
193
193
|
|
194
|
-
rescue => e
|
194
|
+
rescue StandardError => e
|
195
195
|
puts "Exception: #{e}, #{e.message} - closing socket."
|
196
196
|
puts e.backtrace.last(10).join("\n")
|
197
197
|
sock.close
|
data/http-2.gemspec
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
1
|
+
lib = File.expand_path('./lib', __dir__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
3
|
require 'http/2/version'
|
5
4
|
|
@@ -19,5 +18,5 @@ Gem::Specification.new do |spec|
|
|
19
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
19
|
spec.require_paths = ['lib']
|
21
20
|
|
22
|
-
spec.add_development_dependency 'bundler'
|
21
|
+
spec.add_development_dependency 'bundler'
|
23
22
|
end
|
data/lib/http/2/client.rb
CHANGED
@@ -38,14 +38,31 @@ module HTTP2
|
|
38
38
|
super(frame)
|
39
39
|
end
|
40
40
|
|
41
|
+
def receive(frame)
|
42
|
+
send_connection_preface
|
43
|
+
super(frame)
|
44
|
+
end
|
45
|
+
|
46
|
+
# sends the preface and initializes the first stream in half-closed state
|
47
|
+
def upgrade
|
48
|
+
fail ProtocolError unless @stream_id == 1
|
49
|
+
send_connection_preface
|
50
|
+
new_stream(state: :half_closed_local)
|
51
|
+
end
|
52
|
+
|
41
53
|
# Emit the connection preface if not yet
|
42
54
|
def send_connection_preface
|
43
55
|
return unless @state == :waiting_connection_preface
|
44
56
|
@state = :connected
|
45
57
|
emit(:frame, CONNECTION_PREFACE_MAGIC)
|
46
58
|
|
47
|
-
payload = @local_settings.
|
59
|
+
payload = @local_settings.reject { |k, v| v == SPEC_DEFAULT_CONNECTION_SETTINGS[k] }
|
48
60
|
settings(payload)
|
49
61
|
end
|
62
|
+
|
63
|
+
def self.settings_header(**settings)
|
64
|
+
frame = Framer.new.generate(type: :settings, stream: 0, payload: settings)
|
65
|
+
Base64.urlsafe_encode64(frame[9..-1])
|
66
|
+
end
|
50
67
|
end
|
51
68
|
end
|
data/lib/http/2/compressor.rb
CHANGED
@@ -139,12 +139,16 @@ module HTTP2
|
|
139
139
|
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10#section-4.1
|
140
140
|
#
|
141
141
|
# @param cmd [Hash] { type:, name:, value:, index: }
|
142
|
-
# @return [Array] +[name, value]+ header field that is added to the decoded header list
|
142
|
+
# @return [Array, nil] +[name, value]+ header field that is added to the decoded header list,
|
143
|
+
# or nil if +cmd[:type]+ is +:changetablesize+
|
143
144
|
def process(cmd)
|
144
145
|
emit = nil
|
145
146
|
|
146
147
|
case cmd[:type]
|
147
148
|
when :changetablesize
|
149
|
+
if cmd[:value] > @limit
|
150
|
+
fail CompressionError, 'dynamic table size update exceed limit'
|
151
|
+
end
|
148
152
|
self.table_size = cmd[:value]
|
149
153
|
|
150
154
|
when :indexed
|
@@ -199,8 +203,12 @@ module HTTP2
|
|
199
203
|
commands = []
|
200
204
|
# Literals commands are marked with :noindex when index is not used
|
201
205
|
noindex = [:static, :never].include?(@options[:index])
|
202
|
-
headers.each do |
|
203
|
-
|
206
|
+
headers.each do |field, value|
|
207
|
+
# Literal header names MUST be translated to lowercase before
|
208
|
+
# encoding and transmission.
|
209
|
+
field = field.downcase
|
210
|
+
value = '/' if field == ':path' && value.empty?
|
211
|
+
cmd = addcmd(field, value)
|
204
212
|
cmd[:type] = :noindex if noindex && cmd[:type] == :incremental
|
205
213
|
commands << cmd
|
206
214
|
process(cmd)
|
@@ -220,7 +228,7 @@ module HTTP2
|
|
220
228
|
#
|
221
229
|
# @param header [Array] +[name, value]+
|
222
230
|
# @return [Hash] command
|
223
|
-
def addcmd(header)
|
231
|
+
def addcmd(*header)
|
224
232
|
exact = nil
|
225
233
|
name_only = nil
|
226
234
|
|
@@ -441,11 +449,8 @@ module HTTP2
|
|
441
449
|
# @return [Buffer]
|
442
450
|
def encode(headers)
|
443
451
|
buffer = Buffer.new
|
444
|
-
|
445
|
-
|
446
|
-
# encoding and transmission.
|
447
|
-
headers.map! { |hk, hv| [hk.downcase, hv] }
|
448
|
-
|
452
|
+
pseudo_headers, regular_headers = headers.partition { |f, _| f.start_with? ':' }
|
453
|
+
headers = [*pseudo_headers, *regular_headers]
|
449
454
|
commands = @cc.encode(headers)
|
450
455
|
commands.each do |cmd|
|
451
456
|
buffer << header(cmd)
|
@@ -549,8 +554,18 @@ module HTTP2
|
|
549
554
|
# @return [Array] +[[name, value], ...]+
|
550
555
|
def decode(buf)
|
551
556
|
list = []
|
552
|
-
|
553
|
-
|
557
|
+
decoding_pseudo_headers = true
|
558
|
+
until buf.empty?
|
559
|
+
next_header = @cc.process(header(buf))
|
560
|
+
next if next_header.nil?
|
561
|
+
is_pseudo_header = next_header.first.start_with? ':'
|
562
|
+
if !decoding_pseudo_headers && is_pseudo_header
|
563
|
+
fail ProtocolError, 'one or more pseudo headers encountered after regular headers'
|
564
|
+
end
|
565
|
+
decoding_pseudo_headers = is_pseudo_header
|
566
|
+
list << next_header
|
567
|
+
end
|
568
|
+
list
|
554
569
|
end
|
555
570
|
end
|
556
571
|
end
|
data/lib/http/2/connection.rb
CHANGED
@@ -20,9 +20,9 @@ module HTTP2
|
|
20
20
|
|
21
21
|
DEFAULT_CONNECTION_SETTINGS = {
|
22
22
|
settings_header_table_size: 4096,
|
23
|
-
settings_enable_push: 1,
|
23
|
+
settings_enable_push: 1, # enabled for servers
|
24
24
|
settings_max_concurrent_streams: 100,
|
25
|
-
settings_initial_window_size: 65_535,
|
25
|
+
settings_initial_window_size: 65_535,
|
26
26
|
settings_max_frame_size: 16_384,
|
27
27
|
settings_max_header_list_size: 2**31 - 1, # unlimited
|
28
28
|
}.freeze
|
@@ -33,6 +33,9 @@ module HTTP2
|
|
33
33
|
# Default connection "fast-fail" preamble string as defined by the spec.
|
34
34
|
CONNECTION_PREFACE_MAGIC = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".freeze
|
35
35
|
|
36
|
+
# Time to hold recently closed streams until purge (seconds)
|
37
|
+
RECENTLY_CLOSED_STREAMS_TTL = 15
|
38
|
+
|
36
39
|
# Connection encapsulates all of the connection, stream, flow-control,
|
37
40
|
# error management, and other processing logic required for a well-behaved
|
38
41
|
# HTTP 2.0 endpoint.
|
@@ -49,9 +52,6 @@ module HTTP2
|
|
49
52
|
# Connection state (:new, :closed).
|
50
53
|
attr_reader :state
|
51
54
|
|
52
|
-
# Last connection error if connection is aborted.
|
53
|
-
attr_reader :error
|
54
|
-
|
55
55
|
# Size of current connection flow control window (by default, set to
|
56
56
|
# infinity, but is automatically updated on receipt of peer settings).
|
57
57
|
attr_reader :local_window
|
@@ -95,6 +95,13 @@ module HTTP2
|
|
95
95
|
@send_buffer = []
|
96
96
|
@continuation = []
|
97
97
|
@error = nil
|
98
|
+
|
99
|
+
@h2c_upgrade = nil
|
100
|
+
@closed_since = nil
|
101
|
+
end
|
102
|
+
|
103
|
+
def closed?
|
104
|
+
@state == :closed
|
98
105
|
end
|
99
106
|
|
100
107
|
# Allocates new stream for current connection.
|
@@ -141,12 +148,14 @@ module HTTP2
|
|
141
148
|
send(type: :goaway, last_stream: last_stream,
|
142
149
|
error: error, payload: payload)
|
143
150
|
@state = :closed
|
151
|
+
@closed_since = Time.now
|
144
152
|
end
|
145
153
|
|
146
154
|
# Sends a WINDOW_UPDATE frame to the peer.
|
147
155
|
#
|
148
156
|
# @param increment [Integer]
|
149
157
|
def window_update(increment)
|
158
|
+
@local_window += increment
|
150
159
|
send(type: :window_update, stream: 0, increment: increment)
|
151
160
|
end
|
152
161
|
|
@@ -312,6 +321,10 @@ module HTTP2
|
|
312
321
|
else
|
313
322
|
if (stream = @streams[frame[:stream]])
|
314
323
|
stream << frame
|
324
|
+
if frame[:type] == :data
|
325
|
+
update_local_window(frame)
|
326
|
+
calculate_window_update(@local_window_limit)
|
327
|
+
end
|
315
328
|
else
|
316
329
|
case frame[:type]
|
317
330
|
# The PRIORITY frame can be sent for a stream in the "idle" or
|
@@ -346,11 +359,14 @@ module HTTP2
|
|
346
359
|
end
|
347
360
|
end
|
348
361
|
|
349
|
-
rescue => e
|
362
|
+
rescue StandardError => e
|
350
363
|
raise if e.is_a?(Error::Error)
|
351
364
|
connection_error(e: e)
|
352
365
|
end
|
353
|
-
|
366
|
+
|
367
|
+
def <<(*args)
|
368
|
+
receive(*args)
|
369
|
+
end
|
354
370
|
|
355
371
|
private
|
356
372
|
|
@@ -438,12 +454,22 @@ module HTTP2
|
|
438
454
|
# the connection, although a new connection can be established
|
439
455
|
# for new streams.
|
440
456
|
@state = :closed
|
457
|
+
@closed_since = Time.now
|
441
458
|
emit(:goaway, frame[:last_stream], frame[:error], frame[:payload])
|
442
|
-
when :altsvc
|
459
|
+
when :altsvc
|
460
|
+
# 4. The ALTSVC HTTP/2 Frame
|
461
|
+
# An ALTSVC frame on stream 0 with empty (length 0) "Origin"
|
462
|
+
# information is invalid and MUST be ignored.
|
463
|
+
if frame[:origin] && !frame[:origin].empty?
|
464
|
+
emit(frame[:type], frame)
|
465
|
+
end
|
466
|
+
when :blocked
|
443
467
|
emit(frame[:type], frame)
|
444
468
|
else
|
445
469
|
connection_error
|
446
470
|
end
|
471
|
+
when :closed
|
472
|
+
connection_error if (Time.now - @closed_since) > 15
|
447
473
|
else
|
448
474
|
connection_error
|
449
475
|
end
|
@@ -488,7 +514,7 @@ module HTTP2
|
|
488
514
|
# allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
|
489
515
|
# Values outside this range MUST be treated as a connection error
|
490
516
|
# (Section 5.4.1) of type PROTOCOL_ERROR.
|
491
|
-
unless
|
517
|
+
unless v >= 16_384 && v <= 16_777_215
|
492
518
|
return ProtocolError.new("invalid #{key} value")
|
493
519
|
end
|
494
520
|
when :settings_max_header_list_size
|
@@ -598,8 +624,12 @@ module HTTP2
|
|
598
624
|
frame[:payload] = @decompressor.decode(frame[:payload])
|
599
625
|
end
|
600
626
|
|
601
|
-
rescue => e
|
627
|
+
rescue CompressionError => e
|
602
628
|
connection_error(:compression_error, e: e)
|
629
|
+
rescue ProtocolError => e
|
630
|
+
connection_error(:protocol_error, e: e)
|
631
|
+
rescue StandardError => e
|
632
|
+
connection_error(:internal_error, e: e)
|
603
633
|
end
|
604
634
|
|
605
635
|
# Encode headers payload and update connection compressor state.
|
@@ -629,7 +659,7 @@ module HTTP2
|
|
629
659
|
|
630
660
|
frames
|
631
661
|
|
632
|
-
rescue => e
|
662
|
+
rescue StandardError => e
|
633
663
|
connection_error(:compression_error, e: e)
|
634
664
|
nil
|
635
665
|
end
|
@@ -651,24 +681,38 @@ module HTTP2
|
|
651
681
|
# permitted to open.
|
652
682
|
stream.once(:active) { @active_stream_count += 1 }
|
653
683
|
stream.once(:close) do
|
654
|
-
@streams.delete id
|
655
684
|
@active_stream_count -= 1
|
656
685
|
|
657
686
|
# Store a reference to the closed stream, such that we can respond
|
658
687
|
# to any in-flight frames while close is registered on both sides.
|
659
688
|
# References to such streams will be purged whenever another stream
|
660
|
-
# is closed, with a
|
661
|
-
@streams_recently_closed
|
662
|
-
|
689
|
+
# is closed, with a defined RTT time window.
|
690
|
+
@streams_recently_closed[id] = Time.now.to_i
|
691
|
+
cleanup_recently_closed
|
663
692
|
end
|
664
693
|
|
665
694
|
stream.on(:promise, &method(:promise)) if self.is_a? Server
|
666
695
|
stream.on(:frame, &method(:send))
|
667
|
-
stream.on(:window_update, &method(:window_update))
|
668
696
|
|
669
697
|
@streams[id] = stream
|
670
698
|
end
|
671
699
|
|
700
|
+
# Purge recently streams closed within defined RTT time window.
|
701
|
+
def cleanup_recently_closed
|
702
|
+
now_ts = Time.now.to_i
|
703
|
+
to_delete = []
|
704
|
+
@streams_recently_closed.each do |stream_id, ts|
|
705
|
+
# Ruby Hash enumeration is ordered, so once fresh stream is met we can stop searching.
|
706
|
+
break if now_ts - ts < RECENTLY_CLOSED_STREAMS_TTL
|
707
|
+
to_delete << stream_id
|
708
|
+
end
|
709
|
+
|
710
|
+
to_delete.each do |stream_id|
|
711
|
+
@streams.delete stream_id
|
712
|
+
@streams_recently_closed.delete stream_id
|
713
|
+
end
|
714
|
+
end
|
715
|
+
|
672
716
|
# Emit GOAWAY error indicating to peer that the connection is being
|
673
717
|
# aborted, and once sent, raise a local exception.
|
674
718
|
#
|
@@ -689,9 +733,11 @@ module HTTP2
|
|
689
733
|
backtrace = (e && e.backtrace) || []
|
690
734
|
fail Error.const_get(klass), msg, backtrace
|
691
735
|
end
|
736
|
+
alias error connection_error
|
692
737
|
|
693
738
|
def manage_state(_)
|
694
739
|
yield
|
695
740
|
end
|
696
741
|
end
|
742
|
+
# rubocop:enable ClassLength
|
697
743
|
end
|
data/lib/http/2/flow_buffer.rb
CHANGED
@@ -13,6 +13,37 @@ module HTTP2
|
|
13
13
|
|
14
14
|
private
|
15
15
|
|
16
|
+
def update_local_window(frame)
|
17
|
+
frame_size = frame[:payload].bytesize
|
18
|
+
frame_size += frame[:padding] || 0
|
19
|
+
@local_window -= frame_size
|
20
|
+
end
|
21
|
+
|
22
|
+
def calculate_window_update(window_max_size)
|
23
|
+
# If DATA frame is received with length > 0 and
|
24
|
+
# current received window size + delta length is strictly larger than
|
25
|
+
# local window size, it throws a flow control error.
|
26
|
+
#
|
27
|
+
error(:flow_control_error) if @local_window < 0
|
28
|
+
|
29
|
+
# Send WINDOW_UPDATE if the received window size goes over
|
30
|
+
# the local window size / 2.
|
31
|
+
#
|
32
|
+
# The HTTP/2 spec mandates that every DATA frame received
|
33
|
+
# generates a WINDOW_UPDATE to send. In some cases however,
|
34
|
+
# (ex: DATA frames with short payloads),
|
35
|
+
# the noise generated by flow control frames creates enough
|
36
|
+
# congestion for this to be deemed very inefficient.
|
37
|
+
#
|
38
|
+
# This heuristic was inherited from nghttp, which delays the
|
39
|
+
# WINDOW_UPDATE until at least half the window is exhausted.
|
40
|
+
# This works because the sender doesn't need those increments
|
41
|
+
# until the receiver window is exhausted, after which he'll be
|
42
|
+
# waiting for the WINDOW_UPDATE frame.
|
43
|
+
return unless @local_window <= (window_max_size / 2)
|
44
|
+
window_update(window_max_size - @local_window)
|
45
|
+
end
|
46
|
+
|
16
47
|
# Buffers outgoing DATA frames and applies flow control logic to split
|
17
48
|
# and emit DATA frames based on current flow control window. If the
|
18
49
|
# window is large enough, the data is sent immediately. Otherwise, the
|
data/lib/http/2/framer.rb
CHANGED
@@ -359,14 +359,14 @@ module HTTP2
|
|
359
359
|
if frame[:flags].include? :priority
|
360
360
|
e_sd = payload.read_uint32
|
361
361
|
frame[:stream_dependency] = e_sd & RBIT
|
362
|
-
frame[:exclusive] = (e_sd & EBIT) != 0
|
362
|
+
frame[:exclusive] = (e_sd & EBIT) != 0
|
363
363
|
frame[:weight] = payload.getbyte + 1
|
364
364
|
end
|
365
365
|
frame[:payload] = payload.read(frame[:length])
|
366
366
|
when :priority
|
367
367
|
e_sd = payload.read_uint32
|
368
368
|
frame[:stream_dependency] = e_sd & RBIT
|
369
|
-
frame[:exclusive] = (e_sd & EBIT) != 0
|
369
|
+
frame[:exclusive] = (e_sd & EBIT) != 0
|
370
370
|
frame[:weight] = payload.getbyte + 1
|
371
371
|
when :rst_stream
|
372
372
|
frame[:error] = unpack_error payload.read_uint32
|
@@ -442,4 +442,5 @@ module HTTP2
|
|
442
442
|
name || error
|
443
443
|
end
|
444
444
|
end
|
445
|
+
# rubocop:enable ClassLength
|
445
446
|
end
|
data/lib/http/2/stream.rb
CHANGED
@@ -71,22 +71,28 @@ module HTTP2
|
|
71
71
|
# @param exclusive [Boolean]
|
72
72
|
# @param window [Integer]
|
73
73
|
# @param parent [Stream]
|
74
|
-
|
74
|
+
# @param state [Symbol]
|
75
|
+
def initialize(connection:, id:, weight: 16, dependency: 0, exclusive: false, parent: nil, state: :idle)
|
75
76
|
@connection = connection
|
76
77
|
@id = id
|
77
78
|
@weight = weight
|
78
79
|
@dependency = dependency
|
79
80
|
process_priority(weight: weight, stream_dependency: dependency, exclusive: exclusive)
|
81
|
+
@local_window_max_size = connection.local_settings[:settings_initial_window_size]
|
80
82
|
@local_window = connection.local_settings[:settings_initial_window_size]
|
81
83
|
@remote_window = connection.remote_settings[:settings_initial_window_size]
|
82
84
|
@parent = parent
|
83
|
-
@state =
|
85
|
+
@state = state
|
84
86
|
@error = false
|
85
87
|
@closed = false
|
86
88
|
@send_buffer = []
|
87
89
|
|
88
90
|
on(:window) { |v| @remote_window = v }
|
89
|
-
on(:local_window) { |v| @local_window = v }
|
91
|
+
on(:local_window) { |v| @local_window_max_size = @local_window = v }
|
92
|
+
end
|
93
|
+
|
94
|
+
def closed?
|
95
|
+
@state == :closed
|
90
96
|
end
|
91
97
|
|
92
98
|
# Processes incoming HTTP 2.0 frames. The frames must be decoded upstream.
|
@@ -97,21 +103,27 @@ module HTTP2
|
|
97
103
|
|
98
104
|
case frame[:type]
|
99
105
|
when :data
|
100
|
-
|
101
|
-
|
102
|
-
@local_window -= window_size
|
106
|
+
update_local_window(frame)
|
107
|
+
# Emit DATA frame
|
103
108
|
emit(:data, frame[:payload]) unless frame[:ignore]
|
104
|
-
|
105
|
-
|
106
|
-
# assuming that emit(:data) can now receive next data
|
107
|
-
window_update(window_size) if window_size > 0
|
108
|
-
when :headers, :push_promise
|
109
|
+
calculate_window_update(@local_window_max_size)
|
110
|
+
when :headers
|
109
111
|
emit(:headers, frame[:payload]) unless frame[:ignore]
|
112
|
+
when :push_promise
|
113
|
+
emit(:promise_headers, frame[:payload]) unless frame[:ignore]
|
110
114
|
when :priority
|
111
115
|
process_priority(frame)
|
112
116
|
when :window_update
|
113
117
|
process_window_update(frame)
|
114
|
-
when :altsvc
|
118
|
+
when :altsvc
|
119
|
+
# 4. The ALTSVC HTTP/2 Frame
|
120
|
+
# An ALTSVC frame on a
|
121
|
+
# stream other than stream 0 containing non-empty "Origin" information
|
122
|
+
# is invalid and MUST be ignored.
|
123
|
+
if !frame[:origin] || frame[:origin].empty?
|
124
|
+
emit(frame[:type], frame)
|
125
|
+
end
|
126
|
+
when :blocked
|
115
127
|
emit(frame[:type], frame)
|
116
128
|
end
|
117
129
|
|
@@ -144,6 +156,7 @@ module HTTP2
|
|
144
156
|
end
|
145
157
|
|
146
158
|
# Sends a HEADERS frame containing HTTP response headers.
|
159
|
+
# All pseudo-header fields MUST appear in the header block before regular header fields.
|
147
160
|
#
|
148
161
|
# @param headers [Array or Hash] Array of key-value pairs or Hash
|
149
162
|
# @param end_headers [Boolean] indicates that no more headers will be sent
|
@@ -153,7 +166,7 @@ module HTTP2
|
|
153
166
|
flags << :end_headers if end_headers
|
154
167
|
flags << :end_stream if end_stream
|
155
168
|
|
156
|
-
send(type: :headers, flags: flags, payload: headers
|
169
|
+
send(type: :headers, flags: flags, payload: headers)
|
157
170
|
end
|
158
171
|
|
159
172
|
def promise(headers, end_headers: true, &block)
|
@@ -228,8 +241,6 @@ module HTTP2
|
|
228
241
|
#
|
229
242
|
# @param increment [Integer]
|
230
243
|
def window_update(increment)
|
231
|
-
# always emit connection-level WINDOW_UPDATE
|
232
|
-
emit(:window_update, increment)
|
233
244
|
# emit stream-level WINDOW_UPDATE unless stream is closed
|
234
245
|
return if @state == :closed || @state == :remote_closed
|
235
246
|
send(type: :window_update, increment: increment)
|
@@ -602,6 +613,7 @@ module HTTP2
|
|
602
613
|
klass = error.to_s.split('_').map(&:capitalize).join
|
603
614
|
fail Error.const_get(klass), msg
|
604
615
|
end
|
616
|
+
alias error stream_error
|
605
617
|
|
606
618
|
def manage_state(frame)
|
607
619
|
transition(frame, true)
|