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