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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7c247b4c221d0f19ff1383ca0a4092a54bc12cd2f945655fa24e1ab49745eb70
4
- data.tar.gz: cb932a7a048d87eb0913f8c0dbe52750ea4e525e4c4dc32e5fcc531af0daaf41
3
+ metadata.gz: a9d82dc0869e270278088d312d03a6ed914e494da5fb6b7bf6e41c03257ddf41
4
+ data.tar.gz: c113ab336b5ad9e5e72095e79af4702b5adb0efb46117ba43b6879fe2e4a728c
5
5
  SHA512:
6
- metadata.gz: d8e1543054862e8a87c7261884348456da13d4432c3424687b74f506b884283d2773068b6beb10cbe9e209e969b98a918720861b3d0e05bc04d930ba38ec1921
7
- data.tar.gz: f215cc4a92af86542d49f2c4772f0a65af09988779bbb1c44512ab075d4fb5c0758cb51d9765a0ee9d08fe340d31b42cea1610b021c443818fe40fbdcea63b93
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.3.0
35
- ├─ Ruby Version: ruby 4.0.4 (2026-05-12 revision b89eb1bcbf) +YJIT +PRISM [arm64-darwin23]
36
- ├─ Master PID: 31504
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
- [31506] Worker 0 booted
46
- [31507] Worker 1 booted
47
- [31508] Worker 2 booted
48
- [31509] Worker 3 booted
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.3.0 vs Puma 8.0.1:
65
+ Raptor 0.4.0 vs Puma 8.0.1:
66
66
 
67
67
  | Protocol | Raptor | Puma |
68
68
  | --------------------- | ------------ | ------------ |
69
- | HTTP/1.1 | 20.3k req/s | 20.8k req/s |
70
- | HTTP/1.1 (keep-alive) | 60.9k req/s | 45.4k req/s |
71
- | HTTP/2 | 22.9k req/s | N/A |
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.4 +YJIT, macOS Apple Silicon. 4 workers, 3 threads, 12 concurrent connections.
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
- config = options[:Config] ? ::Raptor::CLI.load_config_file(options[:Config]) : {}
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, :pidfile].each do |key|
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
- pidfile: nil,
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("--pidfile PATH", String, "Pidfile path (default: none)") do |path|
239
- @options[:pidfile] = path
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
@@ -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 @pidfile: String?
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] :pidfile path to write the master PID to, or nil to disable
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
- @pidfile = options[:pidfile]
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(@pidfile, File::CREAT | File::EXCL | File::WRONLY) { |file| file.write(Process.pid.to_s) } if @pidfile
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(@pidfile) rescue nil if @pidfile
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
- if parsed_result[:protocol] == :http2
292
- http2.handle_parsed_request(parsed_result, reactor, thread_pool)
293
- else
294
- request.handle_parsed_request(parsed_result, reactor, thread_pool)
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