protocol-http2 0.22.1 → 0.23.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/context/getting-started.md +82 -0
- data/context/index.yaml +12 -0
- data/lib/protocol/http2/client.rb +28 -1
- data/lib/protocol/http2/connection.rb +87 -1
- data/lib/protocol/http2/continuation_frame.rb +42 -4
- data/lib/protocol/http2/data_frame.rb +11 -1
- data/lib/protocol/http2/error.rb +17 -2
- data/lib/protocol/http2/flow_controlled.rb +13 -1
- data/lib/protocol/http2/frame.rb +47 -2
- data/lib/protocol/http2/framer.rb +16 -1
- data/lib/protocol/http2/goaway_frame.rb +11 -1
- data/lib/protocol/http2/headers_frame.rb +15 -1
- data/lib/protocol/http2/padded.rb +12 -1
- data/lib/protocol/http2/ping_frame.rb +17 -1
- data/lib/protocol/http2/priority_update_frame.rb +8 -0
- data/lib/protocol/http2/push_promise_frame.rb +10 -1
- data/lib/protocol/http2/reset_stream_frame.rb +10 -1
- data/lib/protocol/http2/server.rb +27 -1
- data/lib/protocol/http2/settings_frame.rb +58 -1
- data/lib/protocol/http2/stream.rb +48 -1
- data/lib/protocol/http2/version.rb +4 -2
- data/lib/protocol/http2/window.rb +25 -1
- data/lib/protocol/http2/window_update_frame.rb +10 -1
- data/readme.md +12 -0
- data/releases.md +4 -0
- data.tar.gz.sig +0 -0
- metadata +6 -4
- metadata.gz.sig +0 -0
@@ -1,14 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
# Copyright, 2019, by Yuta Iwama.
|
6
6
|
|
7
7
|
require_relative "window_update_frame"
|
8
8
|
|
9
9
|
module Protocol
|
10
10
|
module HTTP2
|
11
|
+
# Provides flow control functionality for HTTP/2 connections and streams.
|
12
|
+
# This module implements window-based flow control as defined in RFC 7540.
|
11
13
|
module FlowControlled
|
14
|
+
# Get the available window size for sending data.
|
15
|
+
# @returns [Integer] The number of bytes that can be sent.
|
12
16
|
def available_size
|
13
17
|
@remote_window.available
|
14
18
|
end
|
@@ -40,17 +44,22 @@ module Protocol
|
|
40
44
|
end
|
41
45
|
end
|
42
46
|
|
47
|
+
# Update the local window after receiving data.
|
48
|
+
# @parameter frame [Frame] The frame that was received.
|
43
49
|
def update_local_window(frame)
|
44
50
|
consume_local_window(frame)
|
45
51
|
request_window_update
|
46
52
|
end
|
47
53
|
|
54
|
+
# Consume local window space for a received frame.
|
55
|
+
# @parameter frame [Frame] The frame that consumed window space.
|
48
56
|
def consume_local_window(frame)
|
49
57
|
# For flow-control calculations, the 9-octet frame header is not counted.
|
50
58
|
amount = frame.length
|
51
59
|
@local_window.consume(amount)
|
52
60
|
end
|
53
61
|
|
62
|
+
# Request a window update if the local window is limited.
|
54
63
|
def request_window_update
|
55
64
|
if @local_window.limited?
|
56
65
|
self.send_window_update(@local_window.wanted)
|
@@ -67,6 +76,9 @@ module Protocol
|
|
67
76
|
@local_window.expand(window_increment)
|
68
77
|
end
|
69
78
|
|
79
|
+
# Process a received WINDOW_UPDATE frame.
|
80
|
+
# @parameter frame [WindowUpdateFrame] The window update frame to process.
|
81
|
+
# @raises [ProtocolError] If the window increment is invalid.
|
70
82
|
def receive_window_update(frame)
|
71
83
|
amount = frame.unpack
|
72
84
|
|
data/lib/protocol/http2/frame.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
# Copyright, 2019, by Yuta Iwama.
|
6
6
|
|
7
7
|
require_relative "error"
|
@@ -17,6 +17,9 @@ module Protocol
|
|
17
17
|
MINIMUM_ALLOWED_FRAME_SIZE = 0x4000
|
18
18
|
MAXIMUM_ALLOWED_FRAME_SIZE = 0xFFFFFF
|
19
19
|
|
20
|
+
# Represents the base class for all HTTP/2 frames.
|
21
|
+
# This class provides common functionality for frame parsing, serialization,
|
22
|
+
# and manipulation according to RFC 7540.
|
20
23
|
class Frame
|
21
24
|
include Comparable
|
22
25
|
|
@@ -43,14 +46,21 @@ module Protocol
|
|
43
46
|
@payload = payload
|
44
47
|
end
|
45
48
|
|
49
|
+
# Check if the frame has a valid type identifier.
|
50
|
+
# @returns [Boolean] True if the frame type is valid.
|
46
51
|
def valid_type?
|
47
52
|
@type == self.class::TYPE
|
48
53
|
end
|
49
54
|
|
55
|
+
# Compare frames based on their essential properties.
|
56
|
+
# @parameter other [Frame] The frame to compare with.
|
57
|
+
# @returns [Integer] -1, 0, or 1 for comparison result.
|
50
58
|
def <=> other
|
51
59
|
to_ary <=> other.to_ary
|
52
60
|
end
|
53
61
|
|
62
|
+
# Convert frame to array representation for comparison.
|
63
|
+
# @returns [Array] Frame properties as an array.
|
54
64
|
def to_ary
|
55
65
|
[@length, @type, @flags, @stream_id, @payload]
|
56
66
|
end
|
@@ -73,10 +83,16 @@ module Protocol
|
|
73
83
|
attr_accessor :stream_id
|
74
84
|
attr_accessor :payload
|
75
85
|
|
86
|
+
# Unpack the frame payload data.
|
87
|
+
# @returns [String] The frame payload.
|
76
88
|
def unpack
|
77
89
|
@payload
|
78
90
|
end
|
79
91
|
|
92
|
+
# Pack payload data into the frame.
|
93
|
+
# @parameter payload [String] The payload data to pack.
|
94
|
+
# @parameter maximum_size [Integer | Nil] Optional maximum payload size.
|
95
|
+
# @raises [ProtocolError] If payload exceeds maximum size.
|
80
96
|
def pack(payload, maximum_size: nil)
|
81
97
|
@payload = payload
|
82
98
|
@length = payload.bytesize
|
@@ -86,14 +102,21 @@ module Protocol
|
|
86
102
|
end
|
87
103
|
end
|
88
104
|
|
105
|
+
# Set specific flags on the frame.
|
106
|
+
# @parameter mask [Integer] The flag bits to set.
|
89
107
|
def set_flags(mask)
|
90
108
|
@flags |= mask
|
91
109
|
end
|
92
110
|
|
111
|
+
# Clear specific flags on the frame.
|
112
|
+
# @parameter mask [Integer] The flag bits to clear.
|
93
113
|
def clear_flags(mask)
|
94
114
|
@flags &= ~mask
|
95
115
|
end
|
96
116
|
|
117
|
+
# Check if specific flags are set on the frame.
|
118
|
+
# @parameter mask [Integer] The flag bits to check.
|
119
|
+
# @returns [Boolean] True if any of the flags are set.
|
97
120
|
def flag_set?(mask)
|
98
121
|
@flags & mask != 0
|
99
122
|
end
|
@@ -101,7 +124,7 @@ module Protocol
|
|
101
124
|
# Check if frame is a connection frame: SETTINGS, PING, GOAWAY, and any
|
102
125
|
# frame addressed to stream ID = 0.
|
103
126
|
#
|
104
|
-
# @return [Boolean]
|
127
|
+
# @return [Boolean] If this is a connection frame.
|
105
128
|
def connection?
|
106
129
|
@stream_id.zero?
|
107
130
|
end
|
@@ -145,6 +168,9 @@ module Protocol
|
|
145
168
|
return length, type, flags, stream_id
|
146
169
|
end
|
147
170
|
|
171
|
+
# Read the frame header from a stream.
|
172
|
+
# @parameter stream [IO] The stream to read from.
|
173
|
+
# @raises [EOFError] If the header cannot be read completely.
|
148
174
|
def read_header(stream)
|
149
175
|
if buffer = stream.read(9) and buffer.bytesize == 9
|
150
176
|
@length, @type, @flags, @stream_id = Frame.parse_header(buffer)
|
@@ -154,6 +180,9 @@ module Protocol
|
|
154
180
|
end
|
155
181
|
end
|
156
182
|
|
183
|
+
# Read the frame payload from a stream.
|
184
|
+
# @parameter stream [IO] The stream to read from.
|
185
|
+
# @raises [EOFError] If the payload cannot be read completely.
|
157
186
|
def read_payload(stream)
|
158
187
|
if payload = stream.read(@length) and payload.bytesize == @length
|
159
188
|
@payload = payload
|
@@ -162,6 +191,11 @@ module Protocol
|
|
162
191
|
end
|
163
192
|
end
|
164
193
|
|
194
|
+
# Read the complete frame (header and payload) from a stream.
|
195
|
+
# @parameter stream [IO] The stream to read from.
|
196
|
+
# @parameter maximum_frame_size [Integer] The maximum allowed frame size.
|
197
|
+
# @raises [FrameSizeError] If the frame exceeds the maximum size.
|
198
|
+
# @returns [Frame] Self for method chaining.
|
165
199
|
def read(stream, maximum_frame_size = MAXIMUM_ALLOWED_FRAME_SIZE)
|
166
200
|
read_header(stream) unless @length
|
167
201
|
|
@@ -172,14 +206,21 @@ module Protocol
|
|
172
206
|
read_payload(stream)
|
173
207
|
end
|
174
208
|
|
209
|
+
# Write the frame header to a stream.
|
210
|
+
# @parameter stream [IO] The stream to write to.
|
175
211
|
def write_header(stream)
|
176
212
|
stream.write self.header
|
177
213
|
end
|
178
214
|
|
215
|
+
# Write the frame payload to a stream.
|
216
|
+
# @parameter stream [IO] The stream to write to.
|
179
217
|
def write_payload(stream)
|
180
218
|
stream.write(@payload) if @payload
|
181
219
|
end
|
182
220
|
|
221
|
+
# Write the complete frame (header and payload) to a stream.
|
222
|
+
# @parameter stream [IO] The stream to write to.
|
223
|
+
# @raises [ProtocolError] If frame validation fails.
|
183
224
|
def write(stream)
|
184
225
|
# Validate the payload size:
|
185
226
|
if @payload.nil?
|
@@ -196,10 +237,14 @@ module Protocol
|
|
196
237
|
self.write_payload(stream)
|
197
238
|
end
|
198
239
|
|
240
|
+
# Apply the frame to a connection for processing.
|
241
|
+
# @parameter connection [Connection] The connection to apply the frame to.
|
199
242
|
def apply(connection)
|
200
243
|
connection.receive_frame(self)
|
201
244
|
end
|
202
245
|
|
246
|
+
# Provide a readable representation of the frame for debugging.
|
247
|
+
# @returns [String] A formatted string representation of the frame.
|
203
248
|
def inspect
|
204
249
|
"\#<#{self.class} stream_id=#{@stream_id} flags=#{@flags} payload=#{self.unpack}>"
|
205
250
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "error"
|
7
7
|
|
@@ -42,28 +42,40 @@ module Protocol
|
|
42
42
|
# Default connection "fast-fail" preamble string as defined by the spec.
|
43
43
|
CONNECTION_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".freeze
|
44
44
|
|
45
|
+
# Handles frame serialization and deserialization for HTTP/2 connections.
|
46
|
+
# This class manages the reading and writing of HTTP/2 frames to/from a stream.
|
45
47
|
class Framer
|
48
|
+
# Initialize a new framer with a stream and frame definitions.
|
49
|
+
# @parameter stream [IO] The underlying stream for frame I/O.
|
50
|
+
# @parameter frames [Array] Frame type definitions to use.
|
46
51
|
def initialize(stream, frames = FRAMES)
|
47
52
|
@stream = stream
|
48
53
|
@frames = frames
|
49
54
|
end
|
50
55
|
|
56
|
+
# Flush the underlying stream.
|
51
57
|
def flush
|
52
58
|
@stream.flush
|
53
59
|
end
|
54
60
|
|
61
|
+
# Close the underlying stream.
|
55
62
|
def close
|
56
63
|
@stream.close
|
57
64
|
end
|
58
65
|
|
66
|
+
# Check if the underlying stream is closed.
|
67
|
+
# @returns [Boolean] True if the stream is closed.
|
59
68
|
def closed?
|
60
69
|
@stream.closed?
|
61
70
|
end
|
62
71
|
|
72
|
+
# Write the HTTP/2 connection preface to the stream.
|
63
73
|
def write_connection_preface
|
64
74
|
@stream.write(CONNECTION_PREFACE)
|
65
75
|
end
|
66
76
|
|
77
|
+
# Read and validate the HTTP/2 connection preface from the stream.
|
78
|
+
# @raises [HandshakeError] If the preface is invalid.
|
67
79
|
def read_connection_preface
|
68
80
|
string = @stream.read(CONNECTION_PREFACE.bytesize)
|
69
81
|
|
@@ -105,6 +117,9 @@ module Protocol
|
|
105
117
|
return frame
|
106
118
|
end
|
107
119
|
|
120
|
+
# Read a frame header from the stream.
|
121
|
+
# @returns [Array] Parsed frame header components: length, type, flags, stream_id.
|
122
|
+
# @raises [EOFError] If the header cannot be read completely.
|
108
123
|
def read_header
|
109
124
|
if buffer = @stream.read(9)
|
110
125
|
if buffer.bytesize == 9
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "frame"
|
7
7
|
|
@@ -21,10 +21,14 @@ module Protocol
|
|
21
21
|
TYPE = 0x7
|
22
22
|
FORMAT = "NN"
|
23
23
|
|
24
|
+
# Check if this frame applies to the connection level.
|
25
|
+
# @returns [Boolean] Always returns true for GOAWAY frames.
|
24
26
|
def connection?
|
25
27
|
true
|
26
28
|
end
|
27
29
|
|
30
|
+
# Unpack the GOAWAY frame payload.
|
31
|
+
# @returns [Array] Last stream ID, error code, and debug data.
|
28
32
|
def unpack
|
29
33
|
data = super
|
30
34
|
|
@@ -33,10 +37,16 @@ module Protocol
|
|
33
37
|
return last_stream_id, error_code, data.slice(8, data.bytesize-8)
|
34
38
|
end
|
35
39
|
|
40
|
+
# Pack GOAWAY frame data into payload.
|
41
|
+
# @parameter last_stream_id [Integer] The last processed stream ID.
|
42
|
+
# @parameter error_code [Integer] The error code for connection termination.
|
43
|
+
# @parameter data [String] Additional debug data.
|
36
44
|
def pack(last_stream_id, error_code, data)
|
37
45
|
super [last_stream_id, error_code].pack(FORMAT) + data
|
38
46
|
end
|
39
47
|
|
48
|
+
# Apply this GOAWAY frame to a connection for processing.
|
49
|
+
# @parameter connection [Connection] The connection to apply the frame to.
|
40
50
|
def apply(connection)
|
41
51
|
connection.receive_goaway(self)
|
42
52
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "frame"
|
7
7
|
require_relative "padded"
|
@@ -28,14 +28,20 @@ module Protocol
|
|
28
28
|
|
29
29
|
TYPE = 0x1
|
30
30
|
|
31
|
+
# Check if this frame contains priority information.
|
32
|
+
# @returns [Boolean] True if the PRIORITY flag is set.
|
31
33
|
def priority?
|
32
34
|
flag_set?(PRIORITY)
|
33
35
|
end
|
34
36
|
|
37
|
+
# Check if this frame ends the stream.
|
38
|
+
# @returns [Boolean] True if the END_STREAM flag is set.
|
35
39
|
def end_stream?
|
36
40
|
flag_set?(END_STREAM)
|
37
41
|
end
|
38
42
|
|
43
|
+
# Unpack the header block fragment from the frame.
|
44
|
+
# @returns [String] The unpacked header block data.
|
39
45
|
def unpack
|
40
46
|
data = super
|
41
47
|
|
@@ -47,6 +53,10 @@ module Protocol
|
|
47
53
|
return data
|
48
54
|
end
|
49
55
|
|
56
|
+
# Pack header block data into the frame.
|
57
|
+
# @parameter data [String] The header block data to pack.
|
58
|
+
# @parameter arguments [Array] Additional arguments.
|
59
|
+
# @parameter options [Hash] Options for packing.
|
50
60
|
def pack(data, *arguments, **options)
|
51
61
|
buffer = String.new.b
|
52
62
|
|
@@ -55,10 +65,14 @@ module Protocol
|
|
55
65
|
super(buffer, *arguments, **options)
|
56
66
|
end
|
57
67
|
|
68
|
+
# Apply this HEADERS frame to a connection for processing.
|
69
|
+
# @parameter connection [Connection] The connection to apply the frame to.
|
58
70
|
def apply(connection)
|
59
71
|
connection.receive_headers(self)
|
60
72
|
end
|
61
73
|
|
74
|
+
# Get a string representation of the headers frame.
|
75
|
+
# @returns [String] Human-readable frame information.
|
62
76
|
def inspect
|
63
77
|
"\#<#{self.class} stream_id=#{@stream_id} flags=#{@flags} #{@length || 0}b>"
|
64
78
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "frame"
|
7
7
|
|
@@ -18,11 +18,19 @@ module Protocol
|
|
18
18
|
# | Padding (*) ...
|
19
19
|
# +---------------------------------------------------------------+
|
20
20
|
#
|
21
|
+
# Provides padding functionality for HTTP/2 frames.
|
22
|
+
# Padding can be used to obscure the actual size of frame payloads.
|
21
23
|
module Padded
|
24
|
+
# Check if the frame has padding enabled.
|
25
|
+
# @returns [Boolean] True if the PADDED flag is set.
|
22
26
|
def padded?
|
23
27
|
flag_set?(PADDED)
|
24
28
|
end
|
25
29
|
|
30
|
+
# Pack data with optional padding into the frame.
|
31
|
+
# @parameter data [String] The data to pack.
|
32
|
+
# @parameter padding_size [Integer | Nil] Number of padding bytes to add.
|
33
|
+
# @parameter maximum_size [Integer | Nil] Maximum frame size limit.
|
26
34
|
def pack(data, padding_size: nil, maximum_size: nil)
|
27
35
|
if padding_size
|
28
36
|
set_flags(PADDED)
|
@@ -44,6 +52,9 @@ module Protocol
|
|
44
52
|
end
|
45
53
|
end
|
46
54
|
|
55
|
+
# Unpack frame data, removing padding if present.
|
56
|
+
# @returns [String] The unpacked data without padding.
|
57
|
+
# @raises [ProtocolError] If padding length is invalid.
|
47
58
|
def unpack
|
48
59
|
if padded?
|
49
60
|
padding_size = @payload[0].ord
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "frame"
|
7
7
|
|
@@ -9,15 +9,22 @@ module Protocol
|
|
9
9
|
module HTTP2
|
10
10
|
ACKNOWLEDGEMENT = 0x1
|
11
11
|
|
12
|
+
# Provides acknowledgement functionality for frames that support it.
|
13
|
+
# This module handles setting and checking acknowledgement flags on frames.
|
12
14
|
module Acknowledgement
|
15
|
+
# Check if the frame is an acknowledgement.
|
16
|
+
# @returns [Boolean] True if the acknowledgement flag is set.
|
13
17
|
def acknowledgement?
|
14
18
|
flag_set?(ACKNOWLEDGEMENT)
|
15
19
|
end
|
16
20
|
|
21
|
+
# Mark this frame as an acknowledgement.
|
17
22
|
def acknowledgement!
|
18
23
|
set_flags(ACKNOWLEDGEMENT)
|
19
24
|
end
|
20
25
|
|
26
|
+
# Create an acknowledgement frame for this frame.
|
27
|
+
# @returns [Frame] A new frame marked as an acknowledgement.
|
21
28
|
def acknowledge
|
22
29
|
frame = self.class.new
|
23
30
|
|
@@ -41,14 +48,20 @@ module Protocol
|
|
41
48
|
|
42
49
|
include Acknowledgement
|
43
50
|
|
51
|
+
# Check if this frame applies to the connection level.
|
52
|
+
# @returns [Boolean] Always returns true for PING frames.
|
44
53
|
def connection?
|
45
54
|
true
|
46
55
|
end
|
47
56
|
|
57
|
+
# Apply this PING frame to a connection for processing.
|
58
|
+
# @parameter connection [Connection] The connection to apply the frame to.
|
48
59
|
def apply(connection)
|
49
60
|
connection.receive_ping(self)
|
50
61
|
end
|
51
62
|
|
63
|
+
# Create an acknowledgement PING frame with the same payload.
|
64
|
+
# @returns [PingFrame] A new PING frame marked as an acknowledgement.
|
52
65
|
def acknowledge
|
53
66
|
frame = super
|
54
67
|
|
@@ -57,6 +70,9 @@ module Protocol
|
|
57
70
|
return frame
|
58
71
|
end
|
59
72
|
|
73
|
+
# Read and validate the PING frame payload.
|
74
|
+
# @parameter stream [IO] The stream to read from.
|
75
|
+
# @raises [ProtocolError] If validation fails.
|
60
76
|
def read_payload(stream)
|
61
77
|
super
|
62
78
|
|
@@ -21,6 +21,8 @@ module Protocol
|
|
21
21
|
TYPE = 0x10
|
22
22
|
FORMAT = "N".freeze
|
23
23
|
|
24
|
+
# Unpack the prioritized stream ID and priority field value.
|
25
|
+
# @returns [Array] An array containing the prioritized stream ID and priority field value.
|
24
26
|
def unpack
|
25
27
|
data = super
|
26
28
|
|
@@ -29,10 +31,16 @@ module Protocol
|
|
29
31
|
return prioritized_stream_id, data.byteslice(4, data.bytesize - 4)
|
30
32
|
end
|
31
33
|
|
34
|
+
# Pack the prioritized stream ID and priority field value into the frame.
|
35
|
+
# @parameter prioritized_stream_id [Integer] The stream ID to prioritize.
|
36
|
+
# @parameter data [String] The priority field value.
|
37
|
+
# @parameter options [Hash] Options for packing.
|
32
38
|
def pack(prioritized_stream_id, data, **options)
|
33
39
|
super([prioritized_stream_id].pack(FORMAT) + data, **options)
|
34
40
|
end
|
35
41
|
|
42
|
+
# Apply this PRIORITY_UPDATE frame to a connection for processing.
|
43
|
+
# @parameter connection [Connection] The connection to apply the frame to.
|
36
44
|
def apply(connection)
|
37
45
|
connection.receive_priority_update(self)
|
38
46
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "frame"
|
7
7
|
require_relative "padded"
|
@@ -27,6 +27,8 @@ module Protocol
|
|
27
27
|
TYPE = 0x5
|
28
28
|
FORMAT = "N".freeze
|
29
29
|
|
30
|
+
# Unpack the promised stream ID and header block fragment.
|
31
|
+
# @returns [Array] An array containing the promised stream ID and header block data.
|
30
32
|
def unpack
|
31
33
|
data = super
|
32
34
|
|
@@ -35,10 +37,17 @@ module Protocol
|
|
35
37
|
return stream_id, data.byteslice(4, data.bytesize - 4)
|
36
38
|
end
|
37
39
|
|
40
|
+
# Pack the promised stream ID and header block data into the frame.
|
41
|
+
# @parameter stream_id [Integer] The promised stream ID.
|
42
|
+
# @parameter data [String] The header block data.
|
43
|
+
# @parameter arguments [Array] Additional arguments.
|
44
|
+
# @parameter options [Hash] Options for packing.
|
38
45
|
def pack(stream_id, data, *arguments, **options)
|
39
46
|
super([stream_id].pack(FORMAT) + data, *arguments, **options)
|
40
47
|
end
|
41
48
|
|
49
|
+
# Apply this PUSH_PROMISE frame to a connection for processing.
|
50
|
+
# @parameter connection [Connection] The connection to apply the frame to.
|
42
51
|
def apply(connection)
|
43
52
|
connection.receive_push_promise(self)
|
44
53
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "frame"
|
7
7
|
|
@@ -32,19 +32,28 @@ module Protocol
|
|
32
32
|
TYPE = 0x3
|
33
33
|
FORMAT = "N".freeze
|
34
34
|
|
35
|
+
# Unpack the error code from the frame payload.
|
36
|
+
# @returns [Integer] The error code.
|
35
37
|
def unpack
|
36
38
|
@payload.unpack1(FORMAT)
|
37
39
|
end
|
38
40
|
|
41
|
+
# Pack an error code into the frame payload.
|
42
|
+
# @parameter error_code [Integer] The error code to pack.
|
39
43
|
def pack(error_code = NO_ERROR)
|
40
44
|
@payload = [error_code].pack(FORMAT)
|
41
45
|
@length = @payload.bytesize
|
42
46
|
end
|
43
47
|
|
48
|
+
# Apply this RST_STREAM frame to a connection for processing.
|
49
|
+
# @parameter connection [Connection] The connection to apply the frame to.
|
44
50
|
def apply(connection)
|
45
51
|
connection.receive_reset_stream(self)
|
46
52
|
end
|
47
53
|
|
54
|
+
# Read and validate the RST_STREAM frame payload.
|
55
|
+
# @parameter stream [IO] The stream to read from.
|
56
|
+
# @raises [FrameSizeError] If the frame length is invalid.
|
48
57
|
def read_payload(stream)
|
49
58
|
super
|
50
59
|
|
@@ -1,29 +1,50 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
require_relative "connection"
|
7
7
|
|
8
8
|
module Protocol
|
9
9
|
module HTTP2
|
10
|
+
# Represents an HTTP/2 server connection.
|
11
|
+
# Manages server-side protocol semantics including stream ID allocation,
|
12
|
+
# connection preface handling, and settings negotiation.
|
10
13
|
class Server < Connection
|
14
|
+
# Initialize a new HTTP/2 server connection.
|
15
|
+
# @parameter framer [Framer] The frame handler for reading/writing HTTP/2 frames.
|
11
16
|
def initialize(framer)
|
12
17
|
super(framer, 2)
|
13
18
|
end
|
14
19
|
|
20
|
+
# Check if the given stream ID represents a locally-initiated stream.
|
21
|
+
# Server streams have even numbered IDs.
|
22
|
+
# @parameter id [Integer] The stream ID to check.
|
23
|
+
# @returns [Boolean] True if the stream ID is locally-initiated.
|
15
24
|
def local_stream_id?(id)
|
16
25
|
id.even?
|
17
26
|
end
|
18
27
|
|
28
|
+
# Check if the given stream ID represents a remotely-initiated stream.
|
29
|
+
# Client streams have odd numbered IDs.
|
30
|
+
# @parameter id [Integer] The stream ID to check.
|
31
|
+
# @returns [Boolean] True if the stream ID is remotely-initiated.
|
19
32
|
def remote_stream_id?(id)
|
20
33
|
id.odd?
|
21
34
|
end
|
22
35
|
|
36
|
+
# Check if the given stream ID is valid for remote initiation.
|
37
|
+
# Client-initiated streams must have odd numbered IDs.
|
38
|
+
# @parameter stream_id [Integer] The stream ID to validate.
|
39
|
+
# @returns [Boolean] True if the stream ID is valid for remote initiation.
|
23
40
|
def valid_remote_stream_id?(stream_id)
|
24
41
|
stream_id.odd?
|
25
42
|
end
|
26
43
|
|
44
|
+
# Read the HTTP/2 connection preface from the client and send initial settings.
|
45
|
+
# This must be called once when the connection is first established.
|
46
|
+
# @parameter settings [Array] Optional settings to send during preface exchange.
|
47
|
+
# @raises [ProtocolError] If called when not in the new state or preface is invalid.
|
27
48
|
def read_connection_preface(settings = [])
|
28
49
|
if @state == :new
|
29
50
|
@framer.read_connection_preface
|
@@ -40,10 +61,15 @@ module Protocol
|
|
40
61
|
end
|
41
62
|
end
|
42
63
|
|
64
|
+
# Servers cannot accept push promise streams from clients.
|
65
|
+
# @parameter stream_id [Integer] The stream ID (unused).
|
66
|
+
# @raises [ProtocolError] Always, as servers cannot accept push promises.
|
43
67
|
def accept_push_promise_stream(stream_id, &block)
|
44
68
|
raise ProtocolError, "Cannot accept push promises on server!"
|
45
69
|
end
|
46
70
|
|
71
|
+
# Check if server push is enabled by the client.
|
72
|
+
# @returns [Boolean] True if push promises are enabled.
|
47
73
|
def enable_push?
|
48
74
|
@remote_settings.enable_push?
|
49
75
|
end
|