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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/README.md +16 -16
- data/ext/raptor_http2/raptor_http2.c +1 -0
- data/lib/rackup/handler/raptor.rb +24 -11
- data/lib/raptor/binder.rb +1 -0
- data/lib/raptor/cli.rb +100 -1
- data/lib/raptor/cluster.rb +96 -26
- data/lib/raptor/http2.rb +333 -44
- data/lib/raptor/reactor.rb +67 -27
- data/lib/raptor/request.rb +196 -69
- data/lib/raptor/server.rb +112 -36
- data/lib/raptor/version.rb +1 -1
- data/lib/raptor.rb +3 -3
- data/sig/generated/raptor/cli.rbs +51 -0
- data/sig/generated/raptor/cluster.rbs +31 -3
- data/sig/generated/raptor/http2.rbs +130 -8
- data/sig/generated/raptor/reactor.rbs +22 -0
- data/sig/generated/raptor/request.rbs +75 -22
- data/sig/generated/raptor/server.rbs +51 -12
- data/sig/generated/raptor.rbs +3 -3
- metadata +1 -1
data/lib/raptor/server.rb
CHANGED
|
@@ -14,8 +14,8 @@ module Raptor
|
|
|
14
14
|
# providing natural backpressure based on system capacity.
|
|
15
15
|
#
|
|
16
16
|
# Supports TCP, Unix domain, and SSL listeners transparently. TCP_NODELAY is
|
|
17
|
-
# applied only to TCP sockets, and SSL handshakes are
|
|
18
|
-
#
|
|
17
|
+
# applied only to TCP sockets, and SSL handshakes are offloaded to the thread
|
|
18
|
+
# pool so a slow client cannot block the server thread.
|
|
19
19
|
#
|
|
20
20
|
# For HTTP/1.1 connections the first request is parsed inline on the server
|
|
21
21
|
# thread and dispatched directly to the thread pool, falling back to the
|
|
@@ -27,7 +27,7 @@ module Raptor
|
|
|
27
27
|
# binder = Binder.new(["tcp://0.0.0.0:3000"])
|
|
28
28
|
# reactor = Reactor.new(thread_pool, ractor_pool, client_options: {})
|
|
29
29
|
# request = Request.new(app, 3000)
|
|
30
|
-
# server = Server.new(binder, reactor, thread_pool, request)
|
|
30
|
+
# server = Server.new(binder, reactor, thread_pool, request, client_options: { first_data_timeout: 30 })
|
|
31
31
|
# server.run
|
|
32
32
|
# # ... later
|
|
33
33
|
# server.shutdown
|
|
@@ -41,6 +41,7 @@ module Raptor
|
|
|
41
41
|
# @rbs @reactor: Reactor
|
|
42
42
|
# @rbs @thread_pool: AtomicThreadPool
|
|
43
43
|
# @rbs @request: Request
|
|
44
|
+
# @rbs @client_options: Hash[Symbol, untyped]
|
|
44
45
|
# @rbs @running: AtomicBoolean
|
|
45
46
|
|
|
46
47
|
# Creates a new Server instance.
|
|
@@ -49,14 +50,16 @@ module Raptor
|
|
|
49
50
|
# @param reactor [Reactor] the reactor for handling client connections
|
|
50
51
|
# @param thread_pool [AtomicThreadPool] thread pool for application processing
|
|
51
52
|
# @param request [Request] the HTTP/1.1 request handler
|
|
53
|
+
# @param client_options [Hash] client timeout configuration, used to bound TLS handshakes
|
|
52
54
|
# @return [void]
|
|
53
55
|
#
|
|
54
|
-
# @rbs (Binder binder, Reactor reactor, AtomicThreadPool thread_pool, Request request) -> void
|
|
55
|
-
def initialize(binder, reactor, thread_pool, request)
|
|
56
|
+
# @rbs (Binder binder, Reactor reactor, AtomicThreadPool thread_pool, Request request, client_options: Hash[Symbol, untyped]) -> void
|
|
57
|
+
def initialize(binder, reactor, thread_pool, request, client_options:)
|
|
56
58
|
@binder = binder
|
|
57
59
|
@reactor = reactor
|
|
58
60
|
@thread_pool = thread_pool
|
|
59
61
|
@request = request
|
|
62
|
+
@client_options = client_options
|
|
60
63
|
@running = AtomicBoolean.new(true)
|
|
61
64
|
end
|
|
62
65
|
|
|
@@ -108,9 +111,11 @@ module Raptor
|
|
|
108
111
|
|
|
109
112
|
# Accepts a connection from the given listener and dispatches it.
|
|
110
113
|
#
|
|
111
|
-
# For SSL
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
+
# For SSL listeners the TLS handshake is offloaded to the thread pool so
|
|
115
|
+
# a slow client cannot block the server thread. For SSL connections with
|
|
116
|
+
# h2 negotiated via ALPN, the server sends initial SETTINGS and adds the
|
|
117
|
+
# connection to the reactor as an HTTP/2 connection. All other connections
|
|
118
|
+
# follow the HTTP/1.1 path.
|
|
114
119
|
#
|
|
115
120
|
# @param listener [TCPServer, UNIXServer, Binder::SslListener] the ready listener
|
|
116
121
|
# @param reactor [Reactor] the reactor to dispatch connections to
|
|
@@ -131,46 +136,117 @@ module Raptor
|
|
|
131
136
|
remote_addr = "127.0.0.1"
|
|
132
137
|
end
|
|
133
138
|
|
|
134
|
-
url_scheme = HTTP_SCHEME
|
|
135
|
-
client = tcp_client
|
|
136
|
-
|
|
137
139
|
if listener.is_a?(Binder::SslListener)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_client, listener.ssl_context)
|
|
141
|
-
ssl_socket.sync_close = true
|
|
142
|
-
ssl_socket.accept
|
|
143
|
-
client = ssl_socket
|
|
144
|
-
rescue OpenSSL::SSL::SSLError => error
|
|
145
|
-
warn "SSL handshake failed: #{error.message}"
|
|
146
|
-
tcp_client.close rescue nil
|
|
147
|
-
return
|
|
140
|
+
@thread_pool << proc do
|
|
141
|
+
dispatch_ssl_connection(listener, tcp_client, remote_addr, reactor)
|
|
148
142
|
end
|
|
143
|
+
return
|
|
144
|
+
end
|
|
149
145
|
|
|
150
|
-
|
|
151
|
-
|
|
146
|
+
@request.eager_accept(
|
|
147
|
+
tcp_client,
|
|
148
|
+
tcp_client.object_id,
|
|
149
|
+
reactor,
|
|
150
|
+
@thread_pool,
|
|
151
|
+
remote_addr,
|
|
152
|
+
HTTP_SCHEME
|
|
153
|
+
)
|
|
154
|
+
end
|
|
152
155
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
156
|
+
# Performs the TLS handshake for an accepted SSL connection and dispatches
|
|
157
|
+
# it through the HTTP/2 or HTTP/1.1 path. The handshake is bounded by
|
|
158
|
+
# `:first_data_timeout` so a slow client cannot pin a worker thread.
|
|
159
|
+
#
|
|
160
|
+
# @param listener [Binder::SslListener] the SSL listener that accepted the connection
|
|
161
|
+
# @param tcp_client [TCPSocket] the accepted TCP socket
|
|
162
|
+
# @param remote_addr [String] the client's IP address
|
|
163
|
+
# @param reactor [Reactor] the reactor to dispatch the connection to
|
|
164
|
+
# @return [void]
|
|
165
|
+
#
|
|
166
|
+
# @rbs (Binder::SslListener listener, TCPSocket tcp_client, String remote_addr, Reactor reactor) -> void
|
|
167
|
+
def dispatch_ssl_connection(listener, tcp_client, remote_addr, reactor)
|
|
168
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_client, listener.ssl_context)
|
|
169
|
+
ssl_socket.sync_close = true
|
|
170
|
+
return unless perform_ssl_handshake(ssl_socket)
|
|
171
|
+
|
|
172
|
+
if ssl_socket.alpn_protocol == H2_PROTOCOL
|
|
173
|
+
ssl_socket.write(Http2.build_server_settings_frame) rescue nil
|
|
174
|
+
|
|
175
|
+
reactor.add(
|
|
176
|
+
id: ssl_socket.object_id,
|
|
177
|
+
socket: ssl_socket,
|
|
178
|
+
remote_addr: remote_addr,
|
|
179
|
+
url_scheme: HTTPS_SCHEME,
|
|
180
|
+
protocol: :http2,
|
|
181
|
+
writer: Http2::Writer.new,
|
|
182
|
+
flow_control: Http2::FlowControl.new
|
|
183
|
+
)
|
|
161
184
|
|
|
162
|
-
|
|
163
|
-
end
|
|
185
|
+
return
|
|
164
186
|
end
|
|
165
187
|
|
|
166
188
|
@request.eager_accept(
|
|
167
|
-
|
|
168
|
-
|
|
189
|
+
ssl_socket,
|
|
190
|
+
ssl_socket.object_id,
|
|
169
191
|
reactor,
|
|
170
192
|
@thread_pool,
|
|
171
193
|
remote_addr,
|
|
172
|
-
|
|
194
|
+
HTTPS_SCHEME
|
|
173
195
|
)
|
|
174
196
|
end
|
|
197
|
+
|
|
198
|
+
# Drives a non-blocking SSL handshake to completion, bounded by the
|
|
199
|
+
# configured first-data timeout. Returns true on success, false on
|
|
200
|
+
# timeout or SSL error.
|
|
201
|
+
#
|
|
202
|
+
# @param ssl_socket [OpenSSL::SSL::SSLSocket] the SSL socket to hand-shake
|
|
203
|
+
# @return [Boolean] true if the handshake completed
|
|
204
|
+
#
|
|
205
|
+
# @rbs (OpenSSL::SSL::SSLSocket ssl_socket) -> bool
|
|
206
|
+
def perform_ssl_handshake(ssl_socket)
|
|
207
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @client_options[:first_data_timeout]
|
|
208
|
+
|
|
209
|
+
begin
|
|
210
|
+
ssl_socket.accept_nonblock
|
|
211
|
+
true
|
|
212
|
+
rescue IO::WaitReadable
|
|
213
|
+
return false unless wait_for_handshake(ssl_socket, deadline, :read)
|
|
214
|
+
|
|
215
|
+
retry
|
|
216
|
+
rescue IO::WaitWritable
|
|
217
|
+
return false unless wait_for_handshake(ssl_socket, deadline, :write)
|
|
218
|
+
|
|
219
|
+
retry
|
|
220
|
+
rescue OpenSSL::SSL::SSLError => error
|
|
221
|
+
warn "SSL handshake failed: #{error.message}"
|
|
222
|
+
ssl_socket.close rescue nil
|
|
223
|
+
false
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Waits up to `deadline` for the socket to become ready for the next step
|
|
228
|
+
# of the SSL handshake. Closes the socket and returns false on timeout.
|
|
229
|
+
#
|
|
230
|
+
# @param ssl_socket [OpenSSL::SSL::SSLSocket] the SSL socket
|
|
231
|
+
# @param deadline [Float] absolute monotonic deadline
|
|
232
|
+
# @param direction [Symbol] either `:read` or `:write`
|
|
233
|
+
# @return [Boolean] true if the socket became ready before the deadline
|
|
234
|
+
#
|
|
235
|
+
# @rbs (OpenSSL::SSL::SSLSocket ssl_socket, Float deadline, Symbol direction) -> bool
|
|
236
|
+
def wait_for_handshake(ssl_socket, deadline, direction)
|
|
237
|
+
remaining = deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
238
|
+
ready = if remaining <= 0
|
|
239
|
+
false
|
|
240
|
+
elsif direction == :read
|
|
241
|
+
ssl_socket.wait_readable(remaining)
|
|
242
|
+
else
|
|
243
|
+
ssl_socket.wait_writable(remaining)
|
|
244
|
+
end
|
|
245
|
+
return true if ready
|
|
246
|
+
|
|
247
|
+
warn "SSL handshake timed out"
|
|
248
|
+
ssl_socket.close rescue nil
|
|
249
|
+
false
|
|
250
|
+
end
|
|
175
251
|
end
|
|
176
252
|
end
|
data/lib/raptor/version.rb
CHANGED
data/lib/raptor.rb
CHANGED
|
@@ -5,9 +5,9 @@ require_relative "raptor/version"
|
|
|
5
5
|
|
|
6
6
|
# Main module for the Raptor web server.
|
|
7
7
|
#
|
|
8
|
-
# Raptor is a high-performance, multi-
|
|
9
|
-
#
|
|
10
|
-
# extensions for HTTP parsing and HPACK compression, and NIO for non-blocking I/O.
|
|
8
|
+
# Raptor is a high-performance, preloading, multi-process, multi-threaded Ruby 4+ web server
|
|
9
|
+
# implementing Rack 3+, leveraging Ractors for parallel HTTP/1.1 and HTTP/2 request processing,
|
|
10
|
+
# native C extensions for HTTP parsing and HPACK compression, and NIO for non-blocking I/O.
|
|
11
11
|
#
|
|
12
12
|
module Raptor
|
|
13
13
|
end
|
|
@@ -20,6 +20,33 @@ module Raptor
|
|
|
20
20
|
|
|
21
21
|
DEFAULT_OPTIONS: untyped
|
|
22
22
|
|
|
23
|
+
DEFAULT_CONFIG_PATHS: untyped
|
|
24
|
+
|
|
25
|
+
# Loads a configuration file and returns the hash it evaluates to.
|
|
26
|
+
#
|
|
27
|
+
# The file is evaluated at the top level so constants like `Raptor::*` resolve
|
|
28
|
+
# the same as in a regular Ruby script. The final expression must be a Hash
|
|
29
|
+
# of cluster options (the same keys accepted by {Raptor::Cluster#initialize}).
|
|
30
|
+
#
|
|
31
|
+
# @param path [String] path to a Ruby file that evaluates to a Hash
|
|
32
|
+
# @return [Hash{Symbol => untyped}] cluster options
|
|
33
|
+
# @raise [ArgumentError] if the file does not evaluate to a Hash
|
|
34
|
+
#
|
|
35
|
+
# @rbs (String path) -> Hash[Symbol, untyped]
|
|
36
|
+
def self.load_config_file: (String path) -> Hash[Symbol, untyped]
|
|
37
|
+
|
|
38
|
+
# Returns the first existing path in {DEFAULT_CONFIG_PATHS} resolved
|
|
39
|
+
# against `root`, or nil if none exist.
|
|
40
|
+
#
|
|
41
|
+
# Used to pick up a project-local config file when no `-c`/`--config`
|
|
42
|
+
# flag was supplied.
|
|
43
|
+
#
|
|
44
|
+
# @param root [String] directory to resolve the default paths against
|
|
45
|
+
# @return [String, nil] the config path, or nil if no default file exists
|
|
46
|
+
#
|
|
47
|
+
# @rbs (?String root) -> String?
|
|
48
|
+
def self.default_config_path: (?String root) -> String?
|
|
49
|
+
|
|
23
50
|
@command: Symbol
|
|
24
51
|
|
|
25
52
|
@options: Hash[Symbol, untyped]
|
|
@@ -61,6 +88,30 @@ module Raptor
|
|
|
61
88
|
# @rbs () -> void
|
|
62
89
|
def run_stats: () -> void
|
|
63
90
|
|
|
91
|
+
# Scans argv for a `-c`/`--config` flag and returns the configured path.
|
|
92
|
+
#
|
|
93
|
+
# The pre-scan runs before the main OptionParser pass so the config file
|
|
94
|
+
# can be applied as a base layer that CLI args then override. All four
|
|
95
|
+
# OptionParser-accepted forms (`-c PATH`, `-cPATH`, `--config PATH`,
|
|
96
|
+
# `--config=PATH`) are recognized.
|
|
97
|
+
#
|
|
98
|
+
# @param argv [Array<String>] command-line arguments to scan
|
|
99
|
+
# @return [String, nil] the config path, or nil if no flag was supplied
|
|
100
|
+
#
|
|
101
|
+
# @rbs (Array[String] argv) -> String?
|
|
102
|
+
def extract_config_path: (Array[String] argv) -> String?
|
|
103
|
+
|
|
104
|
+
# Loads a config file and merges it into `@options` over the defaults.
|
|
105
|
+
#
|
|
106
|
+
# Top-level keys replace defaults; the nested `:client` hash is merged
|
|
107
|
+
# key-by-key so a config file does not need to restate every client option.
|
|
108
|
+
#
|
|
109
|
+
# @param path [String, nil] path to the config file, or nil to no-op
|
|
110
|
+
# @return [void]
|
|
111
|
+
#
|
|
112
|
+
# @rbs (String? path) -> void
|
|
113
|
+
def apply_config_file: (String? path) -> void
|
|
114
|
+
|
|
64
115
|
# Creates the OptionParser instance with all supported command-line options.
|
|
65
116
|
#
|
|
66
117
|
# @return [OptionParser] configured option parser
|
|
@@ -37,7 +37,9 @@ module Raptor
|
|
|
37
37
|
# @rbs (Hash[Symbol, untyped] options) -> void
|
|
38
38
|
def self.run: (Hash[Symbol, untyped] options) -> void
|
|
39
39
|
|
|
40
|
-
@
|
|
40
|
+
@phased_restarting: bool
|
|
41
|
+
|
|
42
|
+
@phased_restart_requested: bool
|
|
41
43
|
|
|
42
44
|
@stats: Stats
|
|
43
45
|
|
|
@@ -51,6 +53,12 @@ module Raptor
|
|
|
51
53
|
|
|
52
54
|
@binder: Binder
|
|
53
55
|
|
|
56
|
+
@pid_file: String?
|
|
57
|
+
|
|
58
|
+
@stats_file: String?
|
|
59
|
+
|
|
60
|
+
@on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
|
|
61
|
+
|
|
54
62
|
@client_options: Hash[Symbol, Integer]
|
|
55
63
|
|
|
56
64
|
@worker_count: Integer
|
|
@@ -72,7 +80,10 @@ module Raptor
|
|
|
72
80
|
# @option options [Array<String>] :binds array of bind URIs
|
|
73
81
|
# @option options [#call] :app pre-built Rack application
|
|
74
82
|
# @option options [String] :rackup path to Rack configuration file
|
|
75
|
-
# @option options [Hash] :client client
|
|
83
|
+
# @option options [Hash] :client client configuration
|
|
84
|
+
# @option options [#call] :on_error callback invoked with (env, exception) when the Rack app raises
|
|
85
|
+
# @option options [String, nil] :stats_file path to write per-worker stats JSON, or nil to disable
|
|
86
|
+
# @option options [String, nil] :pid_file path to write the master PID to, or nil to disable
|
|
76
87
|
# @return [void]
|
|
77
88
|
#
|
|
78
89
|
# @rbs (Hash[Symbol, untyped] options) -> void
|
|
@@ -82,7 +93,8 @@ module Raptor
|
|
|
82
93
|
#
|
|
83
94
|
# Forks the configured number of worker processes and monitors them,
|
|
84
95
|
# automatically restarting any that exit unexpectedly. Handles graceful
|
|
85
|
-
# shutdown via INT or TERM signals,
|
|
96
|
+
# shutdown via INT or TERM signals, stats logging via USR1, and phased
|
|
97
|
+
# restart via USR2.
|
|
86
98
|
#
|
|
87
99
|
# Each worker process includes:
|
|
88
100
|
# - 1 server thread (continuously accepts connections with backpressure control)
|
|
@@ -115,6 +127,22 @@ module Raptor
|
|
|
115
127
|
# @rbs (Integer index) -> void
|
|
116
128
|
def spawn_worker: (Integer index) -> void
|
|
117
129
|
|
|
130
|
+
# Reaps any worker processes that have exited, respawning each one
|
|
131
|
+
# unless the cluster is shutting down.
|
|
132
|
+
#
|
|
133
|
+
# @return [Symbol] :no_children when there are no remaining children, otherwise :reaped
|
|
134
|
+
#
|
|
135
|
+
# @rbs () -> Symbol
|
|
136
|
+
def reap_workers: () -> Symbol
|
|
137
|
+
|
|
138
|
+
# Replaces each worker process one at a time, waiting for the new
|
|
139
|
+
# worker to boot before moving on to the next. Triggered by SIGUSR2.
|
|
140
|
+
#
|
|
141
|
+
# @return [void]
|
|
142
|
+
#
|
|
143
|
+
# @rbs () -> void
|
|
144
|
+
def perform_phased_restart: () -> void
|
|
145
|
+
|
|
118
146
|
# Runs the full server stack inside a worker process.
|
|
119
147
|
#
|
|
120
148
|
# Sets up and coordinates the reactor, server, ractor pool, thread pool,
|
|
@@ -33,6 +33,84 @@ 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. State is held in a single `Atom` so updates use CAS.
|
|
43
|
+
class FlowControl
|
|
44
|
+
ACQUIRE_POLL_INTERVAL: ::Float
|
|
45
|
+
|
|
46
|
+
@connection_window: Atom
|
|
47
|
+
|
|
48
|
+
@stream_windows: Atom
|
|
49
|
+
|
|
50
|
+
@initial_stream_window: Atom
|
|
51
|
+
|
|
52
|
+
# Creates a new FlowControl with the spec-default windows.
|
|
53
|
+
#
|
|
54
|
+
# @rbs () -> void
|
|
55
|
+
def initialize: () -> void
|
|
56
|
+
|
|
57
|
+
# Reserves outbound capacity on the given stream, polling until at
|
|
58
|
+
# least one byte is available on both the connection and stream
|
|
59
|
+
# windows. The returned size is capped at `MAX_FRAME_SIZE`.
|
|
60
|
+
#
|
|
61
|
+
# When `end_stream` is true, `max_bytes` fits within the peer's
|
|
62
|
+
# initial stream window, and no per-stream override has been
|
|
63
|
+
# recorded, only the connection window is consulted. The stream
|
|
64
|
+
# closes on this frame, so its remaining send window will not be
|
|
65
|
+
# consulted again and need not be tracked.
|
|
66
|
+
#
|
|
67
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
68
|
+
# @param max_bytes [Integer] the largest size the caller would like to send
|
|
69
|
+
# @param end_stream [Boolean] true when this is the final frame on the stream
|
|
70
|
+
# @return [Integer] the number of bytes the caller may now send
|
|
71
|
+
#
|
|
72
|
+
# @rbs (Integer stream_id, Integer max_bytes, ?end_stream: bool) -> Integer
|
|
73
|
+
def acquire: (Integer stream_id, Integer max_bytes, ?end_stream: bool) -> Integer
|
|
74
|
+
|
|
75
|
+
# Increments the connection-level send window. Called when the peer
|
|
76
|
+
# sends a WINDOW_UPDATE on stream 0.
|
|
77
|
+
#
|
|
78
|
+
# @param increment [Integer] the byte count to add
|
|
79
|
+
# @return [void]
|
|
80
|
+
#
|
|
81
|
+
# @rbs (Integer increment) -> void
|
|
82
|
+
def add_connection_window: (Integer increment) -> void
|
|
83
|
+
|
|
84
|
+
# Increments the per-stream send window. Called when the peer sends
|
|
85
|
+
# a WINDOW_UPDATE on a specific stream.
|
|
86
|
+
#
|
|
87
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
88
|
+
# @param increment [Integer] the byte count to add
|
|
89
|
+
# @return [void]
|
|
90
|
+
#
|
|
91
|
+
# @rbs (Integer stream_id, Integer increment) -> void
|
|
92
|
+
def add_stream_window: (Integer stream_id, Integer increment) -> void
|
|
93
|
+
|
|
94
|
+
# Updates the peer's `SETTINGS_INITIAL_WINDOW_SIZE`. Shifts every
|
|
95
|
+
# existing stream window by the delta as required by RFC 7540 §6.9.2.
|
|
96
|
+
#
|
|
97
|
+
# @param new_size [Integer] the peer's new initial window size
|
|
98
|
+
# @return [void]
|
|
99
|
+
#
|
|
100
|
+
# @rbs (Integer new_size) -> void
|
|
101
|
+
def set_initial_stream_window: (Integer new_size) -> void
|
|
102
|
+
|
|
103
|
+
# Discards any per-stream tracking for the given stream. Called
|
|
104
|
+
# after a stream closes so `@stream_windows` does not grow without
|
|
105
|
+
# bound across the lifetime of a connection.
|
|
106
|
+
#
|
|
107
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
108
|
+
# @return [void]
|
|
109
|
+
#
|
|
110
|
+
# @rbs (Integer stream_id) -> void
|
|
111
|
+
def discard_stream: (Integer stream_id) -> void
|
|
112
|
+
end
|
|
113
|
+
|
|
36
114
|
FLAG_END_STREAM: ::Integer
|
|
37
115
|
|
|
38
116
|
FLAG_END_HEADERS: ::Integer
|
|
@@ -41,12 +119,22 @@ module Raptor
|
|
|
41
119
|
|
|
42
120
|
FLAG_PRIORITY: ::Integer
|
|
43
121
|
|
|
122
|
+
ERROR_NO_ERROR: ::Integer
|
|
123
|
+
|
|
124
|
+
ERROR_PROTOCOL_ERROR: ::Integer
|
|
125
|
+
|
|
126
|
+
DEFAULT_WINDOW_SIZE: ::Integer
|
|
127
|
+
|
|
128
|
+
MAX_FRAME_SIZE: ::Integer
|
|
129
|
+
|
|
44
130
|
SERVER_PROTOCOL: ::String
|
|
45
131
|
|
|
46
132
|
RACK_HEADER_PREFIX: ::String
|
|
47
133
|
|
|
48
134
|
HOP_BY_HOP_HEADERS: untyped
|
|
49
135
|
|
|
136
|
+
@on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
|
|
137
|
+
|
|
50
138
|
@server_port: Integer
|
|
51
139
|
|
|
52
140
|
@app: ^(Hash[String, untyped]) -> [ Integer, Hash[String, String | Array[String]], untyped ]
|
|
@@ -55,10 +143,11 @@ module Raptor
|
|
|
55
143
|
#
|
|
56
144
|
# @param app [#call] the Rack application to dispatch requests to
|
|
57
145
|
# @param server_port [Integer] port number used to populate SERVER_PORT in the Rack env
|
|
146
|
+
# @param on_error [#call, nil] callback invoked with (env, exception) when the Rack app raises
|
|
58
147
|
# @return [void]
|
|
59
148
|
#
|
|
60
|
-
# @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Integer server_port) -> void
|
|
61
|
-
def initialize: (^(Hash[String, untyped]) -> [ Integer, Hash[String, String | Array[String]], untyped ] app, Integer server_port) -> void
|
|
149
|
+
# @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Integer server_port, ?on_error: ^(Hash[String, untyped]?, Exception) -> void | nil) -> void
|
|
150
|
+
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
|
|
62
151
|
|
|
63
152
|
# Builds the initial server SETTINGS frame to send on connection establishment.
|
|
64
153
|
#
|
|
@@ -79,6 +168,19 @@ module Raptor
|
|
|
79
168
|
# @rbs (Hash[Symbol, untyped] data) -> Hash[Symbol, untyped]
|
|
80
169
|
def self.process_frames: (Hash[Symbol, untyped] data) -> Hash[Symbol, untyped]
|
|
81
170
|
|
|
171
|
+
# Merges a decoded header block into the stream's accumulated state,
|
|
172
|
+
# promoting the stream to `completed_requests` when END_STREAM is set.
|
|
173
|
+
#
|
|
174
|
+
# @param streams [Hash] current open-stream map
|
|
175
|
+
# @param completed_requests [Array<Hash>] accumulator of completed stream requests
|
|
176
|
+
# @param stream_id [Integer] the stream identifier
|
|
177
|
+
# @param decoded_headers [Array<Array(String, String)>] decoded header pairs
|
|
178
|
+
# @param end_stream [Boolean] whether the source frame had END_STREAM set
|
|
179
|
+
# @return [Array(Hash, Array<Hash>)] updated streams and completed_requests
|
|
180
|
+
#
|
|
181
|
+
# @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]]]
|
|
182
|
+
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]] ]
|
|
183
|
+
|
|
82
184
|
# Builds a frozen result hash from the current processing state.
|
|
83
185
|
#
|
|
84
186
|
# @param data [Hash] original connection state
|
|
@@ -87,12 +189,17 @@ module Raptor
|
|
|
87
189
|
# @param streams [Hash] updated stream states
|
|
88
190
|
# @param outgoing_frames [Array<String>] frames to write to the socket
|
|
89
191
|
# @param completed_requests [Array<Hash>] fully received stream requests
|
|
192
|
+
# @param window_updates [Array<Array(Integer, Integer)>] inbound WINDOW_UPDATE pairs as [stream_id, increment]
|
|
193
|
+
# @param peer_initial_window_size [Integer, nil] new SETTINGS_INITIAL_WINDOW_SIZE announced by the peer
|
|
90
194
|
# @param connection_window [Integer] current connection flow control window
|
|
91
195
|
# @param preface_received [Boolean] whether the connection preface has been received
|
|
196
|
+
# @param last_client_stream_id [Integer] highest client-initiated stream ID seen
|
|
197
|
+
# @param pending_headers [Hash, nil] in-progress HEADERS+CONTINUATION assembly
|
|
198
|
+
# @param close_connection [Boolean] whether the connection should be closed after writing outgoing frames
|
|
92
199
|
# @return [Hash] frozen result hash
|
|
93
200
|
#
|
|
94
|
-
# @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]
|
|
95
|
-
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]
|
|
201
|
+
# @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]
|
|
202
|
+
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]
|
|
96
203
|
|
|
97
204
|
# Handles a parsed HTTP/2 request from the ractor pool.
|
|
98
205
|
#
|
|
@@ -109,32 +216,47 @@ module Raptor
|
|
|
109
216
|
|
|
110
217
|
private
|
|
111
218
|
|
|
219
|
+
# Applies inbound flow-control updates from a parsed result to the
|
|
220
|
+
# connection's `FlowControl`.
|
|
221
|
+
#
|
|
222
|
+
# @param flow_control [FlowControl] the per-connection flow controller
|
|
223
|
+
# @param result [Hash] the parsed result from `process_frames`
|
|
224
|
+
# @return [void]
|
|
225
|
+
#
|
|
226
|
+
# @rbs (FlowControl flow_control, Hash[Symbol, untyped] result) -> void
|
|
227
|
+
def apply_flow_control_updates: (FlowControl flow_control, Hash[Symbol, untyped] result) -> void
|
|
228
|
+
|
|
112
229
|
# Dispatches a completed stream request to the Rack app and writes
|
|
113
230
|
# the response back as HTTP/2 frames.
|
|
114
231
|
#
|
|
115
232
|
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
116
233
|
# @param writer [Writer] lock-free frame writer for the connection
|
|
234
|
+
# @param flow_control [FlowControl] per-connection outbound flow controller
|
|
117
235
|
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
118
236
|
# @param headers [Array<Array(String, String)>] request headers
|
|
119
237
|
# @param body [String] request body
|
|
120
238
|
# @param remote_addr [String] the client IP address
|
|
121
239
|
# @return [void]
|
|
122
240
|
#
|
|
123
|
-
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id, Array[[String, String]] headers, String body, remote_addr: String) -> void
|
|
124
|
-
def dispatch_stream_request: (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id, Array[[ String, String ]] headers, String body, remote_addr: String) -> void
|
|
241
|
+
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Array[[String, String]] headers, String body, remote_addr: String) -> void
|
|
242
|
+
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
|
|
125
243
|
|
|
126
244
|
# Writes a Rack response as HTTP/2 frames to the socket.
|
|
127
245
|
#
|
|
246
|
+
# DATA frames are partitioned through `flow_control` so each write fits
|
|
247
|
+
# within the peer's per-stream and connection windows.
|
|
248
|
+
#
|
|
128
249
|
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
129
250
|
# @param writer [Writer] lock-free frame writer for the connection
|
|
251
|
+
# @param flow_control [FlowControl] per-connection outbound flow controller
|
|
130
252
|
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
131
253
|
# @param status [Integer] HTTP status code
|
|
132
254
|
# @param headers [Hash] response headers from the Rack application
|
|
133
255
|
# @param body [Object] response body responding to each
|
|
134
256
|
# @return [void]
|
|
135
257
|
#
|
|
136
|
-
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> void
|
|
137
|
-
def write_http2_response: (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> void
|
|
258
|
+
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> void
|
|
259
|
+
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
|
|
138
260
|
|
|
139
261
|
# Writes a 500 error response as HTTP/2 frames.
|
|
140
262
|
#
|
|
@@ -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]
|
|
@@ -169,6 +171,16 @@ module Raptor
|
|
|
169
171
|
# @rbs (Integer id) -> untyped?
|
|
170
172
|
def writer_for: (Integer id) -> untyped?
|
|
171
173
|
|
|
174
|
+
# Returns the flow controller associated with a given connection, if one
|
|
175
|
+
# was supplied when the connection was added. Used by HTTP/2 stream
|
|
176
|
+
# dispatchers to honour the peer's flow-control windows.
|
|
177
|
+
#
|
|
178
|
+
# @param id [Integer] unique client identifier
|
|
179
|
+
# @return [Object, nil] the flow controller, if found
|
|
180
|
+
#
|
|
181
|
+
# @rbs (Integer id) -> untyped?
|
|
182
|
+
def flow_control_for: (Integer id) -> untyped?
|
|
183
|
+
|
|
172
184
|
# Updates connection state for an HTTP/2 connection after frame processing.
|
|
173
185
|
#
|
|
174
186
|
# Re-registers the socket with the selector for further reads and stores
|
|
@@ -180,6 +192,16 @@ module Raptor
|
|
|
180
192
|
# @rbs (Hash[Symbol, untyped] state) -> void
|
|
181
193
|
def update_http2_state: (Hash[Symbol, untyped] state) -> void
|
|
182
194
|
|
|
195
|
+
# Closes the socket for the given connection and drops all reactor state
|
|
196
|
+
# associated with it. Used to terminate HTTP/2 connections after sending
|
|
197
|
+
# a GOAWAY frame.
|
|
198
|
+
#
|
|
199
|
+
# @param id [Integer] unique client identifier
|
|
200
|
+
# @return [void]
|
|
201
|
+
#
|
|
202
|
+
# @rbs (Integer id) -> void
|
|
203
|
+
def close_connection: (Integer id) -> void
|
|
204
|
+
|
|
183
205
|
# Initiates reactor shutdown.
|
|
184
206
|
#
|
|
185
207
|
# Closes the registration queue and wakes up the selector to begin
|