raptor 0.7.0 → 0.9.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.
@@ -26,10 +26,12 @@ module Raptor
26
26
  # workers: 4, ractors: 2, threads: 8,
27
27
  # binds: ["tcp://0.0.0.0:3000"],
28
28
  # rackup: "config.ru",
29
- # client: { first_data_timeout: 30, chunk_data_timeout: 10 }
29
+ # connection: { first_data_timeout: 30, chunk_data_timeout: 10 }
30
30
  # }
31
31
  # Cluster.run(options)
32
32
  class Cluster
33
+ INHERITED_FDS_ENV: ::String
34
+
33
35
  # Convenience method to create and run a cluster with the given options.
34
36
  #
35
37
  # @param options [Hash] cluster configuration options
@@ -38,13 +40,13 @@ module Raptor
38
40
  # @rbs (Hash[Symbol, untyped] options) -> void
39
41
  def self.run: (Hash[Symbol, untyped] options) -> void
40
42
 
41
- @thread_count: Integer
43
+ @http2_options: Hash[Symbol, untyped]
42
44
 
43
- @client_options: Hash[Symbol, Integer]
45
+ @worker_boot_timeout: Integer
44
46
 
45
47
  @worker_timeout: Integer
46
48
 
47
- @worker_boot_timeout: Integer
49
+ @worker_drain_timeout: Integer
48
50
 
49
51
  @worker_shutdown_timeout: Integer
50
52
 
@@ -52,6 +54,18 @@ module Raptor
52
54
 
53
55
  @pid_file: String?
54
56
 
57
+ @stdout_file: String?
58
+
59
+ @stderr_file: String?
60
+
61
+ @access_log_file: String?
62
+
63
+ @access_log_io: IO?
64
+
65
+ @launch_command: String?
66
+
67
+ @launch_argv: Array[String]?
68
+
55
69
  @on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
56
70
 
57
71
  @binder: Binder
@@ -74,10 +88,24 @@ module Raptor
74
88
 
75
89
  @phased_restarting: bool
76
90
 
91
+ @hot_restart_requested: bool
92
+
93
+ @bpf_active: bool
94
+
95
+ @http1_options: Hash[Symbol, untyped]
96
+
97
+ @connection_options: Hash[Symbol, untyped]
98
+
99
+ @environment: String
100
+
101
+ @thread_count: Integer
102
+
77
103
  @ractor_count: Integer
78
104
 
79
105
  @worker_count: Integer
80
106
 
107
+ @drain_accept_queue: bool
108
+
81
109
  # Creates a new Cluster with the specified configuration.
82
110
  #
83
111
  # Initializes the cluster with worker, ractor, and thread counts,
@@ -86,18 +114,30 @@ module Raptor
86
114
  #
87
115
  # @param options [Hash] cluster configuration options
88
116
  # @option options [Array<String>] :binds array of bind URIs
117
+ # @option options [Integer] :socket_backlog kernel listen() queue depth for TCP/SSL listeners
118
+ # @option options [Boolean] :drain_accept_queue whether to drain the kernel accept queue on shutdown
89
119
  # @option options [Integer] :workers number of worker processes
90
120
  # @option options [Integer] :ractors number of ractors per worker process
91
121
  # @option options [Integer] :threads number of threads per worker process
92
122
  # @option options [#call] :app pre-built Rack application
93
123
  # @option options [String] :rackup path to Rack configuration file
94
- # @option options [Hash] :client client configuration
95
- # @option options [Integer] :worker_timeout seconds to wait for a booted worker to check in before killing it
124
+ # @option options [String, nil] :chdir directory to change to before loading the Rack application, or nil to leave the working directory unchanged
125
+ # @option options [String, nil] :environment Raptor's application environment label; falls back to `$RAILS_ENV`, then `$RACK_ENV`, then `"development"`
126
+ # @option options [Hash] :connection per-connection settings shared across protocols
127
+ # @option options [Hash] :http1 HTTP/1.1-specific settings
128
+ # @option options [Hash] :http2 HTTP/2-specific settings
96
129
  # @option options [Integer] :worker_boot_timeout seconds to wait for a worker to finish booting before killing it
130
+ # @option options [Integer] :worker_timeout seconds to wait for a booted worker to check in before killing it
131
+ # @option options [Integer] :worker_drain_timeout seconds a worker waits for in-flight requests during shutdown before force-killing app threads
97
132
  # @option options [Integer] :worker_shutdown_timeout seconds to wait for graceful worker exit before force-killing
98
133
  # @option options [String, nil] :stats_file path to write per-worker stats JSON, or nil to disable
99
134
  # @option options [String, nil] :pid_file path to write the master PID to, or nil to disable
100
- # @option options [#call] :on_error callback invoked with (env, exception) when the Rack app raises
135
+ # @option options [String, nil] :stdout_file path to redirect stdout to, reopened on SIGHUP, or nil to disable
136
+ # @option options [String, nil] :stderr_file path to redirect stderr to, reopened on SIGHUP, or nil to disable
137
+ # @option options [String, nil] :access_log_file path to write Common Log Format access logs to, reopened on SIGHUP, or nil to disable
138
+ # @option options [String, nil] :launch_command path of the program to re-exec on hot restart, or nil to disable
139
+ # @option options [Array<String>, nil] :launch_argv command-line arguments for the hot-restart exec, or nil to disable
140
+ # @option options [#call, nil] :on_error callback invoked with (env, exception) when the Rack app raises
101
141
  # @return [void]
102
142
  #
103
143
  # @rbs (Hash[Symbol, untyped] options) -> void
@@ -107,8 +147,8 @@ module Raptor
107
147
  #
108
148
  # Forks the configured number of worker processes and monitors them,
109
149
  # restarting any that exit unexpectedly or stop checking in. Handles
110
- # graceful shutdown via INT or TERM signals, stats logging via USR1,
111
- # and phased restart via USR2.
150
+ # graceful shutdown via INT or TERM signals, phased restart via USR1,
151
+ # and hot restart via USR2.
112
152
  #
113
153
  # Each worker process includes:
114
154
  # - 1 server thread (continuously accepts connections with backpressure control)
@@ -133,6 +173,18 @@ module Raptor
133
173
 
134
174
  private
135
175
 
176
+ # Returns the inherited-FDs hash for a systemd socket-activation handoff,
177
+ # pairing each bind URI with the FD systemd passed at the same index.
178
+ # The activation is skipped (with a warning) when the FD count doesn't
179
+ # match the number of bind URIs.
180
+ #
181
+ # @param bind_uris [Array<String>] the configured bind URIs
182
+ # @param filenos [Array<Integer>] file descriptors passed by systemd
183
+ # @return [Hash{String => Array<Integer>}]
184
+ #
185
+ # @rbs (Array[String] bind_uris, Array[Integer] filenos) -> Hash[String, Array[Integer]]
186
+ def pair_systemd_fds: (Array[String] bind_uris, Array[Integer] filenos) -> Hash[String, Array[Integer]]
187
+
136
188
  # Forks a new worker process and registers it at the given index.
137
189
  # The worker inherits the cluster's current phase.
138
190
  #
@@ -170,13 +222,22 @@ module Raptor
170
222
  def timeout_hung_workers: () -> void
171
223
 
172
224
  # Replaces each worker process one at a time, waiting for the new
173
- # worker to boot before moving on to the next. Triggered by SIGUSR2.
225
+ # worker to boot before moving on to the next.
174
226
  #
175
227
  # @return [void]
176
228
  #
177
229
  # @rbs () -> void
178
230
  def perform_phased_restart: () -> void
179
231
 
232
+ # Re-execs the master process with a fresh boot of the same Raptor
233
+ # invocation, handing the new master its listening sockets so accepted
234
+ # connections continue to be served across the swap.
235
+ #
236
+ # @return [void]
237
+ #
238
+ # @rbs () -> void
239
+ def perform_hot_restart: () -> void
240
+
180
241
  # Runs the full server stack inside a worker process.
181
242
  #
182
243
  # Sets up and coordinates the reactor, server, ractor pool, thread pool,
@@ -190,6 +251,16 @@ module Raptor
190
251
  # @rbs (Integer index, Integer phase) -> void
191
252
  def run_worker: (Integer index, Integer phase) -> void
192
253
 
254
+ # Shuts down the worker's application thread pool, force-killing the
255
+ # underlying threads if in-flight requests have not finished within
256
+ # `worker_drain_timeout` seconds.
257
+ #
258
+ # @param thread_pool [AtomicThreadPool] the worker's thread pool
259
+ # @return [void]
260
+ #
261
+ # @rbs (AtomicThreadPool thread_pool) -> void
262
+ def drain_thread_pool: (AtomicThreadPool thread_pool) -> void
263
+
193
264
  # Returns a human-readable description of how a process exited.
194
265
  #
195
266
  # @param status [Process::Status] the exit status of the process
@@ -213,14 +284,21 @@ module Raptor
213
284
  # @rbs () -> void
214
285
  def log_initialization: () -> void
215
286
 
216
- # Logs current stats for all workers to stdout.
287
+ # Redirects `$stdout`, `$stderr`, and the access log to their configured
288
+ # paths. No-op for any stream whose target path is nil.
217
289
  #
218
- # Triggered by SIGUSR1 in the master process.
290
+ # @return [void]
291
+ #
292
+ # @rbs () -> void
293
+ def reopen_logs: () -> void
294
+
295
+ # Reopens the master's log files and forwards SIGHUP to each worker so
296
+ # they reopen their own inherited file descriptors.
219
297
  #
220
298
  # @return [void]
221
299
  #
222
300
  # @rbs () -> void
223
- def log_stats: () -> void
301
+ def reopen_logs_and_signal_workers: () -> void
224
302
 
225
303
  # Writes the stats file on a 1-second interval until shutdown.
226
304
  #
@@ -0,0 +1,65 @@
1
+ # Generated from lib/raptor/http.rb with RBS::Inline
2
+
3
+ module Raptor
4
+ # Shared HTTP utilities used by both the HTTP/1.x and HTTP/2 handlers:
5
+ # Rack env keys that aren't provided by Rack itself, low-level socket
6
+ # writing, and Common Log Format access-log formatting.
7
+ module Http
8
+ WRITE_TIMEOUT: ::Integer
9
+
10
+ CONTENT_LENGTH: ::String
11
+
12
+ CONTENT_TYPE: ::String
13
+
14
+ HTTP_VERSION: ::String
15
+
16
+ REMOTE_ADDR: ::String
17
+
18
+ SERVER_SOFTWARE: ::String
19
+
20
+ SERVER_SOFTWARE_VALUE: untyped
21
+
22
+ class WriteError < StandardError
23
+ # @rbs () -> String
24
+ def message: () -> String
25
+ end
26
+
27
+ # Writes `string` in full, retrying on partial writes. Bounded by
28
+ # `timeout` so a slow client can't pin the writing thread.
29
+ #
30
+ # @param socket [TCPSocket] the socket to write to
31
+ # @param string [String] the data to write
32
+ # @param timeout [Integer] seconds to wait for the socket to become writable on each partial write
33
+ # @return [void]
34
+ # @raise [WriteError] if the socket is not writable within the timeout or raises IOError
35
+ #
36
+ # @rbs (TCPSocket socket, String string, ?timeout: Integer) -> void
37
+ def self.socket_write: (TCPSocket socket, String string, ?timeout: Integer) -> void
38
+
39
+ # Writes `strings` in full via a single `writev(2)` syscall when possible,
40
+ # falling back to per-string writes on partial results. Bounded by
41
+ # `timeout` so a slow client can't pin the writing thread.
42
+ #
43
+ # @param socket [TCPSocket] the socket to write to
44
+ # @param strings [Array<String>] the buffers to write in order
45
+ # @param timeout [Integer] seconds to wait for the socket to become writable on each retry
46
+ # @return [void]
47
+ # @raise [WriteError] if the socket is not writable within the timeout or raises IOError
48
+ #
49
+ # @rbs (TCPSocket socket, Array[String] strings, ?timeout: Integer) -> void
50
+ def self.socket_writev: (TCPSocket socket, Array[String] strings, ?timeout: Integer) -> void
51
+
52
+ # Writes a Common Log Format entry to `io`. Write failures are silently
53
+ # ignored.
54
+ #
55
+ # @param io [IO] the destination IO
56
+ # @param env [Hash] the Rack environment
57
+ # @param status [Integer] the response status code
58
+ # @param size [String] the response body size in bytes, or `-` if unknown
59
+ # @param remote_addr [String] the client IP address
60
+ # @return [void]
61
+ #
62
+ # @rbs (IO io, Hash[String, untyped] env, Integer status, String size, String remote_addr) -> void
63
+ def self.write_access_log: (IO io, Hash[String, untyped] env, Integer status, String size, String remote_addr) -> void
64
+ end
65
+ end
@@ -1,18 +1,20 @@
1
- # Generated from lib/raptor/request.rb with RBS::Inline
1
+ # Generated from lib/raptor/http1.rb with RBS::Inline
2
2
 
3
3
  module Raptor
4
4
  # Parses HTTP/1.x requests and dispatches them to the Rack
5
5
  # application. Coordinates with the Ractor pool for parsing and
6
6
  # with the reactor for requests that need more data before they
7
7
  # can be handled.
8
- class Request
8
+ class Http1
9
9
  BODY_BUFFER_THRESHOLD: untyped
10
10
 
11
11
  FILE_CHUNK_SIZE: untyped
12
12
 
13
+ MAX_CHUNK_OVERHEAD: untyped
14
+
13
15
  READ_BUFFER_SIZE: untyped
14
16
 
15
- WRITE_TIMEOUT: ::Integer
17
+ RESPONSE_BUFFER_CAPACITY: untyped
16
18
 
17
19
  KEEPALIVE_READ_TIMEOUT: ::Float
18
20
 
@@ -28,20 +30,26 @@ module Raptor
28
30
 
29
31
  STATUS_WITH_NO_ENTITY_BODY: untyped
30
32
 
31
- BAD_REQUEST_RESPONSE: ::String
33
+ CONTINUE_RESPONSE: ::String
32
34
 
33
- INTERNAL_SERVER_ERROR_RESPONSE: ::String
35
+ BAD_REQUEST_RESPONSE: ::String
34
36
 
35
37
  CONTENT_TOO_LARGE_RESPONSE: ::String
36
38
 
39
+ INTERNAL_SERVER_ERROR_RESPONSE: ::String
40
+
37
41
  CONNECTION_CLOSE: ::String
38
42
 
39
43
  CONNECTION_KEEPALIVE: ::String
40
44
 
45
+ EXPECT_100_CONTINUE: ::String
46
+
41
47
  TRANSFER_ENCODING_CHUNKED: ::String
42
48
 
43
49
  HTTP_CONNECTION: ::String
44
50
 
51
+ HTTP_EXPECT: ::String
52
+
45
53
  HTTP_TRANSFER_ENCODING: ::String
46
54
 
47
55
  RACK_HEADER_PREFIX: ::String
@@ -54,19 +62,24 @@ module Raptor
54
62
 
55
63
  ILLEGAL_HEADER_VALUE_REGEX: ::Regexp
56
64
 
57
- class Error < StandardError
58
- end
59
-
60
- class WriteError < Error
61
- # @rbs () -> String
62
- def message: () -> String
63
- end
65
+ # Returns true when the message framing shows a request-smuggling vector
66
+ # per RFC 9112 section 6.3: a `Transfer-Encoding` where `chunked` is
67
+ # missing, not the final encoding, or duplicated; a `Transfer-Encoding`
68
+ # paired with a `Content-Length`; or a `Content-Length` containing any
69
+ # non-digit character.
70
+ #
71
+ # @param env [Hash] the Rack environment after header parsing
72
+ # @return [Boolean]
73
+ #
74
+ # @rbs (Hash[String, untyped] env) -> bool
75
+ def self.request_smuggling?: (Hash[String, untyped] env) -> bool
64
76
 
65
77
  # Decodes a chunked transfer-encoded body buffer.
66
78
  #
67
79
  # Returns the decoded bytes and a state symbol: `:complete` when the
68
80
  # terminating zero-length chunk was found, `:too_large` when the decoded
69
- # size would exceed `max_size`, or `:incomplete` otherwise.
81
+ # size would exceed `max_size`, `:malformed` when chunk framing overhead
82
+ # exceeds `MAX_CHUNK_OVERHEAD`, or `:incomplete` otherwise.
70
83
  #
71
84
  # @param buffer [String] the raw body buffer to decode
72
85
  # @param max_size [Integer, nil] maximum decoded body size, or nil for unlimited
@@ -75,41 +88,62 @@ module Raptor
75
88
  # @rbs (String buffer, ?Integer? max_size) -> [String, Symbol]
76
89
  def self.decode_chunked: (String buffer, ?Integer? max_size) -> [ String, Symbol ]
77
90
 
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
91
  @running: AtomicBoolean
90
92
 
91
93
  @on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
92
94
 
95
+ @access_log_io: IO?
96
+
97
+ @max_keepalive_requests: Integer
98
+
93
99
  @body_spool_threshold: Integer?
94
100
 
95
101
  @max_body_size: Integer?
96
102
 
103
+ @write_timeout: Integer
104
+
97
105
  @server_port: Integer
98
106
 
99
107
  @app: ^(Hash[String, untyped]) -> [ Integer, Hash[String, String | Array[String]], untyped ]
100
108
 
101
- # Creates a new Request handler.
109
+ # Creates a new Http1 handler.
102
110
  #
103
111
  # @param app [#call] the Rack application to dispatch complete requests to
104
112
  # @param server_port [Integer] port number used to populate SERVER_PORT in the Rack env
105
- # @param client_options [Hash] client limits configuration
106
- # @option client_options [Integer, nil] :max_body_size maximum request body size in bytes
107
- # @option client_options [Integer, nil] :body_spool_threshold spool bodies larger than this to a tempfile
113
+ # @param connection_options [Hash] per-connection settings shared across protocols
114
+ # @option connection_options [Integer] :write_timeout per-write socket timeout in seconds
115
+ # @option connection_options [Integer, nil] :max_body_size maximum request body size in bytes
116
+ # @option connection_options [Integer, nil] :body_spool_threshold spool bodies larger than this to a tempfile
117
+ # @param http1_options [Hash] HTTP/1.1-specific settings
118
+ # @option http1_options [Integer] :max_keepalive_requests maximum requests per HTTP/1.1 keep-alive connection
119
+ # @param access_log_io [IO, nil] IO to write Common Log Format access entries to, or nil to disable
108
120
  # @param on_error [#call, nil] callback invoked with (env, exception) when the Rack app raises
109
121
  # @return [void]
110
122
  #
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
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
123
+ # @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?, ?on_error: ^(Hash[String, untyped]?, Exception) -> void | nil) -> void
124
+ 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?, ?on_error: ^(Hash[String, untyped]?, Exception) -> void | nil) -> void
125
+
126
+ # Instance-level wrapper around {Http.socket_write} that applies the
127
+ # configured `write_timeout`.
128
+ #
129
+ # @param socket [TCPSocket] the socket to write to
130
+ # @param string [String] the data to write
131
+ # @return [void]
132
+ # @raise [Http::WriteError] if the socket is not writable within the timeout or raises IOError
133
+ #
134
+ # @rbs (TCPSocket socket, String string) -> void
135
+ def socket_write: (TCPSocket socket, String string) -> void
136
+
137
+ # Instance-level wrapper around {Http.socket_writev} that applies the
138
+ # configured `write_timeout`.
139
+ #
140
+ # @param socket [TCPSocket] the socket to write to
141
+ # @param strings [Array<String>] the buffers to write in order
142
+ # @return [void]
143
+ # @raise [Http::WriteError] if the socket is not writable within the timeout or raises IOError
144
+ #
145
+ # @rbs (TCPSocket socket, Array[String] strings) -> void
146
+ def socket_writev: (TCPSocket socket, Array[String] strings) -> void
113
147
 
114
148
  # Signals eager keep-alive loops to stop processing further requests on
115
149
  # their connections. In-flight requests complete normally.
@@ -160,6 +194,28 @@ module Raptor
160
194
 
161
195
  private
162
196
 
197
+ # Returns true if the request expects a 100 Continue response per
198
+ # RFC 7231 section 5.1.1.
199
+ #
200
+ # @param env [Hash] the parsed Rack environment (possibly incomplete)
201
+ # @return [Boolean]
202
+ #
203
+ # @rbs (Hash[String, untyped] env) -> bool
204
+ def expects_100_continue?: (Hash[String, untyped] env) -> bool
205
+
206
+ # Sends an HTTP 100 Continue response when an HTTP/1.1 client requested
207
+ # `Expect: 100-continue` and the request body has not yet been received.
208
+ #
209
+ # Returns the state hash with `:continued` set when the response has been
210
+ # written. A write failure is silently ignored.
211
+ #
212
+ # @param state [Hash] the partially-parsed connection state
213
+ # @param reactor [Reactor] the reactor holding the connection's socket
214
+ # @return [Hash] the state, with `:continued` set if 100 was written
215
+ #
216
+ # @rbs (Hash[Symbol, untyped] state, Reactor reactor) -> Hash[Symbol, untyped]
217
+ def send_continue_if_expected: (Hash[Symbol, untyped] state, Reactor reactor) -> Hash[Symbol, untyped]
218
+
163
219
  # Processes a client connection by handling the current request and,
164
220
  # if keep-alive, eagerly reading subsequent requests inline.
165
221
  #
@@ -278,6 +334,16 @@ module Raptor
278
334
  # @rbs (String? body) -> IO
279
335
  def build_rack_input: (String? body) -> IO
280
336
 
337
+ # Returns true when an upstream proxy signals that it terminated TLS for
338
+ # this request via `X-Forwarded-Proto`, `X-Forwarded-Scheme`, or
339
+ # `X-Forwarded-Ssl`. Only the first comma-separated value is consulted.
340
+ #
341
+ # @param env [Hash] the Rack environment
342
+ # @return [Boolean]
343
+ #
344
+ # @rbs (Hash[String, untyped] env) -> bool
345
+ def forwarded_https?: (Hash[String, untyped] env) -> bool
346
+
281
347
  # Determines whether the connection should be kept alive after the response.
282
348
  #
283
349
  # Returns false if the request limit has been reached. For HTTP/1.1, keep-alive
@@ -353,7 +419,8 @@ module Raptor
353
419
  # @rbs (Hash[String, String | Array[String]] headers, Integer status) -> void
354
420
  def validate_headers: (Hash[String, String | Array[String]] headers, Integer status) -> void
355
421
 
356
- # Builds the HTTP status line string.
422
+ # Returns the HTTP status line for `status`. Callers must not retain the
423
+ # returned string across further calls on the same thread.
357
424
  #
358
425
  # @param http_version [String] "HTTP/1.1" or "HTTP/1.0"
359
426
  # @param status [Integer] HTTP status code
@@ -446,10 +513,7 @@ module Raptor
446
513
  # @rbs (TCPSocket socket, String response, Array[String] body_array, bool use_chunked) -> void
447
514
  def write_array_body: (TCPSocket socket, String response, Array[String] body_array, bool use_chunked) -> void
448
515
 
449
- # Writes a single-element array body, optionally buffering it with the headers.
450
- #
451
- # Small bodies are concatenated with the headers into one write to reduce
452
- # system call overhead.
516
+ # Writes a single-element array body to the socket.
453
517
  #
454
518
  # @param socket [TCPSocket] the client socket
455
519
  # @param response [String] headers already serialized, to be written before the body
@@ -526,6 +590,29 @@ module Raptor
526
590
  # @rbs (Hash[String, untyped] env, Integer? status, Hash[String, String | Array[String]]? headers, Exception? error) -> void
527
591
  def call_response_finished: (Hash[String, untyped] env, Integer? status, Hash[String, String | Array[String]]? headers, Exception? error) -> void
528
592
 
593
+ # Instance-level wrapper around {Http.write_access_log} that routes to
594
+ # the configured `@access_log_io`.
595
+ #
596
+ # @param env [Hash] the Rack environment
597
+ # @param status [Integer] the response status code
598
+ # @param size [String] the response body size in bytes, or `-` if unknown
599
+ # @param remote_addr [String] the client IP address
600
+ # @return [void]
601
+ #
602
+ # @rbs (Hash[String, untyped] env, Integer status, String size, String remote_addr) -> void
603
+ def write_access_log: (Hash[String, untyped] env, Integer status, String size, String remote_addr) -> void
604
+
605
+ # Returns the response body size as a String for the access log, taken
606
+ # from the `content-length` header when set, computed from the body
607
+ # otherwise, or `-` when the size cannot be determined upfront.
608
+ #
609
+ # @param headers [Hash] the response headers
610
+ # @param body [Object] the response body
611
+ # @return [String]
612
+ #
613
+ # @rbs (Hash[String, String | Array[String]] headers, untyped body) -> String
614
+ def response_size: (Hash[String, String | Array[String]] headers, untyped body) -> String
615
+
529
616
  # Enables TCP_CORK on the socket to batch outgoing packets into fewer segments.
530
617
  #
531
618
  # Only applies to TCP sockets. No-op on non-TCP sockets.
@@ -17,10 +17,15 @@ module Raptor
17
17
 
18
18
  @state: Atom
19
19
 
20
+ @write_timeout: Integer
21
+
20
22
  # Creates a new Writer.
21
23
  #
22
- # @rbs () -> void
23
- def initialize: () -> void
24
+ # @param write_timeout [Integer] per-write socket timeout passed through to {Http.socket_write}
25
+ # @return [void]
26
+ #
27
+ # @rbs (write_timeout: Integer) -> void
28
+ def initialize: (write_timeout: Integer) -> void
24
29
 
25
30
  # Writes frames to the socket, coordinating with concurrent writers
26
31
  # so that exactly one thread is actively writing at any time.
@@ -36,7 +41,7 @@ module Raptor
36
41
  # Per-connection outbound flow-control accounting.
37
42
  #
38
43
  # Tracks the peer's connection-level and per-stream receive windows so
39
- # outbound DATA frames respect RFC 7540 §5.2. Threads dispatching stream
44
+ # outbound DATA frames respect RFC 7540 section 5.2. Threads dispatching stream
40
45
  # responses call `acquire` to reserve send capacity; threads applying
41
46
  # inbound WINDOW_UPDATE or SETTINGS frames call the mutating methods to
42
47
  # replenish it. The connection window and per-stream windows live in
@@ -93,7 +98,7 @@ module Raptor
93
98
  def add_stream_window: (Integer stream_id, Integer increment) -> void
94
99
 
95
100
  # 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.
101
+ # existing stream window by the delta as required by RFC 7540 section 6.9.2.
97
102
  #
98
103
  # @param new_size [Integer] the peer's new initial window size
99
104
  # @return [void]
@@ -140,28 +145,44 @@ module Raptor
140
145
 
141
146
  HOP_BY_HOP_HEADERS: untyped
142
147
 
148
+ @initial_settings_frame: String
149
+
143
150
  @on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
144
151
 
152
+ @access_log_io: IO?
153
+
154
+ @write_timeout: Integer
155
+
145
156
  @server_port: Integer
146
157
 
147
158
  @app: ^(Hash[String, untyped]) -> [ Integer, Hash[String, String | Array[String]], untyped ]
148
159
 
160
+ # The initial server SETTINGS frame sent on every new HTTP/2 connection.
161
+ #
162
+ # @return [String] the encoded SETTINGS frame
163
+ attr_reader initial_settings_frame: untyped
164
+
149
165
  # Creates a new Http2 handler.
150
166
  #
151
167
  # @param app [#call] the Rack application to dispatch requests to
152
168
  # @param server_port [Integer] port number used to populate SERVER_PORT in the Rack env
169
+ # @param connection_options [Hash] per-connection settings shared across protocols
170
+ # @option connection_options [Integer] :write_timeout per-write socket timeout in seconds
171
+ # @param http2_options [Hash] HTTP/2-specific settings
172
+ # @option http2_options [Integer] :max_concurrent_streams maximum HTTP/2 concurrent streams per connection
173
+ # @param access_log_io [IO, nil] IO to write Common Log Format access entries to, or nil to disable
153
174
  # @param on_error [#call, nil] callback invoked with (env, exception) when the Rack app raises
154
175
  # @return [void]
155
176
  #
156
- # @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Integer server_port, ?on_error: ^(Hash[String, untyped]?, Exception) -> void | nil) -> void
157
- def initialize: (^(Hash[String, untyped]) -> [ Integer, Hash[String, String | Array[String]], untyped ] app, Integer server_port, ?on_error: ^(Hash[String, untyped]?, Exception) -> void | nil) -> void
177
+ # @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Integer server_port, ?connection_options: Hash[Symbol, untyped], ?http2_options: Hash[Symbol, untyped], ?access_log_io: IO?, ?on_error: ^(Hash[String, untyped]?, Exception) -> void | nil) -> void
178
+ def initialize: (^(Hash[String, untyped]) -> [ Integer, Hash[String, String | Array[String]], untyped ] app, Integer server_port, ?connection_options: Hash[Symbol, untyped], ?http2_options: Hash[Symbol, untyped], ?access_log_io: IO?, ?on_error: ^(Hash[String, untyped]?, Exception) -> void | nil) -> void
158
179
 
159
- # Builds the initial server SETTINGS frame to send on connection establishment.
180
+ # Creates a per-connection {Writer} configured with the handler's write timeout.
160
181
  #
161
- # @return [String] the encoded SETTINGS frame
182
+ # @return [Writer] a new per-connection frame writer
162
183
  #
163
- # @rbs () -> String
164
- def self.build_server_settings_frame: () -> String
184
+ # @rbs () -> Writer
185
+ def create_writer: () -> Writer
165
186
 
166
187
  # Processes HTTP/2 frames from the connection buffer.
167
188
  #
@@ -271,10 +292,10 @@ module Raptor
271
292
  # @param status [Integer] HTTP status code
272
293
  # @param headers [Hash] response headers from the Rack application
273
294
  # @param body [Object] response body responding to each
274
- # @return [void]
295
+ # @return [String] the response body size in bytes
275
296
  #
276
- # @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> void
277
- 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
297
+ # @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> String
298
+ 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
278
299
 
279
300
  # Writes a 500 error response as HTTP/2 frames.
280
301
  #
@@ -286,6 +307,18 @@ module Raptor
286
307
  # @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id) -> void
287
308
  def write_http2_error_response: (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id) -> void
288
309
 
310
+ # Instance-level wrapper around {Http.write_access_log} that routes to
311
+ # the configured `@access_log_io`.
312
+ #
313
+ # @param env [Hash] the Rack environment
314
+ # @param status [Integer] the response status code
315
+ # @param size [String] the response body size in bytes, or `-` if unknown
316
+ # @param remote_addr [String] the client IP address
317
+ # @return [void]
318
+ #
319
+ # @rbs (Hash[String, untyped] env, Integer status, String size, String remote_addr) -> void
320
+ def write_access_log: (Hash[String, untyped] env, Integer status, String size, String remote_addr) -> void
321
+
289
322
  # Builds a Rack environment hash from HTTP/2 headers and body.
290
323
  #
291
324
  # Translates HTTP/2 pseudo-headers into Rack-compatible environment keys