http-2 1.1.2 → 1.2.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 -1
- data/lib/http/2/connection.rb +79 -62
- data/lib/http/2/extensions.rb +16 -10
- data/lib/http/2/flow_buffer.rb +12 -7
- data/lib/http/2/framer.rb +98 -98
- data/lib/http/2/header/compressor.rb +15 -4
- data/lib/http/2/header/decompressor.rb +11 -10
- data/lib/http/2/header/encoding_context.rb +18 -6
- data/lib/http/2/header/huffman.rb +6 -4
- data/lib/http/2/server.rb +12 -18
- data/lib/http/2/settings.rb +42 -0
- data/lib/http/2/stream.rb +19 -12
- data/lib/http/2/version.rb +1 -1
- data/sig/2.rbs +64 -15
- data/sig/connection.rbs +11 -9
- data/sig/flow_buffer.rbs +2 -2
- data/sig/frame_buffer.rbs +6 -2
- data/sig/framer.rbs +8 -4
- data/sig/header/compressor.rbs +1 -0
- data/sig/header/encoding_context.rbs +4 -0
- data/sig/header/huffman.rbs +2 -0
- data/sig/server.rbs +1 -1
- data/sig/stream.rbs +7 -8
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 37f0171472c53a3f13cb8f8259302aed5f07c9dbc90f27893ddf8b868c78e41a
|
|
4
|
+
data.tar.gz: 52b05bc6ebc9d08d2a761ba465f39315e32ad3738a4c22d1d9a3ab58e7357867
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9f9c98e6f3d031388f451d22b142764d221967bcf72252689652182ffcd9e47bb60b90003491b7a7056eea51a7faaaece880c942ef4d02d223579924587ab103
|
|
7
|
+
data.tar.gz: 1bbe211ac07f751a5381b2c8d633392bb5460b5324af0f717f7ad06f4ff8695c7a3a8122bcca8ceb01b7b5063cd3f95c4e8fdff14a1bbcc3ce640e5609d90da9
|
data/lib/http/2/client.rb
CHANGED
|
@@ -69,7 +69,7 @@ module HTTP2
|
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
def self.settings_header(settings)
|
|
72
|
-
frame = Framer.new.generate(type: :settings, stream: 0, payload: settings)
|
|
72
|
+
frame = Framer.new.generate(type: :settings, stream: 0, flags: 0, payload: settings)
|
|
73
73
|
Base64.urlsafe_encode64(frame[9..-1])
|
|
74
74
|
end
|
|
75
75
|
|
data/lib/http/2/connection.rb
CHANGED
|
@@ -41,8 +41,6 @@ module HTTP2
|
|
|
41
41
|
|
|
42
42
|
CONNECTION_FRAME_TYPES = %i[settings ping goaway].freeze
|
|
43
43
|
|
|
44
|
-
HEADERS_FRAME_TYPES = %i[headers push_promise].freeze
|
|
45
|
-
|
|
46
44
|
STREAM_OPEN_STATES = %i[open half_closed_local].freeze
|
|
47
45
|
|
|
48
46
|
# Connection encapsulates all of the connection, stream, flow-control,
|
|
@@ -91,6 +89,7 @@ module HTTP2
|
|
|
91
89
|
@last_stream_id = 0
|
|
92
90
|
@streams = {}
|
|
93
91
|
@streams_recently_closed = {}
|
|
92
|
+
@oldest_stream_recently_closed = nil
|
|
94
93
|
@pending_settings = []
|
|
95
94
|
|
|
96
95
|
@framer = Framer.new(@local_settings[:settings_max_frame_size])
|
|
@@ -102,6 +101,7 @@ module HTTP2
|
|
|
102
101
|
|
|
103
102
|
@recv_buffer = "".b
|
|
104
103
|
@continuation = []
|
|
104
|
+
@continuation_size = 0
|
|
105
105
|
@error = nil
|
|
106
106
|
|
|
107
107
|
@h2c_upgrade = nil
|
|
@@ -156,7 +156,7 @@ module HTTP2
|
|
|
156
156
|
# @param error [Symbol]
|
|
157
157
|
# @param payload [String]
|
|
158
158
|
def goaway(error = :no_error, payload = nil)
|
|
159
|
-
send(type: :goaway, last_stream: @last_stream_id,
|
|
159
|
+
send(type: :goaway, stream: 0, 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)
|
|
@@ -178,7 +178,6 @@ module HTTP2
|
|
|
178
178
|
validate_settings(@local_role, payload)
|
|
179
179
|
@pending_settings << payload
|
|
180
180
|
send(type: :settings, stream: 0, payload: payload)
|
|
181
|
-
@pending_settings << payload
|
|
182
181
|
end
|
|
183
182
|
|
|
184
183
|
# Decodes incoming bytes into HTTP 2.0 frames and routes them to
|
|
@@ -212,9 +211,8 @@ module HTTP2
|
|
|
212
211
|
end
|
|
213
212
|
|
|
214
213
|
while (frame = @framer.parse(@recv_buffer))
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
frame_type = frame[:type]
|
|
214
|
+
stream_id = frame[:stream] #: Integer
|
|
215
|
+
frame_type = frame[:type] #: Symbol?
|
|
218
216
|
|
|
219
217
|
if is_a?(Client) && !@received_frame
|
|
220
218
|
connection_error(:protocol_error, msg: "didn't receive settings") if frame_type != :settings
|
|
@@ -237,15 +235,16 @@ module HTTP2
|
|
|
237
235
|
# Header blocks MUST be transmitted as a contiguous sequence of frames
|
|
238
236
|
# with no interleaved frames of any other type, or from any other stream.
|
|
239
237
|
unless @continuation.empty?
|
|
238
|
+
# @type var frame: continuation_frame
|
|
240
239
|
connection_error unless frame_type == :continuation && stream_id == @continuation.first[:stream]
|
|
241
240
|
|
|
242
241
|
@continuation << frame
|
|
243
|
-
|
|
244
|
-
|
|
242
|
+
@continuation_size += frame[:payload].bytesize
|
|
243
|
+
unless frame[:flags].anybits?(END_HEADERS)
|
|
245
244
|
# prevent HTTP/2 CONTINUATION FLOOD
|
|
246
245
|
# same heuristic as the one from HAProxy: https://www.haproxy.com/blog/haproxy-is-resilient-to-the-http-2-continuation-flood
|
|
247
246
|
# different mitigation (connection closed, instead of 400 response)
|
|
248
|
-
unless
|
|
247
|
+
unless @continuation_size < @local_settings[:settings_max_frame_size]
|
|
249
248
|
connection_error(:protocol_error,
|
|
250
249
|
msg: "too many continuations received")
|
|
251
250
|
end
|
|
@@ -259,10 +258,11 @@ module HTTP2
|
|
|
259
258
|
frame_type = frame[:type]
|
|
260
259
|
|
|
261
260
|
@continuation.clear
|
|
261
|
+
@continuation_size = 0
|
|
262
262
|
|
|
263
263
|
frame.delete(:length)
|
|
264
264
|
frame[:payload] = payload
|
|
265
|
-
frame[:flags]
|
|
265
|
+
frame[:flags] |= END_HEADERS
|
|
266
266
|
end
|
|
267
267
|
|
|
268
268
|
# SETTINGS frames always apply to a connection, never a single stream.
|
|
@@ -271,18 +271,20 @@ module HTTP2
|
|
|
271
271
|
# anything other than 0x0, the endpoint MUST respond with a connection
|
|
272
272
|
# error (Section 5.4.1) of type PROTOCOL_ERROR.
|
|
273
273
|
if connection_frame?(frame)
|
|
274
|
+
# @type var frame: connection_frame
|
|
274
275
|
connection_error(:protocol_error) unless stream_id.zero?
|
|
275
276
|
connection_management(frame)
|
|
276
277
|
else
|
|
277
278
|
case frame_type
|
|
278
279
|
when :headers
|
|
280
|
+
# @type var frame: headers_frame
|
|
279
281
|
# When server receives even-numbered stream identifier,
|
|
280
282
|
# the endpoint MUST respond with a connection error of type PROTOCOL_ERROR.
|
|
281
283
|
connection_error if stream_id.even? && is_a?(Server)
|
|
282
284
|
|
|
283
285
|
# The last frame in a sequence of HEADERS/CONTINUATION
|
|
284
286
|
# frames MUST have the END_HEADERS flag set.
|
|
285
|
-
unless frame[:flags].
|
|
287
|
+
unless frame[:flags].anybits?(END_HEADERS)
|
|
286
288
|
@continuation << frame
|
|
287
289
|
next
|
|
288
290
|
end
|
|
@@ -312,9 +314,10 @@ module HTTP2
|
|
|
312
314
|
stream << frame
|
|
313
315
|
|
|
314
316
|
when :push_promise
|
|
317
|
+
# @type var frame: push_promise_frame
|
|
315
318
|
# The last frame in a sequence of PUSH_PROMISE/CONTINUATION
|
|
316
319
|
# frames MUST have the END_HEADERS flag set
|
|
317
|
-
unless frame[:flags].
|
|
320
|
+
unless frame[:flags].anybits?(END_HEADERS)
|
|
318
321
|
@continuation << frame
|
|
319
322
|
return
|
|
320
323
|
end
|
|
@@ -434,20 +437,27 @@ module HTTP2
|
|
|
434
437
|
# @note all frames are currently delivered in FIFO order.
|
|
435
438
|
# @param frame [Hash]
|
|
436
439
|
def send(frame)
|
|
437
|
-
frame_type = frame[:type]
|
|
438
|
-
|
|
439
440
|
emit(:frame_sent, frame)
|
|
440
|
-
if frame_type == :data
|
|
441
|
-
send_data(frame, true)
|
|
442
441
|
|
|
443
|
-
|
|
444
|
-
# An endpoint can end a connection at any time. In particular, an
|
|
445
|
-
# endpoint MAY choose to treat a stream error as a connection error.
|
|
442
|
+
frame_type = frame[:type] #: Symbol
|
|
446
443
|
|
|
447
|
-
|
|
448
|
-
|
|
444
|
+
case frame_type
|
|
445
|
+
when :data
|
|
446
|
+
#: @type var frame: data_frame
|
|
447
|
+
send_data(frame, true)
|
|
448
|
+
when :headers, :push_promise
|
|
449
449
|
# HEADERS and PUSH_PROMISE may generate CONTINUATION. Also send
|
|
450
450
|
# RST_STREAM that are not protocol errors
|
|
451
|
+
#: @type var frame: headers_frame | push_promise_frame
|
|
452
|
+
encode_headers(frame) # HEADERS and PUSH_PROMISE may create more than one frame
|
|
453
|
+
else
|
|
454
|
+
if frame_type == :rst_stream && frame[:error] == :protocol_error
|
|
455
|
+
# An endpoint can end a connection at any time. In particular, an
|
|
456
|
+
# endpoint MAY choose to treat a stream error as a connection error.
|
|
457
|
+
|
|
458
|
+
goaway(:protocol_error)
|
|
459
|
+
end
|
|
460
|
+
#: @type var frame: connection_frame
|
|
451
461
|
encode(frame)
|
|
452
462
|
end
|
|
453
463
|
end
|
|
@@ -456,11 +466,7 @@ module HTTP2
|
|
|
456
466
|
#
|
|
457
467
|
# @param frame [Hash]
|
|
458
468
|
def encode(frame)
|
|
459
|
-
|
|
460
|
-
encode_headers(frame) # HEADERS and PUSH_PROMISE may create more than one frame
|
|
461
|
-
else
|
|
462
|
-
emit(:frame, @framer.generate(frame))
|
|
463
|
-
end
|
|
469
|
+
emit(:frame, @framer.generate(frame))
|
|
464
470
|
end
|
|
465
471
|
|
|
466
472
|
# Check if frame is a connection frame: SETTINGS, PING, GOAWAY, and any
|
|
@@ -493,12 +499,15 @@ module HTTP2
|
|
|
493
499
|
when :connected
|
|
494
500
|
case frame_type
|
|
495
501
|
when :settings
|
|
502
|
+
# @type var frame: settings_frame
|
|
496
503
|
connection_settings(frame)
|
|
497
504
|
when :window_update
|
|
505
|
+
# @type var frame: window_update_frame
|
|
498
506
|
process_window_update(frame: frame, encode: true)
|
|
499
507
|
when :ping
|
|
500
508
|
ping_management(frame)
|
|
501
509
|
when :goaway
|
|
510
|
+
# @type var frame: goaway_frame
|
|
502
511
|
# Receivers of a GOAWAY frame MUST NOT open additional streams on
|
|
503
512
|
# the connection, although a new connection can be established
|
|
504
513
|
# for new streams.
|
|
@@ -506,19 +515,19 @@ module HTTP2
|
|
|
506
515
|
@closed_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
507
516
|
emit(:goaway, frame[:last_stream], frame[:error], frame[:payload])
|
|
508
517
|
when :altsvc
|
|
518
|
+
# @type var frame: altsvc_frame
|
|
509
519
|
origin = frame[:origin]
|
|
510
520
|
# 4. The ALTSVC HTTP/2 Frame
|
|
511
521
|
# An ALTSVC frame on stream 0 with empty (length 0) "Origin"
|
|
512
522
|
# information is invalid and MUST be ignored.
|
|
513
523
|
emit(:altsvc, frame) if origin && !origin.empty?
|
|
514
524
|
when :origin
|
|
515
|
-
|
|
525
|
+
# @type var frame: origin_frame
|
|
526
|
+
return if @h2c_upgrade || !frame[:flags].zero?
|
|
516
527
|
|
|
517
528
|
frame[:payload].each do |orig|
|
|
518
529
|
emit(:origin, orig)
|
|
519
530
|
end
|
|
520
|
-
when :blocked
|
|
521
|
-
emit(:blocked, frame)
|
|
522
531
|
else
|
|
523
532
|
connection_error
|
|
524
533
|
end
|
|
@@ -538,11 +547,10 @@ module HTTP2
|
|
|
538
547
|
end
|
|
539
548
|
|
|
540
549
|
def ping_management(frame)
|
|
541
|
-
if frame[:flags].
|
|
550
|
+
if frame[:flags].anybits?(ACK)
|
|
542
551
|
emit(:ack, frame[:payload])
|
|
543
552
|
else
|
|
544
|
-
send(type: :ping, stream: 0,
|
|
545
|
-
flags: [:ack], payload: frame[:payload])
|
|
553
|
+
send(type: :ping, stream: 0, flags: ACK, payload: frame[:payload])
|
|
546
554
|
end
|
|
547
555
|
end
|
|
548
556
|
|
|
@@ -605,13 +613,13 @@ module HTTP2
|
|
|
605
613
|
# side =
|
|
606
614
|
# local: previously sent and pended our settings should be effective
|
|
607
615
|
# remote: just received peer settings should immediately be effective
|
|
608
|
-
if frame[:flags].
|
|
616
|
+
if frame[:flags].anybits?(ACK)
|
|
609
617
|
# Process pending settings we have sent.
|
|
610
618
|
settings = @pending_settings.shift
|
|
611
619
|
side = :local
|
|
612
620
|
else
|
|
613
|
-
validate_settings(@remote_role, frame[:payload])
|
|
614
621
|
settings = frame[:payload]
|
|
622
|
+
validate_settings(@remote_role, settings)
|
|
615
623
|
side = :remote
|
|
616
624
|
end
|
|
617
625
|
|
|
@@ -681,7 +689,7 @@ module HTTP2
|
|
|
681
689
|
when :remote
|
|
682
690
|
unless @state == :closed || @h2c_upgrade == :start
|
|
683
691
|
# Send ack to peer
|
|
684
|
-
send(type: :settings, stream: 0, payload:
|
|
692
|
+
send(type: :settings, stream: 0, payload: EMPTY, flags: ACK)
|
|
685
693
|
# when initial window size changes, we try to flush any buffered
|
|
686
694
|
# data.
|
|
687
695
|
@streams.each_value(&:flush)
|
|
@@ -711,45 +719,49 @@ module HTTP2
|
|
|
711
719
|
#
|
|
712
720
|
# @param headers_frame [Hash]
|
|
713
721
|
def encode_headers(headers_frame)
|
|
714
|
-
|
|
722
|
+
headers_payload = headers_frame[:payload]
|
|
715
723
|
begin
|
|
716
|
-
payload =
|
|
724
|
+
payload = headers_payload.is_a?(String) ? headers_payload : @compressor.encode(headers_payload)
|
|
717
725
|
rescue StandardError => e
|
|
718
726
|
connection_error(:compression_error, e: e)
|
|
719
727
|
end
|
|
720
728
|
|
|
729
|
+
#: @type var payload: String
|
|
730
|
+
headers_frame[:payload] = payload
|
|
731
|
+
|
|
721
732
|
max_frame_size = @remote_settings[:settings_max_frame_size]
|
|
722
733
|
|
|
723
734
|
# if single frame, return immediately
|
|
724
735
|
if payload.bytesize <= max_frame_size
|
|
725
|
-
|
|
736
|
+
encode(headers_frame)
|
|
726
737
|
return
|
|
727
738
|
end
|
|
728
739
|
|
|
729
740
|
# split into multiple CONTINUATION frames
|
|
730
|
-
|
|
741
|
+
total = payload.bytesize
|
|
742
|
+
headers_frame[:flags] ^= END_HEADERS
|
|
731
743
|
headers_frame[:payload] = payload.byteslice(0, max_frame_size)
|
|
732
|
-
payload = payload.byteslice(max_frame_size..-1)
|
|
744
|
+
# payload = payload.byteslice(max_frame_size..-1)
|
|
745
|
+
offset = max_frame_size
|
|
733
746
|
|
|
734
747
|
# emit first HEADERS frame
|
|
735
|
-
|
|
748
|
+
encode(headers_frame)
|
|
736
749
|
|
|
737
|
-
|
|
738
|
-
continuation_frame = headers_frame.merge(
|
|
739
|
-
type: :continuation,
|
|
740
|
-
flags: EMPTY,
|
|
741
|
-
payload: payload.byteslice(0, max_frame_size)
|
|
742
|
-
)
|
|
750
|
+
stream_id = headers_frame[:stream]
|
|
743
751
|
|
|
744
|
-
|
|
752
|
+
while offset < total
|
|
753
|
+
chunk_end = offset + max_frame_size
|
|
754
|
+
is_last = chunk_end >= total
|
|
745
755
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
756
|
+
continuation_frame = {
|
|
757
|
+
type: :continuation,
|
|
758
|
+
flags: is_last ? END_HEADERS : 0,
|
|
759
|
+
stream: stream_id,
|
|
760
|
+
payload: payload.byteslice(offset, max_frame_size)
|
|
761
|
+
} #: continuation_frame
|
|
751
762
|
|
|
752
|
-
|
|
763
|
+
encode(continuation_frame)
|
|
764
|
+
offset = chunk_end
|
|
753
765
|
end
|
|
754
766
|
end
|
|
755
767
|
|
|
@@ -776,19 +788,24 @@ module HTTP2
|
|
|
776
788
|
# is closed, with a minimum of 15s RTT time window.
|
|
777
789
|
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
778
790
|
|
|
779
|
-
_, closed_since = @streams_recently_closed.first
|
|
780
|
-
|
|
781
791
|
# forego recently closed recycling if empty or the first element
|
|
782
792
|
# hasn't expired yet (it's ordered).
|
|
783
|
-
if
|
|
793
|
+
if @oldest_stream_recently_closed && (now - @oldest_stream_recently_closed) > 15
|
|
794
|
+
new_oldest = nil
|
|
784
795
|
# discards all streams which have closed for a while.
|
|
785
796
|
# TODO: use a drop_while! variant whenever there is one.
|
|
786
|
-
@streams_recently_closed
|
|
787
|
-
(now - since) > 15
|
|
788
|
-
|
|
797
|
+
@streams_recently_closed.delete_if do |_, since|
|
|
798
|
+
unless (now - since) > 15
|
|
799
|
+
new_oldest ||= since
|
|
800
|
+
break
|
|
801
|
+
end
|
|
802
|
+
|
|
803
|
+
true
|
|
804
|
+
end
|
|
805
|
+
@oldest_stream_recently_closed = new_oldest
|
|
789
806
|
end
|
|
790
807
|
|
|
791
|
-
@streams_recently_closed[id] =
|
|
808
|
+
@streams_recently_closed[id] = now
|
|
792
809
|
end
|
|
793
810
|
|
|
794
811
|
stream.on(:frame, &method(:send))
|
data/lib/http/2/extensions.rb
CHANGED
|
@@ -22,22 +22,28 @@ module HTTP2
|
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
if String.method_defined?(:bytesplice)
|
|
26
|
+
def read_str(str, n)
|
|
27
|
+
return "".b if n == 0
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
chunk = str.byteslice(0, n)
|
|
30
|
+
str.bytesplice(0, chunk.length, "")
|
|
31
|
+
chunk
|
|
32
|
+
end
|
|
33
|
+
else
|
|
34
|
+
def read_str(str, n)
|
|
35
|
+
return "".b if n == 0
|
|
36
|
+
|
|
37
|
+
chunk = str.byteslice(0, n)
|
|
38
|
+
remaining = str.byteslice(n, str.size - n)
|
|
39
|
+
remaining ? str.replace(remaining) : str.clear
|
|
40
|
+
chunk
|
|
41
|
+
end
|
|
32
42
|
end
|
|
33
43
|
|
|
34
44
|
def read_uint32(str)
|
|
35
45
|
read_str(str, 4).unpack1("N")
|
|
36
46
|
end
|
|
37
|
-
|
|
38
|
-
def shift_byte(str)
|
|
39
|
-
read_str(str, 1).ord
|
|
40
|
-
end
|
|
41
47
|
end
|
|
42
48
|
|
|
43
49
|
# this mixin handles backwards-compatibility for the new packing options
|
data/lib/http/2/flow_buffer.rb
CHANGED
|
@@ -70,7 +70,7 @@ module HTTP2
|
|
|
70
70
|
if frame
|
|
71
71
|
if @send_buffer.empty?
|
|
72
72
|
frame_size = frame[:payload].bytesize
|
|
73
|
-
end_stream = frame[:flags].
|
|
73
|
+
end_stream = frame[:flags].anybits?(END_STREAM)
|
|
74
74
|
# if buffer is empty, and frame is either end 0 length OR
|
|
75
75
|
# is within available window size, skip buffering and send immediately.
|
|
76
76
|
if @remote_window.positive?
|
|
@@ -115,6 +115,8 @@ module HTTP2
|
|
|
115
115
|
end
|
|
116
116
|
|
|
117
117
|
class FrameBuffer
|
|
118
|
+
include BufferUtils
|
|
119
|
+
|
|
118
120
|
attr_reader :bytesize
|
|
119
121
|
|
|
120
122
|
def initialize
|
|
@@ -127,6 +129,11 @@ module HTTP2
|
|
|
127
129
|
@bytesize += frame[:payload].bytesize
|
|
128
130
|
end
|
|
129
131
|
|
|
132
|
+
def clear
|
|
133
|
+
@buffer.clear
|
|
134
|
+
@bytesize = 0
|
|
135
|
+
end
|
|
136
|
+
|
|
130
137
|
def empty?
|
|
131
138
|
@buffer.empty?
|
|
132
139
|
end
|
|
@@ -135,7 +142,7 @@ module HTTP2
|
|
|
135
142
|
frame = @buffer.first or return
|
|
136
143
|
|
|
137
144
|
frame_size = frame[:payload].bytesize
|
|
138
|
-
end_stream = frame[:flags].
|
|
145
|
+
end_stream = frame[:flags].anybits?(END_STREAM)
|
|
139
146
|
|
|
140
147
|
# Frames with zero length with the END_STREAM flag set (that
|
|
141
148
|
# is, an empty DATA frame) MAY be sent if there is no available space
|
|
@@ -143,19 +150,17 @@ module HTTP2
|
|
|
143
150
|
return if window_size <= 0 && !(frame_size.zero? && end_stream)
|
|
144
151
|
|
|
145
152
|
if frame_size > window_size
|
|
146
|
-
chunk
|
|
147
|
-
payload = frame[:payload]
|
|
153
|
+
chunk = frame.dup
|
|
148
154
|
|
|
149
155
|
# Split frame so that it fits in the window
|
|
150
156
|
# TODO: consider padding!
|
|
151
157
|
|
|
152
|
-
chunk[:payload] = payload
|
|
158
|
+
chunk[:payload] = read_str(frame[:payload], window_size) # mutates frame[:payload]
|
|
153
159
|
chunk[:length] = window_size
|
|
154
|
-
frame[:payload] = payload.byteslice(window_size..-1)
|
|
155
160
|
frame[:length] = frame_size - window_size
|
|
156
161
|
|
|
157
162
|
# if no longer last frame in sequence...
|
|
158
|
-
chunk[:flags]
|
|
163
|
+
chunk[:flags] ^= END_STREAM if end_stream
|
|
159
164
|
|
|
160
165
|
@bytesize -= window_size
|
|
161
166
|
chunk
|