protocol-http2 0.20.0 → 0.22.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|