protocol-http2 0.20.0 → 0.22.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
- checksums.yaml.gz.sig +0 -0
- data/lib/protocol/http2/client.rb +3 -1
- data/lib/protocol/http2/connection.rb +20 -34
- data/lib/protocol/http2/flow_controlled.rb +1 -1
- data/lib/protocol/http2/frame.rb +2 -2
- data/lib/protocol/http2/framer.rb +9 -2
- data/lib/protocol/http2/headers_frame.rb +3 -11
- data/lib/protocol/http2/priority_update_frame.rb +41 -0
- data/lib/protocol/http2/settings_frame.rb +27 -44
- data/lib/protocol/http2/stream.rb +8 -29
- data/lib/protocol/http2/version.rb +1 -1
- data/lib/protocol/http2/window.rb +1 -1
- data/readme.md +1 -1
- data/releases.md +9 -0
- data.tar.gz.sig +0 -0
- metadata +4 -4
- metadata.gz.sig +0 -0
- data/lib/protocol/http2/dependency.rb +0 -245
- data/lib/protocol/http2/priority_frame.rb +0 -82
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3d3ec4ba460a65fcf1aa11e9c3ce4d0f46fde1cb0e7fa9cc33667b9142eb3dc4
|
4
|
+
data.tar.gz: 0afd95c0651daf938a1b6e472376d38274dc7df70fdd38e9f28cfe6c657bc81f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 205b7683de9faee706dead27493c76b247c0d06c489778f670e4c49f0ecde263ad8b3b7ac1f86312681f83aee8bfaf4f82390f5d43c6a2d79dabdbe3f7d06b6a
|
7
|
+
data.tar.gz: c72cfe3041d0c4edc6e0510a8dc04f88dd547ebff1e96b9322f7ac35a64850fe516677a7e3df8b26258d7f089f10a2a4504211da8871678920ef65aa32b8a76b
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
@@ -33,7 +33,9 @@ module Protocol
|
|
33
33
|
yield if block_given?
|
34
34
|
|
35
35
|
read_frame do |frame|
|
36
|
-
|
36
|
+
unless frame.is_a? SettingsFrame
|
37
|
+
raise ProtocolError, "First frame must be #{SettingsFrame}, but got #{frame.class}"
|
38
|
+
end
|
37
39
|
end
|
38
40
|
else
|
39
41
|
raise ProtocolError, "Cannot send connection preface in state #{@state}"
|
@@ -5,10 +5,10 @@
|
|
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"
|
11
|
+
require "protocol/http/header/priority"
|
12
12
|
|
13
13
|
module Protocol
|
14
14
|
module HTTP2
|
@@ -23,10 +23,6 @@ module Protocol
|
|
23
23
|
# Hash(Integer, Stream)
|
24
24
|
@streams = {}
|
25
25
|
|
26
|
-
# Hash(Integer, Dependency)
|
27
|
-
@dependency = Dependency.new(self, 0)
|
28
|
-
@dependencies = {0 => @dependency}
|
29
|
-
|
30
26
|
@framer = framer
|
31
27
|
|
32
28
|
# The next stream id to use:
|
@@ -95,7 +91,6 @@ module Protocol
|
|
95
91
|
|
96
92
|
def delete(id)
|
97
93
|
@streams.delete(id)
|
98
|
-
@dependencies[id]&.delete!
|
99
94
|
end
|
100
95
|
|
101
96
|
# Close the underlying framer and all streams.
|
@@ -402,24 +397,21 @@ module Protocol
|
|
402
397
|
end
|
403
398
|
end
|
404
399
|
|
405
|
-
def
|
406
|
-
|
407
|
-
frame.pack(priority)
|
408
|
-
|
409
|
-
write_frame(frame)
|
400
|
+
def receive_push_promise(frame)
|
401
|
+
raise ProtocolError, "Unable to receive push promise!"
|
410
402
|
end
|
411
403
|
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
404
|
+
def receive_priority_update(frame)
|
405
|
+
if frame.stream_id != 0
|
406
|
+
raise ProtocolError, "Invalid stream id: #{frame.stream_id}"
|
407
|
+
end
|
408
|
+
|
409
|
+
stream_id, value = frame.unpack
|
410
|
+
|
411
|
+
# Apparently you can set the priority of idle streams, but I'm not sure why that makes sense, so for now let's ignore it.
|
412
|
+
if stream = @streams[stream_id]
|
413
|
+
stream.priority = Protocol::HTTP::Header::Priority.new(value)
|
418
414
|
end
|
419
|
-
end
|
420
|
-
|
421
|
-
def receive_push_promise(frame)
|
422
|
-
raise ProtocolError, "Unable to receive push promise!"
|
423
415
|
end
|
424
416
|
|
425
417
|
def client_stream_id?(id)
|
@@ -470,23 +462,17 @@ module Protocol
|
|
470
462
|
end
|
471
463
|
end
|
472
464
|
|
473
|
-
# Traverse active streams
|
474
|
-
# @
|
465
|
+
# Traverse active streams and allow them to consume the available flow-control window.
|
466
|
+
# @parameter amount [Integer] the amount of data to write. Defaults to the current window capacity.
|
475
467
|
def consume_window(size = self.available_size)
|
476
468
|
# Return if there is no window to consume:
|
477
469
|
return unless size > 0
|
478
470
|
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
# buffer.puts
|
485
|
-
#
|
486
|
-
# @dependency.print_hierarchy(buffer)
|
487
|
-
# end
|
488
|
-
|
489
|
-
@dependency.consume_window(size)
|
471
|
+
@streams.each_value do |stream|
|
472
|
+
if stream.active?
|
473
|
+
stream.window_updated(size)
|
474
|
+
end
|
475
|
+
end
|
490
476
|
end
|
491
477
|
|
492
478
|
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
|
-
# @
|
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
|
data/lib/protocol/http2/frame.rb
CHANGED
@@ -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
|
-
# @
|
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
|
-
# @
|
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"
|
@@ -15,6 +14,7 @@ require_relative "ping_frame"
|
|
15
14
|
require_relative "goaway_frame"
|
16
15
|
require_relative "window_update_frame"
|
17
16
|
require_relative "continuation_frame"
|
17
|
+
require_relative "priority_update_frame"
|
18
18
|
|
19
19
|
module Protocol
|
20
20
|
module HTTP2
|
@@ -22,7 +22,7 @@ module Protocol
|
|
22
22
|
FRAMES = [
|
23
23
|
DataFrame,
|
24
24
|
HeadersFrame,
|
25
|
-
PriorityFrame,
|
25
|
+
nil, # PriorityFrame is deprecated and ignored, instead consider using PriorityUpdateFrame instead.
|
26
26
|
ResetStreamFrame,
|
27
27
|
SettingsFrame,
|
28
28
|
PushPromiseFrame,
|
@@ -30,6 +30,13 @@ module Protocol
|
|
30
30
|
GoawayFrame,
|
31
31
|
WindowUpdateFrame,
|
32
32
|
ContinuationFrame,
|
33
|
+
nil,
|
34
|
+
nil,
|
35
|
+
nil,
|
36
|
+
nil,
|
37
|
+
nil,
|
38
|
+
nil,
|
39
|
+
PriorityUpdateFrame,
|
33
40
|
].freeze
|
34
41
|
|
35
42
|
# Default connection "fast-fail" preamble string as defined by the spec.
|
@@ -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
|
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
|
47
|
+
return data
|
49
48
|
end
|
50
49
|
|
51
|
-
def pack(
|
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)
|
@@ -0,0 +1,41 @@
|
|
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
|
+
require_relative "padded"
|
8
|
+
require_relative "continuation_frame"
|
9
|
+
|
10
|
+
module Protocol
|
11
|
+
module HTTP2
|
12
|
+
# The PRIORITY_UPDATE frame is used by clients to signal the initial priority of a response, or to reprioritize a response or push stream. It carries the stream ID of the response and the priority in ASCII text, using the same representation as the Priority header field value.
|
13
|
+
#
|
14
|
+
# +-+-------------+-----------------------------------------------+
|
15
|
+
# |R| Prioritized Stream ID (31) |
|
16
|
+
# +-+-----------------------------+-------------------------------+
|
17
|
+
# | Priority Field Value (*) ...
|
18
|
+
# +---------------------------------------------------------------+
|
19
|
+
#
|
20
|
+
class PriorityUpdateFrame < Frame
|
21
|
+
TYPE = 0x10
|
22
|
+
FORMAT = "N".freeze
|
23
|
+
|
24
|
+
def unpack
|
25
|
+
data = super
|
26
|
+
|
27
|
+
prioritized_stream_id = data.unpack1(FORMAT)
|
28
|
+
|
29
|
+
return prioritized_stream_id, data.byteslice(4, data.bytesize - 4)
|
30
|
+
end
|
31
|
+
|
32
|
+
def pack(prioritized_stream_id, data, **options)
|
33
|
+
super([prioritized_stream_id].pack(FORMAT) + data, **options)
|
34
|
+
end
|
35
|
+
|
36
|
+
def apply(connection)
|
37
|
+
connection.receive_priority_update(self)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -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,
|
@@ -26,8 +27,22 @@ module Protocol
|
|
26
27
|
:maximum_header_list_size=,
|
27
28
|
nil,
|
28
29
|
:enable_connect_protocol=,
|
30
|
+
:no_rfc7540_priorities=,
|
29
31
|
]
|
30
32
|
|
33
|
+
def initialize
|
34
|
+
# These limits are taken from the RFC:
|
35
|
+
# https://tools.ietf.org/html/rfc7540#section-6.5.2
|
36
|
+
@header_table_size = 4096
|
37
|
+
@enable_push = 1
|
38
|
+
@maximum_concurrent_streams = 0xFFFFFFFF
|
39
|
+
@initial_window_size = 0xFFFF # 2**16 - 1
|
40
|
+
@maximum_frame_size = 0x4000 # 2**14
|
41
|
+
@maximum_header_list_size = 0xFFFFFFFF
|
42
|
+
@enable_connect_protocol = 0
|
43
|
+
@no_rfc7540_priorities = 0
|
44
|
+
end
|
45
|
+
|
31
46
|
# Allows the sender to inform the remote endpoint of the maximum size of the header compression table used to decode header blocks, in octets.
|
32
47
|
attr_accessor :header_table_size
|
33
48
|
|
@@ -90,16 +105,18 @@ module Protocol
|
|
90
105
|
@enable_connect_protocol == 1
|
91
106
|
end
|
92
107
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
108
|
+
attr :no_rfc7540_priorities
|
109
|
+
|
110
|
+
def no_rfc7540_priorities= value
|
111
|
+
if value == 0 or value == 1
|
112
|
+
@no_rfc7540_priorities = value
|
113
|
+
else
|
114
|
+
raise ProtocolError, "Invalid value for no_rfc7540_priorities: #{value}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def no_rfc7540_priorities?
|
119
|
+
@no_rfc7540_priorities == 1
|
103
120
|
end
|
104
121
|
|
105
122
|
def update(changes)
|
@@ -109,40 +126,6 @@ module Protocol
|
|
109
126
|
end
|
110
127
|
end
|
111
128
|
end
|
112
|
-
|
113
|
-
def difference(other)
|
114
|
-
changes = []
|
115
|
-
|
116
|
-
if @header_table_size != other.header_table_size
|
117
|
-
changes << [HEADER_TABLE_SIZE, @header_table_size]
|
118
|
-
end
|
119
|
-
|
120
|
-
if @enable_push != other.enable_push
|
121
|
-
changes << [ENABLE_PUSH, @enable_push]
|
122
|
-
end
|
123
|
-
|
124
|
-
if @maximum_concurrent_streams != other.maximum_concurrent_streams
|
125
|
-
changes << [MAXIMUM_CONCURRENT_STREAMS, @maximum_concurrent_streams]
|
126
|
-
end
|
127
|
-
|
128
|
-
if @initial_window_size != other.initial_window_size
|
129
|
-
changes << [INITIAL_WINDOW_SIZE, @initial_window_size]
|
130
|
-
end
|
131
|
-
|
132
|
-
if @maximum_frame_size != other.maximum_frame_size
|
133
|
-
changes << [MAXIMUM_FRAME_SIZE, @maximum_frame_size]
|
134
|
-
end
|
135
|
-
|
136
|
-
if @maximum_header_list_size != other.maximum_header_list_size
|
137
|
-
changes << [MAXIMUM_HEADER_LIST_SIZE, @maximum_header_list_size]
|
138
|
-
end
|
139
|
-
|
140
|
-
if @enable_connect_protocol != other.enable_connect_protocol
|
141
|
-
changes << [ENABLE_CONNECT_PROTOCOL, @enable_connect_protocol]
|
142
|
-
end
|
143
|
-
|
144
|
-
return changes
|
145
|
-
end
|
146
129
|
end
|
147
130
|
|
148
131
|
class PendingSettings
|
@@ -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
|
@@ -78,7 +77,7 @@ module Protocol
|
|
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
79
|
|
81
|
-
@
|
80
|
+
@priority = nil
|
82
81
|
end
|
83
82
|
|
84
83
|
# The connection this stream belongs to.
|
@@ -90,26 +89,11 @@ module Protocol
|
|
90
89
|
# Stream state, e.g. `idle`, `closed`.
|
91
90
|
attr_accessor :state
|
92
91
|
|
93
|
-
attr :dependency
|
94
|
-
|
95
92
|
attr :local_window
|
96
93
|
attr :remote_window
|
97
94
|
|
98
|
-
|
99
|
-
|
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
|
95
|
+
# @attribute [Protocol::HTTP::Header::Priority | Nil] the priority of the stream.
|
96
|
+
attr_accessor :priority
|
113
97
|
|
114
98
|
def maximum_frame_size
|
115
99
|
@connection.available_frame_size
|
@@ -141,13 +125,13 @@ module Protocol
|
|
141
125
|
@state == :idle or @state == :reserved_local or @state == :open or @state == :half_closed_remote
|
142
126
|
end
|
143
127
|
|
144
|
-
private def write_headers(
|
128
|
+
private def write_headers(headers, flags = 0)
|
145
129
|
frame = HeadersFrame.new(@id, flags)
|
146
130
|
|
147
131
|
@connection.write_frames do |framer|
|
148
132
|
data = @connection.encode_headers(headers)
|
149
133
|
|
150
|
-
frame.pack(
|
134
|
+
frame.pack(data, maximum_size: @connection.maximum_frame_size)
|
151
135
|
|
152
136
|
framer.write_frame(frame)
|
153
137
|
end
|
@@ -236,7 +220,7 @@ module Protocol
|
|
236
220
|
end
|
237
221
|
|
238
222
|
# Transition the stream into the closed state.
|
239
|
-
# @
|
223
|
+
# @parameter error_code [Integer] the error code if the stream was closed due to a stream reset.
|
240
224
|
def close!(error_code = nil)
|
241
225
|
@state = :closed
|
242
226
|
@connection.delete(@id)
|
@@ -265,11 +249,7 @@ module Protocol
|
|
265
249
|
|
266
250
|
def process_headers(frame)
|
267
251
|
# Receiving request headers:
|
268
|
-
|
269
|
-
|
270
|
-
if priority
|
271
|
-
@dependency.process_priority(priority)
|
272
|
-
end
|
252
|
+
data = frame.unpack
|
273
253
|
|
274
254
|
@connection.decode_headers(data)
|
275
255
|
end
|
@@ -401,7 +381,7 @@ module Protocol
|
|
401
381
|
end
|
402
382
|
|
403
383
|
# 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
|
-
# @
|
384
|
+
# @parameter headers [Hash] contains a complete set of request header fields that the server attributes to the request.
|
405
385
|
def send_push_promise(headers)
|
406
386
|
if @state == :open or @state == :half_closed_remote
|
407
387
|
promised_stream = self.create_push_promise_stream(headers)
|
@@ -427,7 +407,6 @@ module Protocol
|
|
427
407
|
headers = @connection.decode_headers(data)
|
428
408
|
|
429
409
|
stream = self.accept_push_promise_stream(promised_stream_id, headers)
|
430
|
-
stream.parent = self
|
431
410
|
stream.reserved_remote!
|
432
411
|
|
433
412
|
return stream, headers
|
@@ -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
|
-
# @
|
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
|
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(
|
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,9 @@
|
|
1
|
+
# Releases
|
2
|
+
|
3
|
+
## Unreleased
|
4
|
+
|
5
|
+
### Added Priority Update Frame and Stream Priority
|
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, and this includes `Stream#send_headers` which no longer takes `priority` as the first argument.
|
8
|
+
|
9
|
+
Optional per-request priority can be set using the `priority` header instead, and this value can be manipulated using the priority update frame.
|
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.
|
4
|
+
version: 0.22.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-
|
43
|
+
date: 2024-12-01 00:00:00.000000000 Z
|
44
44
|
dependencies:
|
45
45
|
- !ruby/object:Gem::Dependency
|
46
46
|
name: protocol-hpack
|
@@ -81,7 +81,6 @@ files:
|
|
81
81
|
- lib/protocol/http2/connection.rb
|
82
82
|
- lib/protocol/http2/continuation_frame.rb
|
83
83
|
- lib/protocol/http2/data_frame.rb
|
84
|
-
- lib/protocol/http2/dependency.rb
|
85
84
|
- lib/protocol/http2/error.rb
|
86
85
|
- lib/protocol/http2/flow_controlled.rb
|
87
86
|
- lib/protocol/http2/frame.rb
|
@@ -90,7 +89,7 @@ files:
|
|
90
89
|
- lib/protocol/http2/headers_frame.rb
|
91
90
|
- lib/protocol/http2/padded.rb
|
92
91
|
- lib/protocol/http2/ping_frame.rb
|
93
|
-
- lib/protocol/http2/
|
92
|
+
- lib/protocol/http2/priority_update_frame.rb
|
94
93
|
- lib/protocol/http2/push_promise_frame.rb
|
95
94
|
- lib/protocol/http2/reset_stream_frame.rb
|
96
95
|
- lib/protocol/http2/server.rb
|
@@ -103,6 +102,7 @@ files:
|
|
103
102
|
- lib/traces/provider/protocol/http2/framer.rb
|
104
103
|
- license.md
|
105
104
|
- readme.md
|
105
|
+
- releases.md
|
106
106
|
homepage: https://github.com/socketry/protocol-http2
|
107
107
|
licenses:
|
108
108
|
- MIT
|
metadata.gz.sig
CHANGED
Binary file
|
@@ -1,245 +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
|
-
def == other
|
62
|
-
@id == other.id
|
63
|
-
end
|
64
|
-
|
65
|
-
# The connection this stream belongs to.
|
66
|
-
attr :connection
|
67
|
-
|
68
|
-
# Stream ID (odd for client initiated streams, even otherwise).
|
69
|
-
attr :id
|
70
|
-
|
71
|
-
# The parent dependency.
|
72
|
-
attr_accessor :parent
|
73
|
-
|
74
|
-
# The dependent children.
|
75
|
-
attr_accessor :children
|
76
|
-
|
77
|
-
# The weight of the stream relative to other siblings.
|
78
|
-
attr_accessor :weight
|
79
|
-
|
80
|
-
def stream
|
81
|
-
@stream ||= @connection.streams[@id]
|
82
|
-
end
|
83
|
-
|
84
|
-
def clear_cache!
|
85
|
-
@ordered_children = nil
|
86
|
-
end
|
87
|
-
|
88
|
-
def delete!
|
89
|
-
@connection.dependencies.delete(@id)
|
90
|
-
|
91
|
-
@parent.remove_child(self)
|
92
|
-
|
93
|
-
@children&.each do |id, child|
|
94
|
-
parent.add_child(child)
|
95
|
-
end
|
96
|
-
|
97
|
-
@connection = nil
|
98
|
-
@parent = nil
|
99
|
-
@children = nil
|
100
|
-
end
|
101
|
-
|
102
|
-
def add_child(dependency)
|
103
|
-
@children ||= {}
|
104
|
-
@children[dependency.id] = dependency
|
105
|
-
|
106
|
-
dependency.parent = self
|
107
|
-
|
108
|
-
if @ordered_children
|
109
|
-
# Binary search for insertion point:
|
110
|
-
index = @ordered_children.bsearch_index do |child|
|
111
|
-
child.weight >= dependency.weight
|
112
|
-
end
|
113
|
-
|
114
|
-
if index
|
115
|
-
@ordered_children.insert(index, dependency)
|
116
|
-
else
|
117
|
-
@ordered_children.push(dependency)
|
118
|
-
end
|
119
|
-
|
120
|
-
@total_weight += dependency.weight
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
def remove_child(dependency)
|
125
|
-
@children&.delete(dependency.id)
|
126
|
-
|
127
|
-
if @ordered_children
|
128
|
-
# Don't do a linear search here, it can be slow. Instead, the child's parent will be set to `nil`, and we check this in {#consume_window} using `delete_if`.
|
129
|
-
# @ordered_children.delete(dependency)
|
130
|
-
@total_weight -= dependency.weight
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
# 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.
|
135
|
-
# @param parent [Dependency] the dependency which will be inserted, taking control of all current children.
|
136
|
-
def exclusive_child(parent)
|
137
|
-
parent.children = @children
|
138
|
-
|
139
|
-
@children&.each_value do |child|
|
140
|
-
child.parent = parent
|
141
|
-
end
|
142
|
-
|
143
|
-
parent.clear_cache!
|
144
|
-
|
145
|
-
@children = {parent.id => parent}
|
146
|
-
self.clear_cache!
|
147
|
-
|
148
|
-
parent.parent = self
|
149
|
-
end
|
150
|
-
|
151
|
-
def process_priority(priority)
|
152
|
-
dependent_id = priority.stream_dependency
|
153
|
-
|
154
|
-
if dependent_id == @id
|
155
|
-
raise ProtocolError, "Stream priority for stream id #{@id} cannot depend on itself!"
|
156
|
-
end
|
157
|
-
|
158
|
-
@weight = priority.weight
|
159
|
-
|
160
|
-
# We essentially ignore `dependent_id` if the dependency does not exist:
|
161
|
-
if parent = @connection.dependencies[dependent_id]
|
162
|
-
if priority.exclusive
|
163
|
-
@parent.remove_child(self)
|
164
|
-
|
165
|
-
parent.exclusive_child(self)
|
166
|
-
elsif !@parent.equal?(parent)
|
167
|
-
@parent.remove_child(self)
|
168
|
-
|
169
|
-
parent.add_child(self)
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
# Change the priority of the stream both locally and remotely.
|
175
|
-
def priority= priority
|
176
|
-
send_priority(priority)
|
177
|
-
process_priority(priority)
|
178
|
-
end
|
179
|
-
|
180
|
-
# The current local priority of the stream.
|
181
|
-
def priority(exclusive = false)
|
182
|
-
Priority.new(exclusive, @parent.id, @weight)
|
183
|
-
end
|
184
|
-
|
185
|
-
def send_priority(priority)
|
186
|
-
@connection.send_priority(@id, priority)
|
187
|
-
end
|
188
|
-
|
189
|
-
def receive_priority(frame)
|
190
|
-
self.process_priority(frame.unpack)
|
191
|
-
end
|
192
|
-
|
193
|
-
def total_weight
|
194
|
-
self.ordered_children
|
195
|
-
|
196
|
-
return @total_weight
|
197
|
-
end
|
198
|
-
|
199
|
-
def ordered_children
|
200
|
-
unless @ordered_children
|
201
|
-
if @children and !@children.empty?
|
202
|
-
@ordered_children = @children.values.sort
|
203
|
-
@total_weight = @ordered_children.sum(&:weight)
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
return @ordered_children
|
208
|
-
end
|
209
|
-
|
210
|
-
# Traverse active streams in order of priority and allow them to consume the available flow-control window.
|
211
|
-
# @param amount [Integer] the amount of data to write. Defaults to the current window capacity.
|
212
|
-
def consume_window(size)
|
213
|
-
# If there is an associated stream, give it priority:
|
214
|
-
if stream = self.stream
|
215
|
-
return if stream.window_updated(size)
|
216
|
-
end
|
217
|
-
|
218
|
-
# Otherwise, allow the dependent children to use up the available window:
|
219
|
-
self.ordered_children&.delete_if do |child|
|
220
|
-
if child.parent
|
221
|
-
# Compute the proportional allocation:
|
222
|
-
allocated = (child.weight * size) / @total_weight
|
223
|
-
|
224
|
-
child.consume_window(allocated) if allocated > 0
|
225
|
-
|
226
|
-
false
|
227
|
-
else
|
228
|
-
true
|
229
|
-
end
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
def inspect
|
234
|
-
"\#<#{self.class} id=#{@id} parent id=#{@parent&.id} weight=#{@weight} #{@children&.size || 0} children>"
|
235
|
-
end
|
236
|
-
|
237
|
-
def print_hierarchy(output = $stderr, indent: 0)
|
238
|
-
output.puts "#{"\t" * indent}#{self.inspect}"
|
239
|
-
@children&.each_value do |child|
|
240
|
-
child.print_hierarchy(output, indent: indent+1)
|
241
|
-
end
|
242
|
-
end
|
243
|
-
end
|
244
|
-
end
|
245
|
-
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
|