http-2 1.0.2 → 1.1.1
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/lib/http/2/client.rb +1 -0
- data/lib/http/2/connection.rb +118 -97
- data/lib/http/2/emitter.rb +2 -9
- data/lib/http/2/extensions.rb +21 -1
- data/lib/http/2/flow_buffer.rb +45 -35
- data/lib/http/2/framer.rb +123 -85
- data/lib/http/2/header/compressor.rb +47 -31
- data/lib/http/2/header/decompressor.rb +27 -20
- data/lib/http/2/header/encoding_context.rb +77 -80
- data/lib/http/2/header/huffman.rb +14 -10
- data/lib/http/2/header/huffman_statemachine.rb +1 -1
- data/lib/http/2/server.rb +7 -1
- data/lib/http/2/stream.rb +54 -60
- data/lib/http/2/version.rb +1 -1
- data/lib/http/2.rb +5 -0
- data/sig/{next.rbs → 2.rbs} +29 -18
- data/sig/client.rbs +2 -0
- data/sig/connection.rbs +20 -8
- data/sig/emitter.rbs +2 -4
- data/sig/extensions.rbs +3 -1
- data/sig/flow_buffer.rbs +7 -5
- data/sig/frame_buffer.rbs +1 -1
- data/sig/framer.rbs +5 -0
- data/sig/header/compressor.rbs +6 -4
- data/sig/header/decompressor.rbs +3 -2
- data/sig/header/encoding_context.rbs +24 -6
- data/sig/header/huffman.rbs +19 -3
- data/sig/header.rbs +11 -8
- data/sig/stream.rbs +8 -5
- metadata +4 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8c3e5ca05eb466a1e2f6c0895ccd5827987cfb8bf8ddbefeeb74210e0e6e3e97
|
4
|
+
data.tar.gz: 8589dab8b8c89aefc5067206219ebcac9bffbc185da25d89a5f6dccc11aafcd8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d66c154674cdd29924c0c169ce60c8eeda6ba833b435b2db74b787e86bab101a7f9748f4fb632ecbecf32aaa216fdf1cd9e8d73c333cd501f454b52d0628c6fe
|
7
|
+
data.tar.gz: 68f86eca1a92474fd7802502af0883595b52afe42c49c975ea6952e79a523c59508ad5213d18f77f9a50a4539deb6200013407b001f81dcdb5560639ef030242
|
data/lib/http/2/client.rb
CHANGED
data/lib/http/2/connection.rb
CHANGED
@@ -36,9 +36,14 @@ module HTTP2
|
|
36
36
|
CONNECTION_PREFACE_MAGIC = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
|
37
37
|
|
38
38
|
REQUEST_MANDATORY_HEADERS = %w[:scheme :method :authority :path].freeze
|
39
|
+
|
39
40
|
RESPONSE_MANDATORY_HEADERS = %w[:status].freeze
|
40
41
|
|
41
|
-
|
42
|
+
CONNECTION_FRAME_TYPES = %i[settings ping goaway].freeze
|
43
|
+
|
44
|
+
HEADERS_FRAME_TYPES = %i[headers push_promise].freeze
|
45
|
+
|
46
|
+
STREAM_OPEN_STATES = %i[open half_closed_local].freeze
|
42
47
|
|
43
48
|
# Connection encapsulates all of the connection, stream, flow-control,
|
44
49
|
# error management, and other processing logic required for a well-behaved
|
@@ -47,7 +52,6 @@ module HTTP2
|
|
47
52
|
# Note that this class should not be used directly. Instead, you want to
|
48
53
|
# use either Client or Server class to drive the HTTP 2.0 exchange.
|
49
54
|
#
|
50
|
-
# rubocop:disable Metrics/ClassLength
|
51
55
|
class Connection
|
52
56
|
include FlowBuffer
|
53
57
|
include Emitter
|
@@ -84,7 +88,6 @@ module HTTP2
|
|
84
88
|
@decompressor = Header::Decompressor.new(settings)
|
85
89
|
|
86
90
|
@active_stream_count = 0
|
87
|
-
@last_activated_stream = 0
|
88
91
|
@last_stream_id = 0
|
89
92
|
@streams = {}
|
90
93
|
@streams_recently_closed = {}
|
@@ -104,6 +107,10 @@ module HTTP2
|
|
104
107
|
@h2c_upgrade = nil
|
105
108
|
@closed_since = nil
|
106
109
|
@received_frame = false
|
110
|
+
|
111
|
+
# from mixins
|
112
|
+
@listeners = Hash.new { |hash, key| hash[key] = [] }
|
113
|
+
@send_buffer = FrameBuffer.new
|
107
114
|
end
|
108
115
|
|
109
116
|
def closed?
|
@@ -119,10 +126,10 @@ module HTTP2
|
|
119
126
|
raise ConnectionClosed if @state == :closed
|
120
127
|
raise StreamLimitExceeded if @active_stream_count >= @remote_settings[:settings_max_concurrent_streams]
|
121
128
|
|
122
|
-
connection_error(:protocol_error, msg: "id is smaller than previous") if @stream_id < @
|
129
|
+
connection_error(:protocol_error, msg: "id is smaller than previous") if @stream_id < @last_stream_id
|
123
130
|
|
124
131
|
stream = activate_stream(id: @stream_id, **args)
|
125
|
-
@
|
132
|
+
@last_stream_id = stream.id
|
126
133
|
|
127
134
|
@stream_id += 2
|
128
135
|
|
@@ -149,13 +156,7 @@ module HTTP2
|
|
149
156
|
# @param error [Symbol]
|
150
157
|
# @param payload [String]
|
151
158
|
def goaway(error = :no_error, payload = nil)
|
152
|
-
|
153
|
-
max.first
|
154
|
-
else
|
155
|
-
0
|
156
|
-
end
|
157
|
-
|
158
|
-
send(type: :goaway, last_stream: last_stream,
|
159
|
+
send(type: :goaway, last_stream: @last_stream_id,
|
159
160
|
error: error, payload: payload)
|
160
161
|
@state = :closed
|
161
162
|
@closed_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
@@ -174,7 +175,6 @@ module HTTP2
|
|
174
175
|
#
|
175
176
|
# @param settings [Array or Hash]
|
176
177
|
def settings(payload)
|
177
|
-
payload = payload.to_a
|
178
178
|
validate_settings(@local_role, payload)
|
179
179
|
@pending_settings << payload
|
180
180
|
send(type: :settings, stream: 0, payload: payload)
|
@@ -187,7 +187,7 @@ module HTTP2
|
|
187
187
|
#
|
188
188
|
# @param data [String] Binary encoded string
|
189
189
|
def receive(data)
|
190
|
-
@recv_buffer
|
190
|
+
append_str(@recv_buffer, data)
|
191
191
|
|
192
192
|
# Upon establishment of a TCP connection and determination that
|
193
193
|
# HTTP/2.0 will be used by both peers, each endpoint MUST send a
|
@@ -212,14 +212,18 @@ module HTTP2
|
|
212
212
|
end
|
213
213
|
|
214
214
|
while (frame = @framer.parse(@recv_buffer))
|
215
|
+
# @type var stream_id: Integer
|
216
|
+
stream_id = frame[:stream]
|
217
|
+
frame_type = frame[:type]
|
218
|
+
|
215
219
|
if is_a?(Client) && !@received_frame
|
216
|
-
connection_error(:protocol_error, msg: "didn't receive settings") if
|
220
|
+
connection_error(:protocol_error, msg: "didn't receive settings") if frame_type != :settings
|
217
221
|
@received_frame = true
|
218
222
|
end
|
219
223
|
|
220
224
|
# Implementations MUST discard frames
|
221
225
|
# that have unknown or unsupported types.
|
222
|
-
if
|
226
|
+
if frame_type.nil?
|
223
227
|
# However, extension frames that appear in
|
224
228
|
# the middle of a header block (Section 4.3) are not permitted; these
|
225
229
|
# MUST be treated as a connection error (Section 5.4.1) of type
|
@@ -233,7 +237,7 @@ module HTTP2
|
|
233
237
|
# Header blocks MUST be transmitted as a contiguous sequence of frames
|
234
238
|
# with no interleaved frames of any other type, or from any other stream.
|
235
239
|
unless @continuation.empty?
|
236
|
-
connection_error unless
|
240
|
+
connection_error unless frame_type == :continuation && stream_id == @continuation.first[:stream]
|
237
241
|
|
238
242
|
@continuation << frame
|
239
243
|
unless frame[:flags].include? :end_headers
|
@@ -252,6 +256,8 @@ module HTTP2
|
|
252
256
|
payload = @continuation.map { |f| f[:payload] }.join
|
253
257
|
|
254
258
|
frame = @continuation.shift
|
259
|
+
frame_type = frame[:type]
|
260
|
+
|
255
261
|
@continuation.clear
|
256
262
|
|
257
263
|
frame.delete(:length)
|
@@ -265,14 +271,14 @@ module HTTP2
|
|
265
271
|
# anything other than 0x0, the endpoint MUST respond with a connection
|
266
272
|
# error (Section 5.4.1) of type PROTOCOL_ERROR.
|
267
273
|
if connection_frame?(frame)
|
268
|
-
connection_error(:protocol_error) unless
|
274
|
+
connection_error(:protocol_error) unless stream_id.zero?
|
269
275
|
connection_management(frame)
|
270
276
|
else
|
271
|
-
case
|
277
|
+
case frame_type
|
272
278
|
when :headers
|
273
279
|
# When server receives even-numbered stream identifier,
|
274
280
|
# the endpoint MUST respond with a connection error of type PROTOCOL_ERROR.
|
275
|
-
connection_error if
|
281
|
+
connection_error if stream_id.even? && is_a?(Server)
|
276
282
|
|
277
283
|
# The last frame in a sequence of HEADERS/CONTINUATION
|
278
284
|
# frames MUST have the END_HEADERS flag set.
|
@@ -289,13 +295,13 @@ module HTTP2
|
|
289
295
|
decode_headers(frame)
|
290
296
|
return if @state == :closed
|
291
297
|
|
292
|
-
stream = @streams[
|
298
|
+
stream = @streams[stream_id]
|
293
299
|
if stream.nil?
|
294
300
|
verify_pseudo_headers(frame)
|
295
301
|
|
296
|
-
verify_stream_order(
|
302
|
+
verify_stream_order(stream_id)
|
297
303
|
stream = activate_stream(
|
298
|
-
id:
|
304
|
+
id: stream_id,
|
299
305
|
weight: frame[:weight] || DEFAULT_WEIGHT,
|
300
306
|
dependency: frame[:dependency] || 0,
|
301
307
|
exclusive: frame[:exclusive] || false
|
@@ -326,18 +332,18 @@ module HTTP2
|
|
326
332
|
# that is not currently in the "idle" state) as a connection error
|
327
333
|
# (Section 5.4.1) of type PROTOCOL_ERROR, unless the receiver
|
328
334
|
# recently sent a RST_STREAM frame to cancel the associated stream.
|
329
|
-
parent = @streams[
|
335
|
+
parent = @streams[stream_id]
|
330
336
|
pid = frame[:promise_stream]
|
331
337
|
|
332
338
|
# if PUSH parent is recently closed, RST_STREAM the push
|
333
|
-
if @streams_recently_closed[
|
339
|
+
if @streams_recently_closed[stream_id]
|
334
340
|
send(type: :rst_stream, stream: pid, error: :refused_stream)
|
335
341
|
return
|
336
342
|
end
|
337
343
|
|
338
344
|
connection_error(msg: "missing parent ID") if parent.nil?
|
339
345
|
|
340
|
-
unless
|
346
|
+
unless STREAM_OPEN_STATES.include?(parent.state)
|
341
347
|
# An endpoint might receive a PUSH_PROMISE frame after it sends
|
342
348
|
# RST_STREAM. PUSH_PROMISE causes a stream to become "reserved".
|
343
349
|
# The RST_STREAM does not cancel any promised stream. Therefore, if
|
@@ -358,21 +364,21 @@ module HTTP2
|
|
358
364
|
emit(:promise, stream)
|
359
365
|
stream << frame
|
360
366
|
else
|
361
|
-
if (stream = @streams[
|
367
|
+
if (stream = @streams[stream_id])
|
362
368
|
stream << frame
|
363
|
-
if
|
369
|
+
if frame_type == :data
|
364
370
|
update_local_window(frame)
|
365
371
|
calculate_window_update(@local_window_limit)
|
366
372
|
end
|
367
373
|
else
|
368
|
-
case
|
374
|
+
case frame_type
|
369
375
|
# The PRIORITY frame can be sent for a stream in the "idle" or
|
370
376
|
# "closed" state. This allows for the reprioritization of a
|
371
377
|
# group of dependent streams by altering the priority of an
|
372
378
|
# unused or closed parent stream.
|
373
379
|
when :priority
|
374
380
|
stream = activate_stream(
|
375
|
-
id:
|
381
|
+
id: stream_id,
|
376
382
|
weight: frame[:weight] || DEFAULT_WEIGHT,
|
377
383
|
dependency: frame[:dependency] || 0,
|
378
384
|
exclusive: frame[:exclusive] || false
|
@@ -387,16 +393,19 @@ module HTTP2
|
|
387
393
|
# "closed" stream. A receiver MUST NOT treat this as an error
|
388
394
|
# (see Section 5.1).
|
389
395
|
when :window_update
|
390
|
-
|
391
|
-
|
396
|
+
unless @streams_recently_closed.key?(stream_id)
|
397
|
+
connection_error(:protocol_error, msg: "sent window update on idle stream")
|
398
|
+
end
|
399
|
+
stream = @streams_recently_closed[stream_id]
|
392
400
|
process_window_update(frame: frame, encode: true)
|
393
401
|
# Endpoints MUST ignore
|
394
402
|
# WINDOW_UPDATE or RST_STREAM frames received in this state (closed), though
|
395
403
|
# endpoints MAY choose to treat frames that arrive a significant
|
396
404
|
# time after sending END_STREAM as a connection error.
|
397
405
|
when :rst_stream
|
398
|
-
|
399
|
-
|
406
|
+
unless @streams_recently_closed.key?(stream_id)
|
407
|
+
connection_error(:protocol_error, msg: "sent window update on idle stream")
|
408
|
+
end
|
400
409
|
else
|
401
410
|
# An endpoint that receives an unexpected stream identifier
|
402
411
|
# MUST respond with a connection error of type PROTOCOL_ERROR.
|
@@ -425,35 +434,33 @@ module HTTP2
|
|
425
434
|
# @note all frames are currently delivered in FIFO order.
|
426
435
|
# @param frame [Hash]
|
427
436
|
def send(frame)
|
437
|
+
frame_type = frame[:type]
|
438
|
+
|
428
439
|
emit(:frame_sent, frame)
|
429
|
-
if
|
440
|
+
if frame_type == :data
|
430
441
|
send_data(frame, true)
|
431
442
|
|
432
|
-
elsif
|
443
|
+
elsif frame_type == :rst_stream && frame[:error] == :protocol_error
|
433
444
|
# An endpoint can end a connection at any time. In particular, an
|
434
445
|
# endpoint MAY choose to treat a stream error as a connection error.
|
435
446
|
|
436
|
-
goaway(
|
447
|
+
goaway(:protocol_error)
|
437
448
|
else
|
438
449
|
# HEADERS and PUSH_PROMISE may generate CONTINUATION. Also send
|
439
450
|
# RST_STREAM that are not protocol errors
|
440
|
-
|
441
|
-
frames.each { |f| emit(:frame, f) }
|
451
|
+
encode(frame)
|
442
452
|
end
|
443
453
|
end
|
444
454
|
|
445
455
|
# Applies HTTP 2.0 binary encoding to the frame.
|
446
456
|
#
|
447
457
|
# @param frame [Hash]
|
448
|
-
# @return [Array of Buffer] encoded frame
|
449
458
|
def encode(frame)
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
frames.map { |f| @framer.generate(f) }
|
459
|
+
if HEADERS_FRAME_TYPES.include?(frame[:type])
|
460
|
+
encode_headers(frame) # HEADERS and PUSH_PROMISE may create more than one frame
|
461
|
+
else
|
462
|
+
emit(:frame, @framer.generate(frame))
|
463
|
+
end
|
457
464
|
end
|
458
465
|
|
459
466
|
# Check if frame is a connection frame: SETTINGS, PING, GOAWAY, and any
|
@@ -462,10 +469,7 @@ module HTTP2
|
|
462
469
|
# @param frame [Hash]
|
463
470
|
# @return [Boolean]
|
464
471
|
def connection_frame?(frame)
|
465
|
-
|
466
|
-
frame[:type] == :settings ||
|
467
|
-
frame[:type] == :ping ||
|
468
|
-
frame[:type] == :goaway
|
472
|
+
frame[:stream].zero? || CONNECTION_FRAME_TYPES.include?(frame[:type])
|
469
473
|
end
|
470
474
|
|
471
475
|
# Process received connection frame (stream ID = 0).
|
@@ -476,14 +480,18 @@ module HTTP2
|
|
476
480
|
#
|
477
481
|
# @param frame [Hash]
|
478
482
|
def connection_management(frame)
|
483
|
+
frame_type = frame[:type]
|
484
|
+
|
479
485
|
case @state
|
480
486
|
when :waiting_connection_preface
|
481
487
|
# The first frame MUST be a SETTINGS frame at the start of a connection.
|
488
|
+
connection_error unless frame[:type] == :settings
|
489
|
+
|
482
490
|
@state = :connected
|
483
491
|
connection_settings(frame)
|
484
492
|
|
485
493
|
when :connected
|
486
|
-
case
|
494
|
+
case frame_type
|
487
495
|
when :settings
|
488
496
|
connection_settings(frame)
|
489
497
|
when :window_update
|
@@ -498,23 +506,24 @@ module HTTP2
|
|
498
506
|
@closed_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
499
507
|
emit(:goaway, frame[:last_stream], frame[:error], frame[:payload])
|
500
508
|
when :altsvc
|
509
|
+
origin = frame[:origin]
|
501
510
|
# 4. The ALTSVC HTTP/2 Frame
|
502
511
|
# An ALTSVC frame on stream 0 with empty (length 0) "Origin"
|
503
512
|
# information is invalid and MUST be ignored.
|
504
|
-
emit(
|
513
|
+
emit(:altsvc, frame) if origin && !origin.empty?
|
505
514
|
when :origin
|
506
515
|
return if @h2c_upgrade || !frame[:flags].empty?
|
507
516
|
|
508
|
-
frame[:payload].each do |
|
509
|
-
emit(
|
517
|
+
frame[:payload].each do |orig|
|
518
|
+
emit(:origin, orig)
|
510
519
|
end
|
511
520
|
when :blocked
|
512
|
-
emit(
|
521
|
+
emit(:blocked, frame)
|
513
522
|
else
|
514
523
|
connection_error
|
515
524
|
end
|
516
525
|
when :closed
|
517
|
-
case
|
526
|
+
case frame_type
|
518
527
|
when :goaway
|
519
528
|
connection_error
|
520
529
|
when :ping
|
@@ -573,7 +582,7 @@ module HTTP2
|
|
573
582
|
# allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
|
574
583
|
# Values outside this range MUST be treated as a connection error
|
575
584
|
# (Section 5.4.1) of type PROTOCOL_ERROR.
|
576
|
-
next if v
|
585
|
+
next if v.between?(16_384, 16_777_215)
|
577
586
|
|
578
587
|
connection_error(:protocol_error, msg: "invalid #{key} value")
|
579
588
|
# when :settings_max_concurrent_streams
|
@@ -591,19 +600,19 @@ module HTTP2
|
|
591
600
|
#
|
592
601
|
# @param frame [Hash]
|
593
602
|
def connection_settings(frame)
|
594
|
-
connection_error unless frame[:type] == :settings && (frame[:stream]).zero?
|
595
|
-
|
596
603
|
# Apply settings.
|
597
604
|
# side =
|
598
605
|
# local: previously sent and pended our settings should be effective
|
599
606
|
# remote: just received peer settings should immediately be effective
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
+
if frame[:flags].include?(:ack)
|
608
|
+
# Process pending settings we have sent.
|
609
|
+
settings = @pending_settings.shift
|
610
|
+
side = :local
|
611
|
+
else
|
612
|
+
validate_settings(@remote_role, frame[:payload])
|
613
|
+
settings = frame[:payload]
|
614
|
+
side = :remote
|
615
|
+
end
|
607
616
|
|
608
617
|
settings.each do |key, v|
|
609
618
|
case side
|
@@ -699,40 +708,48 @@ module HTTP2
|
|
699
708
|
|
700
709
|
# Encode headers payload and update connection compressor state.
|
701
710
|
#
|
702
|
-
# @param
|
703
|
-
|
704
|
-
|
705
|
-
payload = frame[:payload]
|
711
|
+
# @param headers_frame [Hash]
|
712
|
+
def encode_headers(headers_frame)
|
713
|
+
payload = headers_frame[:payload]
|
706
714
|
begin
|
707
|
-
payload =
|
715
|
+
payload = headers_frame[:payload] = @compressor.encode(payload) unless payload.is_a?(String)
|
708
716
|
rescue StandardError => e
|
709
717
|
connection_error(:compression_error, e: e)
|
710
718
|
end
|
711
719
|
|
720
|
+
max_frame_size = @remote_settings[:settings_max_frame_size]
|
721
|
+
|
712
722
|
# if single frame, return immediately
|
713
|
-
|
723
|
+
if payload.bytesize <= max_frame_size
|
724
|
+
emit(:frame, @framer.generate(headers_frame))
|
725
|
+
return
|
726
|
+
end
|
714
727
|
|
715
|
-
|
728
|
+
# split into multiple CONTINUATION frames
|
729
|
+
headers_frame[:flags].delete(:end_headers)
|
730
|
+
headers_frame[:payload] = payload.byteslice(0, max_frame_size)
|
731
|
+
payload = payload.byteslice(max_frame_size..-1)
|
716
732
|
|
717
|
-
|
718
|
-
|
733
|
+
# emit first HEADERS frame
|
734
|
+
emit(:frame, @framer.generate(headers_frame))
|
719
735
|
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
736
|
+
loop do
|
737
|
+
continuation_frame = headers_frame.merge(
|
738
|
+
type: :continuation,
|
739
|
+
flags: EMPTY,
|
740
|
+
payload: payload.byteslice(0, max_frame_size)
|
741
|
+
)
|
725
742
|
|
726
|
-
|
727
|
-
payload = payload.byteslice(@remote_settings[:settings_max_frame_size]..-1)
|
743
|
+
payload = payload.byteslice(max_frame_size..-1)
|
728
744
|
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
745
|
+
if payload.nil? || payload.empty?
|
746
|
+
continuation_frame[:flags] = [:end_headers]
|
747
|
+
emit(:frame, @framer.generate(continuation_frame))
|
748
|
+
break
|
749
|
+
end
|
734
750
|
|
735
|
-
|
751
|
+
emit(:frame, @framer.generate(continuation_frame))
|
752
|
+
end
|
736
753
|
end
|
737
754
|
|
738
755
|
# Activates new incoming or outgoing stream and registers appropriate
|
@@ -758,17 +775,22 @@ module HTTP2
|
|
758
775
|
# is closed, with a minimum of 15s RTT time window.
|
759
776
|
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
760
777
|
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
778
|
+
_, closed_since = @streams_recently_closed.first
|
779
|
+
|
780
|
+
# forego recently closed recycling if empty or the first element
|
781
|
+
# hasn't expired yet (it's ordered).
|
782
|
+
if closed_since && (now - closed_since) > 15
|
783
|
+
# discards all streams which have closed for a while.
|
784
|
+
# TODO: use a drop_while! variant whenever there is one.
|
785
|
+
@streams_recently_closed = @streams_recently_closed.drop_while do |_, since|
|
786
|
+
(now - since) > 15
|
787
|
+
end.to_h
|
788
|
+
end
|
765
789
|
|
766
790
|
@streams_recently_closed[id] = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
767
791
|
end
|
768
792
|
|
769
|
-
stream.on(:
|
770
|
-
stream.on(:frame, &method(:send))
|
771
|
-
|
793
|
+
stream.on(:frame, &method(:send))
|
772
794
|
@streams[id] = stream
|
773
795
|
end
|
774
796
|
|
@@ -820,5 +842,4 @@ module HTTP2
|
|
820
842
|
yield
|
821
843
|
end
|
822
844
|
end
|
823
|
-
# rubocop:enable Metrics/ClassLength
|
824
845
|
end
|
data/lib/http/2/emitter.rb
CHANGED
@@ -12,7 +12,7 @@ module HTTP2
|
|
12
12
|
def on(event, &block)
|
13
13
|
raise ArgumentError, "must provide callback" unless block
|
14
14
|
|
15
|
-
listeners
|
15
|
+
@listeners[event] << block
|
16
16
|
end
|
17
17
|
|
18
18
|
# Subscribe to next event (at most once) for specified type.
|
@@ -32,16 +32,9 @@ module HTTP2
|
|
32
32
|
# @param args [Array] arguments to be passed to the callbacks
|
33
33
|
# @param block [Proc] callback function
|
34
34
|
def emit(event, *args, &block)
|
35
|
-
listeners
|
35
|
+
@listeners[event].delete_if do |cb|
|
36
36
|
:delete == cb.call(*args, &block) # rubocop:disable Style/YodaCondition
|
37
37
|
end
|
38
38
|
end
|
39
|
-
|
40
|
-
private
|
41
|
-
|
42
|
-
def listeners(event)
|
43
|
-
@listeners ||= Hash.new { |hash, key| hash[key] = [] }
|
44
|
-
@listeners[event]
|
45
|
-
end
|
46
39
|
end
|
47
40
|
end
|
data/lib/http/2/extensions.rb
CHANGED
@@ -2,6 +2,26 @@
|
|
2
2
|
|
3
3
|
module HTTP2
|
4
4
|
module BufferUtils
|
5
|
+
if RUBY_VERSION > "3.4.0"
|
6
|
+
def append_str(str, data)
|
7
|
+
str.append_as_bytes(data)
|
8
|
+
end
|
9
|
+
else
|
10
|
+
def append_str(str, data)
|
11
|
+
enc = data.encoding
|
12
|
+
reset = false
|
13
|
+
|
14
|
+
if enc != Encoding::BINARY
|
15
|
+
reset = true
|
16
|
+
data = data.dup if data.frozen?
|
17
|
+
data.force_encoding(Encoding::BINARY)
|
18
|
+
end
|
19
|
+
str << data
|
20
|
+
ensure
|
21
|
+
data.force_encoding(enc) if reset
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
5
25
|
def read_str(str, n)
|
6
26
|
return "".b if n == 0
|
7
27
|
|
@@ -28,7 +48,7 @@ module HTTP2
|
|
28
48
|
packed_str = array_to_pack.pack(template)
|
29
49
|
case offset
|
30
50
|
when -1
|
31
|
-
buffer
|
51
|
+
append_str(buffer, packed_str)
|
32
52
|
when 0
|
33
53
|
buffer.prepend(packed_str)
|
34
54
|
else
|