protocol-http2 0.19.4 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b04111afb8bcb89c8866ccd13f1782ab2cd9ccf438210d5da17aecb384d2cf02
4
- data.tar.gz: 7574a7a9595487e5ad574250e98a6662e5d2c588acffe8536b9d4f642cabfc4b
3
+ metadata.gz: 90e7e77b799044d635c41f870e4d852fb8b9c3e51e5e637e70400420fa318c4f
4
+ data.tar.gz: f2d9106c992e4503e36c5f3b4d57859744cd0281bcd8f261b7d902b7574e7cfd
5
5
  SHA512:
6
- metadata.gz: ec243f9398ad359deb1ca97341d6fca6c563158a40fa7e3617b39af599bb0ebce8c302ef7af3a378b944d1b8ada1689eff8f540e5c274a065fcd2c8420f531af
7
- data.tar.gz: '08f92272a802133333232cac14b26568fdc5e95b4c0054caec717de403e0a5e22d41d3c2aa57ec08af40de11a75e4f2bb508d1b7b4c480513da81d352d58c072'
6
+ metadata.gz: 2b181f107bb843771deeb1d546a58315643faea3abcfa8f5a37198e2bf301a25f33d58d140879e676ac3928f932ed9b4a64325613b62e6b9e1297cc31264ef17
7
+ data.tar.gz: 89ed0a08306f0e6a1835afc1867fac8c9f0c01c28aeb4828eec3f2611f90244ddbf2be01faaf15f775ed30457b044f7a8956bede2cb0f83de913d1945524eccf
checksums.yaml.gz.sig CHANGED
Binary file
@@ -28,6 +28,10 @@ module Protocol
28
28
  if @state == :new
29
29
  @framer.write_connection_preface
30
30
 
31
+ # We don't support RFC7540 priorities:
32
+ settings = settings.to_a
33
+ settings << [Settings::NO_RFC7540_PRIORITIES, 1]
34
+
31
35
  send_settings(settings)
32
36
 
33
37
  yield if block_given?
@@ -5,7 +5,6 @@
5
5
  # Copyright, 2023, by Marco Concetto Rudilosso.
6
6
 
7
7
  require_relative "framer"
8
- require_relative "dependency"
9
8
  require_relative "flow_controlled"
10
9
 
11
10
  require "protocol/hpack"
@@ -23,10 +22,6 @@ module Protocol
23
22
  # Hash(Integer, Stream)
24
23
  @streams = {}
25
24
 
26
- # Hash(Integer, Dependency)
27
- @dependency = Dependency.new(self, 0)
28
- @dependencies = {0 => @dependency}
29
-
30
25
  @framer = framer
31
26
 
32
27
  # The next stream id to use:
@@ -62,7 +57,9 @@ module Protocol
62
57
  @remote_settings.maximum_frame_size
63
58
  end
64
59
 
65
- # The maximum number of concurrent streams that this connection can initiate:
60
+ # The maximum number of concurrent streams that this connection can initiate. This is a setting that can be changed by the remote peer.
61
+ #
62
+ # It is not the same as the number of streams that can be accepted by the connection. The number of streams that can be accepted is determined by the local settings, and the number of streams that can be initiated is determined by the remote settings.
66
63
  def maximum_concurrent_streams
67
64
  @remote_settings.maximum_concurrent_streams
68
65
  end
@@ -93,7 +90,6 @@ module Protocol
93
90
 
94
91
  def delete(id)
95
92
  @streams.delete(id)
96
- @dependencies[id]&.delete!
97
93
  end
98
94
 
99
95
  # Close the underlying framer and all streams.
@@ -400,22 +396,6 @@ module Protocol
400
396
  end
401
397
  end
402
398
 
403
- def send_priority(stream_id, priority)
404
- frame = PriorityFrame.new(stream_id)
405
- frame.pack(priority)
406
-
407
- write_frame(frame)
408
- end
409
-
410
- # Sets the priority for an incoming stream.
411
- def receive_priority(frame)
412
- if dependency = @dependencies[frame.stream_id]
413
- dependency.receive_priority(frame)
414
- elsif idle_stream_id?(frame.stream_id)
415
- Dependency.create(self, frame.stream_id, frame.unpack)
416
- end
417
- end
418
-
419
399
  def receive_push_promise(frame)
420
400
  raise ProtocolError, "Unable to receive push promise!"
421
401
  end
@@ -468,23 +448,17 @@ module Protocol
468
448
  end
469
449
  end
470
450
 
471
- # Traverse active streams in order of priority and allow them to consume the available flow-control window.
472
- # @param amount [Integer] the amount of data to write. Defaults to the current window capacity.
451
+ # Traverse active streams and allow them to consume the available flow-control window.
452
+ # @parameter amount [Integer] the amount of data to write. Defaults to the current window capacity.
473
453
  def consume_window(size = self.available_size)
474
454
  # Return if there is no window to consume:
475
455
  return unless size > 0
476
456
 
477
- # Console.info(self) do |buffer|
478
- # @dependencies.each do |id, dependency|
479
- # buffer.puts "- #{dependency}"
480
- # end
481
- #
482
- # buffer.puts
483
- #
484
- # @dependency.print_hierarchy(buffer)
485
- # end
486
-
487
- @dependency.consume_window(size)
457
+ @streams.each_value do |stream|
458
+ if stream.active?
459
+ stream.window_updated(size)
460
+ end
461
+ end
488
462
  end
489
463
 
490
464
  def receive_window_update(frame)
@@ -78,7 +78,7 @@ module Protocol
78
78
  end
79
79
 
80
80
  # The window has been expanded by the given amount.
81
- # @param size [Integer] the maximum amount of data to send.
81
+ # @parameter size [Integer] the maximum amount of data to send.
82
82
  # @return [Boolean] whether the window update was used or not.
83
83
  def window_updated(size)
84
84
  return false
@@ -34,7 +34,7 @@ module Protocol
34
34
  # The base class does not have any specific type index:
35
35
  TYPE = nil
36
36
 
37
- # @param length [Integer] the length of the payload, or nil if the header has not been read yet.
37
+ # @parameter length [Integer] the length of the payload, or nil if the header has not been read yet.
38
38
  def initialize(stream_id = 0, flags = 0, type = self.class::TYPE, length = nil, payload = nil)
39
39
  @stream_id = stream_id
40
40
  @flags = flags
@@ -134,7 +134,7 @@ module Protocol
134
134
 
135
135
  # Decodes common 9-byte header.
136
136
  #
137
- # @param buffer [String]
137
+ # @parameter buffer [String]
138
138
  def self.parse_header(buffer)
139
139
  length_hi, length_lo, type, flags, stream_id = buffer.unpack(HEADER_FORMAT)
140
140
  length = (length_hi << LENGTH_HISHIFT) | length_lo
@@ -7,7 +7,6 @@ require_relative "error"
7
7
 
8
8
  require_relative "data_frame"
9
9
  require_relative "headers_frame"
10
- require_relative "priority_frame"
11
10
  require_relative "reset_stream_frame"
12
11
  require_relative "settings_frame"
13
12
  require_relative "push_promise_frame"
@@ -16,15 +15,13 @@ require_relative "goaway_frame"
16
15
  require_relative "window_update_frame"
17
16
  require_relative "continuation_frame"
18
17
 
19
- require "traces/provider"
20
-
21
18
  module Protocol
22
19
  module HTTP2
23
20
  # HTTP/2 frame type mapping as defined by the spec
24
21
  FRAMES = [
25
22
  DataFrame,
26
23
  HeadersFrame,
27
- PriorityFrame,
24
+ nil, # PriorityFrame is deprecated / removed.
28
25
  ResetStreamFrame,
29
26
  SettingsFrame,
30
27
  PushPromiseFrame,
@@ -109,50 +106,6 @@ module Protocol
109
106
 
110
107
  raise EOFError, "Could not read frame header!"
111
108
  end
112
-
113
- Traces::Provider(self) do
114
- def write_connection_preface
115
- Traces.trace("protocol.http2.framer.write_connection_preface") do
116
- super
117
- end
118
- end
119
-
120
- def read_connection_preface
121
- Traces.trace("protocol.http2.framer.read_connection_preface") do
122
- super
123
- end
124
- end
125
-
126
- def write_frame(frame)
127
- attributes = {
128
- "frame.length" => frame.length,
129
- "frame.type" => frame.type,
130
- "frame.flags" => frame.flags,
131
- "frame.stream_id" => frame.stream_id,
132
- }
133
-
134
- Traces.trace("protocol.http2.framer.write_frame", attributes: attributes) do
135
- super
136
- end
137
- end
138
-
139
- def read_frame(maximum_frame_size = MAXIMUM_ALLOWED_FRAME_SIZE)
140
- Traces.trace("protocol.http2.framer.read_frame") do |span|
141
- super.tap do |frame|
142
- span["frame.length"] = frame.length
143
- span["frame.type"] = frame.type
144
- span["frame.flags"] = frame.flags
145
- span["frame.stream_id"] = frame.stream_id
146
- end
147
- end
148
- end
149
-
150
- def flush
151
- Traces.trace("protocol.http2.framer.flush") do
152
- super
153
- end
154
- end
155
- end
156
109
  end
157
110
  end
158
111
  end
@@ -6,7 +6,6 @@
6
6
  require_relative "frame"
7
7
  require_relative "padded"
8
8
  require_relative "continuation_frame"
9
- require_relative "priority_frame"
10
9
 
11
10
  module Protocol
12
11
  module HTTP2
@@ -41,23 +40,16 @@ module Protocol
41
40
  data = super
42
41
 
43
42
  if priority?
44
- priority = Priority.unpack(data)
43
+ # We no longer support priority frames, so strip the data:
45
44
  data = data.byteslice(5, data.bytesize - 5)
46
45
  end
47
46
 
48
- return priority, data
47
+ return data
49
48
  end
50
49
 
51
- def pack(priority, data, *arguments, **options)
50
+ def pack(data, *arguments, **options)
52
51
  buffer = String.new.b
53
52
 
54
- if priority
55
- buffer << priority.pack
56
- set_flags(PRIORITY)
57
- else
58
- clear_flags(PRIORITY)
59
- end
60
-
61
53
  buffer << data
62
54
 
63
55
  super(buffer, *arguments, **options)
@@ -28,6 +28,10 @@ module Protocol
28
28
  if @state == :new
29
29
  @framer.read_connection_preface
30
30
 
31
+ # We don't support RFC7540 priorities:
32
+ settings = settings.to_a
33
+ settings << [Settings::NO_RFC7540_PRIORITIES, 1]
34
+
31
35
  send_settings(settings)
32
36
 
33
37
  read_frame do |frame|
@@ -15,6 +15,7 @@ module Protocol
15
15
  MAXIMUM_FRAME_SIZE = 0x5
16
16
  MAXIMUM_HEADER_LIST_SIZE = 0x6
17
17
  ENABLE_CONNECT_PROTOCOL = 0x8
18
+ NO_RFC7540_PRIORITIES = 0x9
18
19
 
19
20
  ASSIGN = [
20
21
  nil,
@@ -4,7 +4,6 @@
4
4
  # Copyright, 2019-2024, by Samuel Williams.
5
5
 
6
6
  require_relative "connection"
7
- require_relative "dependency"
8
7
 
9
8
  module Protocol
10
9
  module HTTP2
@@ -77,8 +76,6 @@ module Protocol
77
76
 
78
77
  @local_window = Window.new(@connection.local_settings.initial_window_size)
79
78
  @remote_window = Window.new(@connection.remote_settings.initial_window_size)
80
-
81
- @dependency = Dependency.create(@connection, @id)
82
79
  end
83
80
 
84
81
  # The connection this stream belongs to.
@@ -90,27 +87,9 @@ module Protocol
90
87
  # Stream state, e.g. `idle`, `closed`.
91
88
  attr_accessor :state
92
89
 
93
- attr :dependency
94
-
95
90
  attr :local_window
96
91
  attr :remote_window
97
92
 
98
- def weight
99
- @dependency.weight
100
- end
101
-
102
- def priority
103
- @dependency.priority
104
- end
105
-
106
- def priority= priority
107
- @dependency.priority = priority
108
- end
109
-
110
- def parent=(stream)
111
- @dependency.parent = stream.dependency
112
- end
113
-
114
93
  def maximum_frame_size
115
94
  @connection.available_frame_size
116
95
  end
@@ -141,13 +120,13 @@ module Protocol
141
120
  @state == :idle or @state == :reserved_local or @state == :open or @state == :half_closed_remote
142
121
  end
143
122
 
144
- private def write_headers(priority, headers, flags = 0)
123
+ private def write_headers(headers, flags = 0)
145
124
  frame = HeadersFrame.new(@id, flags)
146
125
 
147
126
  @connection.write_frames do |framer|
148
127
  data = @connection.encode_headers(headers)
149
128
 
150
- frame.pack(priority, data, maximum_size: @connection.maximum_frame_size)
129
+ frame.pack(data, maximum_size: @connection.maximum_frame_size)
151
130
 
152
131
  framer.write_frame(frame)
153
132
  end
@@ -157,6 +136,10 @@ module Protocol
157
136
 
158
137
  # The HEADERS frame is used to open a stream, and additionally carries a header block fragment. HEADERS frames can be sent on a stream in the "idle", "reserved (local)", "open", or "half-closed (remote)" state.
159
138
  def send_headers(*arguments)
139
+ if arguments.first.nil?
140
+ arguments.shift # Remove nil priority.
141
+ end
142
+
160
143
  if @state == :idle
161
144
  frame = write_headers(*arguments)
162
145
 
@@ -236,7 +219,7 @@ module Protocol
236
219
  end
237
220
 
238
221
  # Transition the stream into the closed state.
239
- # @param error_code [Integer] the error code if the stream was closed due to a stream reset.
222
+ # @parameter error_code [Integer] the error code if the stream was closed due to a stream reset.
240
223
  def close!(error_code = nil)
241
224
  @state = :closed
242
225
  @connection.delete(@id)
@@ -265,11 +248,7 @@ module Protocol
265
248
 
266
249
  def process_headers(frame)
267
250
  # Receiving request headers:
268
- priority, data = frame.unpack
269
-
270
- if priority
271
- @dependency.process_priority(priority)
272
- end
251
+ data = frame.unpack
273
252
 
274
253
  @connection.decode_headers(data)
275
254
  end
@@ -401,7 +380,7 @@ module Protocol
401
380
  end
402
381
 
403
382
  # Server push is semantically equivalent to a server responding to a request; however, in this case, that request is also sent by the server, as a PUSH_PROMISE frame.
404
- # @param headers [Hash] contains a complete set of request header fields that the server attributes to the request.
383
+ # @parameter headers [Hash] contains a complete set of request header fields that the server attributes to the request.
405
384
  def send_push_promise(headers)
406
385
  if @state == :open or @state == :half_closed_remote
407
386
  promised_stream = self.create_push_promise_stream(headers)
@@ -427,7 +406,6 @@ module Protocol
427
406
  headers = @connection.decode_headers(data)
428
407
 
429
408
  stream = self.accept_push_promise_stream(promised_stream_id, headers)
430
- stream.parent = self
431
409
  stream.reserved_remote!
432
410
 
433
411
  return stream, headers
@@ -5,6 +5,6 @@
5
5
 
6
6
  module Protocol
7
7
  module HTTP2
8
- VERSION = "0.19.4"
8
+ VERSION = "0.21.0"
9
9
  end
10
10
  end
@@ -9,7 +9,7 @@ module Protocol
9
9
  # When an HTTP/2 connection is first established, new streams are created with an initial flow-control window size of 65,535 octets. The connection flow-control window is also 65,535 octets.
10
10
  DEFAULT_CAPACITY = 0xFFFF
11
11
 
12
- # @param capacity [Integer] The initial window size, typically from the settings.
12
+ # @parameter capacity [Integer] The initial window size, typically from the settings.
13
13
  def initialize(capacity = DEFAULT_CAPACITY)
14
14
  # This is the main field required:
15
15
  @available = capacity
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024, by Samuel Williams.
5
+
6
+ require "traces/provider"
7
+ require_relative "../../../../protocol/http2/framer"
8
+
9
+ Traces::Provider(Protocol::HTTP2::Framer) do
10
+ def write_connection_preface
11
+ Traces.trace("protocol.http2.framer.write_connection_preface") do
12
+ super
13
+ end
14
+ end
15
+
16
+ def read_connection_preface
17
+ Traces.trace("protocol.http2.framer.read_connection_preface") do
18
+ super
19
+ end
20
+ end
21
+
22
+ def write_frame(frame)
23
+ attributes = {
24
+ "frame.length" => frame.length,
25
+ "frame.class" => frame.class.name,
26
+ "frame.type" => frame.type,
27
+ "frame.flags" => frame.flags,
28
+ "frame.stream_id" => frame.stream_id,
29
+ }
30
+
31
+ Traces.trace("protocol.http2.framer.write_frame", attributes: attributes) do
32
+ super
33
+ end
34
+ end
35
+
36
+ def read_frame(...)
37
+ Traces.trace("protocol.http2.framer.read_frame") do |span|
38
+ super.tap do |frame|
39
+ span["frame.length"] = frame.length
40
+ span["frame.type"] = frame.type
41
+ span["frame.flags"] = frame.flags
42
+ span["frame.stream_id"] = frame.stream_id
43
+ end
44
+ end
45
+ end
46
+
47
+ def flush
48
+ Traces.trace("protocol.http2.framer.flush") do
49
+ super
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024, by Samuel Williams.
5
+
6
+ require_relative "http2/framer"
data/readme.md CHANGED
@@ -58,7 +58,7 @@ Async do
58
58
  ]
59
59
 
60
60
  puts "Sending request on stream id=#{stream.id} state=#{stream.state}..."
61
- stream.send_headers(nil, headers, Protocol::HTTP2::END_STREAM)
61
+ stream.send_headers(headers, Protocol::HTTP2::END_STREAM)
62
62
 
63
63
  puts "Waiting for response..."
64
64
  $count = 0
data/releases.md ADDED
@@ -0,0 +1,7 @@
1
+ # Releases
2
+
3
+ ## Unreleased
4
+
5
+ ### Remove Priority Frame and Dependency Tracking
6
+
7
+ HTTP/2 has deprecated the priority frame and stream dependency tracking. This feature has been effectively removed from the protocol. As a consequence, the internal implementation is greatly simplified. The `Protocol::HTTP2::Stream` class no longer tracks dependencies or priorities, and this includes `Stream#send_headers` which no longer takes `priority` as the first argument.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protocol-http2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.4
4
+ version: 0.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -40,7 +40,7 @@ cert_chain:
40
40
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
41
41
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
42
42
  -----END CERTIFICATE-----
43
- date: 2024-11-03 00:00:00.000000000 Z
43
+ date: 2024-11-28 00:00:00.000000000 Z
44
44
  dependencies:
45
45
  - !ruby/object:Gem::Dependency
46
46
  name: protocol-hpack
@@ -70,20 +70,6 @@ dependencies:
70
70
  - - "~>"
71
71
  - !ruby/object:Gem::Version
72
72
  version: '0.18'
73
- - !ruby/object:Gem::Dependency
74
- name: traces
75
- requirement: !ruby/object:Gem::Requirement
76
- requirements:
77
- - - ">="
78
- - !ruby/object:Gem::Version
79
- version: '0'
80
- type: :runtime
81
- prerelease: false
82
- version_requirements: !ruby/object:Gem::Requirement
83
- requirements:
84
- - - ">="
85
- - !ruby/object:Gem::Version
86
- version: '0'
87
73
  description:
88
74
  email:
89
75
  executables: []
@@ -95,7 +81,6 @@ files:
95
81
  - lib/protocol/http2/connection.rb
96
82
  - lib/protocol/http2/continuation_frame.rb
97
83
  - lib/protocol/http2/data_frame.rb
98
- - lib/protocol/http2/dependency.rb
99
84
  - lib/protocol/http2/error.rb
100
85
  - lib/protocol/http2/flow_controlled.rb
101
86
  - lib/protocol/http2/frame.rb
@@ -104,7 +89,6 @@ files:
104
89
  - lib/protocol/http2/headers_frame.rb
105
90
  - lib/protocol/http2/padded.rb
106
91
  - lib/protocol/http2/ping_frame.rb
107
- - lib/protocol/http2/priority_frame.rb
108
92
  - lib/protocol/http2/push_promise_frame.rb
109
93
  - lib/protocol/http2/reset_stream_frame.rb
110
94
  - lib/protocol/http2/server.rb
@@ -113,8 +97,11 @@ files:
113
97
  - lib/protocol/http2/version.rb
114
98
  - lib/protocol/http2/window.rb
115
99
  - lib/protocol/http2/window_update_frame.rb
100
+ - lib/traces/provider/protocol/http2.rb
101
+ - lib/traces/provider/protocol/http2/framer.rb
116
102
  - license.md
117
103
  - readme.md
104
+ - releases.md
118
105
  homepage: https://github.com/socketry/protocol-http2
119
106
  licenses:
120
107
  - MIT
@@ -136,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
136
123
  - !ruby/object:Gem::Version
137
124
  version: '0'
138
125
  requirements: []
139
- rubygems_version: 3.5.11
126
+ rubygems_version: 3.5.22
140
127
  signing_key:
141
128
  specification_version: 4
142
129
  summary: A low level implementation of the HTTP/2 protocol.
metadata.gz.sig CHANGED
Binary file
@@ -1,218 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2020-2023, by Samuel Williams.
5
-
6
- module Protocol
7
- module HTTP2
8
- DEFAULT_WEIGHT = 16
9
-
10
- class Dependency
11
- def self.create(connection, id, priority = nil)
12
- weight = DEFAULT_WEIGHT
13
- exclusive = false
14
-
15
- if priority
16
- if parent = connection.dependencies[priority.stream_dependency]
17
- exclusive = priority.exclusive
18
- end
19
-
20
- weight = priority.weight
21
- end
22
-
23
- if parent.nil?
24
- parent = connection.dependency
25
- end
26
-
27
- dependency = self.new(connection, id, weight)
28
-
29
- connection.dependencies[id] = dependency
30
-
31
- if exclusive
32
- parent.exclusive_child(dependency)
33
- else
34
- parent.add_child(dependency)
35
- end
36
-
37
- return dependency
38
- end
39
-
40
- def initialize(connection, id, weight = DEFAULT_WEIGHT)
41
- @connection = connection
42
- @id = id
43
-
44
- @parent = nil
45
- @children = nil
46
-
47
- @weight = weight
48
-
49
- # Cache of any associated stream:
50
- @stream = nil
51
-
52
- # Cache of children for window allocation:
53
- @total_weight = 0
54
- @ordered_children = nil
55
- end
56
-
57
- def <=> other
58
- @weight <=> other.weight
59
- end
60
-
61
- # The connection this stream belongs to.
62
- attr :connection
63
-
64
- # Stream ID (odd for client initiated streams, even otherwise).
65
- attr :id
66
-
67
- # The parent dependency.
68
- attr_accessor :parent
69
-
70
- # The dependent children.
71
- attr_accessor :children
72
-
73
- # The weight of the stream relative to other siblings.
74
- attr_accessor :weight
75
-
76
- def stream
77
- @stream ||= @connection.streams[@id]
78
- end
79
-
80
- def clear_cache!
81
- @ordered_children = nil
82
- end
83
-
84
- def delete!
85
- @connection.dependencies.delete(@id)
86
-
87
- @parent.remove_child(self)
88
-
89
- @children&.each do |id, child|
90
- parent.add_child(child)
91
- end
92
-
93
- @connection = nil
94
- @parent = nil
95
- @children = nil
96
- end
97
-
98
- def add_child(dependency)
99
- @children ||= {}
100
- @children[dependency.id] = dependency
101
-
102
- dependency.parent = self
103
-
104
- self.clear_cache!
105
- end
106
-
107
- def remove_child(dependency)
108
- @children&.delete(dependency.id)
109
-
110
- self.clear_cache!
111
- end
112
-
113
- # An exclusive flag allows for the insertion of a new level of dependencies. The exclusive flag causes the stream to become the sole dependency of its parent stream, causing other dependencies to become dependent on the exclusive stream.
114
- # @param parent [Dependency] the dependency which will be inserted, taking control of all current children.
115
- def exclusive_child(parent)
116
- parent.children = @children
117
-
118
- @children&.each_value do |child|
119
- child.parent = parent
120
- end
121
-
122
- parent.clear_cache!
123
-
124
- @children = {parent.id => parent}
125
- self.clear_cache!
126
-
127
- parent.parent = self
128
- end
129
-
130
- def process_priority(priority)
131
- dependent_id = priority.stream_dependency
132
-
133
- if dependent_id == @id
134
- raise ProtocolError, "Stream priority for stream id #{@id} cannot depend on itself!"
135
- end
136
-
137
- @weight = priority.weight
138
-
139
- # We essentially ignore `dependent_id` if the dependency does not exist:
140
- if parent = @connection.dependencies[dependent_id]
141
- if priority.exclusive
142
- @parent.remove_child(self)
143
-
144
- parent.exclusive_child(self)
145
- elsif !@parent.equal?(parent)
146
- @parent.remove_child(self)
147
-
148
- parent.add_child(self)
149
- end
150
- end
151
- end
152
-
153
- # Change the priority of the stream both locally and remotely.
154
- def priority= priority
155
- send_priority(priority)
156
- process_priority(priority)
157
- end
158
-
159
- # The current local priority of the stream.
160
- def priority(exclusive = false)
161
- Priority.new(exclusive, @parent.id, @weight)
162
- end
163
-
164
- def send_priority(priority)
165
- @connection.send_priority(@id, priority)
166
- end
167
-
168
- def receive_priority(frame)
169
- self.process_priority(frame.unpack)
170
- end
171
-
172
- def total_weight
173
- self.ordered_children
174
-
175
- return @total_weight
176
- end
177
-
178
- def ordered_children
179
- unless @ordered_children
180
- if @children and !@children.empty?
181
- @ordered_children = @children.values.sort
182
- @total_weight = @ordered_children.sum(&:weight)
183
- end
184
- end
185
-
186
- return @ordered_children
187
- end
188
-
189
- # Traverse active streams in order of priority and allow them to consume the available flow-control window.
190
- # @param amount [Integer] the amount of data to write. Defaults to the current window capacity.
191
- def consume_window(size)
192
- # If there is an associated stream, give it priority:
193
- if stream = self.stream
194
- return if stream.window_updated(size)
195
- end
196
-
197
- # Otherwise, allow the dependent children to use up the available window:
198
- self.ordered_children&.each do |child|
199
- # Compute the proportional allocation:
200
- allocated = (child.weight * size) / @total_weight
201
-
202
- child.consume_window(allocated) if allocated > 0
203
- end
204
- end
205
-
206
- def inspect
207
- "\#<#{self.class} id=#{@id} parent id=#{@parent&.id} weight=#{@weight} #{@children&.size || 0} children>"
208
- end
209
-
210
- def print_hierarchy(output = $stderr, indent: 0)
211
- output.puts "#{"\t" * indent}#{self.inspect}"
212
- @children&.each_value do |child|
213
- child.print_hierarchy(output, indent: indent+1)
214
- end
215
- end
216
- end
217
- end
218
- end
@@ -1,82 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2019-2024, by Samuel Williams.
5
-
6
- require_relative "frame"
7
-
8
- module Protocol
9
- module HTTP2
10
- VALID_WEIGHT = (1..256)
11
-
12
- # Stream Dependency: A 31-bit stream identifier for the stream that
13
- # this stream depends on (see Section 5.3). This field is only
14
- # present if the PRIORITY flag is set.
15
- class Priority < Struct.new(:exclusive, :stream_dependency, :weight)
16
- FORMAT = "NC".freeze
17
- EXCLUSIVE = 1 << 31
18
-
19
- # All streams are initially assigned a non-exclusive dependency on stream 0x0. Pushed streams (Section 8.2) initially depend on their associated stream. In both cases, streams are assigned a default weight of 16.
20
- def self.default(stream_dependency = 0, weight = 16)
21
- self.new(false, stream_dependency, weight)
22
- end
23
-
24
- def self.unpack(data)
25
- stream_dependency, weight = data.unpack(FORMAT)
26
-
27
- # Weight: An unsigned 8-bit integer representing a priority weight for the stream (see Section 5.3). Add one to the value to obtain a weight between 1 and 256. This field is only present if the PRIORITY flag is set.
28
- return self.new(stream_dependency & EXCLUSIVE != 0, stream_dependency & ~EXCLUSIVE, weight + 1)
29
- end
30
-
31
- def pack
32
- if exclusive
33
- stream_dependency = self.stream_dependency | EXCLUSIVE
34
- else
35
- stream_dependency = self.stream_dependency
36
- end
37
-
38
- return [stream_dependency, self.weight - 1].pack(FORMAT)
39
- end
40
-
41
- def weight= value
42
- if VALID_WEIGHT.include?(value)
43
- super
44
- else
45
- raise ArgumentError, "Weight #{value} must be between 1-256!"
46
- end
47
- end
48
- end
49
-
50
- # The PRIORITY frame specifies the sender-advised priority of a stream. It can be sent in any stream state, including idle or closed streams.
51
- #
52
- # +-+-------------------------------------------------------------+
53
- # |E| Stream Dependency (31) |
54
- # +-+-------------+-----------------------------------------------+
55
- # | Weight (8) |
56
- # +-+-------------+
57
- #
58
- class PriorityFrame < Frame
59
- TYPE = 0x2
60
-
61
- def pack priority
62
- super priority.pack
63
- end
64
-
65
- def unpack
66
- Priority.unpack(super)
67
- end
68
-
69
- def apply(connection)
70
- connection.receive_priority(self)
71
- end
72
-
73
- def read_payload(stream)
74
- super
75
-
76
- if @length != 5
77
- raise FrameSizeError, "Invalid frame length"
78
- end
79
- end
80
- end
81
- end
82
- end