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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c20a98b3c581180e2b9ea2e9cdd23223f4f43438b94852b0b19b8e1b659c751d
|
|
4
|
+
data.tar.gz: bc98ee07befe42ad9c2e30b4645aeac4a1fb369cc598e360a336b591a56d74b1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b065827d528422e5476827c2c01b792cd9b4a83ba38484283c33f05459a744a28a7ce88bfbe396c80fe07209893662350f205dd823a469503be208428f0cc784
|
|
7
|
+
data.tar.gz: '02583bea8f88760256318b3714b9f95cb7936b12ec0d9bb54a120108a734f965a9b85d4e556e6b8e73d7c731a43be6ddf96bd4871228f35d7bf2903df255cc84'
|
data/.mise.toml
ADDED
data/Brewfile
ADDED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.5.0] - 2026-05-31
|
|
4
|
+
|
|
5
|
+
- Apply the default `stats_file` in the Rack handler
|
|
6
|
+
- Add `phase` to per-worker stats
|
|
7
|
+
- Force-kill workers that fail to exit within `--worker-shutdown-timeout` after shutdown is signalled
|
|
8
|
+
- Kill workers that fail to check in within `--worker-timeout` or `--worker-boot-timeout`
|
|
9
|
+
- Add `index`, `busy_threads`, and `thread_capacity` to per-worker stats
|
|
10
|
+
|
|
11
|
+
## [0.4.0] - 2026-05-29
|
|
12
|
+
|
|
13
|
+
- Load `raptor.rb` or `config/raptor.rb` by default when no config path is supplied
|
|
14
|
+
- Honour the peer's HTTP/2 flow-control windows when sending `DATA` frames
|
|
15
|
+
- Assemble HEADERS across `CONTINUATION` frames
|
|
16
|
+
- Validate HTTP/2 stream IDs and emit `GOAWAY` on protocol errors
|
|
17
|
+
- Offload TLS handshakes to the thread pool to keep the server thread responsive
|
|
18
|
+
- Exit eager keep-alive loops on cluster shutdown
|
|
19
|
+
- Apply the write timeout to HTTP/2 frame writes
|
|
20
|
+
- Reject HPACK dynamic table size updates larger than 4096 bytes
|
|
21
|
+
- Reject malformed HTTP/1.1 requests with a 400 response
|
|
22
|
+
- Rescue unexpected errors in the reactor and pipeline collector
|
|
23
|
+
|
|
3
24
|
## [0.3.0] - 2026-05-25
|
|
4
25
|
|
|
5
26
|
- Load cluster options from a Ruby config file via `--config`
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Raptor
|
|
2
2
|
|
|
3
|
-
Raptor is a high-performance, preloading, multi-process, multi-threaded Ruby 4+ web server implementing Rack 3+,
|
|
3
|
+
Raptor is a high-performance, preloading, multi-process, multi-threaded Ruby 4+ web server implementing Rack 3.2+,
|
|
4
4
|
leveraging Ractors for parallel HTTP/1.1 and HTTP/2 request processing, native C extensions for HTTP parsing and HPACK
|
|
5
5
|
compression, and NIO for non-blocking I/O.
|
|
6
6
|
|
|
@@ -29,23 +29,23 @@ run proc { |_env| [200, { "content-type" => "text/plain" }, ["Hello, World!"]] }
|
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
```
|
|
32
|
-
> bundle exec raptor -
|
|
33
|
-
Raptor Cluster initializing:
|
|
34
|
-
├─ Version: 0.
|
|
35
|
-
├─ Ruby Version: ruby 4.0.
|
|
36
|
-
├─ Master PID:
|
|
37
|
-
│ └─ 4 worker processes
|
|
38
|
-
│ ├─ 1 server thread
|
|
39
|
-
│ ├─ 1 reactor thread
|
|
40
|
-
│ ├─ 1 pipeline ractor
|
|
41
|
-
│ ├─ 1 pipeline collector thread
|
|
42
|
-
│ ├─ 3 worker threads
|
|
43
|
-
│ └─ 1 stats thread
|
|
44
|
-
└─ Listening on 0.0.0.0:9292
|
|
45
|
-
[
|
|
46
|
-
[
|
|
47
|
-
[
|
|
48
|
-
[
|
|
32
|
+
> bundle exec raptor -w 4 -t 3 hello_world.ru
|
|
33
|
+
[Raptor 91348|main|main] Cluster initializing:
|
|
34
|
+
[Raptor 91348|main|main] ├─ Version: 0.5.0
|
|
35
|
+
[Raptor 91348|main|main] ├─ Ruby Version: ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +YJIT +PRISM [arm64-darwin23]
|
|
36
|
+
[Raptor 91348|main|main] ├─ Master PID: 91348
|
|
37
|
+
[Raptor 91348|main|main] │ └─ 4 worker processes
|
|
38
|
+
[Raptor 91348|main|main] │ ├─ 1 server thread
|
|
39
|
+
[Raptor 91348|main|main] │ ├─ 1 reactor thread
|
|
40
|
+
[Raptor 91348|main|main] │ ├─ 1 pipeline ractor
|
|
41
|
+
[Raptor 91348|main|main] │ ├─ 1 pipeline collector thread
|
|
42
|
+
[Raptor 91348|main|main] │ ├─ 3 worker threads
|
|
43
|
+
[Raptor 91348|main|main] │ └─ 1 stats thread
|
|
44
|
+
[Raptor 91348|main|main] └─ Listening on 0.0.0.0:9292
|
|
45
|
+
[Raptor 91350|main|main] Worker 0 booted
|
|
46
|
+
[Raptor 91351|main|main] Worker 1 booted
|
|
47
|
+
[Raptor 91352|main|main] Worker 2 booted
|
|
48
|
+
[Raptor 91353|main|main] Worker 3 booted
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
```
|
|
@@ -62,15 +62,18 @@ Also works with `rackup` and `rails server`:
|
|
|
62
62
|
|
|
63
63
|
## (Micro) Benchmarks
|
|
64
64
|
|
|
65
|
-
Raptor 0.
|
|
65
|
+
Raptor 0.5.0 vs Puma 8.0.2:
|
|
66
66
|
|
|
67
|
-
| Protocol | Raptor
|
|
68
|
-
| --------------------- |
|
|
69
|
-
| HTTP/1.1 | 20.
|
|
70
|
-
| HTTP/1.1 (keep-alive) |
|
|
71
|
-
| HTTP/2 | 22.
|
|
67
|
+
| Protocol | Raptor | Puma |
|
|
68
|
+
| --------------------- | ----------- | ----------- |
|
|
69
|
+
| HTTP/1.1 | 20.1k req/s | 20k req/s |
|
|
70
|
+
| HTTP/1.1 (keep-alive) | 61.4k req/s | 39.1k req/s |
|
|
71
|
+
| HTTP/2 | 22.8k req/s | N/A |
|
|
72
72
|
|
|
73
|
-
>
|
|
73
|
+
> ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +YJIT +PRISM [arm64-darwin23]
|
|
74
|
+
> 4 workers, 3 threads, 12 concurrent connections
|
|
75
|
+
|
|
76
|
+
See [bin/benchmark](bin/benchmark) for more details.
|
|
74
77
|
|
|
75
78
|
## Development
|
|
76
79
|
|
|
@@ -457,6 +457,7 @@ static int hpack_decode_header_block(const uint8_t *buf, size_t len,
|
|
|
457
457
|
/* dynamic table size update (RFC 7541 6.3) */
|
|
458
458
|
uint64_t new_size;
|
|
459
459
|
if (hpack_decode_int(buf, len, &pos, 5, &new_size) < 0) return -1;
|
|
460
|
+
if (new_size > HTTP2_DEFAULT_HEADER_TABLE_SIZE) return -1;
|
|
460
461
|
*max_table_size = (long)new_size;
|
|
461
462
|
*dyn_table = dynamic_table_evict(*dyn_table, *max_table_size);
|
|
462
463
|
|
|
@@ -42,9 +42,9 @@ module Rackup
|
|
|
42
42
|
{
|
|
43
43
|
"Host=HOST" => "Hostname to listen on (default: #{DEFAULT_OPTIONS[:Host]})",
|
|
44
44
|
"Port=PORT" => "Port to listen on (default: #{DEFAULT_OPTIONS[:Port]})",
|
|
45
|
-
"Threads=NUM" => "Number of threads per worker (default: 3)",
|
|
46
|
-
"Ractors=NUM" => "Number of pipeline ractors per worker (default: 1)",
|
|
47
45
|
"Workers=NUM" => "Number of worker processes (default: nprocessors)",
|
|
46
|
+
"Ractors=NUM" => "Number of pipeline ractors per worker (default: 1)",
|
|
47
|
+
"Threads=NUM" => "Number of threads per worker (default: 3)",
|
|
48
48
|
"Config=PATH" => "Load additional configuration from PATH"
|
|
49
49
|
}
|
|
50
50
|
end
|
|
@@ -69,29 +69,28 @@ module Rackup
|
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
cli_defaults = ::Raptor::CLI::DEFAULT_OPTIONS
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
binds = if options[:Host] || options[:Port]
|
|
75
|
-
host = options[:Host] || defaults[:Host]
|
|
76
|
-
port = options[:Port] || defaults[:Port]
|
|
77
|
-
["tcp://#{host}:#{port}"]
|
|
78
|
-
else
|
|
79
|
-
config[:binds] || ["tcp://#{defaults[:Host]}:#{defaults[:Port]}"]
|
|
80
|
-
end
|
|
72
|
+
config_path = options[:Config] || ::Raptor::CLI.default_config_path
|
|
73
|
+
config = config_path ? ::Raptor::CLI.load_config_file(config_path) : {}
|
|
81
74
|
|
|
82
75
|
result = {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
76
|
+
binds: if options[:Host] || options[:Port]
|
|
77
|
+
["tcp://#{options[:Host] || defaults[:Host]}:#{options[:Port] || defaults[:Port]}"]
|
|
78
|
+
else
|
|
79
|
+
config[:binds] || ["tcp://#{defaults[:Host]}:#{defaults[:Port]}"]
|
|
80
|
+
end,
|
|
87
81
|
workers: (options[:Workers] || config[:workers] || Etc.nprocessors).to_i,
|
|
88
|
-
|
|
82
|
+
ractors: (options[:Ractors] || config[:ractors] || cli_defaults[:ractors]).to_i,
|
|
83
|
+
threads: (options[:Threads] || config[:threads] || cli_defaults[:threads]).to_i,
|
|
84
|
+
app: app
|
|
89
85
|
}
|
|
90
|
-
|
|
91
|
-
[:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
86
|
+
result[:rackup] = config[:rackup] if config.key?(:rackup)
|
|
87
|
+
result[:client] = cli_defaults[:client].merge(config[:client] || {})
|
|
88
|
+
result[:worker_timeout] = (config[:worker_timeout] || cli_defaults[:worker_timeout]).to_i
|
|
89
|
+
result[:worker_boot_timeout] = (config[:worker_boot_timeout] || cli_defaults[:worker_boot_timeout]).to_i
|
|
90
|
+
result[:worker_shutdown_timeout] = (config[:worker_shutdown_timeout] || cli_defaults[:worker_shutdown_timeout]).to_i
|
|
91
|
+
result[:stats_file] = config.key?(:stats_file) ? config[:stats_file] : cli_defaults[:stats_file]
|
|
92
|
+
result[:pid_file] = config[:pid_file] if config.key?(:pid_file)
|
|
93
|
+
result[:on_error] = config[:on_error] if config.key?(:on_error)
|
|
95
94
|
result
|
|
96
95
|
end
|
|
97
96
|
private_class_method :build_cluster_options
|
data/lib/raptor/cli.rb
CHANGED
|
@@ -12,7 +12,7 @@ module Raptor
|
|
|
12
12
|
#
|
|
13
13
|
# CLI parses command-line arguments and starts the server cluster with the
|
|
14
14
|
# specified configuration options. It supports configuring the number of
|
|
15
|
-
# workers,
|
|
15
|
+
# workers, ractors, threads, bind addresses, and various client timeout
|
|
16
16
|
# settings.
|
|
17
17
|
#
|
|
18
18
|
# @example Basic usage
|
|
@@ -28,9 +28,9 @@ module Raptor
|
|
|
28
28
|
|
|
29
29
|
DEFAULT_OPTIONS = {
|
|
30
30
|
binds: ["tcp://0.0.0.0:9292"].freeze,
|
|
31
|
-
threads: 3,
|
|
32
|
-
ractors: 1,
|
|
33
31
|
workers: DEFAULT_WORKER_COUNT,
|
|
32
|
+
ractors: 1,
|
|
33
|
+
threads: 3,
|
|
34
34
|
rackup: "config.ru",
|
|
35
35
|
client: {
|
|
36
36
|
first_data_timeout: 30,
|
|
@@ -39,10 +39,15 @@ module Raptor
|
|
|
39
39
|
max_body_size: nil,
|
|
40
40
|
body_spool_threshold: 1024 * 1024,
|
|
41
41
|
},
|
|
42
|
+
worker_timeout: 60,
|
|
43
|
+
worker_boot_timeout: 60,
|
|
44
|
+
worker_shutdown_timeout: 30,
|
|
42
45
|
stats_file: "tmp/raptor.json",
|
|
43
|
-
|
|
46
|
+
pid_file: nil,
|
|
44
47
|
}.freeze
|
|
45
48
|
|
|
49
|
+
DEFAULT_CONFIG_PATHS = ["raptor.rb", "config/raptor.rb"].freeze
|
|
50
|
+
|
|
46
51
|
# Loads a configuration file and returns the hash it evaluates to.
|
|
47
52
|
#
|
|
48
53
|
# The file is evaluated at the top level so constants like `Raptor::*` resolve
|
|
@@ -61,6 +66,20 @@ module Raptor
|
|
|
61
66
|
config
|
|
62
67
|
end
|
|
63
68
|
|
|
69
|
+
# Returns the first existing path in {DEFAULT_CONFIG_PATHS} resolved
|
|
70
|
+
# against `root`, or nil if none exist.
|
|
71
|
+
#
|
|
72
|
+
# Used to pick up a project-local config file when no `-c`/`--config`
|
|
73
|
+
# flag was supplied.
|
|
74
|
+
#
|
|
75
|
+
# @param root [String] directory to resolve the default paths against
|
|
76
|
+
# @return [String, nil] the config path, or nil if no default file exists
|
|
77
|
+
#
|
|
78
|
+
# @rbs (?String root) -> String?
|
|
79
|
+
def self.default_config_path(root = Dir.pwd)
|
|
80
|
+
DEFAULT_CONFIG_PATHS.find { |path| File.exist?(File.join(root, path)) }
|
|
81
|
+
end
|
|
82
|
+
|
|
64
83
|
# @rbs @command: Symbol
|
|
65
84
|
# @rbs @options: Hash[Symbol, untyped]
|
|
66
85
|
# @rbs @parser: OptionParser
|
|
@@ -92,7 +111,7 @@ module Raptor
|
|
|
92
111
|
@options = DEFAULT_OPTIONS.dup
|
|
93
112
|
@options[:client] = @options[:client].dup
|
|
94
113
|
|
|
95
|
-
apply_config_file(extract_config_path(argv))
|
|
114
|
+
apply_config_file(extract_config_path(argv) || self.class.default_config_path)
|
|
96
115
|
|
|
97
116
|
@parser = create_parser
|
|
98
117
|
@parser.parse!(argv)
|
|
@@ -127,11 +146,12 @@ module Raptor
|
|
|
127
146
|
data = JSON.parse(File.read(stats_file), symbolize_names: true)
|
|
128
147
|
|
|
129
148
|
puts "Master PID: #{data[:master_pid]}"
|
|
130
|
-
data[:workers].
|
|
149
|
+
data[:workers].each do |worker|
|
|
131
150
|
status = worker[:booted] ? "booted" : "starting"
|
|
132
151
|
last_checkin = Time.at(worker[:last_checkin]).strftime("%H:%M:%S")
|
|
133
|
-
puts "Worker #{index}: pid=#{worker[:pid]}, requests=#{worker[:requests]}, " \
|
|
134
|
-
"
|
|
152
|
+
puts "Worker #{worker[:index]} (phase #{worker[:phase]}): pid=#{worker[:pid]}, requests=#{worker[:requests]}, " \
|
|
153
|
+
"busy=#{worker[:busy_threads]}/#{worker[:thread_capacity]}, backlog=#{worker[:backlog]}, " \
|
|
154
|
+
"#{status}, last_checkin=#{last_checkin}"
|
|
135
155
|
end
|
|
136
156
|
end
|
|
137
157
|
|
|
@@ -199,16 +219,16 @@ module Raptor
|
|
|
199
219
|
end
|
|
200
220
|
end
|
|
201
221
|
|
|
202
|
-
opts.on("-
|
|
203
|
-
@options[:
|
|
222
|
+
opts.on("-w", "--workers NUM", Integer, "Number of worker processes (default: #{DEFAULT_WORKER_COUNT})") do |num|
|
|
223
|
+
@options[:workers] = num
|
|
204
224
|
end
|
|
205
225
|
|
|
206
226
|
opts.on("-r", "--ractors NUM", Integer, "Number of ractors (default: 1)") do |num|
|
|
207
227
|
@options[:ractors] = num
|
|
208
228
|
end
|
|
209
229
|
|
|
210
|
-
opts.on("-
|
|
211
|
-
@options[:
|
|
230
|
+
opts.on("-t", "--threads NUM", Integer, "Number of threads (default: 3)") do |num|
|
|
231
|
+
@options[:threads] = num
|
|
212
232
|
end
|
|
213
233
|
|
|
214
234
|
opts.on("--first-data-timeout SECONDS", Integer, "First data timeout in seconds (default: 30)") do |timeout|
|
|
@@ -231,12 +251,24 @@ module Raptor
|
|
|
231
251
|
@options[:client][:body_spool_threshold] = bytes
|
|
232
252
|
end
|
|
233
253
|
|
|
254
|
+
opts.on("--worker-timeout SECONDS", Integer, "Worker check-in timeout in seconds (default: 60)") do |timeout|
|
|
255
|
+
@options[:worker_timeout] = timeout
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
opts.on("--worker-boot-timeout SECONDS", Integer, "Worker boot timeout in seconds (default: 60)") do |timeout|
|
|
259
|
+
@options[:worker_boot_timeout] = timeout
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
opts.on("--worker-shutdown-timeout SECONDS", Integer, "Worker shutdown timeout in seconds (default: 30)") do |timeout|
|
|
263
|
+
@options[:worker_shutdown_timeout] = timeout
|
|
264
|
+
end
|
|
265
|
+
|
|
234
266
|
opts.on("--stats-file PATH", String, "Stats file path (default: tmp/raptor.json)") do |path|
|
|
235
267
|
@options[:stats_file] = path
|
|
236
268
|
end
|
|
237
269
|
|
|
238
|
-
opts.on("--
|
|
239
|
-
@options[:
|
|
270
|
+
opts.on("--pid-file PATH", String, "PID file path (default: none)") do |path|
|
|
271
|
+
@options[:pid_file] = path
|
|
240
272
|
end
|
|
241
273
|
|
|
242
274
|
opts.on("--help", "Show this help") do
|