http-2 1.2.0 → 1.2.2
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/README.md +9 -0
- data/lib/http/2/connection.rb +56 -19
- data/lib/http/2/error.rb +1 -1
- data/lib/http/2/extensions.rb +9 -2
- data/lib/http/2/version.rb +1 -1
- data/sig/connection.rbs +5 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1b41c25d0512d4056a38265801e7eef05df3cab5720ecbe11ff088c906752019
|
|
4
|
+
data.tar.gz: 0b7322ff7f49e8dd892a9804a1a710be23f3c7105f76f7c67f62b281843616fc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 32b4191d53effc33d5186da7093af95698809f2fec89fe37ce01898628d598e7a1215704875ec06ebd9fcaf84b11517310ee46631fb7a2c6b4a2a17fb3ad8b94
|
|
7
|
+
data.tar.gz: 5ce7ec0abedca471a25e45883ba4b8d315eb1e78879ed5a8c563dc6a58a7e128bbe9f78f878a8d9956dd90254718634bc67323b57039cb33e87d6e1552cac392
|
data/README.md
CHANGED
|
@@ -85,6 +85,15 @@ Events emitted by the connection object:
|
|
|
85
85
|
</tr>
|
|
86
86
|
</table>
|
|
87
87
|
|
|
88
|
+
Connection shutdown happens in two steps. Once a GOAWAY frame is sent or
|
|
89
|
+
received, no new streams may be opened: `closed?` turns true and `new_stream`
|
|
90
|
+
raises `ConnectionClosed` — new work belongs on a new connection. After a
|
|
91
|
+
graceful GOAWAY (`:no_error`), streams the peer may still complete keep
|
|
92
|
+
running: `closing?` distinguishes this draining state, in which the connection
|
|
93
|
+
keeps processing frames — including connection-level flow control — until the
|
|
94
|
+
last tracked stream closes. Streams the GOAWAY refused (id above its
|
|
95
|
+
last-stream-id) are reported via the `:goaway` event, so their callers can
|
|
96
|
+
retry them on a fresh connection.
|
|
88
97
|
|
|
89
98
|
### Stream lifecycle management
|
|
90
99
|
|
data/lib/http/2/connection.rb
CHANGED
|
@@ -56,7 +56,8 @@ module HTTP2
|
|
|
56
56
|
include Error
|
|
57
57
|
include BufferUtils
|
|
58
58
|
|
|
59
|
-
# Connection state (:
|
|
59
|
+
# Connection state (:waiting_magic, :waiting_connection_preface,
|
|
60
|
+
# :connected, :closing, :closed).
|
|
60
61
|
attr_reader :state
|
|
61
62
|
|
|
62
63
|
# Size of current connection flow control window (by default, set to
|
|
@@ -113,8 +114,15 @@ module HTTP2
|
|
|
113
114
|
@send_buffer = FrameBuffer.new
|
|
114
115
|
end
|
|
115
116
|
|
|
117
|
+
# No new streams may be opened (a GOAWAY was sent or received).
|
|
116
118
|
def closed?
|
|
117
|
-
@state == :closed
|
|
119
|
+
@state == :closed || @state == :closing
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Graceful GOAWAY received: tracked streams may still complete,
|
|
123
|
+
# but no new ones can be opened.
|
|
124
|
+
def closing?
|
|
125
|
+
@state == :closing
|
|
118
126
|
end
|
|
119
127
|
|
|
120
128
|
# Allocates new stream for current connection.
|
|
@@ -123,12 +131,15 @@ module HTTP2
|
|
|
123
131
|
# @param window [Integer]
|
|
124
132
|
# @param parent [Stream]
|
|
125
133
|
def new_stream(**args)
|
|
126
|
-
raise ConnectionClosed if
|
|
127
|
-
raise StreamLimitExceeded if @active_stream_count >= @remote_settings[:settings_max_concurrent_streams]
|
|
134
|
+
raise ConnectionClosed if closed?
|
|
128
135
|
|
|
129
136
|
connection_error(:protocol_error, msg: "id is smaller than previous") if @stream_id < @last_stream_id
|
|
130
137
|
|
|
131
|
-
stream = activate_stream(
|
|
138
|
+
stream = activate_stream(
|
|
139
|
+
id: @stream_id,
|
|
140
|
+
max_concurrent_streams: @remote_settings[:settings_max_concurrent_streams],
|
|
141
|
+
**args
|
|
142
|
+
)
|
|
132
143
|
@last_stream_id = stream.id
|
|
133
144
|
|
|
134
145
|
@stream_id += 2
|
|
@@ -158,8 +169,7 @@ module HTTP2
|
|
|
158
169
|
def goaway(error = :no_error, payload = nil)
|
|
159
170
|
send(type: :goaway, stream: 0, last_stream: @last_stream_id,
|
|
160
171
|
error: error, payload: payload)
|
|
161
|
-
|
|
162
|
-
@closed_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
172
|
+
close!
|
|
163
173
|
end
|
|
164
174
|
|
|
165
175
|
# Sends a WINDOW_UPDATE frame to the peer.
|
|
@@ -295,7 +305,11 @@ module HTTP2
|
|
|
295
305
|
# PUSH_PROMISE and CONTINUATION frames MUST be minimally
|
|
296
306
|
# processed to ensure a consistent compression state
|
|
297
307
|
decode_headers(frame)
|
|
298
|
-
|
|
308
|
+
# After a GOAWAY, HEADERS may only complete a tracked stream at
|
|
309
|
+
# or below the highest id seen (Section 6.8); anything else is
|
|
310
|
+
# discarded - decoded above, so the compression state is intact.
|
|
311
|
+
# Skip only this frame: later ones may serve completing streams.
|
|
312
|
+
next if closed? && (stream_id > @last_stream_id || !@streams.key?(stream_id))
|
|
299
313
|
|
|
300
314
|
stream = @streams[stream_id]
|
|
301
315
|
if stream.nil?
|
|
@@ -323,7 +337,7 @@ module HTTP2
|
|
|
323
337
|
end
|
|
324
338
|
|
|
325
339
|
decode_headers(frame)
|
|
326
|
-
|
|
340
|
+
next if closed?
|
|
327
341
|
|
|
328
342
|
# PUSH_PROMISE frames MUST be associated with an existing, peer-
|
|
329
343
|
# initiated stream... A receiver MUST treat the receipt of a
|
|
@@ -375,11 +389,16 @@ module HTTP2
|
|
|
375
389
|
end
|
|
376
390
|
else
|
|
377
391
|
case frame_type
|
|
378
|
-
#
|
|
379
|
-
#
|
|
380
|
-
#
|
|
381
|
-
# unused or closed parent stream.
|
|
392
|
+
# Priority signaling is deprecated (RFC 9113 Section 5.3.2), but
|
|
393
|
+
# a PRIORITY frame may still name a stream id not opened yet:
|
|
394
|
+
# activate it, so the priority sticks if the stream opens.
|
|
382
395
|
when :priority
|
|
396
|
+
# After a GOAWAY, a PRIORITY for a stream already used and
|
|
397
|
+
# closed must not resurrect it: reprioritizing a closed
|
|
398
|
+
# parent is a no-op here, and a phantom stream would hold
|
|
399
|
+
# the draining connection open.
|
|
400
|
+
next if closed? && stream_id <= @last_stream_id
|
|
401
|
+
|
|
383
402
|
stream = activate_stream(
|
|
384
403
|
id: stream_id,
|
|
385
404
|
weight: frame[:weight] || DEFAULT_WEIGHT,
|
|
@@ -399,7 +418,6 @@ module HTTP2
|
|
|
399
418
|
unless @streams_recently_closed.key?(stream_id)
|
|
400
419
|
connection_error(:protocol_error, msg: "sent window update on idle stream")
|
|
401
420
|
end
|
|
402
|
-
stream = @streams_recently_closed[stream_id]
|
|
403
421
|
process_window_update(frame: frame, encode: true)
|
|
404
422
|
# Endpoints MUST ignore
|
|
405
423
|
# WINDOW_UPDATE or RST_STREAM frames received in this state (closed), though
|
|
@@ -496,7 +514,7 @@ module HTTP2
|
|
|
496
514
|
@state = :connected
|
|
497
515
|
connection_settings(frame)
|
|
498
516
|
|
|
499
|
-
when :connected
|
|
517
|
+
when :connected, :closing
|
|
500
518
|
case frame_type
|
|
501
519
|
when :settings
|
|
502
520
|
# @type var frame: settings_frame
|
|
@@ -511,8 +529,13 @@ module HTTP2
|
|
|
511
529
|
# Receivers of a GOAWAY frame MUST NOT open additional streams on
|
|
512
530
|
# the connection, although a new connection can be established
|
|
513
531
|
# for new streams.
|
|
514
|
-
|
|
515
|
-
|
|
532
|
+
if frame[:error] == :no_error && !@streams.empty?
|
|
533
|
+
# A graceful GOAWAY lets tracked streams complete before the
|
|
534
|
+
# connection closes (Section 6.8).
|
|
535
|
+
@state = :closing
|
|
536
|
+
else
|
|
537
|
+
close!
|
|
538
|
+
end
|
|
516
539
|
emit(:goaway, frame[:last_stream], frame[:error], frame[:payload])
|
|
517
540
|
when :altsvc
|
|
518
541
|
# @type var frame: altsvc_frame
|
|
@@ -769,19 +792,28 @@ module HTTP2
|
|
|
769
792
|
# connection managemet callbacks.
|
|
770
793
|
#
|
|
771
794
|
# @param id [Integer]
|
|
795
|
+
# @param max_concurrent_streams [Symbol] threshold to cap the number of active streams with.
|
|
772
796
|
# @param priority [Integer]
|
|
773
797
|
# @param window [Integer]
|
|
774
798
|
# @param parent [Stream]
|
|
775
|
-
def activate_stream(id:, **args)
|
|
799
|
+
def activate_stream(id:, max_concurrent_streams: @local_settings[:settings_max_concurrent_streams], **args)
|
|
776
800
|
connection_error(msg: "Stream ID already exists") if @streams.key?(id)
|
|
777
801
|
|
|
778
|
-
|
|
802
|
+
# SETTINGS_MAX_CONCURRENT_STREAMS limits the number of concurrent streams that the sender
|
|
803
|
+
# of the setting permits the receiver to create (RFC 9113, section 5.1.2), so the bounding
|
|
804
|
+
# limit is the one advertised by the endpoint that did *not* open the stream.
|
|
805
|
+
raise StreamLimitExceeded if @active_stream_count >= max_concurrent_streams
|
|
779
806
|
|
|
780
807
|
stream = Stream.new(connection: self, id: id, **args)
|
|
781
808
|
|
|
782
809
|
stream.once(:close) do
|
|
783
810
|
@streams.delete(id)
|
|
784
811
|
|
|
812
|
+
# A graceful GOAWAY leaves the connection :closing until the
|
|
813
|
+
# tracked streams complete (Section 6.8); the last one to close
|
|
814
|
+
# moves it to its terminal state.
|
|
815
|
+
close! if @state == :closing && @streams.empty?
|
|
816
|
+
|
|
785
817
|
# Store a reference to the closed stream, such that we can respond
|
|
786
818
|
# to any in-flight frames while close is registered on both sides.
|
|
787
819
|
# References to such streams will be purged whenever another stream
|
|
@@ -812,6 +844,11 @@ module HTTP2
|
|
|
812
844
|
@streams[id] = stream
|
|
813
845
|
end
|
|
814
846
|
|
|
847
|
+
def close!
|
|
848
|
+
@state = :closed
|
|
849
|
+
@closed_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
850
|
+
end
|
|
851
|
+
|
|
815
852
|
def verify_stream_order(id)
|
|
816
853
|
return unless id.odd?
|
|
817
854
|
|
data/lib/http/2/error.rb
CHANGED
|
@@ -53,7 +53,7 @@ module HTTP2
|
|
|
53
53
|
# Raised if stream has been closed and new frames cannot be sent.
|
|
54
54
|
class StreamClosed < Error; end
|
|
55
55
|
|
|
56
|
-
# Raised if connection has been closed (or
|
|
56
|
+
# Raised if connection has been closed (or closing) and new stream
|
|
57
57
|
# cannot be opened.
|
|
58
58
|
class ConnectionClosed < Error; end
|
|
59
59
|
|
data/lib/http/2/extensions.rb
CHANGED
|
@@ -26,9 +26,16 @@ module HTTP2
|
|
|
26
26
|
def read_str(str, n)
|
|
27
27
|
return "".b if n == 0
|
|
28
28
|
|
|
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
|
+
|
|
29
36
|
chunk = str.byteslice(0, n)
|
|
30
37
|
str.bytesplice(0, chunk.length, "")
|
|
31
|
-
chunk
|
|
38
|
+
chunk.force_encoding(Encoding::BINARY)
|
|
32
39
|
end
|
|
33
40
|
else
|
|
34
41
|
def read_str(str, n)
|
|
@@ -37,7 +44,7 @@ module HTTP2
|
|
|
37
44
|
chunk = str.byteslice(0, n)
|
|
38
45
|
remaining = str.byteslice(n, str.size - n)
|
|
39
46
|
remaining ? str.replace(remaining) : str.clear
|
|
40
|
-
chunk
|
|
47
|
+
chunk.force_encoding(Encoding::BINARY)
|
|
41
48
|
end
|
|
42
49
|
end
|
|
43
50
|
|
data/lib/http/2/version.rb
CHANGED
data/sig/connection.rbs
CHANGED
|
@@ -59,6 +59,10 @@ module HTTP2
|
|
|
59
59
|
|
|
60
60
|
def closed?: () -> bool
|
|
61
61
|
|
|
62
|
+
def closing?: () -> bool
|
|
63
|
+
|
|
64
|
+
def close!: () -> void
|
|
65
|
+
|
|
62
66
|
def new_stream: (**untyped) -> Stream
|
|
63
67
|
|
|
64
68
|
def ping: (String) -> void
|
|
@@ -95,7 +99,7 @@ module HTTP2
|
|
|
95
99
|
|
|
96
100
|
def encode_headers: (headers_frame | push_promise_frame) -> void
|
|
97
101
|
|
|
98
|
-
def activate_stream: (id: Integer, **untyped) -> Stream
|
|
102
|
+
def activate_stream: (id: Integer, ?max_concurrent_streams: Integer, **untyped) -> Stream
|
|
99
103
|
|
|
100
104
|
def verify_stream_order: (Integer id) -> void
|
|
101
105
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: http-2
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.2.
|
|
4
|
+
version: 1.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tiago Cardoso
|
|
@@ -78,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
78
78
|
- !ruby/object:Gem::Version
|
|
79
79
|
version: '0'
|
|
80
80
|
requirements: []
|
|
81
|
-
rubygems_version:
|
|
81
|
+
rubygems_version: 4.0.16
|
|
82
82
|
specification_version: 4
|
|
83
83
|
summary: Pure-ruby HTTP 2.0 protocol implementation
|
|
84
84
|
test_files: []
|