raptor 0.22.3 → 0.24.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -0
  3. data/Dockerfile +10 -2
  4. data/README.md +183 -61
  5. data/docs/assets/profile_picture.png +0 -0
  6. data/docs/assets/startup_output.svg +37 -0
  7. data/docs/raptor-vs-puma.md +104 -424
  8. data/docs/talk.md +1388 -0
  9. data/ext/raptor_bpf/reuseport_select.bpf.c +18 -9
  10. data/ext/raptor_http/extconf.rb +2 -0
  11. data/ext/raptor_http/raptor_http.c +165 -234
  12. data/ext/raptor_http2/raptor_http2.c +272 -223
  13. data/ext/raptor_native/raptor_native.c +38 -20
  14. data/lib/rackup/handler/raptor.rb +14 -12
  15. data/lib/raptor/binder.rb +12 -12
  16. data/lib/raptor/cli.rb +19 -18
  17. data/lib/raptor/cluster.rb +120 -74
  18. data/lib/raptor/control_server.rb +5 -2
  19. data/lib/raptor/detached_body.rb +6 -2
  20. data/lib/raptor/http.rb +7 -6
  21. data/lib/raptor/http1.rb +281 -234
  22. data/lib/raptor/http2.rb +356 -276
  23. data/lib/raptor/plugin.rb +98 -0
  24. data/lib/raptor/reactor.rb +138 -148
  25. data/lib/raptor/reuseport_bpf.rb +62 -38
  26. data/lib/raptor/server.rb +45 -34
  27. data/lib/raptor/stats.rb +15 -3
  28. data/lib/raptor/thread_locals.rb +4 -3
  29. data/lib/raptor/version.rb +1 -1
  30. data/sig/generated/rackup/handler/raptor.rbs +2 -1
  31. data/sig/generated/raptor/binder.rbs +9 -9
  32. data/sig/generated/raptor/cli.rbs +6 -6
  33. data/sig/generated/raptor/cluster.rbs +18 -5
  34. data/sig/generated/raptor/control_server.rbs +2 -1
  35. data/sig/generated/raptor/detached_body.rbs +5 -1
  36. data/sig/generated/raptor/http.rbs +3 -5
  37. data/sig/generated/raptor/http1.rbs +119 -102
  38. data/sig/generated/raptor/http2.rbs +204 -190
  39. data/sig/generated/raptor/plugin.rbs +52 -0
  40. data/sig/generated/raptor/reactor.rbs +107 -118
  41. data/sig/generated/raptor/reuseport_bpf.rbs +54 -17
  42. data/sig/generated/raptor/server.rbs +26 -26
  43. data/sig/generated/raptor/stats.rbs +3 -2
  44. data/sig/generated/raptor/thread_locals.rbs +4 -3
  45. metadata +6 -2
  46. data/docs/brisrails-talk.md +0 -2398
@@ -11,8 +11,6 @@ module Raptor
11
11
 
12
12
  CONTENT_TYPE: ::String
13
13
 
14
- HTTP_VERSION: ::String
15
-
16
14
  REMOTE_ADDR: ::String
17
15
 
18
16
  REQUEST_URI: ::String
@@ -73,13 +71,13 @@ module Raptor
73
71
  # registration order, rescuing any that raise.
74
72
  #
75
73
  # @param env [Hash, nil] the Rack environment
76
- # @param status [Integer, nil] the response status code
74
+ # @param status [Object, nil] the status returned by the app, if any
77
75
  # @param headers [Hash, nil] the response headers
78
76
  # @param error [Exception, nil] any error raised during processing, or nil on success
79
77
  # @return [void]
80
78
  #
81
- # @rbs (Hash[String, untyped]? env, Integer? status, Hash[String, String | Array[String]]? headers, Exception? error) -> void
82
- def self.call_response_finished: (Hash[String, untyped]? env, Integer? status, Hash[String, String | Array[String]]? headers, Exception? error) -> void
79
+ # @rbs (Hash[String, untyped]? env, untyped status, Hash[String, String | Array[String]]? headers, Exception? error) -> void
80
+ def self.call_response_finished: (Hash[String, untyped]? env, untyped status, Hash[String, String | Array[String]]? headers, Exception? error) -> void
83
81
 
84
82
  # Writes a Common Log Format entry to `io`. Write failures are silently
85
83
  # ignored.
@@ -34,6 +34,10 @@ module Raptor
34
34
 
35
35
  INTERNAL_SERVER_ERROR_RESPONSE: ::String
36
36
 
37
+ NOT_IMPLEMENTED_RESPONSE: ::String
38
+
39
+ CHUNKED_TERMINATOR: ::String
40
+
37
41
  CONNECTION_CLOSE: ::String
38
42
 
39
43
  CONNECTION_KEEPALIVE: ::String
@@ -42,14 +46,18 @@ module Raptor
42
46
 
43
47
  TRANSFER_ENCODING_CHUNKED: ::String
44
48
 
45
- CHUNKED_TERMINATOR: ::String
46
-
47
49
  HTTP_CONNECTION: ::String
48
50
 
49
51
  HTTP_EXPECT: ::String
50
52
 
51
53
  HTTP_TRANSFER_ENCODING: ::String
52
54
 
55
+ HTTP_X_FORWARDED_PROTO: ::String
56
+
57
+ HTTP_X_FORWARDED_SCHEME: ::String
58
+
59
+ HTTP_X_FORWARDED_SSL: ::String
60
+
53
61
  RACK_HEADER_PREFIX: ::String
54
62
 
55
63
  RACK_HIJACKED: ::String
@@ -66,17 +74,23 @@ module Raptor
66
74
  # @rbs (Hash[String, untyped] env) -> bool
67
75
  def self.invalid_host?: (Hash[String, untyped] env) -> bool
68
76
 
69
- # Returns true when the message framing shows a request-smuggling vector
70
- # per RFC 9112 section 6.3: a `Transfer-Encoding` where `chunked` is
71
- # missing, not the final encoding, or duplicated; a `Transfer-Encoding`
72
- # paired with a `Content-Length`; or a `Content-Length` containing any
73
- # non-digit character.
77
+ # Rejects ambiguous message framing per RFC 9112 section 6.3 and
78
+ # transfer codings other than a single `chunked`.
74
79
  #
75
80
  # @param env [Hash] the Rack environment after header parsing
76
- # @return [Boolean]
81
+ # @return [Symbol, nil] `:malformed`, `:unsupported_encoding`, or nil
77
82
  #
78
- # @rbs (Hash[String, untyped] env) -> bool
79
- def self.request_smuggling?: (Hash[String, untyped] env) -> bool
83
+ # @rbs (Hash[String, untyped] env) -> Symbol?
84
+ def self.framing_error: (Hash[String, untyped] env) -> Symbol?
85
+
86
+ # Returns any pipelined bytes following the request, or nil if none remain.
87
+ #
88
+ # @param buffer [String] the raw request bytes
89
+ # @param offset [Integer] the byte offset where the request ends
90
+ # @return [String, nil]
91
+ #
92
+ # @rbs (String buffer, Integer offset) -> String?
93
+ def self.pipelined_bytes: (String buffer, Integer offset) -> String?
80
94
 
81
95
  # Encodes one HTTP/1.1 response chunk.
82
96
  #
@@ -96,9 +110,10 @@ module Raptor
96
110
 
97
111
  # Advances an HTTP/1.x request parse from the state hash's buffered
98
112
  # bytes. A complete well-formed request returns with `:complete` set
99
- # plus populated `:env` and `:body`; malformed or oversized input flips
100
- # `:malformed` or `:too_large`; incomplete input returns the state
101
- # so the reactor can wait for more.
113
+ # plus populated `:env` and `:body`, and records any bytes after it in
114
+ # the parse data's `:pipelined`. Rejected input sets `:malformed`,
115
+ # `:too_large`, or `:unsupported_encoding`; incomplete input returns
116
+ # the state so the reactor can wait for more.
102
117
  #
103
118
  # @param data [Hash] the current parse state
104
119
  # @param env_template [Hash] the Rack env template to seed the request with
@@ -110,8 +125,6 @@ module Raptor
110
125
 
111
126
  @app: ^(Hash[String, untyped]) -> [ Integer, Hash[String, String | Array[String]], untyped ]
112
127
 
113
- @server_port: Integer
114
-
115
128
  @server_port_string: String
116
129
 
117
130
  @write_timeout: Integer
@@ -128,7 +141,7 @@ module Raptor
128
141
 
129
142
  @clean_fiber_locals: bool
130
143
 
131
- @on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
144
+ @on_error: (^(Hash[String, untyped]?, Exception) -> void)?
132
145
 
133
146
  @running: AtomicBoolean
134
147
 
@@ -150,39 +163,16 @@ module Raptor
150
163
  # @param on_error [#call, nil] callback invoked with (env, exception) when the Rack app raises
151
164
  # @return [void]
152
165
  #
153
- # @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Integer server_port, ?connection_options: Hash[Symbol, untyped], ?http1_options: Hash[Symbol, untyped], ?access_log_io: IO?, ?clean_thread_locals: bool, ?clean_fiber_locals: bool, ?on_error: ^(Hash[String, untyped]?, Exception) -> void | nil) -> void
154
- def initialize: (^(Hash[String, untyped]) -> [ Integer, Hash[String, String | Array[String]], untyped ] app, Integer server_port, ?connection_options: Hash[Symbol, untyped], ?http1_options: Hash[Symbol, untyped], ?access_log_io: IO?, ?clean_thread_locals: bool, ?clean_fiber_locals: bool, ?on_error: ^(Hash[String, untyped]?, Exception) -> void | nil) -> void
166
+ # @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Integer server_port, ?connection_options: Hash[Symbol, untyped], ?http1_options: Hash[Symbol, untyped], ?access_log_io: IO?, ?clean_thread_locals: bool, ?clean_fiber_locals: bool, ?on_error: (^(Hash[String, untyped]?, Exception) -> void)?) -> void
167
+ def initialize: (^(Hash[String, untyped]) -> [ Integer, Hash[String, String | Array[String]], untyped ] app, Integer server_port, ?connection_options: Hash[Symbol, untyped], ?http1_options: Hash[Symbol, untyped], ?access_log_io: IO?, ?clean_thread_locals: bool, ?clean_fiber_locals: bool, ?on_error: (^(Hash[String, untyped]?, Exception) -> void)?) -> void
155
168
 
156
- # Instance-level wrapper around {Http.parser_worker} that binds this
157
- # handler's env template and body-size limit into the worker proc.
169
+ # Returns a Ractor-safe proc that parses HTTP/1.x requests.
158
170
  #
159
171
  # @return [Proc]
160
172
  #
161
173
  # @rbs () -> ^(Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
162
174
  def parser_worker: () -> ^(Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
163
175
 
164
- # Instance-level wrapper around {Http.socket_write} that applies the
165
- # configured `write_timeout`.
166
- #
167
- # @param socket [TCPSocket] the socket to write to
168
- # @param string [String] the data to write
169
- # @return [void]
170
- # @raise [Http::WriteError] if the socket is not writable within the timeout or raises IOError
171
- #
172
- # @rbs (TCPSocket socket, String string) -> void
173
- def socket_write: (TCPSocket socket, String string) -> void
174
-
175
- # Instance-level wrapper around {Http.socket_writev} that applies the
176
- # configured `write_timeout`.
177
- #
178
- # @param socket [TCPSocket] the socket to write to
179
- # @param strings [Array<String>] the buffers to write in order
180
- # @return [void]
181
- # @raise [Http::WriteError] if the socket is not writable within the timeout or raises IOError
182
- #
183
- # @rbs (TCPSocket socket, Array[String] strings) -> void
184
- def socket_writev: (TCPSocket socket, Array[String] strings) -> void
185
-
186
176
  # Signals eager keep-alive loops to stop processing further requests on
187
177
  # their connections. In-flight requests complete normally.
188
178
  #
@@ -196,10 +186,10 @@ module Raptor
196
186
  # when complete. Falls back to the reactor when more data is needed.
197
187
  #
198
188
  # @param socket [TCPSocket] the freshly accepted client socket
199
- # @param id [Integer] unique client identifier
189
+ # @param id [Integer] unique connection identifier
200
190
  # @param reactor [Reactor] the reactor for fallback registration
201
191
  # @param thread_pool [AtomicThreadPool] thread pool for application processing
202
- # @param remote_addr [String] client IP address
192
+ # @param remote_addr [String] the client IP address
203
193
  # @param url_scheme [String] "http" or "https"
204
194
  # @return [void]
205
195
  #
@@ -217,46 +207,61 @@ module Raptor
217
207
  # @rbs (Hash[Symbol, untyped] parsed_request, Reactor reactor, AtomicThreadPool thread_pool) -> void
218
208
  def handle_parsed_request: (Hash[Symbol, untyped] parsed_request, Reactor reactor, AtomicThreadPool thread_pool) -> void
219
209
 
210
+ # Processes kept-alive requests on application threads, yielding to queued
211
+ # work and returning idle or incomplete connections to the reactor.
212
+ #
213
+ # @param socket [TCPSocket] the client socket
214
+ # @param id [Integer] unique connection identifier
215
+ # @param reactor [Reactor] the reactor for fallback registration
216
+ # @param thread_pool [AtomicThreadPool] thread pool for application processing
217
+ # @param request_count [Integer] number of requests handled on this connection
218
+ # @param remote_addr [String] the client IP address
219
+ # @param url_scheme [String] "http" or "https"
220
+ # @param resumed [Boolean] whether the reactor queued the connection once its next request became readable
221
+ # @param pipelined [String, nil] bytes the client sent after the previous request
222
+ # @return [void]
223
+ #
224
+ # @rbs (TCPSocket socket, Integer id, Reactor reactor, AtomicThreadPool thread_pool, Integer request_count, String remote_addr, String url_scheme, ?resumed: bool, ?pipelined: String?) -> void
225
+ def eager_keepalive: (TCPSocket socket, Integer id, Reactor reactor, AtomicThreadPool thread_pool, Integer request_count, String remote_addr, String url_scheme, ?resumed: bool, ?pipelined: String?) -> void
226
+
220
227
  private
221
228
 
222
229
  # Reads pending bytes off `socket` into the thread-local read buffer,
223
230
  # draining any additional SSL-buffered bytes so `pending` is empty on
224
- # return. Raises `IO::WaitReadable`, `EOFError`, or `IOError` like the
225
- # underlying `read_nonblock` does.
231
+ # return.
226
232
  #
227
233
  # @param socket [TCPSocket] the socket to read from
228
234
  # @return [String] the thread-local buffer, freshly populated
235
+ # @raise [IO::WaitReadable] if no bytes are ready to read
236
+ # @raise [EOFError, IOError, SystemCallError] if the connection is closed or fails
229
237
  #
230
238
  # @rbs (TCPSocket socket) -> String
231
239
  def read_into_thread_buffer: (TCPSocket socket) -> String
232
240
 
233
- # Runs a fresh HTTP/1.x parse against `buffer`, returning
234
- # `[env, parse_data, nread, parser]`. Raises `HttpParserError` on
235
- # malformed input.
241
+ # Runs a fresh HTTP/1.x parse against `buffer`.
236
242
  #
237
243
  # @param buffer [String] the raw request bytes
238
- # @return [Array(Hash, Hash, Integer, HttpParser)]
244
+ # @return [Array(Hash, Hash, Integer, HttpParser)] the env, parse data, bytes read, and parser
245
+ # @raise [HttpParserError] if the request is malformed
239
246
  #
240
247
  # @rbs (String buffer) -> [Hash[String, untyped], Hash[Symbol, untyped], Integer, HttpParser]
241
248
  def parse_next_request: (String buffer) -> [ Hash[String, untyped], Hash[Symbol, untyped], Integer, HttpParser ]
242
249
 
243
- # Resolves the request body for a finished parse. Returns the body
244
- # `String` (or `nil` when the request has no body), or one of
245
- # `:incomplete`, `:too_large`, `:malformed` when the caller must
246
- # fall back or reject.
250
+ # Validates request framing and extracts the body, preserving pipelined bytes.
247
251
  #
248
252
  # @param buffer [String] the raw request bytes
249
253
  # @param env [Hash] the Rack environment being built
254
+ # @param parse_data [Hash] metadata from the parsing pass
250
255
  # @param parser [HttpParser] the parser holding the finished parse state
251
256
  # @param nread [Integer] the byte offset where the body begins in `buffer`
252
- # @param decode_chunked [Boolean] whether to decode chunked bodies inline; when false chunked bodies signal `:incomplete`
253
- # @return [String, nil, Symbol]
257
+ # @return [String, nil, Symbol] body, nil when absent, or `:incomplete`,
258
+ # `:too_large`, `:malformed`, or `:unsupported_encoding`
254
259
  #
255
- # @rbs (String buffer, Hash[String, untyped] env, HttpParser parser, Integer nread, decode_chunked: bool) -> (String | Symbol)?
256
- def extract_body: (String buffer, Hash[String, untyped] env, HttpParser parser, Integer nread, decode_chunked: bool) -> (String | Symbol)?
260
+ # @rbs (String buffer, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, HttpParser parser, Integer nread) -> (String | Symbol)?
261
+ def extract_body: (String buffer, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, HttpParser parser, Integer nread) -> (String | Symbol)?
257
262
 
258
- # Returns true if the request expects a 100 Continue response per
259
- # RFC 7231 section 5.1.1.
263
+ # Returns true when the request expects a 100 Continue response per
264
+ # RFC 9110 section 10.1.1.
260
265
  #
261
266
  # @param env [Hash] the parsed Rack environment (possibly incomplete)
262
267
  # @return [Boolean]
@@ -279,32 +284,32 @@ module Raptor
279
284
  # if keep-alive, eagerly reading subsequent requests inline.
280
285
  #
281
286
  # @param socket [TCPSocket] the client socket
282
- # @param id [Integer] unique client identifier
287
+ # @param id [Integer] unique connection identifier
283
288
  # @param env [Hash] partial env hash from the HTTP parser
284
289
  # @param parse_data [Hash] metadata from the parsing pass
285
290
  # @param body [String, nil] decoded request body
286
291
  # @param reactor [Reactor] the reactor managing the client connection
287
292
  # @param thread_pool [AtomicThreadPool] thread pool for application processing
288
293
  # @param request_count [Integer] number of requests handled on this connection
289
- # @param remote_addr [String] client IP address
294
+ # @param remote_addr [String] the client IP address
290
295
  # @param url_scheme [String] "http" or "https"
291
296
  # @return [void]
292
297
  #
293
298
  # @rbs (TCPSocket socket, Integer id, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, String? body, Reactor reactor, AtomicThreadPool thread_pool, Integer request_count, String remote_addr, String url_scheme) -> void
294
299
  def process_client: (TCPSocket socket, Integer id, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, String? body, Reactor reactor, AtomicThreadPool thread_pool, Integer request_count, String remote_addr, String url_scheme) -> void
295
300
 
296
- # Processes a single request. Builds the Rack env, calls the app,
297
- # writes the response, and returns whether the connection stays open
298
- # for another request.
301
+ # Processes a single request, isolating fiber and thread locals as
302
+ # configured, and returns whether the connection stays open for another
303
+ # request.
299
304
  #
300
305
  # @param socket [TCPSocket] the client socket
301
- # @param id [Integer] unique client identifier
306
+ # @param id [Integer] unique connection identifier
302
307
  # @param env [Hash] partial env hash from the HTTP parser
303
308
  # @param parse_data [Hash] metadata from the parsing pass
304
309
  # @param body [String, nil] decoded request body
305
310
  # @param reactor [Reactor] the reactor managing the client connection
306
311
  # @param request_count [Integer] number of requests handled on this connection
307
- # @param remote_addr [String] client IP address
312
+ # @param remote_addr [String] the client IP address
308
313
  # @param url_scheme [String] "http" or "https"
309
314
  # @return [Boolean] true if the connection should be kept alive
310
315
  #
@@ -314,13 +319,13 @@ module Raptor
314
319
  # Calls the Rack app and writes its response for one request.
315
320
  #
316
321
  # @param socket [TCPSocket] the client socket
317
- # @param id [Integer] unique client identifier
322
+ # @param id [Integer] unique connection identifier
318
323
  # @param env [Hash] partial env hash from the HTTP parser
319
324
  # @param parse_data [Hash] metadata from the parsing pass
320
325
  # @param body [String, nil] decoded request body
321
326
  # @param reactor [Reactor] the reactor managing the client connection
322
327
  # @param request_count [Integer] number of requests handled on this connection
323
- # @param remote_addr [String] client IP address
328
+ # @param remote_addr [String] the client IP address
324
329
  # @param url_scheme [String] "http" or "https"
325
330
  # @return [Boolean] true if the connection should be kept alive
326
331
  #
@@ -334,50 +339,33 @@ module Raptor
334
339
  #
335
340
  # @param socket [TCPSocket] the client socket
336
341
  # @param rack_env [Hash, nil] the Rack environment, if it was built
337
- # @param status [Integer, nil] the status returned by the app, if any
342
+ # @param status [Object, nil] the status returned by the app, if any
338
343
  # @param headers [Hash, nil] the headers returned by the app, if any
339
344
  # @param error [Exception] the exception raised
340
345
  # @param response_started [Boolean] whether any response bytes have been written
341
346
  # @param hijacked [Boolean] whether the app took over the socket
342
347
  # @return [void]
343
348
  #
344
- # @rbs (TCPSocket socket, Hash[String, untyped]? rack_env, Integer? status, Hash[String, String | Array[String]]? headers, Exception error, response_started: bool, hijacked: bool) -> void
345
- def handle_app_error: (TCPSocket socket, Hash[String, untyped]? rack_env, Integer? status, Hash[String, String | Array[String]]? headers, Exception error, response_started: bool, hijacked: bool) -> void
346
-
347
- # Reads and processes subsequent requests inline on a kept-alive
348
- # connection. Returns the connection to the reactor when no data is ready
349
- # or a request is incomplete, and queues complete requests behind work
350
- # that is already waiting.
351
- #
352
- # @param socket [TCPSocket] the client socket
353
- # @param id [Integer] unique client identifier
354
- # @param reactor [Reactor] the reactor for fallback registration
355
- # @param thread_pool [AtomicThreadPool] thread pool for deprioritization
356
- # @param request_count [Integer] number of requests handled on this connection
357
- # @param remote_addr [String] client IP address
358
- # @param url_scheme [String] "http" or "https"
359
- # @return [void]
360
- #
361
- # @rbs (TCPSocket socket, Integer id, Reactor reactor, AtomicThreadPool thread_pool, Integer request_count, String remote_addr, String url_scheme) -> void
362
- def eager_keepalive: (TCPSocket socket, Integer id, Reactor reactor, AtomicThreadPool thread_pool, Integer request_count, String remote_addr, String url_scheme) -> void
349
+ # @rbs (TCPSocket socket, Hash[String, untyped]? rack_env, untyped status, Hash[String, String | Array[String]]? headers, Exception error, response_started: bool, hijacked: bool) -> void
350
+ def handle_app_error: (TCPSocket socket, Hash[String, untyped]? rack_env, untyped status, Hash[String, String | Array[String]]? headers, Exception error, response_started: bool, hijacked: bool) -> void
363
351
 
364
352
  # Re-registers a socket with the reactor for further processing when
365
353
  # an incomplete request is received on the fast path.
366
354
  #
367
355
  # @param socket [TCPSocket] the client socket
368
- # @param id [Integer] unique client identifier
356
+ # @param id [Integer] unique connection identifier
369
357
  # @param buffer [String] the partial request data already read
370
358
  # @param env [Hash] partial env hash from the HTTP parser
371
359
  # @param parse_data [Hash] metadata from the parsing pass
372
360
  # @param reactor [Reactor] the reactor to re-register with
373
361
  # @param request_count [Integer] number of requests handled on this connection
374
- # @param remote_addr [String] client IP address
362
+ # @param remote_addr [String] the client IP address
375
363
  # @param url_scheme [String] "http" or "https"
376
364
  # @param persisted [Boolean] whether the connection has already completed at least one request
377
365
  # @return [void]
378
366
  #
379
- # @rbs (TCPSocket socket, Integer id, String buffer, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, Reactor reactor, Integer request_count, String remote_addr, String url_scheme, persisted: bool) -> void
380
- def fallback_to_reactor: (TCPSocket socket, Integer id, String buffer, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, Reactor reactor, Integer request_count, String remote_addr, String url_scheme, persisted: bool) -> void
367
+ # @rbs (TCPSocket socket, Integer id, String buffer, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, Reactor reactor, Integer request_count, String remote_addr, String url_scheme, ?persisted: bool) -> void
368
+ def fallback_to_reactor: (TCPSocket socket, Integer id, String buffer, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, Reactor reactor, Integer request_count, String remote_addr, String url_scheme, ?persisted: bool) -> void
381
369
 
382
370
  # Writes a 413 response and closes the socket.
383
371
  #
@@ -395,13 +383,21 @@ module Raptor
395
383
  # @rbs (TCPSocket socket) -> void
396
384
  def reject_malformed: (TCPSocket socket) -> void
397
385
 
386
+ # Writes a 501 response and closes the socket.
387
+ #
388
+ # @param socket [TCPSocket] the client socket
389
+ # @return [void]
390
+ #
391
+ # @rbs (TCPSocket socket) -> void
392
+ def reject_unsupported_encoding: (TCPSocket socket) -> void
393
+
398
394
  # Builds a Rack environment hash from parsed HTTP request data.
399
395
  #
400
396
  # @param env [Hash] partial env hash from the HTTP parser
401
397
  # @param parse_data [Hash] metadata from the parsing pass, including content_length
402
398
  # @param body [String, nil] decoded request body, or nil if no body
403
399
  # @param socket [TCPSocket] the client socket, used for hijack support
404
- # @param remote_addr [String] client IP address
400
+ # @param remote_addr [String] the client IP address
405
401
  # @param url_scheme [String] "http" or "https"
406
402
  # @return [Hash] fully populated Rack environment hash
407
403
  #
@@ -410,7 +406,8 @@ module Raptor
410
406
 
411
407
  # Builds the `rack.input` IO object for the request body. Returns an
412
408
  # in-memory StringIO for bodies up to the spool threshold, or a Tempfile
413
- # for larger bodies to bound per-worker memory.
409
+ # for larger bodies. The complete body is already buffered at this point,
410
+ # so spooling does not bound peak receive memory.
414
411
  #
415
412
  # @param body [String, nil] decoded request body
416
413
  # @return [IO] an IO-like object positioned at the start of the body
@@ -481,10 +478,10 @@ module Raptor
481
478
  # @raise [TypeError] if status is not an Integer
482
479
  # @raise [ArgumentError] if status is less than 100
483
480
  #
484
- # @rbs (Integer status) -> void
485
- def validate_status: (Integer status) -> void
481
+ # @rbs (untyped status) -> void
482
+ def validate_status: (untyped status) -> void
486
483
 
487
- # Returns a normalised copy of the response headers with lowercased
484
+ # Returns a normalized copy of the response headers with lowercased
488
485
  # keys and illegal/`rack.*`/`status` entries dropped.
489
486
  #
490
487
  # @param headers [Hash] raw headers from the Rack application
@@ -515,8 +512,8 @@ module Raptor
515
512
  # @rbs (String http_version, Integer status) -> String
516
513
  def build_status_line: (String http_version, Integer status) -> String
517
514
 
518
- # Writes the response headers, uncorks the socket, and hands the raw
519
- # socket to the hijack callback.
515
+ # Writes the response headers and hands the raw socket to the hijack
516
+ # callback.
520
517
  #
521
518
  # @param socket [TCPSocket] the client socket
522
519
  # @param response [String] the status line accumulated so far
@@ -550,6 +547,7 @@ module Raptor
550
547
  # @param body [Object] the response body
551
548
  # @param http_version [String] "HTTP/1.1" or "HTTP/1.0"
552
549
  # @return [void]
550
+ # @raise [TypeError] if the body responds to none of `each`, `to_ary`, or `to_path`
553
551
  #
554
552
  # @rbs (TCPSocket socket, String response, Hash[String, String | Array[String]] headers, untyped body, String http_version) -> void
555
553
  def write_full_response: (TCPSocket socket, String response, Hash[String, String | Array[String]] headers, untyped body, String http_version) -> void
@@ -622,8 +620,27 @@ module Raptor
622
620
  # @rbs (TCPSocket socket, String response, untyped body, bool use_chunked) -> void
623
621
  def write_enumerable_body: (TCPSocket socket, String response, untyped body, bool use_chunked) -> void
624
622
 
625
- # Instance-level wrapper around {Http.write_access_log} that routes to
626
- # the configured `@access_log_io`.
623
+ # Writes response bytes within the configured write timeout.
624
+ #
625
+ # @param socket [TCPSocket] the socket to write to
626
+ # @param string [String] the data to write
627
+ # @return [void]
628
+ # @raise [Http::WriteError] if the socket is not writable within the timeout or raises IOError
629
+ #
630
+ # @rbs (TCPSocket socket, String string) -> void
631
+ def socket_write: (TCPSocket socket, String string) -> void
632
+
633
+ # Writes response buffers in order within the configured write timeout.
634
+ #
635
+ # @param socket [TCPSocket] the socket to write to
636
+ # @param strings [Array<String>] the buffers to write in order
637
+ # @return [void]
638
+ # @raise [Http::WriteError] if the socket is not writable within the timeout or raises IOError
639
+ #
640
+ # @rbs (TCPSocket socket, Array[String] strings) -> void
641
+ def socket_writev: (TCPSocket socket, Array[String] strings) -> void
642
+
643
+ # Writes a completed request to the access log.
627
644
  #
628
645
  # @param env [Hash] the Rack environment
629
646
  # @param status [Integer] the response status code