protocol-http2 0.22.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3d3ec4ba460a65fcf1aa11e9c3ce4d0f46fde1cb0e7fa9cc33667b9142eb3dc4
4
- data.tar.gz: 0afd95c0651daf938a1b6e472376d38274dc7df70fdd38e9f28cfe6c657bc81f
3
+ metadata.gz: d4df424d3c1c97120be6031179d3febd9b6bcbbaeb163b24d53b03f05ae28696
4
+ data.tar.gz: baf0e59d7998a934d35eb1d33fa6205ccbae7bb6c5d352721b1d5d1ba0880131
5
5
  SHA512:
6
- metadata.gz: 205b7683de9faee706dead27493c76b247c0d06c489778f670e4c49f0ecde263ad8b3b7ac1f86312681f83aee8bfaf4f82390f5d43c6a2d79dabdbe3f7d06b6a
7
- data.tar.gz: c72cfe3041d0c4edc6e0510a8dc04f88dd547ebff1e96b9322f7ac35a64850fe516677a7e3df8b26258d7f089f10a2a4504211da8871678920ef65aa32b8a76b
6
+ metadata.gz: 4c7f24cd932068ecdcdf604bd9a9e3757c0fe40167844dfd351340f088507b6975a18288991c34342c15ad4217bf111a6e4dfa620c68c1bb7c83c149f0a5d21d
7
+ data.tar.gz: 404eff6b4e7a395cdcd44fd366564dbd67edc292857685d6294144b66892eda4dbdc60518cd0e2f58e034d001b45c0aeeffb389fccf49ad54464b97332bcc398
checksums.yaml.gz.sig CHANGED
Binary file
@@ -0,0 +1,82 @@
1
+ # Getting Started
2
+
3
+ This guide explains how to use the `protocol-http2` gem to implement a basic HTTP/2 client.
4
+
5
+ ## Installation
6
+
7
+ Add the gem to your project:
8
+
9
+ ``` bash
10
+ $ bundle add protocol-http2
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ This gem provides a low-level implementation of the HTTP/2 protocol. It is designed to be used in conjunction with other libraries to provide a complete HTTP/2 client or server. However, it is straight forward to give examples of how to use the library directly.
16
+
17
+ ### Client
18
+
19
+ Here is a basic HTTP/2 client:
20
+
21
+ ``` ruby
22
+ require 'async'
23
+ require 'async/io/stream'
24
+ require 'async/http/endpoint'
25
+ require 'protocol/http2/client'
26
+
27
+ Async do
28
+ endpoint = Async::HTTP::Endpoint.parse("https://www.google.com/search?q=kittens")
29
+
30
+ peer = endpoint.connect
31
+
32
+ puts "Connected to #{peer.inspect}"
33
+
34
+ # IO Buffering:
35
+ stream = Async::IO::Stream.new(peer)
36
+
37
+ framer = Protocol::HTTP2::Framer.new(stream)
38
+ client = Protocol::HTTP2::Client.new(framer)
39
+
40
+ puts "Sending connection preface..."
41
+ client.send_connection_preface
42
+
43
+ puts "Creating stream..."
44
+ stream = client.create_stream
45
+
46
+ headers = [
47
+ [":scheme", endpoint.scheme],
48
+ [":method", "GET"],
49
+ [":authority", "www.google.com"],
50
+ [":path", endpoint.path],
51
+ ["accept", "*/*"],
52
+ ]
53
+
54
+ puts "Sending request on stream id=#{stream.id} state=#{stream.state}..."
55
+ stream.send_headers(headers, Protocol::HTTP2::END_STREAM)
56
+
57
+ puts "Waiting for response..."
58
+ $count = 0
59
+
60
+ def stream.process_headers(frame)
61
+ headers = super
62
+ puts "Got response headers: #{headers} (#{frame.end_stream?})"
63
+ end
64
+
65
+ def stream.receive_data(frame)
66
+ data = super
67
+
68
+ $count += data.scan(/kittens/).count
69
+
70
+ puts "Got response data: #{data.bytesize}"
71
+ end
72
+
73
+ until stream.closed?
74
+ frame = client.read_frame
75
+ end
76
+
77
+ puts "Got #{$count} kittens!"
78
+
79
+ puts "Closing client..."
80
+ client.close
81
+ end
82
+ ```
@@ -0,0 +1,12 @@
1
+ # Automatically generated context index for Utopia::Project guides.
2
+ # Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
3
+ ---
4
+ description: A low level implementation of the HTTP/2 protocol.
5
+ metadata:
6
+ documentation_uri: https://socketry.github.io/protocol-http2/
7
+ source_code_uri: https://github.com/socketry/protocol-http2.git
8
+ files:
9
+ - path: getting-started.md
10
+ title: Getting Started
11
+ description: This guide explains how to use the `protocol-http2` gem to implement
12
+ a basic HTTP/2 client.
@@ -1,29 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2024, by Samuel Williams.
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 client connection.
11
+ # Manages client-side protocol semantics including stream ID allocation,
12
+ # connection preface handling, and push promise processing.
10
13
  class Client < Connection
14
+ # Initialize a new HTTP/2 client connection.
15
+ # @parameter framer [Framer] The frame handler for reading/writing HTTP/2 frames.
11
16
  def initialize(framer)
12
17
  super(framer, 1)
13
18
  end
14
19
 
20
+ # Check if the given stream ID represents a locally-initiated stream.
21
+ # Client streams have odd numbered IDs.
22
+ # @parameter id [Integer] The stream ID to check.
23
+ # @returns [bool] True if the stream ID is locally-initiated.
15
24
  def local_stream_id?(id)
16
25
  id.odd?
17
26
  end
18
27
 
28
+ # Check if the given stream ID represents a remotely-initiated stream.
29
+ # Server streams have even numbered IDs.
30
+ # @parameter id [Integer] The stream ID to check.
31
+ # @returns [bool] True if the stream ID is remotely-initiated.
19
32
  def remote_stream_id?(id)
20
33
  id.even?
21
34
  end
22
35
 
36
+ # Check if the given stream ID is valid for remote initiation.
37
+ # Server-initiated streams must have even numbered IDs.
38
+ # @parameter stream_id [Integer] The stream ID to validate.
39
+ # @returns [bool] True if the stream ID is valid for remote initiation.
23
40
  def valid_remote_stream_id?(stream_id)
24
41
  stream_id.even?
25
42
  end
26
43
 
44
+ # Send the HTTP/2 connection preface and initial settings.
45
+ # This must be called once when the connection is first established.
46
+ # @parameter settings [Array] Optional settings to send with the connection preface.
47
+ # @raises [ProtocolError] If called when not in the new state.
48
+ # @yields Allows custom processing during preface exchange.
27
49
  def send_connection_preface(settings = [])
28
50
  if @state == :new
29
51
  @framer.write_connection_preface
@@ -42,10 +64,15 @@ module Protocol
42
64
  end
43
65
  end
44
66
 
67
+ # Clients cannot create push promise streams.
68
+ # @raises [ProtocolError] Always, as clients cannot initiate push promises.
45
69
  def create_push_promise_stream
46
70
  raise ProtocolError, "Cannot create push promises from client!"
47
71
  end
48
72
 
73
+ # Process a push promise frame received from the server.
74
+ # @parameter frame [PushPromiseFrame] The push promise frame to process.
75
+ # @returns [Array(Stream, Hash) | Nil] The promised stream and request headers, or nil if no associated stream.
49
76
  def receive_push_promise(frame)
50
77
  if frame.stream_id == 0
51
78
  raise ProtocolError, "Cannot receive headers for stream 0!"
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2024, by Samuel Williams.
4
+ # Copyright, 2019-2025, by Samuel Williams.
5
5
  # Copyright, 2023, by Marco Concetto Rudilosso.
6
6
 
7
7
  require_relative "framer"
@@ -12,9 +12,14 @@ require "protocol/http/header/priority"
12
12
 
13
13
  module Protocol
14
14
  module HTTP2
15
+ # This is the core connection class that handles HTTP/2 protocol semantics including
16
+ # stream management, settings negotiation, and frame processing.
15
17
  class Connection
16
18
  include FlowControlled
17
19
 
20
+ # Initialize a new HTTP/2 connection.
21
+ # @parameter framer [Framer] The frame handler for reading/writing HTTP/2 frames.
22
+ # @parameter local_stream_id [Integer] The starting stream ID for locally-initiated streams.
18
23
  def initialize(framer, local_stream_id)
19
24
  super()
20
25
 
@@ -41,10 +46,15 @@ module Protocol
41
46
  @remote_window = Window.new
42
47
  end
43
48
 
49
+ # The connection stream ID (always 0 for connection-level operations).
50
+ # @returns [Integer] Always returns 0 for the connection itself.
44
51
  def id
45
52
  0
46
53
  end
47
54
 
55
+ # Access streams by ID, with 0 returning the connection itself.
56
+ # @parameter id [Integer] The stream ID to look up.
57
+ # @returns [Connection | Stream | Nil] The connection (if id=0), stream, or nil.
48
58
  def [] id
49
59
  if id.zero?
50
60
  self
@@ -89,6 +99,9 @@ module Protocol
89
99
  @state == :closed || @framer.nil?
90
100
  end
91
101
 
102
+ # Remove a stream from the active streams collection.
103
+ # @parameter id [Integer] The stream ID to remove.
104
+ # @returns [Stream | Nil] The removed stream, or nil if not found.
92
105
  def delete(id)
93
106
  @streams.delete(id)
94
107
  end
@@ -106,10 +119,17 @@ module Protocol
106
119
  end
107
120
  end
108
121
 
122
+ # Encode headers using HPACK compression.
123
+ # @parameter headers [Array] The headers to encode.
124
+ # @parameter buffer [String] Optional buffer for encoding output.
125
+ # @returns [String] The encoded header block.
109
126
  def encode_headers(headers, buffer = String.new.b)
110
127
  HPACK::Compressor.new(buffer, @encoder, table_size_limit: @remote_settings.header_table_size).encode(headers)
111
128
  end
112
129
 
130
+ # Decode headers using HPACK decompression.
131
+ # @parameter data [String] The encoded header block data.
132
+ # @returns [Array] The decoded headers.
113
133
  def decode_headers(data)
114
134
  HPACK::Decompressor.new(data, @decoder, table_size_limit: @local_settings.header_table_size).decode
115
135
  end
@@ -141,6 +161,9 @@ module Protocol
141
161
  end
142
162
  end
143
163
 
164
+ # Execute a block within a synchronized context.
165
+ # This method provides a synchronization primitive for thread safety.
166
+ # @yields The block to execute within the synchronized context.
144
167
  def synchronize
145
168
  yield
146
169
  end
@@ -171,6 +194,8 @@ module Protocol
171
194
  raise
172
195
  end
173
196
 
197
+ # Send updated settings to the remote peer.
198
+ # @parameter changes [Hash] The settings changes to send.
174
199
  def send_settings(changes)
175
200
  @local_settings.append(changes)
176
201
 
@@ -197,6 +222,9 @@ module Protocol
197
222
  self.close!
198
223
  end
199
224
 
225
+ # Process a GOAWAY frame from the remote peer.
226
+ # @parameter frame [GoawayFrame] The GOAWAY frame to process.
227
+ # @raises [GoawayError] If the frame indicates a connection error.
200
228
  def receive_goaway(frame)
201
229
  # We capture the last stream that was processed.
202
230
  @remote_stream_id, error_code, message = frame.unpack
@@ -209,6 +237,8 @@ module Protocol
209
237
  end
210
238
  end
211
239
 
240
+ # Write a single frame to the connection.
241
+ # @parameter frame [Frame] The frame to write.
212
242
  def write_frame(frame)
213
243
  synchronize do
214
244
  @framer.write_frame(frame)
@@ -217,6 +247,10 @@ module Protocol
217
247
  @framer.flush
218
248
  end
219
249
 
250
+ # Write multiple frames within a synchronized block.
251
+ # @yields {|framer| ...} The framer for writing multiple frames.
252
+ # @parameter framer [Framer] The framer instance.
253
+ # @raises [EOFError] If the connection is closed.
220
254
  def write_frames
221
255
  if @framer
222
256
  synchronize do
@@ -229,6 +263,8 @@ module Protocol
229
263
  end
230
264
  end
231
265
 
266
+ # Update local settings and adjust stream window capacities.
267
+ # @parameter changes [Hash] The settings changes to apply locally.
232
268
  def update_local_settings(changes)
233
269
  capacity = @local_settings.initial_window_size
234
270
 
@@ -239,6 +275,8 @@ module Protocol
239
275
  @local_window.desired = capacity
240
276
  end
241
277
 
278
+ # Update remote settings and adjust stream window capacities.
279
+ # @parameter changes [Hash] The settings changes to apply to remote peer.
242
280
  def update_remote_settings(changes)
243
281
  capacity = @remote_settings.initial_window_size
244
282
 
@@ -273,12 +311,17 @@ module Protocol
273
311
  end
274
312
  end
275
313
 
314
+ # Transition the connection to the open state.
315
+ # @returns [Connection] Self for method chaining.
276
316
  def open!
277
317
  @state = :open
278
318
 
279
319
  return self
280
320
  end
281
321
 
322
+ # Receive and process a SETTINGS frame from the remote peer.
323
+ # @parameter frame [SettingsFrame] The settings frame to process.
324
+ # @raises [ProtocolError] If the connection is in an invalid state.
282
325
  def receive_settings(frame)
283
326
  if @state == :new
284
327
  # We transition to :open when we receive acknowledgement of first settings frame:
@@ -290,6 +333,8 @@ module Protocol
290
333
  end
291
334
  end
292
335
 
336
+ # Send a PING frame to the remote peer.
337
+ # @parameter data [String] The 8-byte ping payload data.
293
338
  def send_ping(data)
294
339
  if @state != :closed
295
340
  frame = PingFrame.new
@@ -301,6 +346,9 @@ module Protocol
301
346
  end
302
347
  end
303
348
 
349
+ # Process a PING frame from the remote peer.
350
+ # @parameter frame [PingFrame] The ping frame to process.
351
+ # @raises [ProtocolError] If ping is received in invalid state.
304
352
  def receive_ping(frame)
305
353
  if @state != :closed
306
354
  # This is handled in `read_payload`:
@@ -318,6 +366,9 @@ module Protocol
318
366
  end
319
367
  end
320
368
 
369
+ # Process a DATA frame from the remote peer.
370
+ # @parameter frame [DataFrame] The data frame to process.
371
+ # @raises [ProtocolError] If data is received for invalid stream.
321
372
  def receive_data(frame)
322
373
  update_local_window(frame)
323
374
 
@@ -330,6 +381,10 @@ module Protocol
330
381
  end
331
382
  end
332
383
 
384
+ # Check if the given stream ID is valid for remote initiation.
385
+ # This method should be overridden by client/server implementations.
386
+ # @parameter stream_id [Integer] The stream ID to validate.
387
+ # @returns [Boolean] True if the stream ID is valid for remote initiation.
333
388
  def valid_remote_stream_id?(stream_id)
334
389
  false
335
390
  end
@@ -366,6 +421,10 @@ module Protocol
366
421
  end
367
422
  end
368
423
 
424
+ # Create a push promise stream.
425
+ # This method should be overridden by client/server implementations.
426
+ # @yields {|stream| ...} Optional block to configure the created stream.
427
+ # @returns [Stream] The created push promise stream.
369
428
  def create_push_promise_stream(&block)
370
429
  create_stream(&block)
371
430
  end
@@ -397,10 +456,16 @@ module Protocol
397
456
  end
398
457
  end
399
458
 
459
+ # Receive and process a PUSH_PROMISE frame.
460
+ # @parameter frame [PushPromiseFrame] The push promise frame.
461
+ # @raises [ProtocolError] Always raises as push promises are not supported.
400
462
  def receive_push_promise(frame)
401
463
  raise ProtocolError, "Unable to receive push promise!"
402
464
  end
403
465
 
466
+ # Receive and process a PRIORITY_UPDATE frame.
467
+ # @parameter frame [PriorityUpdateFrame] The priority update frame.
468
+ # @raises [ProtocolError] If the stream ID is invalid.
404
469
  def receive_priority_update(frame)
405
470
  if frame.stream_id != 0
406
471
  raise ProtocolError, "Invalid stream id: #{frame.stream_id}"
@@ -414,14 +479,25 @@ module Protocol
414
479
  end
415
480
  end
416
481
 
482
+ # Check if the given stream ID represents a client-initiated stream.
483
+ # Client streams always have odd numbered IDs.
484
+ # @parameter id [Integer] The stream ID to check.
485
+ # @returns [Boolean] True if the stream ID is client-initiated.
417
486
  def client_stream_id?(id)
418
487
  id.odd?
419
488
  end
420
489
 
490
+ # Check if the given stream ID represents a server-initiated stream.
491
+ # Server streams always have even numbered IDs.
492
+ # @parameter id [Integer] The stream ID to check.
493
+ # @returns [Boolean] True if the stream ID is server-initiated.
421
494
  def server_stream_id?(id)
422
495
  id.even?
423
496
  end
424
497
 
498
+ # Check if the given stream ID represents an idle stream.
499
+ # @parameter id [Integer] The stream ID to check.
500
+ # @returns [Boolean] True if the stream ID is idle (not yet used).
425
501
  def idle_stream_id?(id)
426
502
  if id.even?
427
503
  # Server-initiated streams are even.
@@ -450,6 +526,9 @@ module Protocol
450
526
  end
451
527
  end
452
528
 
529
+ # Receive and process a RST_STREAM frame.
530
+ # @parameter frame [ResetStreamFrame] The reset stream frame.
531
+ # @raises [ProtocolError] If the frame is invalid for connection context.
453
532
  def receive_reset_stream(frame)
454
533
  if frame.connection?
455
534
  raise ProtocolError, "Cannot reset connection!"
@@ -475,6 +554,8 @@ module Protocol
475
554
  end
476
555
  end
477
556
 
557
+ # Receive and process a WINDOW_UPDATE frame.
558
+ # @parameter frame [WindowUpdateFrame] The window update frame.
478
559
  def receive_window_update(frame)
479
560
  if frame.connection?
480
561
  super
@@ -494,10 +575,15 @@ module Protocol
494
575
  end
495
576
  end
496
577
 
578
+ # Receive and process a CONTINUATION frame.
579
+ # @parameter frame [ContinuationFrame] The continuation frame.
580
+ # @raises [ProtocolError] Always raises as unexpected continuation frames are not supported.
497
581
  def receive_continuation(frame)
498
582
  raise ProtocolError, "Received unexpected continuation: #{frame.class}"
499
583
  end
500
584
 
585
+ # Receive and process a generic frame (default handler).
586
+ # @parameter frame [Frame] The frame to receive.
501
587
  def receive_frame(frame)
502
588
  # ignore.
503
589
  end
@@ -1,31 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2024, by Samuel Williams.
4
+ # Copyright, 2019-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "frame"
7
7
 
8
8
  module Protocol
9
9
  module HTTP2
10
+ # Module for frames that can be continued with CONTINUATION frames.
10
11
  module Continued
12
+ # @constant [Integer] The maximum number of continuation frames to read to prevent resource exhaustion.
13
+ LIMIT = 8
14
+
15
+ # Initialize a continuable frame.
16
+ # @parameter arguments [Array] Arguments passed to parent constructor.
11
17
  def initialize(*)
12
18
  super
13
19
 
14
20
  @continuation = nil
15
21
  end
16
22
 
23
+ # Check if this frame has continuation frames.
24
+ # @returns [Boolean] True if there are continuation frames.
17
25
  def continued?
18
26
  !!@continuation
19
27
  end
20
28
 
29
+ # Check if this is the last header block fragment.
30
+ # @returns [Boolean] True if the END_HEADERS flag is set.
21
31
  def end_headers?
22
32
  flag_set?(END_HEADERS)
23
33
  end
24
34
 
25
- def read(stream, maximum_frame_size)
26
- super
35
+ # Read the frame and any continuation frames from the stream.
36
+ #
37
+ # There is an upper limit to the number of continuation frames that can be read to prevent resource exhaustion. If the limit is 0, only one frame will be read (the initial frame). Otherwise, the limit decrements with each continuation frame read.
38
+ #
39
+ # @parameter stream [IO] The stream to read from.
40
+ # @parameter maximum_frame_size [Integer] Maximum allowed frame size.
41
+ # @parameter limit [Integer] The maximum number of continuation frames to read.
42
+ def read(stream, maximum_frame_size, limit = LIMIT)
43
+ super(stream, maximum_frame_size)
27
44
 
28
45
  unless end_headers?
46
+ if limit.zero?
47
+ raise ProtocolError, "Too many continuation frames!"
48
+ end
49
+
29
50
  continuation = ContinuationFrame.new
30
51
  continuation.read_header(stream)
31
52
 
@@ -38,12 +59,14 @@ module Protocol
38
59
  raise ProtocolError, "Invalid stream id: #{continuation.stream_id} for continuation of stream id: #{@stream_id}!"
39
60
  end
40
61
 
41
- continuation.read(stream, maximum_frame_size)
62
+ continuation.read(stream, maximum_frame_size, limit - 1)
42
63
 
43
64
  @continuation = continuation
44
65
  end
45
66
  end
46
67
 
68
+ # Write the frame and any continuation frames to the stream.
69
+ # @parameter stream [IO] The stream to write to.
47
70
  def write(stream)
48
71
  super
49
72
 
@@ -54,6 +77,9 @@ module Protocol
54
77
 
55
78
  attr_accessor :continuation
56
79
 
80
+ # Pack data into this frame, creating continuation frames if needed.
81
+ # @parameter data [String] The data to pack.
82
+ # @parameter options [Hash] Options including maximum_size.
57
83
  def pack(data, **options)
58
84
  maximum_size = options[:maximum_size]
59
85
 
@@ -75,6 +101,8 @@ module Protocol
75
101
  end
76
102
  end
77
103
 
104
+ # Unpack data from this frame and any continuation frames.
105
+ # @returns [String] The complete unpacked data.
78
106
  def unpack
79
107
  if @continuation.nil?
80
108
  super
@@ -95,11 +123,21 @@ module Protocol
95
123
 
96
124
  TYPE = 0x9
97
125
 
126
+ # Read the frame and any continuation frames from the stream.
127
+ # @parameter stream [IO] The stream to read from.
128
+ # @parameter maximum_frame_size [Integer] Maximum allowed frame size.
129
+ # @parameter limit [Integer] The maximum number of continuation frames to read.
130
+ def read(stream, maximum_frame_size, limit = 8)
131
+ super
132
+ end
133
+
98
134
  # This is only invoked if the continuation is received out of the normal flow.
99
135
  def apply(connection)
100
136
  connection.receive_continuation(self)
101
137
  end
102
138
 
139
+ # Get a string representation of the continuation frame.
140
+ # @returns [String] Human-readable frame information.
103
141
  def inspect
104
142
  "\#<#{self.class} stream_id=#{@stream_id} flags=#{@flags} length=#{@length || 0}b>"
105
143
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2024, by Samuel Williams.
4
+ # Copyright, 2019-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "frame"
7
7
  require_relative "padded"
@@ -25,10 +25,16 @@ module Protocol
25
25
 
26
26
  TYPE = 0x0
27
27
 
28
+ # Check if this frame marks the end of the stream.
29
+ # @returns [Boolean] True if the END_STREAM flag is set.
28
30
  def end_stream?
29
31
  flag_set?(END_STREAM)
30
32
  end
31
33
 
34
+ # Pack data into the frame, handling empty data as stream end.
35
+ # @parameter data [String | Nil] The data to pack into the frame.
36
+ # @parameter arguments [Array] Additional arguments passed to super.
37
+ # @parameter options [Hash] Additional options passed to super.
32
38
  def pack(data, *arguments, **options)
33
39
  if data
34
40
  super
@@ -38,10 +44,14 @@ module Protocol
38
44
  end
39
45
  end
40
46
 
47
+ # Apply this DATA frame to a connection for processing.
48
+ # @parameter connection [Connection] The connection to apply the frame to.
41
49
  def apply(connection)
42
50
  connection.receive_data(self)
43
51
  end
44
52
 
53
+ # Provide a readable representation of the frame for debugging.
54
+ # @returns [String] A formatted string representation of the frame.
45
55
  def inspect
46
56
  "\#<#{self.class} stream_id=#{@stream_id} flags=#{@flags} #{@length || 0}b>"
47
57
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2024, by Samuel Williams.
4
+ # Copyright, 2019-2025, by Samuel Williams.
5
5
 
6
6
  require "protocol/http/error"
7
7
 
@@ -57,11 +57,14 @@ module Protocol
57
57
  # connection must be aborted.
58
58
  class HandshakeError < Error
59
59
  end
60
-
60
+
61
61
  # Raised by stream or connection handlers, results in GOAWAY frame
62
62
  # which signals termination of the current connection. You *cannot*
63
63
  # recover from this exception, or any exceptions subclassed from it.
64
64
  class ProtocolError < Error
65
+ # Initialize a protocol error with message and error code.
66
+ # @parameter message [String] The error message.
67
+ # @parameter code [Integer] The HTTP/2 error code.
65
68
  def initialize(message, code = PROTOCOL_ERROR)
66
69
  super(message)
67
70
 
@@ -71,26 +74,36 @@ module Protocol
71
74
  attr :code
72
75
  end
73
76
 
77
+ # Represents an error specific to stream operations.
74
78
  class StreamError < ProtocolError
75
79
  end
76
80
 
81
+ # Represents an error for operations on closed streams.
77
82
  class StreamClosed < StreamError
83
+ # Initialize a stream closed error.
84
+ # @parameter message [String] The error message.
78
85
  def initialize(message)
79
86
  super message, STREAM_CLOSED
80
87
  end
81
88
  end
82
89
 
90
+ # Represents a GOAWAY-related protocol error.
83
91
  class GoawayError < ProtocolError
84
92
  end
85
93
 
86
94
  # When the frame payload does not match expectations.
87
95
  class FrameSizeError < ProtocolError
96
+ # Initialize a frame size error.
97
+ # @parameter message [String] The error message.
88
98
  def initialize(message)
89
99
  super message, FRAME_SIZE_ERROR
90
100
  end
91
101
  end
92
102
 
103
+ # Represents a header processing error.
93
104
  class HeaderError < StreamClosed
105
+ # Initialize a header error.
106
+ # @parameter message [String] The error message.
94
107
  def initialize(message)
95
108
  super(message)
96
109
  end
@@ -98,6 +111,8 @@ module Protocol
98
111
 
99
112
  # Raised on invalid flow control frame or command.
100
113
  class FlowControlError < ProtocolError
114
+ # Initialize a flow control error.
115
+ # @parameter message [String] The error message.
101
116
  def initialize(message)
102
117
  super message, FLOW_CONTROL_ERROR
103
118
  end