http-2 1.2.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d1db63e3235efa25ad18a270b128adc3955489766148712fd9405142d9cf1bd7
4
- data.tar.gz: d3cfbb973cb9a3bc53466c106a6a33b6521f7ba8415d832a646ef96419c906cd
3
+ metadata.gz: 1b41c25d0512d4056a38265801e7eef05df3cab5720ecbe11ff088c906752019
4
+ data.tar.gz: 0b7322ff7f49e8dd892a9804a1a710be23f3c7105f76f7c67f62b281843616fc
5
5
  SHA512:
6
- metadata.gz: 9a17f2bdcbc5fcd07ff0797994af22a0f170744f516702b5b5c2c8c032f60a15cf4c70869804c56f4e15e10829c2c51c8ea0fe5575751cf28b1fe0207389543a
7
- data.tar.gz: e599eb9b1bfa7a4985b6a0f53fe1592e6d26ea780fc16d8471d23cc2abe5020a91e28fba80e1d227492525373dd117677e2c436bb289022875c3986f3733bb9e
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
 
@@ -56,7 +56,8 @@ module HTTP2
56
56
  include Error
57
57
  include BufferUtils
58
58
 
59
- # Connection state (:new, :closed).
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 @state == :closed
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(id: @stream_id, **args)
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
- @state = :closed
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
- return if @state == :closed
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
- return if @state == :closed
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
- # The PRIORITY frame can be sent for a stream in the "idle" or
379
- # "closed" state. This allows for the reprioritization of a
380
- # group of dependent streams by altering the priority of an
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,
@@ -495,7 +514,7 @@ module HTTP2
495
514
  @state = :connected
496
515
  connection_settings(frame)
497
516
 
498
- when :connected
517
+ when :connected, :closing
499
518
  case frame_type
500
519
  when :settings
501
520
  # @type var frame: settings_frame
@@ -510,8 +529,13 @@ module HTTP2
510
529
  # Receivers of a GOAWAY frame MUST NOT open additional streams on
511
530
  # the connection, although a new connection can be established
512
531
  # for new streams.
513
- @state = :closed
514
- @closed_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
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
515
539
  emit(:goaway, frame[:last_stream], frame[:error], frame[:payload])
516
540
  when :altsvc
517
541
  # @type var frame: altsvc_frame
@@ -768,19 +792,28 @@ module HTTP2
768
792
  # connection managemet callbacks.
769
793
  #
770
794
  # @param id [Integer]
795
+ # @param max_concurrent_streams [Symbol] threshold to cap the number of active streams with.
771
796
  # @param priority [Integer]
772
797
  # @param window [Integer]
773
798
  # @param parent [Stream]
774
- def activate_stream(id:, **args)
799
+ def activate_stream(id:, max_concurrent_streams: @local_settings[:settings_max_concurrent_streams], **args)
775
800
  connection_error(msg: "Stream ID already exists") if @streams.key?(id)
776
801
 
777
- raise StreamLimitExceeded if @active_stream_count >= @local_settings[:settings_max_concurrent_streams]
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
778
806
 
779
807
  stream = Stream.new(connection: self, id: id, **args)
780
808
 
781
809
  stream.once(:close) do
782
810
  @streams.delete(id)
783
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
+
784
817
  # Store a reference to the closed stream, such that we can respond
785
818
  # to any in-flight frames while close is registered on both sides.
786
819
  # References to such streams will be purged whenever another stream
@@ -811,6 +844,11 @@ module HTTP2
811
844
  @streams[id] = stream
812
845
  end
813
846
 
847
+ def close!
848
+ @state = :closed
849
+ @closed_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
850
+ end
851
+
814
852
  def verify_stream_order(id)
815
853
  return unless id.odd?
816
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 draining) and new stream
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTP2
4
- VERSION = "1.2.1"
4
+ VERSION = "1.2.2"
5
5
  end
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.1
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: 3.6.9
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: []