http-2 0.7.0 → 0.8.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/.rspec +1 -0
- data/.rubocop.yml +18 -0
- data/.rubocop_todo.yml +46 -0
- data/.travis.yml +10 -2
- data/Gemfile +7 -5
- data/README.md +35 -37
- data/Rakefile +9 -10
- data/example/README.md +0 -13
- data/example/client.rb +12 -15
- data/example/helper.rb +2 -2
- data/example/server.rb +19 -19
- data/example/upgrade_server.rb +191 -0
- data/http-2.gemspec +10 -9
- data/lib/http/2.rb +13 -13
- data/lib/http/2/buffer.rb +6 -10
- data/lib/http/2/client.rb +5 -10
- data/lib/http/2/compressor.rb +134 -146
- data/lib/http/2/connection.rb +104 -100
- data/lib/http/2/emitter.rb +2 -4
- data/lib/http/2/error.rb +7 -7
- data/lib/http/2/flow_buffer.rb +11 -10
- data/lib/http/2/framer.rb +78 -87
- data/lib/http/2/huffman.rb +265 -274
- data/lib/http/2/huffman_statemachine.rb +257 -257
- data/lib/http/2/server.rb +81 -6
- data/lib/http/2/stream.rb +195 -130
- data/lib/http/2/version.rb +1 -1
- data/lib/tasks/generate_huffman_table.rb +30 -24
- data/spec/buffer_spec.rb +11 -13
- data/spec/client_spec.rb +41 -42
- data/spec/compressor_spec.rb +243 -242
- data/spec/connection_spec.rb +252 -248
- data/spec/emitter_spec.rb +12 -12
- data/spec/framer_spec.rb +177 -179
- data/spec/helper.rb +56 -57
- data/spec/huffman_spec.rb +33 -33
- data/spec/server_spec.rb +15 -15
- data/spec/stream_spec.rb +356 -265
- metadata +7 -6
- data/spec/hpack_test_spec.rb +0 -83
data/lib/http/2/connection.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
module HTTP2
|
2
|
-
|
3
2
|
# Default connection and stream flow control window (64KB).
|
4
|
-
DEFAULT_FLOW_WINDOW =
|
3
|
+
DEFAULT_FLOW_WINDOW = 65_535
|
5
4
|
|
6
5
|
# Default header table size
|
7
6
|
DEFAULT_HEADER_SIZE = 4096
|
@@ -14,8 +13,8 @@ module HTTP2
|
|
14
13
|
settings_header_table_size: 4096,
|
15
14
|
settings_enable_push: 1, # enabled for servers
|
16
15
|
settings_max_concurrent_streams: Framer::MAX_STREAM_ID, # unlimited
|
17
|
-
settings_initial_window_size:
|
18
|
-
settings_max_frame_size:
|
16
|
+
settings_initial_window_size: 65_535,
|
17
|
+
settings_max_frame_size: 16_384,
|
19
18
|
settings_max_header_list_size: 2**31 - 1, # unlimited
|
20
19
|
}.freeze
|
21
20
|
|
@@ -23,8 +22,8 @@ module HTTP2
|
|
23
22
|
settings_header_table_size: 4096,
|
24
23
|
settings_enable_push: 1, # enabled for servers
|
25
24
|
settings_max_concurrent_streams: 100,
|
26
|
-
settings_initial_window_size:
|
27
|
-
settings_max_frame_size:
|
25
|
+
settings_initial_window_size: 65_535, #
|
26
|
+
settings_max_frame_size: 16_384,
|
28
27
|
settings_max_header_list_size: 2**31 - 1, # unlimited
|
29
28
|
}.freeze
|
30
29
|
|
@@ -32,7 +31,7 @@ module HTTP2
|
|
32
31
|
DEFAULT_WEIGHT = 16
|
33
32
|
|
34
33
|
# Default connection "fast-fail" preamble string as defined by the spec.
|
35
|
-
CONNECTION_PREFACE_MAGIC = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
|
34
|
+
CONNECTION_PREFACE_MAGIC = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".freeze
|
36
35
|
|
37
36
|
# Connection encapsulates all of the connection, stream, flow-control,
|
38
37
|
# error management, and other processing logic required for a well-behaved
|
@@ -40,6 +39,8 @@ module HTTP2
|
|
40
39
|
#
|
41
40
|
# Note that this class should not be used directly. Instead, you want to
|
42
41
|
# use either Client or Server class to drive the HTTP 2.0 exchange.
|
42
|
+
#
|
43
|
+
# rubocop:disable ClassLength
|
43
44
|
class Connection
|
44
45
|
include FlowBuffer
|
45
46
|
include Emitter
|
@@ -55,7 +56,7 @@ module HTTP2
|
|
55
56
|
# infinity, but is automatically updated on receipt of peer settings).
|
56
57
|
attr_reader :local_window
|
57
58
|
attr_reader :remote_window
|
58
|
-
|
59
|
+
alias_method :window, :local_window
|
59
60
|
|
60
61
|
# Current settings value for local and peer
|
61
62
|
attr_reader :local_settings
|
@@ -101,8 +102,8 @@ module HTTP2
|
|
101
102
|
# @param window [Integer]
|
102
103
|
# @param parent [Stream]
|
103
104
|
def new_stream(**args)
|
104
|
-
|
105
|
-
|
105
|
+
fail ConnectionClosed if @state == :closed
|
106
|
+
fail StreamLimitExceeded if @active_stream_count >= @remote_settings[:settings_max_concurrent_streams]
|
106
107
|
|
107
108
|
stream = activate_stream(id: @stream_id, **args)
|
108
109
|
@stream_id += 2
|
@@ -115,7 +116,7 @@ module HTTP2
|
|
115
116
|
# @param payload [String] optional payload must be 8 bytes long
|
116
117
|
# @param blk [Proc] callback to execute when PONG is received
|
117
118
|
def ping(payload, &blk)
|
118
|
-
send(
|
119
|
+
send(type: :ping, stream: 0, payload: payload)
|
119
120
|
once(:ack, &blk) if blk
|
120
121
|
end
|
121
122
|
|
@@ -130,10 +131,14 @@ module HTTP2
|
|
130
131
|
# @param error [Symbol]
|
131
132
|
# @param payload [String]
|
132
133
|
def goaway(error = :no_error, payload = nil)
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
134
|
+
last_stream = if (max = @streams.max)
|
135
|
+
max.first
|
136
|
+
else
|
137
|
+
0
|
138
|
+
end
|
139
|
+
|
140
|
+
send(type: :goaway, last_stream: last_stream,
|
141
|
+
error: error, payload: payload)
|
137
142
|
@state = :closed
|
138
143
|
end
|
139
144
|
|
@@ -143,10 +148,9 @@ module HTTP2
|
|
143
148
|
# @param settings [Array or Hash]
|
144
149
|
def settings(payload)
|
145
150
|
payload = payload.to_a
|
146
|
-
|
147
|
-
check and connection_error
|
151
|
+
connection_error if validate_settings(@local_role, payload)
|
148
152
|
@pending_settings << payload
|
149
|
-
send(
|
153
|
+
send(type: :settings, stream: 0, payload: payload)
|
150
154
|
@pending_settings << payload
|
151
155
|
end
|
152
156
|
|
@@ -168,36 +172,34 @@ module HTTP2
|
|
168
172
|
if @state == :waiting_magic
|
169
173
|
if @recv_buffer.size < 24
|
170
174
|
if !CONNECTION_PREFACE_MAGIC.start_with? @recv_buffer
|
171
|
-
|
175
|
+
fail HandshakeError
|
172
176
|
else
|
173
177
|
return # maybe next time
|
174
178
|
end
|
175
|
-
|
176
|
-
elsif @recv_buffer.read(24) != CONNECTION_PREFACE_MAGIC
|
177
|
-
raise HandshakeError.new
|
178
|
-
else
|
179
|
+
elsif @recv_buffer.read(24) == CONNECTION_PREFACE_MAGIC
|
179
180
|
# MAGIC is OK. Send our settings
|
180
181
|
@state = :waiting_connection_preface
|
181
|
-
payload = @local_settings.
|
182
|
+
payload = @local_settings.reject { |k, v| v == SPEC_DEFAULT_CONNECTION_SETTINGS[k] }
|
182
183
|
settings(payload)
|
184
|
+
else
|
185
|
+
fail HandshakeError
|
183
186
|
end
|
184
187
|
end
|
185
188
|
|
186
|
-
while frame = @framer.parse(@recv_buffer)
|
189
|
+
while (frame = @framer.parse(@recv_buffer))
|
187
190
|
emit(:frame_received, frame)
|
188
191
|
|
189
192
|
# Header blocks MUST be transmitted as a contiguous sequence of frames
|
190
193
|
# with no interleaved frames of any other type, or from any other stream.
|
191
|
-
|
192
|
-
|
193
|
-
frame[:stream] != @continuation.first[:stream]
|
194
|
+
unless @continuation.empty?
|
195
|
+
unless frame[:type] == :continuation && frame[:stream] == @continuation.first[:stream]
|
194
196
|
connection_error
|
195
197
|
end
|
196
198
|
|
197
199
|
@continuation << frame
|
198
|
-
return
|
200
|
+
return unless frame[:flags].include? :end_headers
|
199
201
|
|
200
|
-
payload = @continuation.map {|f| f[:payload]}.join
|
202
|
+
payload = @continuation.map { |f| f[:payload] }.join
|
201
203
|
|
202
204
|
frame = @continuation.shift
|
203
205
|
@continuation.clear
|
@@ -219,7 +221,7 @@ module HTTP2
|
|
219
221
|
when :headers
|
220
222
|
# The last frame in a sequence of HEADERS/CONTINUATION
|
221
223
|
# frames MUST have the END_HEADERS flag set.
|
222
|
-
|
224
|
+
unless frame[:flags].include? :end_headers
|
223
225
|
@continuation << frame
|
224
226
|
return
|
225
227
|
end
|
@@ -234,10 +236,12 @@ module HTTP2
|
|
234
236
|
|
235
237
|
stream = @streams[frame[:stream]]
|
236
238
|
if stream.nil?
|
237
|
-
stream = activate_stream(
|
238
|
-
|
239
|
-
|
240
|
-
|
239
|
+
stream = activate_stream(
|
240
|
+
id: frame[:stream],
|
241
|
+
weight: frame[:weight] || DEFAULT_WEIGHT,
|
242
|
+
dependency: frame[:dependency] || 0,
|
243
|
+
exclusive: frame[:exclusive] || false,
|
244
|
+
)
|
241
245
|
emit(:stream, stream)
|
242
246
|
end
|
243
247
|
|
@@ -246,7 +250,7 @@ module HTTP2
|
|
246
250
|
when :push_promise
|
247
251
|
# The last frame in a sequence of PUSH_PROMISE/CONTINUATION
|
248
252
|
# frames MUST have the END_HEADERS flag set
|
249
|
-
|
253
|
+
unless frame[:flags].include? :end_headers
|
250
254
|
@continuation << frame
|
251
255
|
return
|
252
256
|
end
|
@@ -269,7 +273,7 @@ module HTTP2
|
|
269
273
|
|
270
274
|
connection_error(msg: 'missing parent ID') if parent.nil?
|
271
275
|
|
272
|
-
|
276
|
+
unless parent.state == :open || parent.state == :half_closed_local
|
273
277
|
# An endpoint might receive a PUSH_PROMISE frame after it sends
|
274
278
|
# RST_STREAM. PUSH_PROMISE causes a stream to become "reserved".
|
275
279
|
# The RST_STREAM does not cancel any promised stream. Therefore, if
|
@@ -278,7 +282,7 @@ module HTTP2
|
|
278
282
|
if parent.closed == :local_rst
|
279
283
|
# We can either (a) 'resurrect' the parent, or (b) RST_STREAM
|
280
284
|
# ... sticking with (b), might need to revisit later.
|
281
|
-
send(
|
285
|
+
send(type: :rst_stream, stream: pid, error: :refused_stream)
|
282
286
|
else
|
283
287
|
connection_error
|
284
288
|
end
|
@@ -288,21 +292,38 @@ module HTTP2
|
|
288
292
|
emit(:promise, stream)
|
289
293
|
stream << frame
|
290
294
|
else
|
291
|
-
if stream = @streams[frame[:stream]]
|
295
|
+
if (stream = @streams[frame[:stream]])
|
292
296
|
stream << frame
|
293
297
|
else
|
294
|
-
#
|
295
|
-
#
|
296
|
-
|
298
|
+
# The PRIORITY frame can be sent for a stream in the "idle" or
|
299
|
+
# "closed" state. This allows for the reprioritization of a
|
300
|
+
# group of dependent streams by altering the priority of an
|
301
|
+
# unused or closed parent stream.
|
302
|
+
if frame[:type] == :priority
|
303
|
+
stream = activate_stream(
|
304
|
+
id: frame[:stream],
|
305
|
+
weight: frame[:weight] || DEFAULT_WEIGHT,
|
306
|
+
dependency: frame[:dependency] || 0,
|
307
|
+
exclusive: frame[:exclusive] || false,
|
308
|
+
)
|
309
|
+
|
310
|
+
emit(:stream, stream)
|
311
|
+
stream << frame
|
312
|
+
else
|
313
|
+
# An endpoint that receives an unexpected stream identifier
|
314
|
+
# MUST respond with a connection error of type PROTOCOL_ERROR.
|
315
|
+
connection_error
|
316
|
+
end
|
297
317
|
end
|
298
318
|
end
|
299
319
|
end
|
300
320
|
end
|
301
321
|
|
302
322
|
rescue => e
|
323
|
+
raise if e.is_a?(Error::Error)
|
303
324
|
connection_error
|
304
325
|
end
|
305
|
-
|
326
|
+
alias_method :<<, :receive
|
306
327
|
|
307
328
|
private
|
308
329
|
|
@@ -320,14 +341,13 @@ module HTTP2
|
|
320
341
|
else
|
321
342
|
# An endpoint can end a connection at any time. In particular, an
|
322
343
|
# endpoint MAY choose to treat a stream error as a connection error.
|
323
|
-
if frame[:type] == :rst_stream
|
324
|
-
|
325
|
-
goaway(frame[:error])
|
326
|
-
end
|
344
|
+
if frame[:type] == :rst_stream && frame[:error] == :protocol_error
|
345
|
+
goaway(frame[:error])
|
327
346
|
else
|
328
|
-
# HEADERS and PUSH_PROMISE may generate CONTINUATION
|
347
|
+
# HEADERS and PUSH_PROMISE may generate CONTINUATION. Also send
|
348
|
+
# RST_STREAM that are not protocol errors
|
329
349
|
frames = encode(frame)
|
330
|
-
frames.each {|f| emit(:frame, f) }
|
350
|
+
frames.each { |f| emit(:frame, f) }
|
331
351
|
end
|
332
352
|
end
|
333
353
|
end
|
@@ -339,14 +359,13 @@ module HTTP2
|
|
339
359
|
def encode(frame)
|
340
360
|
frames = []
|
341
361
|
|
342
|
-
if frame[:type] == :headers ||
|
343
|
-
frame[:type] == :push_promise
|
362
|
+
if frame[:type] == :headers || frame[:type] == :push_promise
|
344
363
|
frames = encode_headers(frame) # HEADERS and PUSH_PROMISE may create more than one frame
|
345
364
|
else
|
346
365
|
frames = [frame] # otherwise one frame
|
347
366
|
end
|
348
367
|
|
349
|
-
frames.map {|f| @framer.generate(f) }
|
368
|
+
frames.map { |f| @framer.generate(f) }
|
350
369
|
end
|
351
370
|
|
352
371
|
# Check if frame is a connection frame: SETTINGS, PING, GOAWAY, and any
|
@@ -356,9 +375,9 @@ module HTTP2
|
|
356
375
|
# @return [Boolean]
|
357
376
|
def connection_frame?(frame)
|
358
377
|
frame[:stream] == 0 ||
|
359
|
-
|
360
|
-
|
361
|
-
|
378
|
+
frame[:type] == :settings ||
|
379
|
+
frame[:type] == :ping ||
|
380
|
+
frame[:type] == :goaway
|
362
381
|
end
|
363
382
|
|
364
383
|
# Process received connection frame (stream ID = 0).
|
@@ -386,10 +405,8 @@ module HTTP2
|
|
386
405
|
if frame[:flags].include? :ack
|
387
406
|
emit(:ack, frame[:payload])
|
388
407
|
else
|
389
|
-
send(
|
390
|
-
|
391
|
-
flags: [:ack], payload: frame[:payload]
|
392
|
-
})
|
408
|
+
send(type: :ping, stream: 0,
|
409
|
+
flags: [:ack], payload: frame[:payload])
|
393
410
|
end
|
394
411
|
when :goaway
|
395
412
|
# Receivers of a GOAWAY frame MUST NOT open additional streams on
|
@@ -412,7 +429,7 @@ module HTTP2
|
|
412
429
|
# @param role [Symbol] The sender's role: :client or :server
|
413
430
|
# @return nil if no error. Exception object in case of any error.
|
414
431
|
def validate_settings(role, settings)
|
415
|
-
settings.each do |key,v|
|
432
|
+
settings.each do |key, v|
|
416
433
|
case key
|
417
434
|
when :settings_header_table_size
|
418
435
|
# Any value is valid
|
@@ -423,9 +440,7 @@ module HTTP2
|
|
423
440
|
# Clients MUST reject any attempt to change the
|
424
441
|
# SETTINGS_ENABLE_PUSH setting to a value other than 0 by treating the
|
425
442
|
# message as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
|
426
|
-
unless v == 0
|
427
|
-
return ProtocolError.new("invalid #{key} value")
|
428
|
-
end
|
443
|
+
return ProtocolError.new("invalid #{key} value") unless v == 0
|
429
444
|
when :client
|
430
445
|
# Any value other than 0 or 1 MUST be treated as a
|
431
446
|
# connection error (Section 5.4.1) of type PROTOCOL_ERROR.
|
@@ -448,13 +463,12 @@ module HTTP2
|
|
448
463
|
# allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
|
449
464
|
# Values outside this range MUST be treated as a connection error
|
450
465
|
# (Section 5.4.1) of type PROTOCOL_ERROR.
|
451
|
-
unless
|
466
|
+
unless 16_384 <= v && v <= 16_777_215
|
452
467
|
return ProtocolError.new("invalid #{key} value")
|
453
468
|
end
|
454
469
|
when :settings_max_header_list_size
|
455
470
|
# Any value is valid
|
456
|
-
|
457
|
-
# ignore unknown settings
|
471
|
+
# else # ignore unknown settings
|
458
472
|
end
|
459
473
|
end
|
460
474
|
nil
|
@@ -464,25 +478,21 @@ module HTTP2
|
|
464
478
|
#
|
465
479
|
# @param frame [Hash]
|
466
480
|
def connection_settings(frame)
|
467
|
-
|
468
|
-
connection_error
|
469
|
-
end
|
481
|
+
connection_error unless frame[:type] == :settings && frame[:stream] == 0
|
470
482
|
|
471
483
|
# Apply settings.
|
472
484
|
# side =
|
473
485
|
# local: previously sent and pended our settings should be effective
|
474
486
|
# remote: just received peer settings should immediately be effective
|
475
|
-
settings, side =
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
[frame[:payload], :remote]
|
483
|
-
end
|
487
|
+
settings, side = if frame[:flags].include?(:ack)
|
488
|
+
# Process pending settings we have sent.
|
489
|
+
[@pending_settings.shift, :local]
|
490
|
+
else
|
491
|
+
connection_error(check) if validate_settings(@remote_role, frame[:payload])
|
492
|
+
[frame[:payload], :remote]
|
493
|
+
end
|
484
494
|
|
485
|
-
settings.each do |key,v|
|
495
|
+
settings.each do |key, v|
|
486
496
|
case side
|
487
497
|
when :local
|
488
498
|
@local_settings[key] = v
|
@@ -504,14 +514,14 @@ module HTTP2
|
|
504
514
|
case side
|
505
515
|
when :local
|
506
516
|
@local_window = @local_window - @local_window_limit + v
|
507
|
-
@streams.each do |
|
517
|
+
@streams.each do |_id, stream|
|
508
518
|
stream.emit(:local_window, stream.local_window - @local_window_limit + v)
|
509
519
|
end
|
510
520
|
|
511
521
|
@local_window_limit = v
|
512
522
|
when :remote
|
513
523
|
@remote_window = @remote_window - @remote_window_limit + v
|
514
|
-
@streams.each do |
|
524
|
+
@streams.each do |_id, stream|
|
515
525
|
# Event name is :window, not :remote_window
|
516
526
|
stream.emit(:window, stream.remote_window - @remote_window_limit + v)
|
517
527
|
end
|
@@ -523,9 +533,9 @@ module HTTP2
|
|
523
533
|
# Setting header table size might cause some headers evicted
|
524
534
|
case side
|
525
535
|
when :local
|
526
|
-
@decompressor.
|
536
|
+
@decompressor.table_size = v
|
527
537
|
when :remote
|
528
|
-
@compressor.
|
538
|
+
@compressor.table_size = v
|
529
539
|
end
|
530
540
|
|
531
541
|
when :settings_enable_push
|
@@ -534,8 +544,7 @@ module HTTP2
|
|
534
544
|
when :settings_max_frame_size
|
535
545
|
# nothing to do
|
536
546
|
|
537
|
-
|
538
|
-
# ignore unknown settings
|
547
|
+
# else # ignore unknown settings
|
539
548
|
end
|
540
549
|
end
|
541
550
|
|
@@ -544,9 +553,9 @@ module HTTP2
|
|
544
553
|
# Received a settings_ack. Notify application layer.
|
545
554
|
emit(:settings_ack, frame, @pending_settings.size)
|
546
555
|
when :remote
|
547
|
-
|
556
|
+
unless @state == :closed || @h2c_upgrade == :start
|
548
557
|
# Send ack to peer
|
549
|
-
send(
|
558
|
+
send(type: :settings, stream: 0, payload: [], flags: [:ack])
|
550
559
|
end
|
551
560
|
end
|
552
561
|
end
|
@@ -564,7 +573,7 @@ module HTTP2
|
|
564
573
|
frame[:payload] = @decompressor.decode(frame[:payload])
|
565
574
|
end
|
566
575
|
|
567
|
-
rescue
|
576
|
+
rescue => e
|
568
577
|
connection_error(:compression_error, msg: e.message)
|
569
578
|
end
|
570
579
|
|
@@ -574,9 +583,7 @@ module HTTP2
|
|
574
583
|
# @return [Array of Frame]
|
575
584
|
def encode_headers(frame)
|
576
585
|
payload = frame[:payload]
|
577
|
-
unless payload.is_a? String
|
578
|
-
payload = @compressor.encode(payload)
|
579
|
-
end
|
586
|
+
payload = @compressor.encode(payload) unless payload.is_a? String
|
580
587
|
|
581
588
|
frames = []
|
582
589
|
|
@@ -592,12 +599,12 @@ module HTTP2
|
|
592
599
|
else
|
593
600
|
frames.first[:type] = frame[:type]
|
594
601
|
frames.first[:flags] = frame[:flags] - [:end_headers]
|
595
|
-
frames.last[:flags]
|
602
|
+
frames.last[:flags] << :end_headers
|
596
603
|
end
|
597
604
|
|
598
605
|
frames
|
599
606
|
|
600
|
-
rescue
|
607
|
+
rescue => e
|
601
608
|
[connection_error(:compression_error, msg: e.message)]
|
602
609
|
end
|
603
610
|
|
@@ -609,11 +616,9 @@ module HTTP2
|
|
609
616
|
# @param window [Integer]
|
610
617
|
# @param parent [Stream]
|
611
618
|
def activate_stream(id: nil, **args)
|
612
|
-
if @streams.key?(id)
|
613
|
-
connection_error(msg: 'Stream ID already exists')
|
614
|
-
end
|
619
|
+
connection_error(msg: 'Stream ID already exists') if @streams.key?(id)
|
615
620
|
|
616
|
-
stream = Stream.new({connection: self, id: id}.merge(args))
|
621
|
+
stream = Stream.new({ connection: self, id: id }.merge(args))
|
617
622
|
|
618
623
|
# Streams that are in the "open" state, or either of the "half closed"
|
619
624
|
# states count toward the maximum number of streams that an endpoint is
|
@@ -638,12 +643,11 @@ module HTTP2
|
|
638
643
|
# @option error [Symbol] :compression_error
|
639
644
|
# @param msg [String]
|
640
645
|
def connection_error(error = :protocol_error, msg: nil)
|
641
|
-
goaway(error)
|
646
|
+
goaway(error) unless @state == :closed || @state == :new
|
642
647
|
|
643
648
|
@state, @error = :closed, error
|
644
649
|
klass = error.to_s.split('_').map(&:capitalize).join
|
645
|
-
|
650
|
+
fail Error.const_get(klass), msg
|
646
651
|
end
|
647
|
-
|
648
652
|
end
|
649
653
|
end
|