http-2 1.1.3 → 1.2.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 -1
- data/lib/http/2/connection.rb +79 -63
- data/lib/http/2/extensions.rb +23 -10
- data/lib/http/2/flow_buffer.rb +7 -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 +4 -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: d1db63e3235efa25ad18a270b128adc3955489766148712fd9405142d9cf1bd7
|
|
4
|
+
data.tar.gz: d3cfbb973cb9a3bc53466c106a6a33b6521f7ba8415d832a646ef96419c906cd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9a17f2bdcbc5fcd07ff0797994af22a0f170744f516702b5b5c2c8c032f60a15cf4c70869804c56f4e15e10829c2c51c8ea0fe5575751cf28b1fe0207389543a
|
|
7
|
+
data.tar.gz: e599eb9b1bfa7a4985b6a0f53fe1592e6d26ea780fc16d8471d23cc2abe5020a91e28fba80e1d227492525373dd117677e2c436bb289022875c3986f3733bb9e
|
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
|
|
@@ -396,7 +399,6 @@ module HTTP2
|
|
|
396
399
|
unless @streams_recently_closed.key?(stream_id)
|
|
397
400
|
connection_error(:protocol_error, msg: "sent window update on idle stream")
|
|
398
401
|
end
|
|
399
|
-
stream = @streams_recently_closed[stream_id]
|
|
400
402
|
process_window_update(frame: frame, encode: true)
|
|
401
403
|
# Endpoints MUST ignore
|
|
402
404
|
# WINDOW_UPDATE or RST_STREAM frames received in this state (closed), though
|
|
@@ -434,20 +436,27 @@ module HTTP2
|
|
|
434
436
|
# @note all frames are currently delivered in FIFO order.
|
|
435
437
|
# @param frame [Hash]
|
|
436
438
|
def send(frame)
|
|
437
|
-
frame_type = frame[:type]
|
|
438
|
-
|
|
439
439
|
emit(:frame_sent, frame)
|
|
440
|
-
if frame_type == :data
|
|
441
|
-
send_data(frame, true)
|
|
442
440
|
|
|
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.
|
|
441
|
+
frame_type = frame[:type] #: Symbol
|
|
446
442
|
|
|
447
|
-
|
|
448
|
-
|
|
443
|
+
case frame_type
|
|
444
|
+
when :data
|
|
445
|
+
#: @type var frame: data_frame
|
|
446
|
+
send_data(frame, true)
|
|
447
|
+
when :headers, :push_promise
|
|
449
448
|
# HEADERS and PUSH_PROMISE may generate CONTINUATION. Also send
|
|
450
449
|
# RST_STREAM that are not protocol errors
|
|
450
|
+
#: @type var frame: headers_frame | push_promise_frame
|
|
451
|
+
encode_headers(frame) # HEADERS and PUSH_PROMISE may create more than one frame
|
|
452
|
+
else
|
|
453
|
+
if frame_type == :rst_stream && frame[:error] == :protocol_error
|
|
454
|
+
# An endpoint can end a connection at any time. In particular, an
|
|
455
|
+
# endpoint MAY choose to treat a stream error as a connection error.
|
|
456
|
+
|
|
457
|
+
goaway(:protocol_error)
|
|
458
|
+
end
|
|
459
|
+
#: @type var frame: connection_frame
|
|
451
460
|
encode(frame)
|
|
452
461
|
end
|
|
453
462
|
end
|
|
@@ -456,11 +465,7 @@ module HTTP2
|
|
|
456
465
|
#
|
|
457
466
|
# @param frame [Hash]
|
|
458
467
|
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
|
|
468
|
+
emit(:frame, @framer.generate(frame))
|
|
464
469
|
end
|
|
465
470
|
|
|
466
471
|
# Check if frame is a connection frame: SETTINGS, PING, GOAWAY, and any
|
|
@@ -493,12 +498,15 @@ module HTTP2
|
|
|
493
498
|
when :connected
|
|
494
499
|
case frame_type
|
|
495
500
|
when :settings
|
|
501
|
+
# @type var frame: settings_frame
|
|
496
502
|
connection_settings(frame)
|
|
497
503
|
when :window_update
|
|
504
|
+
# @type var frame: window_update_frame
|
|
498
505
|
process_window_update(frame: frame, encode: true)
|
|
499
506
|
when :ping
|
|
500
507
|
ping_management(frame)
|
|
501
508
|
when :goaway
|
|
509
|
+
# @type var frame: goaway_frame
|
|
502
510
|
# Receivers of a GOAWAY frame MUST NOT open additional streams on
|
|
503
511
|
# the connection, although a new connection can be established
|
|
504
512
|
# for new streams.
|
|
@@ -506,19 +514,19 @@ module HTTP2
|
|
|
506
514
|
@closed_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
507
515
|
emit(:goaway, frame[:last_stream], frame[:error], frame[:payload])
|
|
508
516
|
when :altsvc
|
|
517
|
+
# @type var frame: altsvc_frame
|
|
509
518
|
origin = frame[:origin]
|
|
510
519
|
# 4. The ALTSVC HTTP/2 Frame
|
|
511
520
|
# An ALTSVC frame on stream 0 with empty (length 0) "Origin"
|
|
512
521
|
# information is invalid and MUST be ignored.
|
|
513
522
|
emit(:altsvc, frame) if origin && !origin.empty?
|
|
514
523
|
when :origin
|
|
515
|
-
|
|
524
|
+
# @type var frame: origin_frame
|
|
525
|
+
return if @h2c_upgrade || !frame[:flags].zero?
|
|
516
526
|
|
|
517
527
|
frame[:payload].each do |orig|
|
|
518
528
|
emit(:origin, orig)
|
|
519
529
|
end
|
|
520
|
-
when :blocked
|
|
521
|
-
emit(:blocked, frame)
|
|
522
530
|
else
|
|
523
531
|
connection_error
|
|
524
532
|
end
|
|
@@ -538,11 +546,10 @@ module HTTP2
|
|
|
538
546
|
end
|
|
539
547
|
|
|
540
548
|
def ping_management(frame)
|
|
541
|
-
if frame[:flags].
|
|
549
|
+
if frame[:flags].anybits?(ACK)
|
|
542
550
|
emit(:ack, frame[:payload])
|
|
543
551
|
else
|
|
544
|
-
send(type: :ping, stream: 0,
|
|
545
|
-
flags: [:ack], payload: frame[:payload])
|
|
552
|
+
send(type: :ping, stream: 0, flags: ACK, payload: frame[:payload])
|
|
546
553
|
end
|
|
547
554
|
end
|
|
548
555
|
|
|
@@ -605,13 +612,13 @@ module HTTP2
|
|
|
605
612
|
# side =
|
|
606
613
|
# local: previously sent and pended our settings should be effective
|
|
607
614
|
# remote: just received peer settings should immediately be effective
|
|
608
|
-
if frame[:flags].
|
|
615
|
+
if frame[:flags].anybits?(ACK)
|
|
609
616
|
# Process pending settings we have sent.
|
|
610
617
|
settings = @pending_settings.shift
|
|
611
618
|
side = :local
|
|
612
619
|
else
|
|
613
|
-
validate_settings(@remote_role, frame[:payload])
|
|
614
620
|
settings = frame[:payload]
|
|
621
|
+
validate_settings(@remote_role, settings)
|
|
615
622
|
side = :remote
|
|
616
623
|
end
|
|
617
624
|
|
|
@@ -681,7 +688,7 @@ module HTTP2
|
|
|
681
688
|
when :remote
|
|
682
689
|
unless @state == :closed || @h2c_upgrade == :start
|
|
683
690
|
# Send ack to peer
|
|
684
|
-
send(type: :settings, stream: 0, payload:
|
|
691
|
+
send(type: :settings, stream: 0, payload: EMPTY, flags: ACK)
|
|
685
692
|
# when initial window size changes, we try to flush any buffered
|
|
686
693
|
# data.
|
|
687
694
|
@streams.each_value(&:flush)
|
|
@@ -711,45 +718,49 @@ module HTTP2
|
|
|
711
718
|
#
|
|
712
719
|
# @param headers_frame [Hash]
|
|
713
720
|
def encode_headers(headers_frame)
|
|
714
|
-
|
|
721
|
+
headers_payload = headers_frame[:payload]
|
|
715
722
|
begin
|
|
716
|
-
payload =
|
|
723
|
+
payload = headers_payload.is_a?(String) ? headers_payload : @compressor.encode(headers_payload)
|
|
717
724
|
rescue StandardError => e
|
|
718
725
|
connection_error(:compression_error, e: e)
|
|
719
726
|
end
|
|
720
727
|
|
|
728
|
+
#: @type var payload: String
|
|
729
|
+
headers_frame[:payload] = payload
|
|
730
|
+
|
|
721
731
|
max_frame_size = @remote_settings[:settings_max_frame_size]
|
|
722
732
|
|
|
723
733
|
# if single frame, return immediately
|
|
724
734
|
if payload.bytesize <= max_frame_size
|
|
725
|
-
|
|
735
|
+
encode(headers_frame)
|
|
726
736
|
return
|
|
727
737
|
end
|
|
728
738
|
|
|
729
739
|
# split into multiple CONTINUATION frames
|
|
730
|
-
|
|
740
|
+
total = payload.bytesize
|
|
741
|
+
headers_frame[:flags] ^= END_HEADERS
|
|
731
742
|
headers_frame[:payload] = payload.byteslice(0, max_frame_size)
|
|
732
|
-
payload = payload.byteslice(max_frame_size..-1)
|
|
743
|
+
# payload = payload.byteslice(max_frame_size..-1)
|
|
744
|
+
offset = max_frame_size
|
|
733
745
|
|
|
734
746
|
# emit first HEADERS frame
|
|
735
|
-
|
|
747
|
+
encode(headers_frame)
|
|
736
748
|
|
|
737
|
-
|
|
738
|
-
continuation_frame = headers_frame.merge(
|
|
739
|
-
type: :continuation,
|
|
740
|
-
flags: EMPTY,
|
|
741
|
-
payload: payload.byteslice(0, max_frame_size)
|
|
742
|
-
)
|
|
749
|
+
stream_id = headers_frame[:stream]
|
|
743
750
|
|
|
744
|
-
|
|
751
|
+
while offset < total
|
|
752
|
+
chunk_end = offset + max_frame_size
|
|
753
|
+
is_last = chunk_end >= total
|
|
745
754
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
755
|
+
continuation_frame = {
|
|
756
|
+
type: :continuation,
|
|
757
|
+
flags: is_last ? END_HEADERS : 0,
|
|
758
|
+
stream: stream_id,
|
|
759
|
+
payload: payload.byteslice(offset, max_frame_size)
|
|
760
|
+
} #: continuation_frame
|
|
751
761
|
|
|
752
|
-
|
|
762
|
+
encode(continuation_frame)
|
|
763
|
+
offset = chunk_end
|
|
753
764
|
end
|
|
754
765
|
end
|
|
755
766
|
|
|
@@ -776,19 +787,24 @@ module HTTP2
|
|
|
776
787
|
# is closed, with a minimum of 15s RTT time window.
|
|
777
788
|
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
778
789
|
|
|
779
|
-
_, closed_since = @streams_recently_closed.first
|
|
780
|
-
|
|
781
790
|
# forego recently closed recycling if empty or the first element
|
|
782
791
|
# hasn't expired yet (it's ordered).
|
|
783
|
-
if
|
|
792
|
+
if @oldest_stream_recently_closed && (now - @oldest_stream_recently_closed) > 15
|
|
793
|
+
new_oldest = nil
|
|
784
794
|
# discards all streams which have closed for a while.
|
|
785
795
|
# TODO: use a drop_while! variant whenever there is one.
|
|
786
|
-
@streams_recently_closed
|
|
787
|
-
(now - since) > 15
|
|
788
|
-
|
|
796
|
+
@streams_recently_closed.delete_if do |_, since|
|
|
797
|
+
unless (now - since) > 15
|
|
798
|
+
new_oldest ||= since
|
|
799
|
+
break
|
|
800
|
+
end
|
|
801
|
+
|
|
802
|
+
true
|
|
803
|
+
end
|
|
804
|
+
@oldest_stream_recently_closed = new_oldest
|
|
789
805
|
end
|
|
790
806
|
|
|
791
|
-
@streams_recently_closed[id] =
|
|
807
|
+
@streams_recently_closed[id] = now
|
|
792
808
|
end
|
|
793
809
|
|
|
794
810
|
stream.on(:frame, &method(:send))
|
data/lib/http/2/extensions.rb
CHANGED
|
@@ -22,22 +22,35 @@ 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
|
+
enc = str.encoding
|
|
30
|
+
|
|
31
|
+
if enc != Encoding::BINARY
|
|
32
|
+
str = str.dup if str.frozen?
|
|
33
|
+
str.force_encoding(Encoding::BINARY)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
chunk = str.byteslice(0, n)
|
|
37
|
+
str.bytesplice(0, chunk.length, "")
|
|
38
|
+
chunk.force_encoding(Encoding::BINARY)
|
|
39
|
+
end
|
|
40
|
+
else
|
|
41
|
+
def read_str(str, n)
|
|
42
|
+
return "".b if n == 0
|
|
43
|
+
|
|
44
|
+
chunk = str.byteslice(0, n)
|
|
45
|
+
remaining = str.byteslice(n, str.size - n)
|
|
46
|
+
remaining ? str.replace(remaining) : str.clear
|
|
47
|
+
chunk.force_encoding(Encoding::BINARY)
|
|
48
|
+
end
|
|
32
49
|
end
|
|
33
50
|
|
|
34
51
|
def read_uint32(str)
|
|
35
52
|
read_str(str, 4).unpack1("N")
|
|
36
53
|
end
|
|
37
|
-
|
|
38
|
-
def shift_byte(str)
|
|
39
|
-
read_str(str, 1).ord
|
|
40
|
-
end
|
|
41
54
|
end
|
|
42
55
|
|
|
43
56
|
# 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
|
|
@@ -140,7 +142,7 @@ module HTTP2
|
|
|
140
142
|
frame = @buffer.first or return
|
|
141
143
|
|
|
142
144
|
frame_size = frame[:payload].bytesize
|
|
143
|
-
end_stream = frame[:flags].
|
|
145
|
+
end_stream = frame[:flags].anybits?(END_STREAM)
|
|
144
146
|
|
|
145
147
|
# Frames with zero length with the END_STREAM flag set (that
|
|
146
148
|
# is, an empty DATA frame) MAY be sent if there is no available space
|
|
@@ -148,19 +150,17 @@ module HTTP2
|
|
|
148
150
|
return if window_size <= 0 && !(frame_size.zero? && end_stream)
|
|
149
151
|
|
|
150
152
|
if frame_size > window_size
|
|
151
|
-
chunk
|
|
152
|
-
payload = frame[:payload]
|
|
153
|
+
chunk = frame.dup
|
|
153
154
|
|
|
154
155
|
# Split frame so that it fits in the window
|
|
155
156
|
# TODO: consider padding!
|
|
156
157
|
|
|
157
|
-
chunk[:payload] = payload
|
|
158
|
+
chunk[:payload] = read_str(frame[:payload], window_size) # mutates frame[:payload]
|
|
158
159
|
chunk[:length] = window_size
|
|
159
|
-
frame[:payload] = payload.byteslice(window_size..-1)
|
|
160
160
|
frame[:length] = frame_size - window_size
|
|
161
161
|
|
|
162
162
|
# if no longer last frame in sequence...
|
|
163
|
-
chunk[:flags]
|
|
163
|
+
chunk[:flags] ^= END_STREAM if end_stream
|
|
164
164
|
|
|
165
165
|
@bytesize -= window_size
|
|
166
166
|
chunk
|