http-2 1.0.2 → 1.1.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/lib/http/2/client.rb +1 -0
- data/lib/http/2/connection.rb +116 -96
- 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 +113 -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 +9 -4
- 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: b5f246529e7f64c9b6c841e7733a8bcc74ce34452fa0569c48bf1a858d47d659
|
4
|
+
data.tar.gz: ae38ca8cb1814c168fa2e90f729b12b0a3017a41d35406472dfe425308adb107
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5cc781007b5fa286344fb6ebc1495b4caaddc5c25749ae1ab6fb938f18f17ac50624bbf0160cb3e6661b91d1290ea350a550696e6ed28db20f6bb34c0ca0b6c7
|
7
|
+
data.tar.gz: 2bbcd46df4b28f59f7b64c68338998bae8d5199ee71942704ae32fb8e643c328b888b2579db4d8bf1b8ed220e76ea1bc27d9ef14ecc5ad21c05cb35252f2e4d1
|
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,15 +393,17 @@ 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
|
-
stream = @streams_recently_closed[
|
406
|
+
stream = @streams_recently_closed[stream_id]
|
399
407
|
connection_error(:protocol_error, msg: "sent window update on idle stream") unless stream
|
400
408
|
else
|
401
409
|
# An endpoint that receives an unexpected stream identifier
|
@@ -425,35 +433,33 @@ module HTTP2
|
|
425
433
|
# @note all frames are currently delivered in FIFO order.
|
426
434
|
# @param frame [Hash]
|
427
435
|
def send(frame)
|
436
|
+
frame_type = frame[:type]
|
437
|
+
|
428
438
|
emit(:frame_sent, frame)
|
429
|
-
if
|
439
|
+
if frame_type == :data
|
430
440
|
send_data(frame, true)
|
431
441
|
|
432
|
-
elsif
|
442
|
+
elsif frame_type == :rst_stream && frame[:error] == :protocol_error
|
433
443
|
# An endpoint can end a connection at any time. In particular, an
|
434
444
|
# endpoint MAY choose to treat a stream error as a connection error.
|
435
445
|
|
436
|
-
goaway(
|
446
|
+
goaway(:protocol_error)
|
437
447
|
else
|
438
448
|
# HEADERS and PUSH_PROMISE may generate CONTINUATION. Also send
|
439
449
|
# RST_STREAM that are not protocol errors
|
440
|
-
|
441
|
-
frames.each { |f| emit(:frame, f) }
|
450
|
+
encode(frame)
|
442
451
|
end
|
443
452
|
end
|
444
453
|
|
445
454
|
# Applies HTTP 2.0 binary encoding to the frame.
|
446
455
|
#
|
447
456
|
# @param frame [Hash]
|
448
|
-
# @return [Array of Buffer] encoded frame
|
449
457
|
def encode(frame)
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
frames.map { |f| @framer.generate(f) }
|
458
|
+
if HEADERS_FRAME_TYPES.include?(frame[:type])
|
459
|
+
encode_headers(frame) # HEADERS and PUSH_PROMISE may create more than one frame
|
460
|
+
else
|
461
|
+
emit(:frame, @framer.generate(frame))
|
462
|
+
end
|
457
463
|
end
|
458
464
|
|
459
465
|
# Check if frame is a connection frame: SETTINGS, PING, GOAWAY, and any
|
@@ -462,10 +468,7 @@ module HTTP2
|
|
462
468
|
# @param frame [Hash]
|
463
469
|
# @return [Boolean]
|
464
470
|
def connection_frame?(frame)
|
465
|
-
|
466
|
-
frame[:type] == :settings ||
|
467
|
-
frame[:type] == :ping ||
|
468
|
-
frame[:type] == :goaway
|
471
|
+
frame[:stream].zero? || CONNECTION_FRAME_TYPES.include?(frame[:type])
|
469
472
|
end
|
470
473
|
|
471
474
|
# Process received connection frame (stream ID = 0).
|
@@ -476,14 +479,18 @@ module HTTP2
|
|
476
479
|
#
|
477
480
|
# @param frame [Hash]
|
478
481
|
def connection_management(frame)
|
482
|
+
frame_type = frame[:type]
|
483
|
+
|
479
484
|
case @state
|
480
485
|
when :waiting_connection_preface
|
481
486
|
# The first frame MUST be a SETTINGS frame at the start of a connection.
|
487
|
+
connection_error unless frame[:type] == :settings
|
488
|
+
|
482
489
|
@state = :connected
|
483
490
|
connection_settings(frame)
|
484
491
|
|
485
492
|
when :connected
|
486
|
-
case
|
493
|
+
case frame_type
|
487
494
|
when :settings
|
488
495
|
connection_settings(frame)
|
489
496
|
when :window_update
|
@@ -498,23 +505,24 @@ module HTTP2
|
|
498
505
|
@closed_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
499
506
|
emit(:goaway, frame[:last_stream], frame[:error], frame[:payload])
|
500
507
|
when :altsvc
|
508
|
+
origin = frame[:origin]
|
501
509
|
# 4. The ALTSVC HTTP/2 Frame
|
502
510
|
# An ALTSVC frame on stream 0 with empty (length 0) "Origin"
|
503
511
|
# information is invalid and MUST be ignored.
|
504
|
-
emit(
|
512
|
+
emit(:altsvc, frame) if origin && !origin.empty?
|
505
513
|
when :origin
|
506
514
|
return if @h2c_upgrade || !frame[:flags].empty?
|
507
515
|
|
508
|
-
frame[:payload].each do |
|
509
|
-
emit(
|
516
|
+
frame[:payload].each do |orig|
|
517
|
+
emit(:origin, orig)
|
510
518
|
end
|
511
519
|
when :blocked
|
512
|
-
emit(
|
520
|
+
emit(:blocked, frame)
|
513
521
|
else
|
514
522
|
connection_error
|
515
523
|
end
|
516
524
|
when :closed
|
517
|
-
case
|
525
|
+
case frame_type
|
518
526
|
when :goaway
|
519
527
|
connection_error
|
520
528
|
when :ping
|
@@ -573,7 +581,7 @@ module HTTP2
|
|
573
581
|
# allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
|
574
582
|
# Values outside this range MUST be treated as a connection error
|
575
583
|
# (Section 5.4.1) of type PROTOCOL_ERROR.
|
576
|
-
next if v
|
584
|
+
next if v.between?(16_384, 16_777_215)
|
577
585
|
|
578
586
|
connection_error(:protocol_error, msg: "invalid #{key} value")
|
579
587
|
# when :settings_max_concurrent_streams
|
@@ -591,19 +599,19 @@ module HTTP2
|
|
591
599
|
#
|
592
600
|
# @param frame [Hash]
|
593
601
|
def connection_settings(frame)
|
594
|
-
connection_error unless frame[:type] == :settings && (frame[:stream]).zero?
|
595
|
-
|
596
602
|
# Apply settings.
|
597
603
|
# side =
|
598
604
|
# local: previously sent and pended our settings should be effective
|
599
605
|
# remote: just received peer settings should immediately be effective
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
606
|
+
if frame[:flags].include?(:ack)
|
607
|
+
# Process pending settings we have sent.
|
608
|
+
settings = @pending_settings.shift
|
609
|
+
side = :local
|
610
|
+
else
|
611
|
+
validate_settings(@remote_role, frame[:payload])
|
612
|
+
settings = frame[:payload]
|
613
|
+
side = :remote
|
614
|
+
end
|
607
615
|
|
608
616
|
settings.each do |key, v|
|
609
617
|
case side
|
@@ -699,40 +707,48 @@ module HTTP2
|
|
699
707
|
|
700
708
|
# Encode headers payload and update connection compressor state.
|
701
709
|
#
|
702
|
-
# @param
|
703
|
-
|
704
|
-
|
705
|
-
payload = frame[:payload]
|
710
|
+
# @param headers_frame [Hash]
|
711
|
+
def encode_headers(headers_frame)
|
712
|
+
payload = headers_frame[:payload]
|
706
713
|
begin
|
707
|
-
payload =
|
714
|
+
payload = headers_frame[:payload] = @compressor.encode(payload) unless payload.is_a?(String)
|
708
715
|
rescue StandardError => e
|
709
716
|
connection_error(:compression_error, e: e)
|
710
717
|
end
|
711
718
|
|
712
|
-
|
713
|
-
return [frame] if payload.bytesize <= @remote_settings[:settings_max_frame_size]
|
719
|
+
max_frame_size = @remote_settings[:settings_max_frame_size]
|
714
720
|
|
715
|
-
|
721
|
+
# if single frame, return immediately
|
722
|
+
if payload.bytesize <= max_frame_size
|
723
|
+
emit(:frame, @framer.generate(headers_frame))
|
724
|
+
return
|
725
|
+
end
|
716
726
|
|
717
|
-
|
718
|
-
|
727
|
+
# split into multiple CONTINUATION frames
|
728
|
+
headers_frame[:flags].delete(:end_headers)
|
729
|
+
headers_frame[:payload] = payload.byteslice(0, max_frame_size)
|
730
|
+
payload = payload.byteslice(max_frame_size..-1)
|
719
731
|
|
720
|
-
|
721
|
-
|
722
|
-
cont[:type] = :continuation
|
723
|
-
cont[:flags] = EMPTY
|
724
|
-
end
|
732
|
+
# emit first HEADERS frame
|
733
|
+
emit(:frame, @framer.generate(headers_frame))
|
725
734
|
|
726
|
-
|
727
|
-
|
735
|
+
loop do
|
736
|
+
continuation_frame = headers_frame.merge(
|
737
|
+
type: :continuation,
|
738
|
+
flags: EMPTY,
|
739
|
+
payload: payload.byteslice(0, max_frame_size)
|
740
|
+
)
|
728
741
|
|
729
|
-
|
730
|
-
end
|
742
|
+
payload = payload.byteslice(max_frame_size..-1)
|
731
743
|
|
732
|
-
|
733
|
-
|
744
|
+
if payload.nil? || payload.empty?
|
745
|
+
continuation_frame[:flags] = [:end_headers]
|
746
|
+
emit(:frame, @framer.generate(continuation_frame))
|
747
|
+
break
|
748
|
+
end
|
734
749
|
|
735
|
-
|
750
|
+
emit(:frame, @framer.generate(continuation_frame))
|
751
|
+
end
|
736
752
|
end
|
737
753
|
|
738
754
|
# Activates new incoming or outgoing stream and registers appropriate
|
@@ -758,17 +774,22 @@ module HTTP2
|
|
758
774
|
# is closed, with a minimum of 15s RTT time window.
|
759
775
|
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
760
776
|
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
777
|
+
_, closed_since = @streams_recently_closed.first
|
778
|
+
|
779
|
+
# forego recently closed recycling if empty or the first element
|
780
|
+
# hasn't expired yet (it's ordered).
|
781
|
+
if closed_since && (now - closed_since) > 15
|
782
|
+
# discards all streams which have closed for a while.
|
783
|
+
# TODO: use a drop_while! variant whenever there is one.
|
784
|
+
@streams_recently_closed = @streams_recently_closed.drop_while do |_, since|
|
785
|
+
(now - since) > 15
|
786
|
+
end.to_h
|
787
|
+
end
|
765
788
|
|
766
789
|
@streams_recently_closed[id] = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
767
790
|
end
|
768
791
|
|
769
|
-
stream.on(:
|
770
|
-
stream.on(:frame, &method(:send))
|
771
|
-
|
792
|
+
stream.on(:frame, &method(:send))
|
772
793
|
@streams[id] = stream
|
773
794
|
end
|
774
795
|
|
@@ -820,5 +841,4 @@ module HTTP2
|
|
820
841
|
yield
|
821
842
|
end
|
822
843
|
end
|
823
|
-
# rubocop:enable Metrics/ClassLength
|
824
844
|
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
|