raptor 0.20.2 → 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.
@@ -3,12 +3,14 @@
3
3
  module Raptor
4
4
  # Handles HTTP/2 request processing and Rack application integration.
5
5
  class Http2
6
- # Serialises concurrent frame writes on a single HTTP/2 connection so
7
- # exactly one thread is writing at any moment.
6
+ RESPONSE_TRAILERS: ::String
7
+
8
+ # Queues concurrent frame writes while the reactor remains the sole
9
+ # owner of the connection socket.
8
10
  class Writer
9
- IDLE: ::Symbol
11
+ @reactor: Reactor?
10
12
 
11
- @state: Atom
13
+ @connection_id: Integer?
12
14
 
13
15
  @write_timeout: Integer
14
16
 
@@ -20,27 +22,48 @@ module Raptor
20
22
  # @rbs (write_timeout: Integer) -> void
21
23
  def initialize: (write_timeout: Integer) -> void
22
24
 
23
- # Writes frames to the socket, coordinating with concurrent writers
24
- # so that exactly one thread is actively writing at any time.
25
+ # Routes subsequent frame writes through the connection's reactor.
26
+ #
27
+ # @param reactor [Reactor] the reactor that owns the connection
28
+ # @param connection_id [Integer] unique connection identifier
29
+ # @return [void]
30
+ #
31
+ # @rbs (Reactor reactor, Integer connection_id) -> void
32
+ def attach: (Reactor reactor, Integer connection_id) -> void
33
+
34
+ # Queues frames for the reactor to write. Before attachment, writes
35
+ # directly so the writer remains usable during connection setup.
25
36
  #
26
37
  # @param socket [OpenSSL::SSL::SSLSocket] the connection socket
27
- # @param frames [Array<String>] frame bytes to write in order
38
+ # @param frames [Array<String>, nil] frame bytes to write in order
28
39
  # @return [void]
29
40
  #
30
- # @rbs (OpenSSL::SSL::SSLSocket socket, Array[String] frames) -> void
31
- def write_frames: (OpenSSL::SSL::SSLSocket socket, Array[String] frames) -> void
41
+ # @rbs (OpenSSL::SSL::SSLSocket socket, Array[String]? frames) -> void
42
+ def write_frames: (OpenSSL::SSL::SSLSocket socket, Array[String]? frames) -> void
43
+
44
+ # Attaches a response body that outlives its application thread.
45
+ #
46
+ # @rbs (Integer stream_id, DetachedBody body, ^(Symbol) -> void finished) -> bool
47
+ def attach_body: (Integer stream_id, DetachedBody body, ^(Symbol) -> void finished) -> bool
32
48
  end
33
49
 
34
- # Tracks the peer's connection-level and per-stream receive windows so
35
- # outbound `DATA` frames respect RFC 7540 section 5.2.
50
+ class StreamClosedError < StandardError
51
+ # @rbs () -> String
52
+ def message: () -> String
53
+ end
54
+
55
+ # Coordinates outbound flow control and stream cancellation without
56
+ # polling or mutexes.
36
57
  class FlowControl
37
- ACQUIRE_POLL_INTERVAL: ::Float
58
+ @available: AtomicConditionVariable
38
59
 
39
- @connection_window: Atom
60
+ @cancelled_streams: Atom
61
+
62
+ @initial_stream_window: Atom
40
63
 
41
64
  @stream_windows: Atom
42
65
 
43
- @initial_stream_window: Atom
66
+ @connection_window: Atom
44
67
 
45
68
  # Creates a new FlowControl with the spec-default windows.
46
69
  #
@@ -49,17 +72,25 @@ module Raptor
49
72
  # @rbs () -> void
50
73
  def initialize: () -> void
51
74
 
52
- # Reserves outbound capacity on the given stream, polling until at
75
+ # Reserves outbound capacity on the given stream, waiting until at
53
76
  # least one byte is available on both the connection and stream
54
- # windows. The returned size is capped at `MAX_FRAME_SIZE`.
77
+ # windows. Raises when the stream or connection closes.
55
78
  #
56
79
  # @param stream_id [Integer] the HTTP/2 stream identifier
57
80
  # @param max_bytes [Integer] the largest size the caller would like to send
58
- # @param end_stream [Boolean] true when this is the final frame on the stream
59
81
  # @return [Integer] the number of bytes the caller may now send
60
82
  #
61
- # @rbs (Integer stream_id, Integer max_bytes, ?end_stream: bool) -> Integer
62
- def acquire: (Integer stream_id, Integer max_bytes, ?end_stream: bool) -> Integer
83
+ # @rbs (Integer stream_id, Integer max_bytes) -> Integer
84
+ def acquire: (Integer stream_id, Integer max_bytes) -> Integer
85
+
86
+ # Reserves outbound capacity without waiting for a window update.
87
+ #
88
+ # @param stream_id [Integer] the HTTP/2 stream identifier
89
+ # @param max_bytes [Integer] the largest size the caller would like to send
90
+ # @return [Integer] available bytes, or 0 when flow control is exhausted
91
+ #
92
+ # @rbs (Integer stream_id, Integer max_bytes) -> Integer
93
+ def try_acquire: (Integer stream_id, Integer max_bytes) -> Integer
63
94
 
64
95
  # Increments the connection-level send window by `increment` bytes.
65
96
  #
@@ -87,6 +118,30 @@ module Raptor
87
118
  # @rbs (Integer new_size) -> void
88
119
  def set_initial_stream_window: (Integer new_size) -> void
89
120
 
121
+ # Raises when the given stream can no longer write a response.
122
+ #
123
+ # @param stream_id [Integer] the HTTP/2 stream identifier
124
+ # @return [void]
125
+ # @raise [StreamClosedError] if the stream or connection has closed
126
+ #
127
+ # @rbs (Integer stream_id) -> void
128
+ def check: (Integer stream_id) -> void
129
+
130
+ # Cancels pending and future writes for the given stream.
131
+ #
132
+ # @param stream_id [Integer] the HTTP/2 stream identifier
133
+ # @return [void]
134
+ #
135
+ # @rbs (Integer stream_id) -> void
136
+ def cancel_stream: (Integer stream_id) -> void
137
+
138
+ # Cancels pending and future writes for the connection.
139
+ #
140
+ # @return [void]
141
+ #
142
+ # @rbs () -> void
143
+ def close: () -> void
144
+
90
145
  # Discards any per-stream tracking for the given stream.
91
146
  #
92
147
  # @param stream_id [Integer] the HTTP/2 stream identifier
@@ -108,6 +163,100 @@ module Raptor
108
163
  def reserve_connection: (Integer capped) -> Integer
109
164
  end
110
165
 
166
+ # Adapts one HTTP/2 response stream to Rack's streaming body interface.
167
+ class ResponseStream
168
+ @closed: bool
169
+
170
+ @bytes_written: Integer
171
+
172
+ @parser: Http2Parser
173
+
174
+ @trailers: Hash[String, String | Array[String]]
175
+
176
+ @stream_id: Integer
177
+
178
+ @flow_control: FlowControl
179
+
180
+ @writer: Writer
181
+
182
+ @socket: OpenSSL::SSL::SSLSocket
183
+
184
+ # Returns the number of response body bytes written to the stream.
185
+ #
186
+ # @return [Integer]
187
+ attr_reader bytes_written: Integer
188
+
189
+ # Creates a response stream for a Rack streaming body.
190
+ #
191
+ # @param socket [OpenSSL::SSL::SSLSocket] the connection socket
192
+ # @param writer [Writer] frame writer for the connection
193
+ # @param flow_control [FlowControl] outbound flow controller
194
+ # @param stream_id [Integer] the HTTP/2 stream identifier
195
+ # @param trailers [Hash] trailing response headers populated by the Rack application
196
+ # @return [void]
197
+ #
198
+ # @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Hash[String, String | Array[String]] trailers) -> void
199
+ def initialize: (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Hash[String, String | Array[String]] trailers) -> void
200
+
201
+ # Streaming responses are write-only.
202
+ #
203
+ # @raise [IOError]
204
+ #
205
+ # @rbs (*untyped) -> bot
206
+ def read: (*untyped) -> bot
207
+
208
+ # Writes response body bytes to the stream.
209
+ #
210
+ # @param chunk [String] response body bytes
211
+ # @return [Integer] number of bytes written
212
+ #
213
+ # @rbs (String chunk) -> Integer
214
+ def write: (String chunk) -> Integer
215
+
216
+ # Writes response body bytes and returns the stream.
217
+ #
218
+ # @param chunk [String] response body bytes
219
+ # @return [ResponseStream]
220
+ #
221
+ # @rbs (String chunk) -> ResponseStream
222
+ def <<: (String chunk) -> ResponseStream
223
+
224
+ # Flushes the stream. Frame writes are already immediate.
225
+ #
226
+ # @return [ResponseStream]
227
+ #
228
+ # @rbs () -> ResponseStream
229
+ def flush: () -> ResponseStream
230
+
231
+ # Finishes the stream.
232
+ #
233
+ # @return [void]
234
+ #
235
+ # @rbs () -> void
236
+ def close: () -> void
237
+
238
+ # Closes the unsupported read side of the stream.
239
+ #
240
+ # @raise [IOError]
241
+ #
242
+ # @rbs () -> bot
243
+ def close_read: () -> bot
244
+
245
+ # Finishes the writable side of the stream.
246
+ #
247
+ # @return [void]
248
+ #
249
+ # @rbs () -> void
250
+ def close_write: () -> void
251
+
252
+ # Returns whether the stream has finished.
253
+ #
254
+ # @return [Boolean]
255
+ #
256
+ # @rbs () -> bool
257
+ def closed?: () -> bool
258
+ end
259
+
111
260
  EAGER_READ_TIMEOUT: ::Float
112
261
 
113
262
  EAGER_READ_BUFFER_SIZE: untyped
@@ -126,6 +275,10 @@ module Raptor
126
275
 
127
276
  ERROR_PROTOCOL_ERROR: ::Integer
128
277
 
278
+ ERROR_INTERNAL_ERROR: ::Integer
279
+
280
+ ERROR_REFUSED_STREAM: ::Integer
281
+
129
282
  DEFAULT_WINDOW_SIZE: ::Integer
130
283
 
131
284
  MAX_FRAME_SIZE: ::Integer
@@ -148,6 +301,8 @@ module Raptor
148
301
  # @rbs (Array[[String, String]] headers) -> bool
149
302
  def self.invalid_pseudo_headers?: (Array[[ String, String ]] headers) -> bool
150
303
 
304
+ @running: AtomicBoolean
305
+
151
306
  @initial_settings_frame: String
152
307
 
153
308
  @on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
@@ -194,6 +349,15 @@ module Raptor
194
349
  # @rbs () -> Writer
195
350
  def create_writer: () -> Writer
196
351
 
352
+ # Stops new stream dispatch and asks clients to finish streams already
353
+ # handed to the application.
354
+ #
355
+ # @param reactor [Reactor] the reactor managing HTTP/2 connections
356
+ # @return [void]
357
+ #
358
+ # @rbs (Reactor reactor) -> void
359
+ def shutdown: (Reactor reactor) -> void
360
+
197
361
  # Returns a Ractor-safe proc that parses HTTP/2 frames from the
198
362
  # state hash's buffered bytes.
199
363
  #
@@ -212,6 +376,22 @@ module Raptor
212
376
  # @rbs (Hash[Symbol, untyped] data) -> Hash[Symbol, untyped]
213
377
  def self.process_frames: (Hash[Symbol, untyped] data) -> Hash[Symbol, untyped]
214
378
 
379
+ # Applies one complete request header block to its stream, resetting
380
+ # malformed initial headers or trailers and completing valid trailers.
381
+ #
382
+ # @param streams [Hash] current open-stream map
383
+ # @param completed_requests [Array<Hash>] accumulator of completed stream requests
384
+ # @param outgoing_frames [Array<String>] accumulator of protocol response frames
385
+ # @param parser [Http2Parser] frame parser and encoder
386
+ # @param stream_id [Integer] the stream identifier
387
+ # @param decoded_headers [Array<Array(String, String)>] decoded header pairs
388
+ # @param end_stream [Boolean] whether the source frame had END_STREAM set
389
+ # @param trailers [Boolean] whether this is a trailing header block
390
+ # @return [Array(Hash, Array<Hash>)] updated streams and completed requests
391
+ #
392
+ # @rbs (Hash[Integer, Hash[Symbol, untyped]] streams, Array[Hash[Symbol, untyped]] completed_requests, Array[String] outgoing_frames, Http2Parser parser, Integer stream_id, Array[[String, String]] decoded_headers, bool end_stream, bool trailers) -> [Hash[Integer, Hash[Symbol, untyped]], Array[Hash[Symbol, untyped]]]
393
+ def self.complete_header_block: (Hash[Integer, Hash[Symbol, untyped]] streams, Array[Hash[Symbol, untyped]] completed_requests, Array[String] outgoing_frames, Http2Parser parser, Integer stream_id, Array[[ String, String ]] decoded_headers, bool end_stream, bool trailers) -> [ Hash[Integer, Hash[Symbol, untyped]], Array[Hash[Symbol, untyped]] ]
394
+
215
395
  # Merges a decoded header block into the stream's accumulated state,
216
396
  # promoting the stream to `completed_requests` when END_STREAM is set.
217
397
  #
@@ -233,7 +413,9 @@ module Raptor
233
413
  # @param streams [Hash] updated stream states
234
414
  # @param outgoing_frames [Array<String>] frames to write to the socket
235
415
  # @param completed_requests [Array<Hash>] fully received stream requests
416
+ # @param cancelled_streams [Array<Integer>] streams reset by the peer
236
417
  # @param window_updates [Array<Array(Integer, Integer)>] inbound WINDOW_UPDATE pairs as [stream_id, increment]
418
+ # @param ping_acknowledgements [Array<String>] acknowledged PING payloads
237
419
  # @param peer_initial_window_size [Integer, nil] new SETTINGS_INITIAL_WINDOW_SIZE announced by the peer
238
420
  # @param connection_window [Integer] current connection flow control window
239
421
  # @param preface_received [Boolean] whether the connection preface has been received
@@ -242,8 +424,8 @@ module Raptor
242
424
  # @param close_connection [Boolean] whether the connection should be closed after writing outgoing frames
243
425
  # @return [Hash] frozen result hash
244
426
  #
245
- # @rbs (Hash[Symbol, untyped] data, String buffer, Array[untyped] hpack_table, Hash[Integer, Hash[Symbol, untyped]] streams, Array[String] outgoing_frames, Array[Hash[Symbol, untyped]] completed_requests, Array[[Integer, Integer]] window_updates, Integer? peer_initial_window_size, Integer connection_window, bool preface_received, Integer last_client_stream_id, Hash[Symbol, untyped]? pending_headers, bool close_connection) -> Hash[Symbol, untyped]
246
- def self.build_result: (Hash[Symbol, untyped] data, String buffer, Array[untyped] hpack_table, Hash[Integer, Hash[Symbol, untyped]] streams, Array[String] outgoing_frames, Array[Hash[Symbol, untyped]] completed_requests, Array[[ Integer, Integer ]] window_updates, Integer? peer_initial_window_size, Integer connection_window, bool preface_received, Integer last_client_stream_id, Hash[Symbol, untyped]? pending_headers, bool close_connection) -> Hash[Symbol, untyped]
427
+ # @rbs (Hash[Symbol, untyped] data, String buffer, Array[untyped] hpack_table, Hash[Integer, Hash[Symbol, untyped]] streams, Array[String] outgoing_frames, Array[Hash[Symbol, untyped]] completed_requests, Array[Integer] cancelled_streams, Array[[Integer, Integer]] window_updates, Array[String] ping_acknowledgements, Integer? peer_initial_window_size, Integer connection_window, bool preface_received, Integer last_client_stream_id, Hash[Symbol, untyped]? pending_headers, bool close_connection) -> Hash[Symbol, untyped]
428
+ def self.build_result: (Hash[Symbol, untyped] data, String buffer, Array[untyped] hpack_table, Hash[Integer, Hash[Symbol, untyped]] streams, Array[String] outgoing_frames, Array[Hash[Symbol, untyped]] completed_requests, Array[Integer] cancelled_streams, Array[[ Integer, Integer ]] window_updates, Array[String] ping_acknowledgements, Integer? peer_initial_window_size, Integer connection_window, bool preface_received, Integer last_client_stream_id, Hash[Symbol, untyped]? pending_headers, bool close_connection) -> Hash[Symbol, untyped]
247
429
 
248
430
  # Sends the server SETTINGS frame on a freshly negotiated HTTP/2
249
431
  # connection, then eagerly reads and parses the first client frame
@@ -315,6 +497,11 @@ module Raptor
315
497
  # @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Array[[String, String]] headers, String body, remote_addr: String) -> void
316
498
  def perform_stream_request: (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Array[[ String, String ]] headers, String body, remote_addr: String) -> void
317
499
 
500
+ # Starts a detached HTTP/2 response after writing its response headers.
501
+ #
502
+ # @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, DetachedBody body, ^(Symbol) -> void finished) { () -> void } -> bool
503
+ def write_http2_detached_response: (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, DetachedBody body, ^(Symbol) -> void finished) { () -> void } -> bool
504
+
318
505
  # Writes a Rack response as HTTP/2 frames to the socket, partitioning
319
506
  # DATA frames through `flow_control` to fit within the peer's windows.
320
507
  #
@@ -325,10 +512,35 @@ module Raptor
325
512
  # @param status [Integer] HTTP status code
326
513
  # @param headers [Hash] response headers from the Rack application
327
514
  # @param body [Object] response body responding to each
515
+ # @param trailers [Hash] trailing response headers populated by the Rack application
516
+ # @param request_method [String] request method used to suppress HEAD response bodies
328
517
  # @return [String] the response body size in bytes
329
518
  #
330
- # @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> String
331
- def write_http2_response: (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> String
519
+ # @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body, trailers: Hash[String, String | Array[String]], request_method: String) ?{ () -> void } -> String
520
+ def write_http2_response: (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body, trailers: Hash[String, String | Array[String]], request_method: String) ?{ () -> void } -> String
521
+
522
+ # Sends an HTTP 103 Early Hints response.
523
+ #
524
+ # @param socket [OpenSSL::SSL::SSLSocket] the connection socket
525
+ # @param writer [Writer] frame writer for the connection
526
+ # @param flow_control [FlowControl] per-connection outbound flow controller
527
+ # @param stream_id [Integer] the HTTP/2 stream identifier
528
+ # @param hints [Hash] response headers to send as early hints
529
+ # @return [void]
530
+ #
531
+ # @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Hash[String, String | Array[String]] hints) -> void
532
+ def send_early_hints: (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Hash[String, String | Array[String]] hints) -> void
533
+
534
+ # Resets one HTTP/2 stream with the given error code.
535
+ #
536
+ # @param socket [OpenSSL::SSL::SSLSocket] the connection socket
537
+ # @param writer [Writer] frame writer for the connection
538
+ # @param stream_id [Integer] the HTTP/2 stream identifier
539
+ # @param error_code [Integer] the HTTP/2 error code
540
+ # @return [void]
541
+ #
542
+ # @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id, Integer error_code) -> void
543
+ def write_http2_reset_stream: (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id, Integer error_code) -> void
332
544
 
333
545
  # Writes a 500 error response as HTTP/2 frames.
334
546
  #
@@ -356,11 +568,14 @@ module Raptor
356
568
  #
357
569
  # @param headers [Array<Array(String, String)>] HTTP/2 header pairs
358
570
  # @param body [String] the request body
571
+ # @param socket [OpenSSL::SSL::SSLSocket] the connection socket
572
+ # @param writer [Writer] frame writer for the connection
573
+ # @param stream_id [Integer] the HTTP/2 stream identifier
359
574
  # @param remote_addr [String] the client IP address
360
575
  # @return [Hash] fully populated Rack environment hash
361
576
  #
362
- # @rbs (Array[[String, String]] headers, String body, remote_addr: String) -> Hash[String, untyped]
363
- def build_rack_env: (Array[[ String, String ]] headers, String body, remote_addr: String) -> Hash[String, untyped]
577
+ # @rbs (Array[[String, String]] headers, String body, OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, remote_addr: String) -> Hash[String, untyped]
578
+ def build_rack_env: (Array[[ String, String ]] headers, String body, OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, remote_addr: String) -> Hash[String, untyped]
364
579
 
365
580
  # Populates SERVER_NAME and SERVER_PORT from the HTTP_HOST header.
366
581
  #