raptor 0.2.0 → 0.4.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,7 +33,11 @@ module Raptor
33
33
 
34
34
  STATUS_WITH_NO_ENTITY_BODY: untyped
35
35
 
36
- ERROR_RESPONSE_500: ::String
36
+ BAD_REQUEST_RESPONSE: ::String
37
+
38
+ INTERNAL_SERVER_ERROR_RESPONSE: ::String
39
+
40
+ CONTENT_TOO_LARGE_RESPONSE: ::String
37
41
 
38
42
  CONNECTION_CLOSE: ::String
39
43
 
@@ -65,15 +69,37 @@ module Raptor
65
69
 
66
70
  # Decodes a chunked transfer-encoded body buffer.
67
71
  #
68
- # Returns the decoded bytes and a flag indicating whether the terminating
69
- # zero-length chunk was found. The decoder stops at the first unparseable
70
- # boundary (incomplete CRLF) or zero-length chunk.
72
+ # Returns the decoded bytes and a state symbol: `:complete` when the
73
+ # terminating zero-length chunk was found, `:too_large` when the decoded
74
+ # size would exceed `max_size`, or `:incomplete` otherwise.
71
75
  #
72
76
  # @param buffer [String] the raw body buffer to decode
73
- # @return [Array(String, Boolean)] decoded body and completion flag
77
+ # @param max_size [Integer, nil] maximum decoded body size, or nil for unlimited
78
+ # @return [Array(String, Symbol)] decoded body and completion state
79
+ #
80
+ # @rbs (String buffer, ?Integer? max_size) -> [String, Symbol]
81
+ def self.decode_chunked: (String buffer, ?Integer? max_size) -> [ String, Symbol ]
82
+
83
+ # Writes a string to the socket, retrying on partial writes and flow control blocks.
84
+ #
85
+ # Uses write_nonblock with `WRITE_TIMEOUT` to avoid blocking the thread
86
+ # indefinitely on slow clients.
74
87
  #
75
- # @rbs (String buffer) -> [String, bool]
76
- def self.decode_chunked: (String buffer) -> [ String, bool ]
88
+ # @param socket [TCPSocket] the socket to write to
89
+ # @param string [String] the data to write
90
+ # @return [void]
91
+ # @raise [WriteError] if the socket is not writable within the timeout or raises IOError
92
+ #
93
+ # @rbs (TCPSocket socket, String string) -> void
94
+ def self.socket_write: (TCPSocket socket, String string) -> void
95
+
96
+ @running: AtomicBoolean
97
+
98
+ @on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
99
+
100
+ @body_spool_threshold: Integer?
101
+
102
+ @max_body_size: Integer?
77
103
 
78
104
  @server_port: Integer
79
105
 
@@ -83,10 +109,22 @@ module Raptor
83
109
  #
84
110
  # @param app [#call] the Rack application to dispatch complete requests to
85
111
  # @param server_port [Integer] port number used to populate SERVER_PORT in the Rack env
112
+ # @param client_options [Hash] client limits configuration
113
+ # @option client_options [Integer, nil] :max_body_size maximum request body size in bytes
114
+ # @option client_options [Integer, nil] :body_spool_threshold spool bodies larger than this to a tempfile
115
+ # @param on_error [#call, nil] callback invoked with (env, exception) when the Rack app raises
86
116
  # @return [void]
87
117
  #
88
- # @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Integer server_port) -> void
89
- def initialize: (^(Hash[String, untyped]) -> [ Integer, Hash[String, String | Array[String]], untyped ] app, Integer server_port) -> void
118
+ # @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
119
+ 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
120
+
121
+ # Signals eager keep-alive loops to stop processing further requests on
122
+ # their connections. In-flight requests complete normally.
123
+ #
124
+ # @return [void]
125
+ #
126
+ # @rbs () -> void
127
+ def shutdown: () -> void
90
128
 
91
129
  # Eagerly reads and parses the first request on a freshly accepted
92
130
  # connection on the server thread, dispatching directly to the thread pool
@@ -203,6 +241,24 @@ module Raptor
203
241
  # @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
204
242
  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
205
243
 
244
+ # Writes a 413 response and closes the socket. Used when a request body
245
+ # exceeds the configured maximum size.
246
+ #
247
+ # @param socket [TCPSocket] the client socket
248
+ # @return [void]
249
+ #
250
+ # @rbs (TCPSocket socket) -> void
251
+ def reject_oversized: (TCPSocket socket) -> void
252
+
253
+ # Writes a 400 response and closes the socket. Used when the HTTP parser
254
+ # rejects the request line or headers.
255
+ #
256
+ # @param socket [TCPSocket] the client socket
257
+ # @return [void]
258
+ #
259
+ # @rbs (TCPSocket socket) -> void
260
+ def reject_malformed: (TCPSocket socket) -> void
261
+
206
262
  # Builds a Rack environment hash from parsed HTTP request data.
207
263
  #
208
264
  # Populates all required Rack env keys including rack.* keys, REMOTE_ADDR,
@@ -219,6 +275,16 @@ module Raptor
219
275
  # @rbs (Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, String? body, TCPSocket socket, ?remote_addr: String, ?url_scheme: String) -> Hash[String, untyped]
220
276
  def build_rack_env: (Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, String? body, TCPSocket socket, ?remote_addr: String, ?url_scheme: String) -> Hash[String, untyped]
221
277
 
278
+ # Builds the `rack.input` IO object for the request body. Returns an
279
+ # in-memory StringIO for bodies up to the spool threshold, or a Tempfile
280
+ # for larger bodies to bound per-worker memory.
281
+ #
282
+ # @param body [String, nil] decoded request body
283
+ # @return [IO] an IO-like object positioned at the start of the body
284
+ #
285
+ # @rbs (String? body) -> IO
286
+ def build_rack_input: (String? body) -> IO
287
+
222
288
  # Determines whether the connection should be kept alive after the response.
223
289
  #
224
290
  # Returns false if the request limit has been reached. For HTTP/1.1, keep-alive
@@ -467,19 +533,6 @@ module Raptor
467
533
  # @rbs (Hash[String, untyped] env, Integer? status, Hash[String, String | Array[String]]? headers, Exception? error) -> void
468
534
  def call_response_finished: (Hash[String, untyped] env, Integer? status, Hash[String, String | Array[String]]? headers, Exception? error) -> void
469
535
 
470
- # Writes a string to the socket, retrying on partial writes and flow control blocks.
471
- #
472
- # Uses write_nonblock with a 5-second writable timeout to avoid blocking the
473
- # thread indefinitely on slow clients.
474
- #
475
- # @param socket [TCPSocket] the socket to write to
476
- # @param string [String] the data to write
477
- # @return [void]
478
- # @raise [WriteError] if the socket is not writable within the timeout or raises IOError
479
- #
480
- # @rbs (TCPSocket socket, String string) -> void
481
- def socket_write: (TCPSocket socket, String string) -> void
482
-
483
536
  # Enables TCP_CORK on the socket to batch outgoing packets into fewer segments.
484
537
  #
485
538
  # Only applies to TCP sockets. No-op on non-TCP sockets.
@@ -9,8 +9,8 @@ module Raptor
9
9
  # providing natural backpressure based on system capacity.
10
10
  #
11
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.
12
+ # applied only to TCP sockets, and SSL handshakes are offloaded to the thread
13
+ # pool so a slow client cannot block the server thread.
14
14
  #
15
15
  # For HTTP/1.1 connections the first request is parsed inline on the server
16
16
  # thread and dispatched directly to the thread pool, falling back to the
@@ -22,7 +22,7 @@ module Raptor
22
22
  # binder = Binder.new(["tcp://0.0.0.0:3000"])
23
23
  # reactor = Reactor.new(thread_pool, ractor_pool, client_options: {})
24
24
  # request = Request.new(app, 3000)
25
- # server = Server.new(binder, reactor, thread_pool, request)
25
+ # server = Server.new(binder, reactor, thread_pool, request, client_options: { first_data_timeout: 30 })
26
26
  # server.run
27
27
  # # ... later
28
28
  # server.shutdown
@@ -33,15 +33,17 @@ module Raptor
33
33
 
34
34
  H2_PROTOCOL: ::String
35
35
 
36
- @binder: Binder
36
+ @running: AtomicBoolean
37
37
 
38
- @reactor: Reactor
38
+ @client_options: Hash[Symbol, untyped]
39
+
40
+ @request: Request
39
41
 
40
42
  @thread_pool: AtomicThreadPool
41
43
 
42
- @request: Request
44
+ @reactor: Reactor
43
45
 
44
- @running: AtomicBoolean
46
+ @binder: Binder
45
47
 
46
48
  # Creates a new Server instance.
47
49
  #
@@ -49,10 +51,11 @@ module Raptor
49
51
  # @param reactor [Reactor] the reactor for handling client connections
50
52
  # @param thread_pool [AtomicThreadPool] thread pool for application processing
51
53
  # @param request [Request] the HTTP/1.1 request handler
54
+ # @param client_options [Hash] client timeout configuration, used to bound TLS handshakes
52
55
  # @return [void]
53
56
  #
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
57
+ # @rbs (Binder binder, Reactor reactor, AtomicThreadPool thread_pool, Request request, client_options: Hash[Symbol, untyped]) -> void
58
+ def initialize: (Binder binder, Reactor reactor, AtomicThreadPool thread_pool, Request request, client_options: Hash[Symbol, untyped]) -> void
56
59
 
57
60
  # Starts the server's main accept loop in a new thread.
58
61
  #
@@ -80,9 +83,11 @@ module Raptor
80
83
 
81
84
  # Accepts a connection from the given listener and dispatches it.
82
85
  #
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.
86
+ # For SSL listeners the TLS handshake is offloaded to the thread pool so
87
+ # a slow client cannot block the server thread. For SSL connections with
88
+ # h2 negotiated via ALPN, the server sends initial SETTINGS and adds the
89
+ # connection to the reactor as an HTTP/2 connection. All other connections
90
+ # follow the HTTP/1.1 path.
86
91
  #
87
92
  # @param listener [TCPServer, UNIXServer, Binder::SslListener] the ready listener
88
93
  # @param reactor [Reactor] the reactor to dispatch connections to
@@ -90,5 +95,39 @@ module Raptor
90
95
  #
91
96
  # @rbs (TCPServer | UNIXServer | Binder::SslListener listener, Reactor reactor) -> void
92
97
  def accept_connection: (TCPServer | UNIXServer | Binder::SslListener listener, Reactor reactor) -> void
98
+
99
+ # Performs the TLS handshake for an accepted SSL connection and dispatches
100
+ # it through the HTTP/2 or HTTP/1.1 path. The handshake is bounded by
101
+ # `:first_data_timeout` so a slow client cannot pin a worker thread.
102
+ #
103
+ # @param listener [Binder::SslListener] the SSL listener that accepted the connection
104
+ # @param tcp_client [TCPSocket] the accepted TCP socket
105
+ # @param remote_addr [String] the client's IP address
106
+ # @param reactor [Reactor] the reactor to dispatch the connection to
107
+ # @return [void]
108
+ #
109
+ # @rbs (Binder::SslListener listener, TCPSocket tcp_client, String remote_addr, Reactor reactor) -> void
110
+ def dispatch_ssl_connection: (Binder::SslListener listener, TCPSocket tcp_client, String remote_addr, Reactor reactor) -> void
111
+
112
+ # Drives a non-blocking SSL handshake to completion, bounded by the
113
+ # configured first-data timeout. Returns true on success, false on
114
+ # timeout or SSL error.
115
+ #
116
+ # @param ssl_socket [OpenSSL::SSL::SSLSocket] the SSL socket to hand-shake
117
+ # @return [Boolean] true if the handshake completed
118
+ #
119
+ # @rbs (OpenSSL::SSL::SSLSocket ssl_socket) -> bool
120
+ def perform_ssl_handshake: (OpenSSL::SSL::SSLSocket ssl_socket) -> bool
121
+
122
+ # Waits up to `deadline` for the socket to become ready for the next step
123
+ # of the SSL handshake. Closes the socket and returns false on timeout.
124
+ #
125
+ # @param ssl_socket [OpenSSL::SSL::SSLSocket] the SSL socket
126
+ # @param deadline [Float] absolute monotonic deadline
127
+ # @param direction [Symbol] either `:read` or `:write`
128
+ # @return [Boolean] true if the socket became ready before the deadline
129
+ #
130
+ # @rbs (OpenSSL::SSL::SSLSocket ssl_socket, Float deadline, Symbol direction) -> bool
131
+ def wait_for_handshake: (OpenSSL::SSL::SSLSocket ssl_socket, Float deadline, Symbol direction) -> bool
93
132
  end
94
133
  end
@@ -2,8 +2,8 @@
2
2
 
3
3
  # Main module for the Raptor web server.
4
4
  #
5
- # Raptor is a high-performance, multi-threaded, multi-process Ruby web server that
6
- # leverages Ractors for parallel HTTP/1.1 and HTTP/2 request processing, native C
7
- # extensions for HTTP parsing and HPACK compression, and NIO for non-blocking I/O.
5
+ # Raptor is a high-performance, preloading, multi-process, multi-threaded Ruby 4+ web server
6
+ # implementing Rack 3+, leveraging Ractors for parallel HTTP/1.1 and HTTP/2 request processing,
7
+ # native C extensions for HTTP parsing and HPACK compression, and NIO for non-blocking I/O.
8
8
  module Raptor
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: raptor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Young