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.
- checksums.yaml +4 -4
- data/.mise.toml +2 -0
- data/Brewfile +2 -0
- data/CHANGELOG.md +21 -0
- data/README.md +28 -25
- data/ext/raptor_http2/raptor_http2.c +1 -0
- data/lib/rackup/handler/raptor.rb +20 -21
- data/lib/raptor/cli.rb +46 -14
- data/lib/raptor/cluster.rb +142 -64
- data/lib/raptor/http2.rb +324 -42
- data/lib/raptor/log.rb +55 -0
- data/lib/raptor/reactor.rb +89 -53
- data/lib/raptor/request.rb +106 -61
- data/lib/raptor/server.rb +125 -51
- data/lib/raptor/stats.rb +30 -26
- data/lib/raptor/version.rb +1 -1
- data/sig/generated/raptor/cli.rbs +15 -1
- data/sig/generated/raptor/cluster.rbs +70 -38
- data/sig/generated/raptor/http2.rbs +126 -6
- data/sig/generated/raptor/log.rbs +41 -0
- data/sig/generated/raptor/reactor.rbs +44 -25
- data/sig/generated/raptor/request.rbs +36 -22
- data/sig/generated/raptor/server.rbs +63 -26
- data/sig/generated/raptor/stats.rbs +24 -20
- metadata +5 -1
|
@@ -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
|
|
9
|
-
# pools for
|
|
10
|
-
#
|
|
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(
|
|
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
|
-
#
|
|
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
|
-
#
|
|
28
|
+
# Semantic alias for the inherited `data` slot.
|
|
31
29
|
#
|
|
32
|
-
# @return [Hash] the client connection state
|
|
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
|
-
#
|
|
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]
|
|
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
|
-
#
|
|
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
|
|
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
|
|
89
|
-
def initialize: (untyped
|
|
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
|
-
#
|
|
125
|
-
#
|
|
126
|
-
#
|
|
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
|
|
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
|
-
#
|
|
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
|
-
#
|
|
186
|
-
#
|
|
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
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
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(
|
|
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
|
-
|
|
30
|
+
DEFAULT_REMOTE_ADDR: ::String
|
|
37
31
|
|
|
38
|
-
|
|
32
|
+
DEFAULT_SERVER_NAME: ::String
|
|
39
33
|
|
|
40
|
-
@
|
|
34
|
+
@running: AtomicBoolean
|
|
35
|
+
|
|
36
|
+
@client_options: Hash[Symbol, untyped]
|
|
41
37
|
|
|
42
38
|
@request: Request
|
|
43
39
|
|
|
44
|
-
@
|
|
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
|
|
84
|
-
#
|
|
85
|
-
#
|
|
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
|