raptor 0.3.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 +13 -0
- data/README.md +13 -13
- data/ext/raptor_http2/raptor_http2.c +1 -0
- data/lib/rackup/handler/raptor.rb +3 -2
- data/lib/raptor/cli.rb +20 -4
- data/lib/raptor/cluster.rb +16 -10
- data/lib/raptor/http2.rb +321 -40
- data/lib/raptor/reactor.rb +67 -27
- data/lib/raptor/request.rb +100 -49
- data/lib/raptor/server.rb +112 -36
- data/lib/raptor/version.rb +1 -1
- data/sig/generated/raptor/cli.rbs +14 -0
- data/sig/generated/raptor/cluster.rbs +2 -2
- data/sig/generated/raptor/http2.rbs +125 -6
- data/sig/generated/raptor/reactor.rbs +22 -0
- data/sig/generated/raptor/request.rbs +34 -13
- data/sig/generated/raptor/server.rbs +51 -12
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a9d82dc0869e270278088d312d03a6ed914e494da5fb6b7bf6e41c03257ddf41
|
|
4
|
+
data.tar.gz: c113ab336b5ad9e5e72095e79af4702b5adb0efb46117ba43b6879fe2e4a728c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 365e09dbfcc7cbb265b8ac6569a66e66a0629ecfb4fd176164cb4c55b6f0223ef05f3e17f3318f7ec6cd58be94396996885b232c496e46620f42049ca9a1fe84
|
|
7
|
+
data.tar.gz: d6d2b9b18cd0da790c2f13fce8994f799bac41e94270f9c69f4b57241f6931d98a8ef81573e8257d1766dfde0a63807d2f06cf57fa4a44ef8f754533b733aeff
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.4.0] - 2026-05-29
|
|
4
|
+
|
|
5
|
+
- Load `raptor.rb` or `config/raptor.rb` by default when no config path is supplied
|
|
6
|
+
- Honour the peer's HTTP/2 flow-control windows when sending `DATA` frames
|
|
7
|
+
- Assemble HEADERS across `CONTINUATION` frames
|
|
8
|
+
- Validate HTTP/2 stream IDs and emit `GOAWAY` on protocol errors
|
|
9
|
+
- Offload TLS handshakes to the thread pool to keep the server thread responsive
|
|
10
|
+
- Exit eager keep-alive loops on cluster shutdown
|
|
11
|
+
- Apply the write timeout to HTTP/2 frame writes
|
|
12
|
+
- Reject HPACK dynamic table size updates larger than 4096 bytes
|
|
13
|
+
- Reject malformed HTTP/1.1 requests with a 400 response
|
|
14
|
+
- Rescue unexpected errors in the reactor and pipeline collector
|
|
15
|
+
|
|
3
16
|
## [0.3.0] - 2026-05-25
|
|
4
17
|
|
|
5
18
|
- 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
|
|
|
@@ -31,9 +31,9 @@ run proc { |_env| [200, { "content-type" => "text/plain" }, ["Hello, World!"]] }
|
|
|
31
31
|
```
|
|
32
32
|
> bundle exec raptor -t 3 -w 4 hello_world.ru
|
|
33
33
|
Raptor Cluster initializing:
|
|
34
|
-
├─ Version: 0.
|
|
35
|
-
├─ Ruby Version: ruby 4.0.
|
|
36
|
-
├─ Master PID:
|
|
34
|
+
├─ Version: 0.4.0
|
|
35
|
+
├─ Ruby Version: ruby 4.0.5 (2026-05-20 revision 64336ffd0e) +YJIT +PRISM [arm64-darwin23]
|
|
36
|
+
├─ Master PID: 26456
|
|
37
37
|
│ └─ 4 worker processes
|
|
38
38
|
│ ├─ 1 server thread
|
|
39
39
|
│ ├─ 1 reactor thread
|
|
@@ -42,10 +42,10 @@ Raptor Cluster initializing:
|
|
|
42
42
|
│ ├─ 3 worker threads
|
|
43
43
|
│ └─ 1 stats thread
|
|
44
44
|
└─ Listening on 0.0.0.0:9292
|
|
45
|
-
[
|
|
46
|
-
[
|
|
47
|
-
[
|
|
48
|
-
[
|
|
45
|
+
[26459] Worker 0 booted
|
|
46
|
+
[26460] Worker 1 booted
|
|
47
|
+
[26461] Worker 2 booted
|
|
48
|
+
[26462] Worker 3 booted
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
```
|
|
@@ -62,15 +62,15 @@ Also works with `rackup` and `rails server`:
|
|
|
62
62
|
|
|
63
63
|
## (Micro) Benchmarks
|
|
64
64
|
|
|
65
|
-
Raptor 0.
|
|
65
|
+
Raptor 0.4.0 vs Puma 8.0.1:
|
|
66
66
|
|
|
67
67
|
| Protocol | Raptor | Puma |
|
|
68
68
|
| --------------------- | ------------ | ------------ |
|
|
69
|
-
| HTTP/1.1 |
|
|
70
|
-
| HTTP/1.1 (keep-alive) |
|
|
71
|
-
| HTTP/2 |
|
|
69
|
+
| HTTP/1.1 | ~20k req/s | ~21k req/s |
|
|
70
|
+
| HTTP/1.1 (keep-alive) | ~60k req/s | ~45k req/s |
|
|
71
|
+
| HTTP/2 | ~23k req/s | N/A |
|
|
72
72
|
|
|
73
|
-
> Ruby 4.0.
|
|
73
|
+
> Ruby 4.0.5 +YJIT, macOS Apple Silicon. 4 workers, 3 threads, 12 concurrent connections.
|
|
74
74
|
|
|
75
75
|
## Development
|
|
76
76
|
|
|
@@ -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
|
|
|
@@ -69,7 +69,8 @@ module Rackup
|
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
cli_defaults = ::Raptor::CLI::DEFAULT_OPTIONS
|
|
72
|
-
|
|
72
|
+
config_path = options[:Config] || ::Raptor::CLI.default_config_path
|
|
73
|
+
config = config_path ? ::Raptor::CLI.load_config_file(config_path) : {}
|
|
73
74
|
|
|
74
75
|
binds = if options[:Host] || options[:Port]
|
|
75
76
|
host = options[:Host] || defaults[:Host]
|
|
@@ -88,7 +89,7 @@ module Rackup
|
|
|
88
89
|
client: cli_defaults[:client].merge(config[:client] || {})
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
[:rackup, :on_error, :stats_file, :
|
|
92
|
+
[:rackup, :on_error, :stats_file, :pid_file].each do |key|
|
|
92
93
|
result[key] = config[key] if config.key?(key)
|
|
93
94
|
end
|
|
94
95
|
|
data/lib/raptor/cli.rb
CHANGED
|
@@ -40,9 +40,11 @@ module Raptor
|
|
|
40
40
|
body_spool_threshold: 1024 * 1024,
|
|
41
41
|
},
|
|
42
42
|
stats_file: "tmp/raptor.json",
|
|
43
|
-
|
|
43
|
+
pid_file: nil,
|
|
44
44
|
}.freeze
|
|
45
45
|
|
|
46
|
+
DEFAULT_CONFIG_PATHS = ["raptor.rb", "config/raptor.rb"].freeze
|
|
47
|
+
|
|
46
48
|
# Loads a configuration file and returns the hash it evaluates to.
|
|
47
49
|
#
|
|
48
50
|
# The file is evaluated at the top level so constants like `Raptor::*` resolve
|
|
@@ -61,6 +63,20 @@ module Raptor
|
|
|
61
63
|
config
|
|
62
64
|
end
|
|
63
65
|
|
|
66
|
+
# Returns the first existing path in {DEFAULT_CONFIG_PATHS} resolved
|
|
67
|
+
# against `root`, or nil if none exist.
|
|
68
|
+
#
|
|
69
|
+
# Used to pick up a project-local config file when no `-c`/`--config`
|
|
70
|
+
# flag was supplied.
|
|
71
|
+
#
|
|
72
|
+
# @param root [String] directory to resolve the default paths against
|
|
73
|
+
# @return [String, nil] the config path, or nil if no default file exists
|
|
74
|
+
#
|
|
75
|
+
# @rbs (?String root) -> String?
|
|
76
|
+
def self.default_config_path(root = Dir.pwd)
|
|
77
|
+
DEFAULT_CONFIG_PATHS.find { |path| File.exist?(File.join(root, path)) }
|
|
78
|
+
end
|
|
79
|
+
|
|
64
80
|
# @rbs @command: Symbol
|
|
65
81
|
# @rbs @options: Hash[Symbol, untyped]
|
|
66
82
|
# @rbs @parser: OptionParser
|
|
@@ -92,7 +108,7 @@ module Raptor
|
|
|
92
108
|
@options = DEFAULT_OPTIONS.dup
|
|
93
109
|
@options[:client] = @options[:client].dup
|
|
94
110
|
|
|
95
|
-
apply_config_file(extract_config_path(argv))
|
|
111
|
+
apply_config_file(extract_config_path(argv) || self.class.default_config_path)
|
|
96
112
|
|
|
97
113
|
@parser = create_parser
|
|
98
114
|
@parser.parse!(argv)
|
|
@@ -235,8 +251,8 @@ module Raptor
|
|
|
235
251
|
@options[:stats_file] = path
|
|
236
252
|
end
|
|
237
253
|
|
|
238
|
-
opts.on("--
|
|
239
|
-
@options[:
|
|
254
|
+
opts.on("--pid-file PATH", String, "PID file path (default: none)") do |path|
|
|
255
|
+
@options[:pid_file] = path
|
|
240
256
|
end
|
|
241
257
|
|
|
242
258
|
opts.on("--help", "Show this help") do
|
data/lib/raptor/cluster.rb
CHANGED
|
@@ -60,7 +60,7 @@ module Raptor
|
|
|
60
60
|
# @rbs @client_options: Hash[Symbol, Integer]
|
|
61
61
|
# @rbs @on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
|
|
62
62
|
# @rbs @stats_file: String?
|
|
63
|
-
# @rbs @
|
|
63
|
+
# @rbs @pid_file: String?
|
|
64
64
|
# @rbs @binder: Binder
|
|
65
65
|
# @rbs @server_port: Integer
|
|
66
66
|
# @rbs @app: untyped
|
|
@@ -86,7 +86,7 @@ module Raptor
|
|
|
86
86
|
# @option options [Hash] :client client configuration
|
|
87
87
|
# @option options [#call] :on_error callback invoked with (env, exception) when the Rack app raises
|
|
88
88
|
# @option options [String, nil] :stats_file path to write per-worker stats JSON, or nil to disable
|
|
89
|
-
# @option options [String, nil] :
|
|
89
|
+
# @option options [String, nil] :pid_file path to write the master PID to, or nil to disable
|
|
90
90
|
# @return [void]
|
|
91
91
|
#
|
|
92
92
|
# @rbs (Hash[Symbol, untyped] options) -> void
|
|
@@ -97,7 +97,7 @@ module Raptor
|
|
|
97
97
|
@client_options = options[:client]
|
|
98
98
|
@on_error = options[:on_error]
|
|
99
99
|
@stats_file = options[:stats_file]
|
|
100
|
-
@
|
|
100
|
+
@pid_file = options[:pid_file]
|
|
101
101
|
|
|
102
102
|
@binder = Binder.new(options[:binds])
|
|
103
103
|
@server_port = @binder.server_port
|
|
@@ -135,7 +135,7 @@ module Raptor
|
|
|
135
135
|
trap("USR1") { log_stats }
|
|
136
136
|
trap("USR2") { @phased_restart_requested = true }
|
|
137
137
|
|
|
138
|
-
File.open(@
|
|
138
|
+
File.open(@pid_file, File::CREAT | File::EXCL | File::WRONLY) { |file| file.write(Process.pid.to_s) } if @pid_file
|
|
139
139
|
|
|
140
140
|
@worker_count.times { |index| spawn_worker(index) }
|
|
141
141
|
|
|
@@ -159,7 +159,7 @@ module Raptor
|
|
|
159
159
|
@workers.values.each { |pid| Process.wait(pid) rescue nil }
|
|
160
160
|
stats_file_thread&.join
|
|
161
161
|
File.delete(@stats_file) rescue nil if @stats_file
|
|
162
|
-
File.delete(@
|
|
162
|
+
File.delete(@pid_file) rescue nil if @pid_file
|
|
163
163
|
@stats.unmap
|
|
164
164
|
end
|
|
165
165
|
|
|
@@ -288,17 +288,22 @@ module Raptor
|
|
|
288
288
|
size: @ractor_count,
|
|
289
289
|
worker: request.http_parser_worker
|
|
290
290
|
) do |parsed_result|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
291
|
+
begin
|
|
292
|
+
if parsed_result[:protocol] == :http2
|
|
293
|
+
http2.handle_parsed_request(parsed_result, reactor, thread_pool)
|
|
294
|
+
else
|
|
295
|
+
request.handle_parsed_request(parsed_result, reactor, thread_pool)
|
|
296
|
+
end
|
|
297
|
+
rescue => error
|
|
298
|
+
warn "#{Thread.current.name} rescued:"
|
|
299
|
+
warn error.full_message
|
|
295
300
|
end
|
|
296
301
|
end
|
|
297
302
|
|
|
298
303
|
reactor = Reactor.new(thread_pool, ractor_pool, client_options: @client_options)
|
|
299
304
|
reactor_thread = reactor.run
|
|
300
305
|
|
|
301
|
-
server = Server.new(@binder, reactor, thread_pool, request)
|
|
306
|
+
server = Server.new(@binder, reactor, thread_pool, request, client_options: @client_options)
|
|
302
307
|
server_thread = server.run
|
|
303
308
|
|
|
304
309
|
puts "[#{Process.pid}] Worker #{index} booted"
|
|
@@ -333,6 +338,7 @@ module Raptor
|
|
|
333
338
|
reactor.shutdown
|
|
334
339
|
reactor_thread.join
|
|
335
340
|
ractor_pool.shutdown
|
|
341
|
+
request.shutdown
|
|
336
342
|
thread_pool.shutdown
|
|
337
343
|
stats_thread.join
|
|
338
344
|
end
|