raptor 0.3.0 → 0.5.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.
@@ -33,6 +33,85 @@ module Raptor
33
33
  def write_frames: (OpenSSL::SSL::SSLSocket socket, Array[String] frames) -> void
34
34
  end
35
35
 
36
+ # Per-connection outbound flow-control accounting.
37
+ #
38
+ # Tracks the peer's connection-level and per-stream receive windows so
39
+ # outbound DATA frames respect RFC 7540 §5.2. Threads dispatching stream
40
+ # responses call `acquire` to reserve send capacity; threads applying
41
+ # inbound WINDOW_UPDATE or SETTINGS frames call the mutating methods to
42
+ # replenish it. The connection window and per-stream windows live in
43
+ # separate `Atom`s so the common fast path skips per-stream tracking.
44
+ class FlowControl
45
+ ACQUIRE_POLL_INTERVAL: ::Float
46
+
47
+ @connection_window: Atom
48
+
49
+ @stream_windows: Atom
50
+
51
+ @initial_stream_window: Atom
52
+
53
+ # Creates a new FlowControl with the spec-default windows.
54
+ #
55
+ # @rbs () -> void
56
+ def initialize: () -> void
57
+
58
+ # Reserves outbound capacity on the given stream, polling until at
59
+ # least one byte is available on both the connection and stream
60
+ # windows. The returned size is capped at `MAX_FRAME_SIZE`.
61
+ #
62
+ # When `end_stream` is true, `max_bytes` fits within the peer's
63
+ # initial stream window, and no per-stream override has been
64
+ # recorded, only the connection window is consulted. The stream
65
+ # closes on this frame, so its remaining send window will not be
66
+ # consulted again and need not be tracked.
67
+ #
68
+ # @param stream_id [Integer] the HTTP/2 stream identifier
69
+ # @param max_bytes [Integer] the largest size the caller would like to send
70
+ # @param end_stream [Boolean] true when this is the final frame on the stream
71
+ # @return [Integer] the number of bytes the caller may now send
72
+ #
73
+ # @rbs (Integer stream_id, Integer max_bytes, ?end_stream: bool) -> Integer
74
+ def acquire: (Integer stream_id, Integer max_bytes, ?end_stream: bool) -> Integer
75
+
76
+ # Increments the connection-level send window. Called when the peer
77
+ # sends a WINDOW_UPDATE on stream 0.
78
+ #
79
+ # @param increment [Integer] the byte count to add
80
+ # @return [void]
81
+ #
82
+ # @rbs (Integer increment) -> void
83
+ def add_connection_window: (Integer increment) -> void
84
+
85
+ # Increments the per-stream send window. Called when the peer sends
86
+ # a WINDOW_UPDATE on a specific stream.
87
+ #
88
+ # @param stream_id [Integer] the HTTP/2 stream identifier
89
+ # @param increment [Integer] the byte count to add
90
+ # @return [void]
91
+ #
92
+ # @rbs (Integer stream_id, Integer increment) -> void
93
+ def add_stream_window: (Integer stream_id, Integer increment) -> void
94
+
95
+ # Updates the peer's `SETTINGS_INITIAL_WINDOW_SIZE`. Shifts every
96
+ # existing stream window by the delta as required by RFC 7540 §6.9.2.
97
+ #
98
+ # @param new_size [Integer] the peer's new initial window size
99
+ # @return [void]
100
+ #
101
+ # @rbs (Integer new_size) -> void
102
+ def set_initial_stream_window: (Integer new_size) -> void
103
+
104
+ # Discards any per-stream tracking for the given stream. Called
105
+ # after a stream closes so `@stream_windows` does not grow without
106
+ # bound across the lifetime of a connection.
107
+ #
108
+ # @param stream_id [Integer] the HTTP/2 stream identifier
109
+ # @return [void]
110
+ #
111
+ # @rbs (Integer stream_id) -> void
112
+ def discard_stream: (Integer stream_id) -> void
113
+ end
114
+
36
115
  FLAG_END_STREAM: ::Integer
37
116
 
38
117
  FLAG_END_HEADERS: ::Integer
@@ -41,6 +120,14 @@ module Raptor
41
120
 
42
121
  FLAG_PRIORITY: ::Integer
43
122
 
123
+ ERROR_NO_ERROR: ::Integer
124
+
125
+ ERROR_PROTOCOL_ERROR: ::Integer
126
+
127
+ DEFAULT_WINDOW_SIZE: ::Integer
128
+
129
+ MAX_FRAME_SIZE: ::Integer
130
+
44
131
  SERVER_PROTOCOL: ::String
45
132
 
46
133
  RACK_HEADER_PREFIX: ::String
@@ -82,6 +169,19 @@ module Raptor
82
169
  # @rbs (Hash[Symbol, untyped] data) -> Hash[Symbol, untyped]
83
170
  def self.process_frames: (Hash[Symbol, untyped] data) -> Hash[Symbol, untyped]
84
171
 
172
+ # Merges a decoded header block into the stream's accumulated state,
173
+ # promoting the stream to `completed_requests` when END_STREAM is set.
174
+ #
175
+ # @param streams [Hash] current open-stream map
176
+ # @param completed_requests [Array<Hash>] accumulator of completed stream requests
177
+ # @param stream_id [Integer] the stream identifier
178
+ # @param decoded_headers [Array<Array(String, String)>] decoded header pairs
179
+ # @param end_stream [Boolean] whether the source frame had END_STREAM set
180
+ # @return [Array(Hash, Array<Hash>)] updated streams and completed_requests
181
+ #
182
+ # @rbs (Hash[Integer, Hash[Symbol, untyped]] streams, Array[Hash[Symbol, untyped]] completed_requests, Integer stream_id, Array[[String, String]] decoded_headers, bool end_stream) -> [Hash[Integer, Hash[Symbol, untyped]], Array[Hash[Symbol, untyped]]]
183
+ def self.finalize_headers: (Hash[Integer, Hash[Symbol, untyped]] streams, Array[Hash[Symbol, untyped]] completed_requests, Integer stream_id, Array[[ String, String ]] decoded_headers, bool end_stream) -> [ Hash[Integer, Hash[Symbol, untyped]], Array[Hash[Symbol, untyped]] ]
184
+
85
185
  # Builds a frozen result hash from the current processing state.
86
186
  #
87
187
  # @param data [Hash] original connection state
@@ -90,12 +190,17 @@ module Raptor
90
190
  # @param streams [Hash] updated stream states
91
191
  # @param outgoing_frames [Array<String>] frames to write to the socket
92
192
  # @param completed_requests [Array<Hash>] fully received stream requests
193
+ # @param window_updates [Array<Array(Integer, Integer)>] inbound WINDOW_UPDATE pairs as [stream_id, increment]
194
+ # @param peer_initial_window_size [Integer, nil] new SETTINGS_INITIAL_WINDOW_SIZE announced by the peer
93
195
  # @param connection_window [Integer] current connection flow control window
94
196
  # @param preface_received [Boolean] whether the connection preface has been received
197
+ # @param last_client_stream_id [Integer] highest client-initiated stream ID seen
198
+ # @param pending_headers [Hash, nil] in-progress HEADERS+CONTINUATION assembly
199
+ # @param close_connection [Boolean] whether the connection should be closed after writing outgoing frames
95
200
  # @return [Hash] frozen result hash
96
201
  #
97
- # @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, Integer connection_window, bool preface_received) -> Hash[Symbol, untyped]
98
- 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, Integer connection_window, bool preface_received) -> Hash[Symbol, untyped]
202
+ # @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]
203
+ 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]
99
204
 
100
205
  # Handles a parsed HTTP/2 request from the ractor pool.
101
206
  #
@@ -112,32 +217,47 @@ module Raptor
112
217
 
113
218
  private
114
219
 
220
+ # Applies inbound flow-control updates from a parsed result to the
221
+ # connection's `FlowControl`.
222
+ #
223
+ # @param flow_control [FlowControl] the per-connection flow controller
224
+ # @param result [Hash] the parsed result from `process_frames`
225
+ # @return [void]
226
+ #
227
+ # @rbs (FlowControl flow_control, Hash[Symbol, untyped] result) -> void
228
+ def apply_flow_control_updates: (FlowControl flow_control, Hash[Symbol, untyped] result) -> void
229
+
115
230
  # Dispatches a completed stream request to the Rack app and writes
116
231
  # the response back as HTTP/2 frames.
117
232
  #
118
233
  # @param socket [OpenSSL::SSL::SSLSocket] the connection socket
119
234
  # @param writer [Writer] lock-free frame writer for the connection
235
+ # @param flow_control [FlowControl] per-connection outbound flow controller
120
236
  # @param stream_id [Integer] the HTTP/2 stream identifier
121
237
  # @param headers [Array<Array(String, String)>] request headers
122
238
  # @param body [String] request body
123
239
  # @param remote_addr [String] the client IP address
124
240
  # @return [void]
125
241
  #
126
- # @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id, Array[[String, String]] headers, String body, remote_addr: String) -> void
127
- def dispatch_stream_request: (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id, Array[[ String, String ]] headers, String body, remote_addr: String) -> void
242
+ # @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Array[[String, String]] headers, String body, remote_addr: String) -> void
243
+ def dispatch_stream_request: (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Array[[ String, String ]] headers, String body, remote_addr: String) -> void
128
244
 
129
245
  # Writes a Rack response as HTTP/2 frames to the socket.
130
246
  #
247
+ # DATA frames are partitioned through `flow_control` so each write fits
248
+ # within the peer's per-stream and connection windows.
249
+ #
131
250
  # @param socket [OpenSSL::SSL::SSLSocket] the connection socket
132
251
  # @param writer [Writer] lock-free frame writer for the connection
252
+ # @param flow_control [FlowControl] per-connection outbound flow controller
133
253
  # @param stream_id [Integer] the HTTP/2 stream identifier
134
254
  # @param status [Integer] HTTP status code
135
255
  # @param headers [Hash] response headers from the Rack application
136
256
  # @param body [Object] response body responding to each
137
257
  # @return [void]
138
258
  #
139
- # @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> void
140
- def write_http2_response: (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> void
259
+ # @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> void
260
+ 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) -> void
141
261
 
142
262
  # Writes a 500 error response as HTTP/2 frames.
143
263
  #
@@ -0,0 +1,41 @@
1
+ # Generated from lib/raptor/log.rb with RBS::Inline
2
+
3
+ module Raptor
4
+ # Shared logging helpers. Every line is prefixed with
5
+ # `[Raptor <pid>|<ractor>|<thread>]` so output is identifiable
6
+ # and traceable to its source in a mixed log stream.
7
+ module Log
8
+ # Writes an informational message to stdout.
9
+ #
10
+ # @param message [String] the message to log
11
+ # @return [void]
12
+ #
13
+ # @rbs (String message) -> void
14
+ def self.info: (String message) -> void
15
+
16
+ # Writes a warning to stderr.
17
+ #
18
+ # @param message [String] the message to log
19
+ # @return [void]
20
+ #
21
+ # @rbs (String message) -> void
22
+ def self.warn: (String message) -> void
23
+
24
+ # Logs a rescued exception to stderr. The full message (class,
25
+ # message, backtrace) is written on subsequent unprefixed lines.
26
+ #
27
+ # @param error [Exception] the rescued exception
28
+ # @return [void]
29
+ #
30
+ # @rbs (Exception error) -> void
31
+ def self.rescued_error: (Exception error) -> void
32
+
33
+ # Builds the log line prefix from the current process, ractor,
34
+ # and thread. Unnamed ractors and threads are reported as `main`.
35
+ #
36
+ # @return [String] the prefix
37
+ #
38
+ # @rbs () -> String
39
+ def self.prefix: () -> String
40
+ end
41
+ end
@@ -5,12 +5,12 @@ module Raptor
5
5
  #
6
6
  # Reactor uses NIO selectors for efficient I/O multiplexing and implements
7
7
  # client timeouts using a red-black tree for O(log n) timeout management.
8
- # It coordinates between thread pools for blocking operations and ractor
9
- # pools for CPU-intensive HTTP parsing, and provides backlog metrics
10
- # that the server uses for backpressure control to prevent overload.
8
+ # It coordinates between ractor pools for CPU-intensive HTTP parsing and
9
+ # thread pools for blocking operations, and provides backlog metrics that
10
+ # the server uses for backpressure control to prevent overload.
11
11
  #
12
12
  # @example
13
- # reactor = Reactor.new(thread_pool, ractor_pool, client_options: {
13
+ # reactor = Reactor.new(ractor_pool, thread_pool, client_options: {
14
14
  # first_data_timeout: 30,
15
15
  # chunk_data_timeout: 10
16
16
  # })
@@ -19,33 +19,33 @@ module Raptor
19
19
  # # ... later
20
20
  # reactor.shutdown
21
21
  class Reactor
22
- # Red-black tree node representing a client connection with timeout tracking.
23
- #
24
- # TimeoutClient extends RedBlackTree::Node to enable efficient timeout
25
- # management using the tree's ordering properties.
22
+ # A client connection node ordered by absolute expiry time so the
23
+ # soonest-to-expire is always at the tree's minimum.
26
24
  class TimeoutClient < RedBlackTree::Node
27
25
  # @rbs attr_accessor timeout_at: Float
28
26
  attr_accessor timeout_at: untyped
29
27
 
30
- # Returns the client data stored in this timeout node.
28
+ # Semantic alias for the inherited `data` slot.
31
29
  #
32
- # @return [Hash] the client connection state data
30
+ # @return [Hash] the client connection state
33
31
  #
34
32
  # @rbs () -> Hash[Symbol, untyped]
35
33
  def client_data: () -> Hash[Symbol, untyped]
36
34
 
37
- # Calculates remaining timeout duration from the current time.
35
+ # Returns seconds until expiry, clamped to 0 so an already-expired
36
+ # client doesn't push the next selector wait into the future.
38
37
  #
39
38
  # @param now [Float] current monotonic timestamp
40
- # @return [Float] remaining timeout in seconds, minimum 0
39
+ # @return [Float] seconds until expiry, never negative
41
40
  #
42
41
  # @rbs (Float now) -> Float
43
42
  def timeout: (Float now) -> Float
44
43
 
45
- # Compares timeout nodes by their timeout_at values for tree ordering.
44
+ # Orders nodes by `timeout_at` so the tree minimum is the next
45
+ # client to expire.
46
46
  #
47
47
  # @param other [TimeoutClient] another timeout client to compare
48
- # @return [Integer] -1, 0, or 1 for ordering
48
+ # @return [Integer] -1, 0, or 1
49
49
  #
50
50
  # @rbs (TimeoutClient other) -> Integer
51
51
  def <=>: (TimeoutClient other) -> Integer
@@ -55,6 +55,8 @@ module Raptor
55
55
 
56
56
  TIMEOUT_RESPONSE: ::String
57
57
 
58
+ @id_to_flow_control: Hash[Integer, untyped]
59
+
58
60
  @id_to_writer: Hash[Integer, untyped]
59
61
 
60
62
  @id_to_timeout: Hash[Integer, TimeoutClient]
@@ -77,16 +79,16 @@ module Raptor
77
79
 
78
80
  # Creates a new Reactor instance.
79
81
  #
80
- # @param thread_pool [AtomicThreadPool] thread pool for application processing
81
82
  # @param ractor_pool [RactorPool] ractor pool for HTTP parsing
83
+ # @param thread_pool [AtomicThreadPool] thread pool for application processing
82
84
  # @param client_options [Hash] timeout configuration options
83
85
  # @option client_options [Integer] :first_data_timeout timeout for initial data
84
86
  # @option client_options [Integer] :chunk_data_timeout timeout for subsequent chunks
85
87
  # @option client_options [Integer] :persistent_data_timeout timeout for keep-alive connections
86
88
  # @return [void]
87
89
  #
88
- # @rbs (untyped thread_pool, untyped ractor_pool, client_options: Hash[Symbol, Integer]) -> void
89
- def initialize: (untyped thread_pool, untyped ractor_pool, client_options: Hash[Symbol, Integer]) -> void
90
+ # @rbs (untyped ractor_pool, untyped thread_pool, client_options: Hash[Symbol, Integer]) -> void
91
+ def initialize: (untyped ractor_pool, untyped thread_pool, client_options: Hash[Symbol, Integer]) -> void
90
92
 
91
93
  # Starts the reactor's main event loop in a new thread.
92
94
  #
@@ -121,13 +123,12 @@ module Raptor
121
123
  # @rbs (Hash[Symbol, untyped] state) -> void
122
124
  def update_state: (Hash[Symbol, untyped] state) -> void
123
125
 
124
- # Removes a client connection from the reactor.
125
- #
126
- # Called when an HTTP request is complete and ready for application
127
- # processing. Triggers server accept re-enabling if system capacity allows.
126
+ # Drops the reactor's references to a client whose parsed request
127
+ # has been handed off to the thread pool. The socket itself is kept
128
+ # open so the worker can write the response.
128
129
  #
129
130
  # @param id [Integer] unique client identifier
130
- # @return [TCPSocket, nil] the removed socket, if found
131
+ # @return [TCPSocket, nil] the socket associated with `id`, if any
131
132
  #
132
133
  # @rbs (Integer id) -> TCPSocket?
133
134
  def remove: (Integer id) -> TCPSocket?
@@ -169,6 +170,16 @@ module Raptor
169
170
  # @rbs (Integer id) -> untyped?
170
171
  def writer_for: (Integer id) -> untyped?
171
172
 
173
+ # Returns the flow controller associated with a given connection, if one
174
+ # was supplied when the connection was added. Used by HTTP/2 stream
175
+ # dispatchers to honour the peer's flow-control windows.
176
+ #
177
+ # @param id [Integer] unique client identifier
178
+ # @return [Object, nil] the flow controller, if found
179
+ #
180
+ # @rbs (Integer id) -> untyped?
181
+ def flow_control_for: (Integer id) -> untyped?
182
+
172
183
  # Updates connection state for an HTTP/2 connection after frame processing.
173
184
  #
174
185
  # Re-registers the socket with the selector for further reads and stores
@@ -180,10 +191,18 @@ module Raptor
180
191
  # @rbs (Hash[Symbol, untyped] state) -> void
181
192
  def update_http2_state: (Hash[Symbol, untyped] state) -> void
182
193
 
183
- # Initiates reactor shutdown.
194
+ # Closes the socket for the given connection and drops all reactor state
195
+ # associated with it. Used to terminate HTTP/2 connections after sending
196
+ # a GOAWAY frame.
184
197
  #
185
- # Closes the registration queue and wakes up the selector to begin
186
- # graceful shutdown process.
198
+ # @param id [Integer] unique client identifier
199
+ # @return [void]
200
+ #
201
+ # @rbs (Integer id) -> void
202
+ def close_connection: (Integer id) -> void
203
+
204
+ # Closes the registration queue and wakes the selector so the
205
+ # event loop drains pending work and exits.
187
206
  #
188
207
  # @return [void]
189
208
  #
@@ -1,13 +1,10 @@
1
1
  # Generated from lib/raptor/request.rb with RBS::Inline
2
2
 
3
3
  module Raptor
4
- # Handles HTTP request processing and Rack application integration.
5
- #
6
- # Request manages the HTTP parsing pipeline using Ractors and coordinates
7
- # with the reactor for connection state management. It bridges between the
8
- # low-level HTTP parsing and high-level Rack application interface, handling
9
- # both incomplete requests (that need more data) and complete requests
10
- # (ready for application processing).
4
+ # Parses HTTP/1.x requests and dispatches them to the Rack
5
+ # application. Coordinates with the Ractor pool for parsing and
6
+ # with the reactor for requests that need more data before they
7
+ # can be handled.
11
8
  class Request
12
9
  BODY_BUFFER_THRESHOLD: untyped
13
10
 
@@ -21,8 +18,6 @@ module Raptor
21
18
 
22
19
  MAX_KEEPALIVE_REQUESTS: ::Integer
23
20
 
24
- HTTP_SCHEME: ::String
25
-
26
21
  HTTP_10: ::String
27
22
 
28
23
  HTTP_11: ::String
@@ -33,6 +28,8 @@ module Raptor
33
28
 
34
29
  STATUS_WITH_NO_ENTITY_BODY: untyped
35
30
 
31
+ BAD_REQUEST_RESPONSE: ::String
32
+
36
33
  INTERNAL_SERVER_ERROR_RESPONSE: ::String
37
34
 
38
35
  CONTENT_TOO_LARGE_RESPONSE: ::String
@@ -78,6 +75,19 @@ module Raptor
78
75
  # @rbs (String buffer, ?Integer? max_size) -> [String, Symbol]
79
76
  def self.decode_chunked: (String buffer, ?Integer? max_size) -> [ String, Symbol ]
80
77
 
78
+ # Writes `string` in full, retrying on partial writes. Bounded by
79
+ # `WRITE_TIMEOUT` so a slow client can't pin the writing thread.
80
+ #
81
+ # @param socket [TCPSocket] the socket to write to
82
+ # @param string [String] the data to write
83
+ # @return [void]
84
+ # @raise [WriteError] if the socket is not writable within the timeout or raises IOError
85
+ #
86
+ # @rbs (TCPSocket socket, String string) -> void
87
+ def self.socket_write: (TCPSocket socket, String string) -> void
88
+
89
+ @running: AtomicBoolean
90
+
81
91
  @on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
82
92
 
83
93
  @body_spool_threshold: Integer?
@@ -101,6 +111,14 @@ module Raptor
101
111
  # @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Integer server_port, ?client_options: Hash[Symbol, untyped], ?on_error: ^(Hash[String, untyped]?, Exception) -> void | nil) -> void
102
112
  def initialize: (^(Hash[String, untyped]) -> [ Integer, Hash[String, String | Array[String]], untyped ] app, Integer server_port, ?client_options: Hash[Symbol, untyped], ?on_error: ^(Hash[String, untyped]?, Exception) -> void | nil) -> void
103
113
 
114
+ # Signals eager keep-alive loops to stop processing further requests on
115
+ # their connections. In-flight requests complete normally.
116
+ #
117
+ # @return [void]
118
+ #
119
+ # @rbs () -> void
120
+ def shutdown: () -> void
121
+
104
122
  # Eagerly reads and parses the first request on a freshly accepted
105
123
  # connection on the server thread, dispatching directly to the thread pool
106
124
  # when complete. Falls back to the reactor when more data is needed.
@@ -225,6 +243,15 @@ module Raptor
225
243
  # @rbs (TCPSocket socket) -> void
226
244
  def reject_oversized: (TCPSocket socket) -> void
227
245
 
246
+ # Writes a 400 response and closes the socket. Used when the HTTP parser
247
+ # rejects the request line or headers.
248
+ #
249
+ # @param socket [TCPSocket] the client socket
250
+ # @return [void]
251
+ #
252
+ # @rbs (TCPSocket socket) -> void
253
+ def reject_malformed: (TCPSocket socket) -> void
254
+
228
255
  # Builds a Rack environment hash from parsed HTTP request data.
229
256
  #
230
257
  # Populates all required Rack env keys including rack.* keys, REMOTE_ADDR,
@@ -499,19 +526,6 @@ module Raptor
499
526
  # @rbs (Hash[String, untyped] env, Integer? status, Hash[String, String | Array[String]]? headers, Exception? error) -> void
500
527
  def call_response_finished: (Hash[String, untyped] env, Integer? status, Hash[String, String | Array[String]]? headers, Exception? error) -> void
501
528
 
502
- # Writes a string to the socket, retrying on partial writes and flow control blocks.
503
- #
504
- # Uses write_nonblock with a 5-second writable timeout to avoid blocking the
505
- # thread indefinitely on slow clients.
506
- #
507
- # @param socket [TCPSocket] the socket to write to
508
- # @param string [String] the data to write
509
- # @return [void]
510
- # @raise [WriteError] if the socket is not writable within the timeout or raises IOError
511
- #
512
- # @rbs (TCPSocket socket, String string) -> void
513
- def socket_write: (TCPSocket socket, String string) -> void
514
-
515
529
  # Enables TCP_CORK on the socket to batch outgoing packets into fewer segments.
516
530
  #
517
531
  # Only applies to TCP sockets. No-op on non-TCP sockets.
@@ -1,28 +1,22 @@
1
1
  # Generated from lib/raptor/server.rb with RBS::Inline
2
2
 
3
3
  module Raptor
4
- # High-performance HTTP server that accepts connections and dispatches them.
4
+ # Accepts client connections and dispatches them into the request
5
+ # pipeline. Skips acceptance when the reactor backlog is high so an
6
+ # overloaded process leaves connections for peers that can absorb
7
+ # them (via shared `SO_REUSEPORT` listeners).
5
8
  #
6
- # Server manages the main accept loop, handling incoming client connections from
7
- # bound sockets. It uses IO.select for efficient polling and implements automatic
8
- # load balancing by checking reactor backlog before accepting connections,
9
- # providing natural backpressure based on system capacity.
10
- #
11
- # Supports TCP, Unix domain, and SSL listeners transparently. TCP_NODELAY is
12
- # applied only to TCP sockets, and SSL handshakes are performed synchronously
13
- # before the connection is dispatched.
14
- #
15
- # For HTTP/1.1 connections the first request is parsed inline on the server
16
- # thread and dispatched directly to the thread pool, falling back to the
17
- # reactor only when more data is needed. For HTTP/2 connections (negotiated
18
- # via ALPN) the server sends initial SETTINGS and registers the connection
19
- # with the reactor for frame processing through the ractor pool.
9
+ # Supports TCP, Unix, and SSL listeners. SSL handshakes are offloaded
10
+ # to the thread pool so a slow client can't pin the server thread.
11
+ # For HTTP/1.1 the first request is parsed inline and dispatched
12
+ # straight to the thread pool; HTTP/2 (negotiated via ALPN) is
13
+ # registered with the reactor for frame processing.
20
14
  #
21
15
  # @example
22
16
  # binder = Binder.new(["tcp://0.0.0.0:3000"])
23
- # reactor = Reactor.new(thread_pool, ractor_pool, client_options: {})
17
+ # reactor = Reactor.new(ractor_pool, thread_pool, client_options: {})
24
18
  # request = Request.new(app, 3000)
25
- # server = Server.new(binder, reactor, thread_pool, request)
19
+ # server = Server.new(binder, reactor, thread_pool, request, client_options: { first_data_timeout: 30 })
26
20
  # server.run
27
21
  # # ... later
28
22
  # server.shutdown
@@ -33,15 +27,21 @@ module Raptor
33
27
 
34
28
  H2_PROTOCOL: ::String
35
29
 
36
- @binder: Binder
30
+ DEFAULT_REMOTE_ADDR: ::String
37
31
 
38
- @reactor: Reactor
32
+ DEFAULT_SERVER_NAME: ::String
39
33
 
40
- @thread_pool: AtomicThreadPool
34
+ @running: AtomicBoolean
35
+
36
+ @client_options: Hash[Symbol, untyped]
41
37
 
42
38
  @request: Request
43
39
 
44
- @running: AtomicBoolean
40
+ @thread_pool: AtomicThreadPool
41
+
42
+ @reactor: Reactor
43
+
44
+ @binder: Binder
45
45
 
46
46
  # Creates a new Server instance.
47
47
  #
@@ -49,10 +49,11 @@ module Raptor
49
49
  # @param reactor [Reactor] the reactor for handling client connections
50
50
  # @param thread_pool [AtomicThreadPool] thread pool for application processing
51
51
  # @param request [Request] the HTTP/1.1 request handler
52
+ # @param client_options [Hash] client timeout configuration, used to bound TLS handshakes
52
53
  # @return [void]
53
54
  #
54
- # @rbs (Binder binder, Reactor reactor, AtomicThreadPool thread_pool, Request request) -> void
55
- def initialize: (Binder binder, Reactor reactor, AtomicThreadPool thread_pool, Request request) -> void
55
+ # @rbs (Binder binder, Reactor reactor, AtomicThreadPool thread_pool, Request request, client_options: Hash[Symbol, untyped]) -> void
56
+ def initialize: (Binder binder, Reactor reactor, AtomicThreadPool thread_pool, Request request, client_options: Hash[Symbol, untyped]) -> void
56
57
 
57
58
  # Starts the server's main accept loop in a new thread.
58
59
  #
@@ -80,9 +81,11 @@ module Raptor
80
81
 
81
82
  # Accepts a connection from the given listener and dispatches it.
82
83
  #
83
- # For SSL connections with h2 negotiated via ALPN, the server sends
84
- # initial SETTINGS and adds the connection to the reactor as an HTTP/2
85
- # connection. All other connections follow the HTTP/1.1 path.
84
+ # For SSL listeners the TLS handshake is offloaded to the thread pool so
85
+ # a slow client cannot block the server thread. For SSL connections with
86
+ # h2 negotiated via ALPN, the server sends initial SETTINGS and adds the
87
+ # connection to the reactor as an HTTP/2 connection. All other connections
88
+ # follow the HTTP/1.1 path.
86
89
  #
87
90
  # @param listener [TCPServer, UNIXServer, Binder::SslListener] the ready listener
88
91
  # @param reactor [Reactor] the reactor to dispatch connections to
@@ -90,5 +93,39 @@ module Raptor
90
93
  #
91
94
  # @rbs (TCPServer | UNIXServer | Binder::SslListener listener, Reactor reactor) -> void
92
95
  def accept_connection: (TCPServer | UNIXServer | Binder::SslListener listener, Reactor reactor) -> void
96
+
97
+ # Performs the TLS handshake for an accepted SSL connection and dispatches
98
+ # it through the HTTP/2 or HTTP/1.1 path. The handshake is bounded by
99
+ # `:first_data_timeout` so a slow client cannot pin a worker thread.
100
+ #
101
+ # @param listener [Binder::SslListener] the SSL listener that accepted the connection
102
+ # @param tcp_client [TCPSocket] the accepted TCP socket
103
+ # @param remote_addr [String] the client's IP address
104
+ # @param reactor [Reactor] the reactor to dispatch the connection to
105
+ # @return [void]
106
+ #
107
+ # @rbs (Binder::SslListener listener, TCPSocket tcp_client, String remote_addr, Reactor reactor) -> void
108
+ def dispatch_ssl_connection: (Binder::SslListener listener, TCPSocket tcp_client, String remote_addr, Reactor reactor) -> void
109
+
110
+ # Drives a non-blocking SSL handshake to completion, bounded by the
111
+ # configured first-data timeout. Returns true on success, false on
112
+ # timeout or SSL error.
113
+ #
114
+ # @param ssl_socket [OpenSSL::SSL::SSLSocket] the SSL socket to hand-shake
115
+ # @return [Boolean] true if the handshake completed
116
+ #
117
+ # @rbs (OpenSSL::SSL::SSLSocket ssl_socket) -> bool
118
+ def perform_ssl_handshake: (OpenSSL::SSL::SSLSocket ssl_socket) -> bool
119
+
120
+ # Waits up to `deadline` for the socket to become ready for the next step
121
+ # of the SSL handshake. Closes the socket and returns false on timeout.
122
+ #
123
+ # @param ssl_socket [OpenSSL::SSL::SSLSocket] the SSL socket
124
+ # @param deadline [Float] absolute monotonic deadline
125
+ # @param direction [Symbol] either `:read` or `:write`
126
+ # @return [Boolean] true if the socket became ready before the deadline
127
+ #
128
+ # @rbs (OpenSSL::SSL::SSLSocket ssl_socket, Float deadline, Symbol direction) -> bool
129
+ def wait_for_handshake: (OpenSSL::SSL::SSLSocket ssl_socket, Float deadline, Symbol direction) -> bool
93
130
  end
94
131
  end