raptor 0.1.0 → 0.2.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: 3d7a708bc4146a30fa1b03dbca5404dff1173c0b39348de9e3ae3a0330907063
4
- data.tar.gz: 5db6986d6c9ca964873ec42f408d13087755b59c2517e7f942b8d2421ab88133
3
+ metadata.gz: 8391f9399db2e88dab106ad995a34060e6ae07f34c84825bfdcb066cd129dad4
4
+ data.tar.gz: 25cb3658e2aded284f302b84a1ee91d67344a5b5dd36c6c0e854381cfb4df048
5
5
  SHA512:
6
- metadata.gz: 8e163192ea38a0fa01852fe9c8ea31b82ef90b7fdd8f1b2b65b04e73f49e10792ba01225644d23104b42a7c1d3f07f47c273123a36ff4e77ef6259e695f967b8
7
- data.tar.gz: 90b1789fff941091795ebac8092deb97d6cdc51f915e38399bc782c05d6a101866f651d42a6183c948b99c25ab70532357c70d1090a68609cda0f6f3be46de8b
6
+ metadata.gz: e398237ccb391aff663c4d75e5470c0e4e0e6f6b273e26bface4cd6793691c3297053ab0a5d744deac8fd087c4f7dec0beb8426d8a762d3a089902d11dbe53e5
7
+ data.tar.gz: c4cfd5f7ffdcf7f970cc8c85868629bac6c6061312120c0dc2a3c64fa6431cf475bc8fbb877dce0bad3febc2bd87ccc5342e2bc9c8dad196c69306431ae91345
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2026-05-25
4
+
5
+ - Add Rack handler for booting via `rackup` or `rails server`
6
+ - Replace the HTTP/2 per-connection write mutex with a lock-free writer
7
+ - Parse the first HTTP/1.1 request inline on the server thread
8
+
3
9
  ## [0.1.0] - 2026-05-22
4
10
 
5
11
  - Initial release
data/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # Raptor
2
2
 
3
- Raptor is a high-performance, multi-threaded, multi-process Ruby web server that leverages Ractors for parallel HTTP/1.1
4
- and HTTP/2 request processing, native C extensions for HTTP parsing and HPACK compression, and NIO for non-blocking I/O.
3
+ Raptor is a high-performance, multi-threaded, multi-process Ruby Rack 3 web server that leverages Ractors for parallel
4
+ HTTP/1.1 and HTTP/2 request processing, native C extensions for HTTP parsing and HPACK compression, and NIO for
5
+ non-blocking I/O.
5
6
 
6
7
  ## Installation
7
8
 
@@ -52,15 +53,22 @@ Raptor Cluster initializing:
52
53
  Hello, World!%
53
54
  ```
54
55
 
56
+ Also works with `rackup` and `rails server`:
57
+
58
+ ```
59
+ > bundle exec rackup -s raptor hello_world.ru
60
+ > bundle exec rails server -u raptor
61
+ ```
62
+
55
63
  ## Benchmarks
56
64
 
57
65
  Raptor 0.1.0 vs Puma 8.0.1:
58
66
 
59
67
  | Protocol | Raptor | Puma |
60
68
  | --------------------- | ------------ | ------------ |
61
- | HTTP/1.1 | 17.5k req/s | 20.8k req/s |
62
- | HTTP/1.1 (keep-alive) | 61.2k req/s | 45.4k req/s |
63
- | HTTP/2 | 23.4k req/s | N/A |
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 |
64
72
 
65
73
  > Ruby 4.0.4 +YJIT, macOS Apple Silicon. 4 workers, 3 threads, 12 concurrent connections.
66
74
 
@@ -0,0 +1,90 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ require "etc"
5
+
6
+ module Rackup
7
+ module Handler
8
+ # Rack handler for booting Raptor through Rackup, `rails server`, or any
9
+ # other host that follows the Rack handler protocol.
10
+ #
11
+ module Raptor
12
+ DEFAULT_OPTIONS = {
13
+ Host: "0.0.0.0",
14
+ Port: 9292
15
+ }.freeze
16
+
17
+ # Boots a Raptor cluster serving the given Rack application.
18
+ #
19
+ # @param app [#call] the Rack application to serve
20
+ # @param options [Hash] handler options provided by Rackup or the host
21
+ # @yield [cluster] the cluster instance, before it starts running
22
+ # @return [void]
23
+ #
24
+ # @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, **untyped options) { (::Raptor::Cluster) -> void } -> void
25
+ def self.run(app, **options)
26
+ require_relative "../../raptor/cli"
27
+ require_relative "../../raptor/cluster"
28
+
29
+ cluster = ::Raptor::Cluster.new(build_cluster_options(app, options))
30
+
31
+ yield cluster if block_given?
32
+
33
+ cluster.run
34
+ end
35
+
36
+ # Returns the handler-specific options surfaced by `rackup --help`.
37
+ #
38
+ # @return [Hash{String => String}] option spec to description mapping
39
+ #
40
+ # @rbs () -> Hash[String, String]
41
+ def self.valid_options
42
+ {
43
+ "Host=HOST" => "Hostname to listen on (default: #{DEFAULT_OPTIONS[:Host]})",
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
+ "Workers=NUM" => "Number of worker processes (default: nprocessors)"
48
+ }
49
+ end
50
+
51
+ # Builds a Raptor cluster options hash from Rack handler options.
52
+ #
53
+ # Options not explicitly supplied by the user (per the `:user_supplied_options`
54
+ # key) are treated as host-provided defaults.
55
+ #
56
+ # @param app [#call] the Rack application to serve
57
+ # @param options [Hash] handler options provided by Rackup or the host
58
+ # @return [Hash{Symbol => untyped}] cluster configuration
59
+ #
60
+ # @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Hash[Symbol, untyped] options) -> Hash[Symbol, untyped]
61
+ def self.build_cluster_options(app, options)
62
+ defaults = DEFAULT_OPTIONS.dup
63
+
64
+ if user_supplied_options = options.delete(:user_supplied_options)
65
+ (options.keys - user_supplied_options).each do |key|
66
+ defaults[key] = options.delete(key)
67
+ end
68
+ end
69
+
70
+ host = options[:Host] || defaults[:Host]
71
+ port = options[:Port] || defaults[:Port]
72
+
73
+ cli_defaults = ::Raptor::CLI::DEFAULT_OPTIONS
74
+
75
+ {
76
+ app: app,
77
+ binds: ["tcp://#{host}:#{port}"],
78
+ threads: (options[:Threads] || cli_defaults[:threads]).to_i,
79
+ ractors: (options[:Ractors] || cli_defaults[:ractors]).to_i,
80
+ workers: (options[:Workers] || Etc.nprocessors).to_i,
81
+ client: cli_defaults[:client],
82
+ stats_file: nil
83
+ }
84
+ end
85
+ private_class_method :build_cluster_options
86
+ end
87
+
88
+ register :raptor, Raptor
89
+ end
90
+ end
@@ -77,6 +77,7 @@ module Raptor
77
77
  # @option options [Integer] :ractors number of ractors per worker process
78
78
  # @option options [Integer] :workers number of worker processes
79
79
  # @option options [Array<String>] :binds array of bind URIs
80
+ # @option options [#call] :app pre-built Rack application
80
81
  # @option options [String] :rackup path to Rack configuration file
81
82
  # @option options [Hash] :client client timeout configuration
82
83
  # @return [void]
@@ -90,7 +91,7 @@ module Raptor
90
91
 
91
92
  @binder = Binder.new(options[:binds])
92
93
  @server_port = @binder.server_port
93
- @app = Rack::Builder.parse_file(options[:rackup])
94
+ @app = options[:app] || Rack::Builder.parse_file(options[:rackup])
94
95
  log_initialization
95
96
 
96
97
  @shutdown = false
@@ -233,7 +234,7 @@ module Raptor
233
234
  reactor = Reactor.new(thread_pool, ractor_pool, client_options: @client_options)
234
235
  reactor_thread = reactor.run
235
236
 
236
- server = Server.new(@binder, reactor, thread_pool)
237
+ server = Server.new(@binder, reactor, thread_pool, request)
237
238
  server_thread = server.run
238
239
 
239
240
  puts "[#{Process.pid}] Worker #{index} booted"
data/lib/raptor/http2.rb CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  require "stringio"
5
5
 
6
+ require "atomic-ruby/atom"
6
7
  require "rack"
7
8
 
8
9
  require_relative "raptor_http2"
@@ -16,6 +17,63 @@ module Raptor
16
17
  # pipeline used by HTTP/1.1 connections.
17
18
  #
18
19
  class Http2
20
+ # Lock-free per-connection frame writer.
21
+ #
22
+ # Serializes concurrent socket writes from multiple stream workers
23
+ # without blocking any of them.
24
+ #
25
+ class Writer
26
+ IDLE = :idle
27
+
28
+ # @rbs @state: Atom
29
+
30
+ # Creates a new Writer.
31
+ #
32
+ # @rbs () -> void
33
+ def initialize
34
+ @state = Atom.new(IDLE)
35
+ end
36
+
37
+ # Writes frames to the socket, coordinating with concurrent writers
38
+ # so that exactly one thread is actively writing at any time.
39
+ #
40
+ # @param socket [OpenSSL::SSL::SSLSocket] the connection socket
41
+ # @param frames [Array<String>] frame bytes to write in order
42
+ # @return [void]
43
+ #
44
+ # @rbs (OpenSSL::SSL::SSLSocket socket, Array[String] frames) -> void
45
+ def write_frames(socket, frames)
46
+ return if frames.nil? || frames.empty?
47
+
48
+ claimed = false
49
+ @state.swap do |current|
50
+ if current.equal?(IDLE)
51
+ claimed = true
52
+ frames
53
+ else
54
+ claimed = false
55
+ current + frames
56
+ end
57
+ end
58
+
59
+ return unless claimed
60
+
61
+ loop do
62
+ pending = nil
63
+ @state.swap do |current|
64
+ pending = current
65
+ current.empty? ? IDLE : []
66
+ end
67
+
68
+ break if pending.empty?
69
+
70
+ pending.each do |frame|
71
+ socket.write(frame) rescue nil
72
+ end
73
+ end
74
+ end
75
+ end
76
+
19
77
  FLAG_END_STREAM = 0x1
20
78
  FLAG_END_HEADERS = 0x4
21
79
  FLAG_ACK = 0x1
@@ -215,13 +273,9 @@ module Raptor
215
273
  socket = reactor.socket_for(result[:id])
216
274
  return unless socket
217
275
 
218
- mutex = reactor.mutex_for(result[:id])
276
+ writer = reactor.writer_for(result[:id])
219
277
 
220
- if result[:outgoing_frames]&.any?
221
- mutex.synchronize do
222
- result[:outgoing_frames].each { |frame| socket.write(frame) rescue nil }
223
- end
224
- end
278
+ writer.write_frames(socket, result[:outgoing_frames])
225
279
 
226
280
  reactor.update_http2_state(result)
227
281
 
@@ -231,7 +285,7 @@ module Raptor
231
285
 
232
286
  thread_pool << proc do
233
287
  dispatch_stream_request(
234
- socket, mutex, stream_id,
288
+ socket, writer, stream_id,
235
289
  request[:headers], request[:body],
236
290
  remote_addr: remote_addr
237
291
  )
@@ -245,21 +299,21 @@ module Raptor
245
299
  # the response back as HTTP/2 frames.
246
300
  #
247
301
  # @param socket [OpenSSL::SSL::SSLSocket] the connection socket
248
- # @param mutex [Mutex] mutex for serializing writes to the connection socket
302
+ # @param writer [Writer] lock-free frame writer for the connection
249
303
  # @param stream_id [Integer] the HTTP/2 stream identifier
250
304
  # @param headers [Array<Array(String, String)>] request headers
251
305
  # @param body [String] request body
252
306
  # @param remote_addr [String] the client IP address
253
307
  # @return [void]
254
308
  #
255
- # @rbs (OpenSSL::SSL::SSLSocket socket, Mutex mutex, Integer stream_id, Array[[String, String]] headers, String body, remote_addr: String) -> void
256
- def dispatch_stream_request(socket, mutex, stream_id, headers, body, remote_addr:)
309
+ # @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id, Array[[String, String]] headers, String body, remote_addr: String) -> void
310
+ def dispatch_stream_request(socket, writer, stream_id, headers, body, remote_addr:)
257
311
  env = build_rack_env(headers, body, remote_addr: remote_addr)
258
312
  status, response_headers, response_body = @app.call(env)
259
313
 
260
- write_http2_response(socket, mutex, stream_id, status, response_headers, response_body)
314
+ write_http2_response(socket, writer, stream_id, status, response_headers, response_body)
261
315
  rescue
262
- write_http2_error_response(socket, mutex, stream_id)
316
+ write_http2_error_response(socket, writer, stream_id)
263
317
  raise
264
318
  ensure
265
319
  response_body.close if response_body.respond_to?(:close)
@@ -268,15 +322,15 @@ module Raptor
268
322
  # Writes a Rack response as HTTP/2 frames to the socket.
269
323
  #
270
324
  # @param socket [OpenSSL::SSL::SSLSocket] the connection socket
271
- # @param mutex [Mutex] mutex for serializing writes to the connection socket
325
+ # @param writer [Writer] lock-free frame writer for the connection
272
326
  # @param stream_id [Integer] the HTTP/2 stream identifier
273
327
  # @param status [Integer] HTTP status code
274
328
  # @param headers [Hash] response headers from the Rack application
275
329
  # @param body [Object] response body responding to each
276
330
  # @return [void]
277
331
  #
278
- # @rbs (OpenSSL::SSL::SSLSocket socket, Mutex mutex, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> void
279
- def write_http2_response(socket, mutex, stream_id, status, headers, body)
332
+ # @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> void
333
+ def write_http2_response(socket, writer, stream_id, status, headers, body)
280
334
  parser = Http2Parser.new
281
335
 
282
336
  header_pairs = [[":status", status.to_s]]
@@ -296,40 +350,38 @@ module Raptor
296
350
  body_chunks = []
297
351
  body.each { |chunk| body_chunks << chunk unless chunk.empty? }
298
352
 
299
- mutex.synchronize do
300
- if body_chunks.empty?
301
- socket.write(parser.build_frame(:headers, FLAG_END_STREAM | FLAG_END_HEADERS, stream_id, encoded_headers))
302
- else
303
- socket.write(parser.build_frame(:headers, FLAG_END_HEADERS, stream_id, encoded_headers))
353
+ frames = []
354
+ if body_chunks.empty?
355
+ frames << parser.build_frame(:headers, FLAG_END_STREAM | FLAG_END_HEADERS, stream_id, encoded_headers)
356
+ else
357
+ frames << parser.build_frame(:headers, FLAG_END_HEADERS, stream_id, encoded_headers)
304
358
 
305
- body_chunks.each_with_index do |chunk, index|
306
- last = index == body_chunks.size - 1
307
- flags = last ? FLAG_END_STREAM : 0
308
- socket.write(parser.build_frame(:data, flags, stream_id, chunk))
309
- end
359
+ last_index = body_chunks.size - 1
360
+ body_chunks.each_with_index do |chunk, index|
361
+ flags = index == last_index ? FLAG_END_STREAM : 0
362
+ frames << parser.build_frame(:data, flags, stream_id, chunk)
310
363
  end
311
364
  end
312
- rescue IOError, Errno::EPIPE
313
- # Connection closed
365
+
366
+ writer.write_frames(socket, frames)
314
367
  end
315
368
 
316
369
  # Writes a 500 error response as HTTP/2 frames.
317
370
  #
318
371
  # @param socket [OpenSSL::SSL::SSLSocket] the connection socket
319
- # @param mutex [Mutex] mutex for serializing writes to the connection socket
372
+ # @param writer [Writer] lock-free frame writer for the connection
320
373
  # @param stream_id [Integer] the HTTP/2 stream identifier
321
374
  # @return [void]
322
375
  #
323
- # @rbs (OpenSSL::SSL::SSLSocket socket, Mutex mutex, Integer stream_id) -> void
324
- def write_http2_error_response(socket, mutex, stream_id)
376
+ # @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id) -> void
377
+ def write_http2_error_response(socket, writer, stream_id)
325
378
  parser = Http2Parser.new
326
379
  encoded = parser.encode_headers([[":status", "500"]])
327
380
 
328
- mutex.synchronize do
329
- socket.write(parser.build_frame(:headers, FLAG_END_STREAM | FLAG_END_HEADERS, stream_id, encoded))
330
- end
331
- rescue IOError, Errno::EPIPE
332
- # Connection closed
381
+ writer.write_frames(
382
+ socket,
383
+ [parser.build_frame(:headers, FLAG_END_STREAM | FLAG_END_HEADERS, stream_id, encoded)]
384
+ )
333
385
  end
334
386
 
335
387
  # Builds a Rack environment hash from HTTP/2 headers and body.
@@ -75,7 +75,7 @@ module Raptor
75
75
  # @rbs @id_to_socket: Hash[Integer, TCPSocket]
76
76
  # @rbs @socket_to_state: Hash[TCPSocket, Hash[Symbol, untyped]]
77
77
  # @rbs @id_to_timeout: Hash[Integer, TimeoutClient]
78
- # @rbs @id_to_mutex: Hash[Integer, Mutex]
78
+ # @rbs @id_to_writer: Hash[Integer, untyped]
79
79
 
80
80
  # Creates a new Reactor instance.
81
81
  #
@@ -100,7 +100,7 @@ module Raptor
100
100
  @id_to_socket = {}
101
101
  @socket_to_state = {}
102
102
  @id_to_timeout = {}
103
- @id_to_mutex = {}
103
+ @id_to_writer = {}
104
104
  end
105
105
 
106
106
  # Starts the reactor's main event loop in a new thread.
@@ -162,12 +162,10 @@ module Raptor
162
162
  def add(state)
163
163
  socket = state[:socket]
164
164
  state.delete(:socket)
165
+ writer = state.delete(:writer)
165
166
  @id_to_socket[state[:id]] = socket
166
167
  @socket_to_state[socket] = state
167
-
168
- if state[:protocol] == :http2
169
- @id_to_mutex[state[:id]] = Mutex.new
170
- end
168
+ @id_to_writer[state[:id]] = writer if writer
171
169
 
172
170
  read_and_queue_for_parse(socket, state)
173
171
  end
@@ -252,14 +250,16 @@ module Raptor
252
250
  @id_to_socket[id]
253
251
  end
254
252
 
255
- # Returns the mutex for a given HTTP/2 connection.
253
+ # Returns the writer object associated with a given connection, if one
254
+ # was supplied when the connection was added. Used by protocol handlers
255
+ # that need to coordinate concurrent socket writes.
256
256
  #
257
257
  # @param id [Integer] unique client identifier
258
- # @return [Mutex, nil] the mutex, if found
258
+ # @return [Object, nil] the writer, if found
259
259
  #
260
- # @rbs (Integer id) -> Mutex?
261
- def mutex_for(id)
262
- @id_to_mutex[id]
260
+ # @rbs (Integer id) -> untyped?
261
+ def writer_for(id)
262
+ @id_to_writer[id]
263
263
  end
264
264
 
265
265
  # Updates connection state for an HTTP/2 connection after frame processing.
@@ -384,7 +384,7 @@ module Raptor
384
384
  def cleanup(socket)
385
385
  state = @socket_to_state.delete(socket)
386
386
  @id_to_socket.delete(state[:id])
387
- @id_to_mutex.delete(state[:id])
387
+ @id_to_writer.delete(state[:id])
388
388
  socket.close
389
389
  end
390
390
 
@@ -20,7 +20,7 @@ module Raptor
20
20
  class Request
21
21
  BODY_BUFFER_THRESHOLD = 256 * 1024
22
22
  FILE_CHUNK_SIZE = 64 * 1024
23
- KEEPALIVE_BUFFER_SIZE = 64 * 1024
23
+ READ_BUFFER_SIZE = 64 * 1024
24
24
  WRITE_TIMEOUT = 5
25
25
  KEEPALIVE_READ_TIMEOUT = 0.001
26
26
  MAX_KEEPALIVE_REQUESTS = 100
@@ -59,6 +59,35 @@ module Raptor
59
59
  def message = "could not write response"
60
60
  end
61
61
 
62
+ # Decodes a chunked transfer-encoded body buffer.
63
+ #
64
+ # Returns the decoded bytes and a flag indicating whether the terminating
65
+ # zero-length chunk was found. The decoder stops at the first unparseable
66
+ # boundary (incomplete CRLF) or zero-length chunk.
67
+ #
68
+ # @param buffer [String] the raw body buffer to decode
69
+ # @return [Array(String, Boolean)] decoded body and completion flag
70
+ #
71
+ # @rbs (String buffer) -> [String, bool]
72
+ def self.decode_chunked(buffer)
73
+ decoded = String.new
74
+ offset = 0
75
+
76
+ while offset < buffer.bytesize
77
+ crlf = buffer.index("\r\n", offset)
78
+ return [decoded, false] unless crlf
79
+
80
+ chunk_size = buffer.byteslice(offset, crlf - offset).to_i(16)
81
+ return [decoded, true] if chunk_size == 0
82
+
83
+ offset = crlf + 2
84
+ decoded << buffer.byteslice(offset, chunk_size)
85
+ offset += chunk_size + 2
86
+ end
87
+
88
+ [decoded, false]
89
+ end
90
+
62
91
  # @rbs @app: ^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped]
63
92
  # @rbs @server_port: Integer
64
93
 
@@ -74,6 +103,73 @@ module Raptor
74
103
  @server_port = server_port
75
104
  end
76
105
 
106
+ # Eagerly reads and parses the first request on a freshly accepted
107
+ # connection on the server thread, dispatching directly to the thread pool
108
+ # when complete. Falls back to the reactor when more data is needed.
109
+ #
110
+ # @param socket [TCPSocket] the freshly accepted client socket
111
+ # @param id [Integer] unique client identifier
112
+ # @param reactor [Reactor] the reactor for fallback registration
113
+ # @param thread_pool [AtomicThreadPool] thread pool for application processing
114
+ # @param remote_addr [String] client IP address
115
+ # @param url_scheme [String] "http" or "https"
116
+ # @return [void]
117
+ #
118
+ # @rbs (TCPSocket socket, Integer id, Reactor reactor, AtomicThreadPool thread_pool, String remote_addr, String url_scheme) -> void
119
+ def eager_accept(socket, id, reactor, thread_pool, remote_addr, url_scheme)
120
+ data = begin
121
+ socket.read_nonblock(READ_BUFFER_SIZE)
122
+ rescue IO::WaitReadable
123
+ reactor.add(
124
+ id: id,
125
+ socket: socket,
126
+ remote_addr: remote_addr,
127
+ url_scheme: url_scheme
128
+ )
129
+ return
130
+ rescue EOFError, IOError
131
+ socket.close rescue nil
132
+ return
133
+ end
134
+
135
+ buffer = String.new
136
+ buffer << data
137
+
138
+ while socket.respond_to?(:pending) && socket.pending > 0
139
+ buffer << socket.read_nonblock(socket.pending)
140
+ end
141
+
142
+ parser = HttpParser.new
143
+ env = {}
144
+ nread = parser.execute(env, buffer, 0)
145
+ parse_data = { parse_count: 1, content_length: parser.content_length }
146
+
147
+ body = nil
148
+ if !parser.finished?
149
+ fallback_to_reactor(socket, id, buffer, env, parse_data, reactor, 0, remote_addr, url_scheme, persisted: false)
150
+ return
151
+ elsif parser.has_body?
152
+ body = buffer.byteslice(nread..-1) || ""
153
+
154
+ if env[HTTP_TRANSFER_ENCODING]&.include?(TRANSFER_ENCODING_CHUNKED)
155
+ body, chunked_complete = Request.decode_chunked(body)
156
+ if chunked_complete
157
+ env.delete(HTTP_TRANSFER_ENCODING)
158
+ else
159
+ fallback_to_reactor(socket, id, buffer, env, parse_data, reactor, 0, remote_addr, url_scheme, persisted: false)
160
+ return
161
+ end
162
+ elsif parser.content_length > body.bytesize
163
+ fallback_to_reactor(socket, id, buffer, env, parse_data, reactor, 0, remote_addr, url_scheme, persisted: false)
164
+ return
165
+ end
166
+ end
167
+
168
+ thread_pool << proc do
169
+ process_client(socket, id, env, parse_data, body, reactor, thread_pool, 1, remote_addr, url_scheme)
170
+ end
171
+ end
172
+
77
173
  # Returns a Proc for HTTP parsing work in Ractor context.
78
174
  #
79
175
  # The returned Proc processes raw socket data through the appropriate
@@ -102,25 +198,7 @@ module Raptor
102
198
  body_buffer = data[:buffer].byteslice(nread..-1) || ""
103
199
 
104
200
  if env[HTTP_TRANSFER_ENCODING]&.include?(TRANSFER_ENCODING_CHUNKED)
105
- decoded_body = String.new
106
- offset = 0
107
- chunked_complete = false
108
-
109
- while offset < body_buffer.bytesize
110
- crlf = body_buffer.index("\r\n", offset)
111
- break unless crlf
112
-
113
- chunk_size = body_buffer.byteslice(offset, crlf - offset).to_i(16)
114
-
115
- if chunk_size == 0
116
- chunked_complete = true
117
- break
118
- end
119
-
120
- offset = crlf + 2
121
- decoded_body << body_buffer.byteslice(offset, chunk_size)
122
- offset += chunk_size + 2
123
- end
201
+ decoded_body, chunked_complete = Raptor::Request.decode_chunked(body_buffer)
124
202
 
125
203
  if chunked_complete
126
204
  env.delete(HTTP_TRANSFER_ENCODING)
@@ -278,7 +356,7 @@ module Raptor
278
356
  end
279
357
 
280
358
  data = begin
281
- socket.read_nonblock(KEEPALIVE_BUFFER_SIZE)
359
+ socket.read_nonblock(READ_BUFFER_SIZE)
282
360
  rescue IO::WaitReadable
283
361
  reactor.persist(socket, id, request_count, remote_addr: remote_addr, url_scheme: url_scheme)
284
362
  return
@@ -346,8 +424,12 @@ module Raptor
346
424
  end
347
425
  end
348
426
 
349
- # Re-registers a socket with the reactor for further processing
350
- # when an incomplete request is received during eager keep-alive.
427
+ # Re-registers a socket with the reactor for further processing when
428
+ # an incomplete request is received during eager accept or eager keep-alive.
429
+ #
430
+ # The persisted flag selects between persistent_data_timeout (for
431
+ # kept-alive connections awaiting the next request) and chunk_data_timeout
432
+ # (for fresh connections awaiting the rest of the first request).
351
433
  #
352
434
  # @param socket [TCPSocket] the client socket
353
435
  # @param id [Integer] unique client identifier
@@ -358,21 +440,23 @@ module Raptor
358
440
  # @param request_count [Integer] number of requests handled on this connection
359
441
  # @param remote_addr [String] client IP address
360
442
  # @param url_scheme [String] "http" or "https"
443
+ # @param persisted [Boolean] whether the connection has already completed at least one request
361
444
  # @return [void]
362
445
  #
363
- # @rbs (TCPSocket socket, Integer id, String buffer, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, Reactor reactor, Integer request_count, String remote_addr, String url_scheme) -> void
364
- def fallback_to_reactor(socket, id, buffer, env, parse_data, reactor, request_count, remote_addr, url_scheme)
446
+ # @rbs (TCPSocket socket, Integer id, String buffer, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, Reactor reactor, Integer request_count, String remote_addr, String url_scheme, persisted: bool) -> void
447
+ def fallback_to_reactor(socket, id, buffer, env, parse_data, reactor, request_count, remote_addr, url_scheme, persisted: true)
365
448
  reactor.persist(socket, id, request_count, remote_addr: remote_addr, url_scheme: url_scheme)
366
- reactor.update_state(Ractor.make_shareable({
449
+ state = {
367
450
  id: id,
368
451
  buffer: buffer,
369
452
  env: env,
370
453
  request_count: request_count,
371
454
  parse_data: parse_data,
372
455
  remote_addr: remote_addr,
373
- url_scheme: url_scheme,
374
- persisted: true
375
- }))
456
+ url_scheme: url_scheme
457
+ }
458
+ state[:persisted] = true if persisted
459
+ reactor.update_state(Ractor.make_shareable(state))
376
460
  end
377
461
 
378
462
  # Builds a Rack environment hash from parsed HTTP request data.
data/lib/raptor/server.rb CHANGED
@@ -15,16 +15,19 @@ module Raptor
15
15
  #
16
16
  # Supports TCP, Unix domain, and SSL listeners transparently. TCP_NODELAY is
17
17
  # applied only to TCP sockets, and SSL handshakes are performed synchronously
18
- # before handing the connection to the reactor.
18
+ # before the connection is dispatched.
19
19
  #
20
- # For SSL connections, ALPN negotiation determines the protocol. HTTP/2
21
- # connections are added to the reactor with initial SETTINGS and processed
22
- # through the same ractor pool pipeline as HTTP/1.1 connections.
20
+ # For HTTP/1.1 connections the first request is parsed inline on the server
21
+ # thread and dispatched directly to the thread pool, falling back to the
22
+ # reactor only when more data is needed. For HTTP/2 connections (negotiated
23
+ # via ALPN) the server sends initial SETTINGS and registers the connection
24
+ # with the reactor for frame processing through the ractor pool.
23
25
  #
24
26
  # @example
25
27
  # binder = Binder.new(["tcp://0.0.0.0:3000"])
26
28
  # reactor = Reactor.new(thread_pool, ractor_pool, client_options: {})
27
- # server = Server.new(binder, reactor, thread_pool)
29
+ # request = Request.new(app, 3000)
30
+ # server = Server.new(binder, reactor, thread_pool, request)
28
31
  # server.run
29
32
  # # ... later
30
33
  # server.shutdown
@@ -37,6 +40,7 @@ module Raptor
37
40
  # @rbs @binder: Binder
38
41
  # @rbs @reactor: Reactor
39
42
  # @rbs @thread_pool: AtomicThreadPool
43
+ # @rbs @request: Request
40
44
  # @rbs @running: AtomicBoolean
41
45
 
42
46
  # Creates a new Server instance.
@@ -44,13 +48,15 @@ module Raptor
44
48
  # @param binder [Binder] the binder managing listening sockets
45
49
  # @param reactor [Reactor] the reactor for handling client connections
46
50
  # @param thread_pool [AtomicThreadPool] thread pool for application processing
51
+ # @param request [Request] the HTTP/1.1 request handler
47
52
  # @return [void]
48
53
  #
49
- # @rbs (Binder binder, Reactor reactor, AtomicThreadPool thread_pool) -> void
50
- def initialize(binder, reactor, thread_pool)
54
+ # @rbs (Binder binder, Reactor reactor, AtomicThreadPool thread_pool, Request request) -> void
55
+ def initialize(binder, reactor, thread_pool, request)
51
56
  @binder = binder
52
57
  @reactor = reactor
53
58
  @thread_pool = thread_pool
59
+ @request = request
54
60
  @running = AtomicBoolean.new(true)
55
61
  end
56
62
 
@@ -149,18 +155,21 @@ module Raptor
149
155
  socket: ssl_socket,
150
156
  remote_addr: remote_addr,
151
157
  url_scheme: HTTPS_SCHEME,
152
- protocol: :http2
158
+ protocol: :http2,
159
+ writer: Http2::Writer.new
153
160
  )
154
161
 
155
162
  return
156
163
  end
157
164
  end
158
165
 
159
- reactor.add(
160
- id: client.object_id,
161
- socket: client,
162
- remote_addr: remote_addr,
163
- url_scheme: url_scheme
166
+ @request.eager_accept(
167
+ client,
168
+ client.object_id,
169
+ reactor,
170
+ @thread_pool,
171
+ remote_addr,
172
+ url_scheme
164
173
  )
165
174
  end
166
175
  end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Raptor
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.0"
6
6
  end
@@ -0,0 +1,40 @@
1
+ # Generated from lib/rackup/handler/raptor.rb with RBS::Inline
2
+
3
+ module Rackup
4
+ module Handler
5
+ # Rack handler for booting Raptor through Rackup, `rails server`, or any
6
+ # other host that follows the Rack handler protocol.
7
+ module Raptor
8
+ DEFAULT_OPTIONS: untyped
9
+
10
+ # Boots a Raptor cluster serving the given Rack application.
11
+ #
12
+ # @param app [#call] the Rack application to serve
13
+ # @param options [Hash] handler options provided by Rackup or the host
14
+ # @yield [cluster] the cluster instance, before it starts running
15
+ # @return [void]
16
+ #
17
+ # @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, **untyped options) { (::Raptor::Cluster) -> void } -> void
18
+ def self.run: (^(Hash[String, untyped]) -> [ Integer, Hash[String, String | Array[String]], untyped ] app, **untyped options) { (::Raptor::Cluster) -> void } -> void
19
+
20
+ # Returns the handler-specific options surfaced by `rackup --help`.
21
+ #
22
+ # @return [Hash{String => String}] option spec to description mapping
23
+ #
24
+ # @rbs () -> Hash[String, String]
25
+ def self.valid_options: () -> Hash[String, String]
26
+
27
+ # Builds a Raptor cluster options hash from Rack handler options.
28
+ #
29
+ # Options not explicitly supplied by the user (per the `:user_supplied_options`
30
+ # key) are treated as host-provided defaults.
31
+ #
32
+ # @param app [#call] the Rack application to serve
33
+ # @param options [Hash] handler options provided by Rackup or the host
34
+ # @return [Hash{Symbol => untyped}] cluster configuration
35
+ #
36
+ # @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Hash[Symbol, untyped] options) -> Hash[Symbol, untyped]
37
+ def self.build_cluster_options: (^(Hash[String, untyped]) -> [ Integer, Hash[String, String | Array[String]], untyped ] app, Hash[Symbol, untyped] options) -> Hash[Symbol, untyped]
38
+ end
39
+ end
40
+ end
@@ -70,6 +70,7 @@ module Raptor
70
70
  # @option options [Integer] :ractors number of ractors per worker process
71
71
  # @option options [Integer] :workers number of worker processes
72
72
  # @option options [Array<String>] :binds array of bind URIs
73
+ # @option options [#call] :app pre-built Rack application
73
74
  # @option options [String] :rackup path to Rack configuration file
74
75
  # @option options [Hash] :client client timeout configuration
75
76
  # @return [void]
@@ -8,6 +8,31 @@ module Raptor
8
8
  # It integrates with the same reactor, ractor pool, and thread pool
9
9
  # pipeline used by HTTP/1.1 connections.
10
10
  class Http2
11
+ # Lock-free per-connection frame writer.
12
+ #
13
+ # Serializes concurrent socket writes from multiple stream workers
14
+ # without blocking any of them.
15
+ class Writer
16
+ IDLE: ::Symbol
17
+
18
+ @state: Atom
19
+
20
+ # Creates a new Writer.
21
+ #
22
+ # @rbs () -> void
23
+ def initialize: () -> void
24
+
25
+ # Writes frames to the socket, coordinating with concurrent writers
26
+ # so that exactly one thread is actively writing at any time.
27
+ #
28
+ # @param socket [OpenSSL::SSL::SSLSocket] the connection socket
29
+ # @param frames [Array<String>] frame bytes to write in order
30
+ # @return [void]
31
+ #
32
+ # @rbs (OpenSSL::SSL::SSLSocket socket, Array[String] frames) -> void
33
+ def write_frames: (OpenSSL::SSL::SSLSocket socket, Array[String] frames) -> void
34
+ end
35
+
11
36
  FLAG_END_STREAM: ::Integer
12
37
 
13
38
  FLAG_END_HEADERS: ::Integer
@@ -88,38 +113,38 @@ module Raptor
88
113
  # the response back as HTTP/2 frames.
89
114
  #
90
115
  # @param socket [OpenSSL::SSL::SSLSocket] the connection socket
91
- # @param mutex [Mutex] mutex for serializing writes to the connection socket
116
+ # @param writer [Writer] lock-free frame writer for the connection
92
117
  # @param stream_id [Integer] the HTTP/2 stream identifier
93
118
  # @param headers [Array<Array(String, String)>] request headers
94
119
  # @param body [String] request body
95
120
  # @param remote_addr [String] the client IP address
96
121
  # @return [void]
97
122
  #
98
- # @rbs (OpenSSL::SSL::SSLSocket socket, Mutex mutex, Integer stream_id, Array[[String, String]] headers, String body, remote_addr: String) -> void
99
- def dispatch_stream_request: (OpenSSL::SSL::SSLSocket socket, Mutex mutex, Integer stream_id, Array[[ String, String ]] headers, String body, remote_addr: String) -> void
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
100
125
 
101
126
  # Writes a Rack response as HTTP/2 frames to the socket.
102
127
  #
103
128
  # @param socket [OpenSSL::SSL::SSLSocket] the connection socket
104
- # @param mutex [Mutex] mutex for serializing writes to the connection socket
129
+ # @param writer [Writer] lock-free frame writer for the connection
105
130
  # @param stream_id [Integer] the HTTP/2 stream identifier
106
131
  # @param status [Integer] HTTP status code
107
132
  # @param headers [Hash] response headers from the Rack application
108
133
  # @param body [Object] response body responding to each
109
134
  # @return [void]
110
135
  #
111
- # @rbs (OpenSSL::SSL::SSLSocket socket, Mutex mutex, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> void
112
- def write_http2_response: (OpenSSL::SSL::SSLSocket socket, Mutex mutex, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> void
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
113
138
 
114
139
  # Writes a 500 error response as HTTP/2 frames.
115
140
  #
116
141
  # @param socket [OpenSSL::SSL::SSLSocket] the connection socket
117
- # @param mutex [Mutex] mutex for serializing writes to the connection socket
142
+ # @param writer [Writer] lock-free frame writer for the connection
118
143
  # @param stream_id [Integer] the HTTP/2 stream identifier
119
144
  # @return [void]
120
145
  #
121
- # @rbs (OpenSSL::SSL::SSLSocket socket, Mutex mutex, Integer stream_id) -> void
122
- def write_http2_error_response: (OpenSSL::SSL::SSLSocket socket, Mutex mutex, Integer stream_id) -> void
146
+ # @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id) -> void
147
+ def write_http2_error_response: (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id) -> void
123
148
 
124
149
  # Builds a Rack environment hash from HTTP/2 headers and body.
125
150
  #
@@ -55,7 +55,7 @@ module Raptor
55
55
 
56
56
  TIMEOUT_RESPONSE: ::String
57
57
 
58
- @id_to_mutex: Hash[Integer, Mutex]
58
+ @id_to_writer: Hash[Integer, untyped]
59
59
 
60
60
  @id_to_timeout: Hash[Integer, TimeoutClient]
61
61
 
@@ -159,13 +159,15 @@ module Raptor
159
159
  # @rbs (Integer id) -> TCPSocket?
160
160
  def socket_for: (Integer id) -> TCPSocket?
161
161
 
162
- # Returns the mutex for a given HTTP/2 connection.
162
+ # Returns the writer object associated with a given connection, if one
163
+ # was supplied when the connection was added. Used by protocol handlers
164
+ # that need to coordinate concurrent socket writes.
163
165
  #
164
166
  # @param id [Integer] unique client identifier
165
- # @return [Mutex, nil] the mutex, if found
167
+ # @return [Object, nil] the writer, if found
166
168
  #
167
- # @rbs (Integer id) -> Mutex?
168
- def mutex_for: (Integer id) -> Mutex?
169
+ # @rbs (Integer id) -> untyped?
170
+ def writer_for: (Integer id) -> untyped?
169
171
 
170
172
  # Updates connection state for an HTTP/2 connection after frame processing.
171
173
  #
@@ -13,7 +13,7 @@ module Raptor
13
13
 
14
14
  FILE_CHUNK_SIZE: untyped
15
15
 
16
- KEEPALIVE_BUFFER_SIZE: untyped
16
+ READ_BUFFER_SIZE: untyped
17
17
 
18
18
  WRITE_TIMEOUT: ::Integer
19
19
 
@@ -63,6 +63,18 @@ module Raptor
63
63
  def message: () -> String
64
64
  end
65
65
 
66
+ # Decodes a chunked transfer-encoded body buffer.
67
+ #
68
+ # Returns the decoded bytes and a flag indicating whether the terminating
69
+ # zero-length chunk was found. The decoder stops at the first unparseable
70
+ # boundary (incomplete CRLF) or zero-length chunk.
71
+ #
72
+ # @param buffer [String] the raw body buffer to decode
73
+ # @return [Array(String, Boolean)] decoded body and completion flag
74
+ #
75
+ # @rbs (String buffer) -> [String, bool]
76
+ def self.decode_chunked: (String buffer) -> [ String, bool ]
77
+
66
78
  @server_port: Integer
67
79
 
68
80
  @app: ^(Hash[String, untyped]) -> [ Integer, Hash[String, String | Array[String]], untyped ]
@@ -76,6 +88,21 @@ module Raptor
76
88
  # @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Integer server_port) -> void
77
89
  def initialize: (^(Hash[String, untyped]) -> [ Integer, Hash[String, String | Array[String]], untyped ] app, Integer server_port) -> void
78
90
 
91
+ # Eagerly reads and parses the first request on a freshly accepted
92
+ # connection on the server thread, dispatching directly to the thread pool
93
+ # when complete. Falls back to the reactor when more data is needed.
94
+ #
95
+ # @param socket [TCPSocket] the freshly accepted client socket
96
+ # @param id [Integer] unique client identifier
97
+ # @param reactor [Reactor] the reactor for fallback registration
98
+ # @param thread_pool [AtomicThreadPool] thread pool for application processing
99
+ # @param remote_addr [String] client IP address
100
+ # @param url_scheme [String] "http" or "https"
101
+ # @return [void]
102
+ #
103
+ # @rbs (TCPSocket socket, Integer id, Reactor reactor, AtomicThreadPool thread_pool, String remote_addr, String url_scheme) -> void
104
+ def eager_accept: (TCPSocket socket, Integer id, Reactor reactor, AtomicThreadPool thread_pool, String remote_addr, String url_scheme) -> void
105
+
79
106
  # Returns a Proc for HTTP parsing work in Ractor context.
80
107
  #
81
108
  # The returned Proc processes raw socket data through the appropriate
@@ -154,8 +181,12 @@ module Raptor
154
181
  # @rbs (TCPSocket socket, Integer id, Reactor reactor, AtomicThreadPool thread_pool, Integer request_count, String remote_addr, String url_scheme) -> void
155
182
  def eager_keepalive: (TCPSocket socket, Integer id, Reactor reactor, AtomicThreadPool thread_pool, Integer request_count, String remote_addr, String url_scheme) -> void
156
183
 
157
- # Re-registers a socket with the reactor for further processing
158
- # when an incomplete request is received during eager keep-alive.
184
+ # Re-registers a socket with the reactor for further processing when
185
+ # an incomplete request is received during eager accept or eager keep-alive.
186
+ #
187
+ # The persisted flag selects between persistent_data_timeout (for
188
+ # kept-alive connections awaiting the next request) and chunk_data_timeout
189
+ # (for fresh connections awaiting the rest of the first request).
159
190
  #
160
191
  # @param socket [TCPSocket] the client socket
161
192
  # @param id [Integer] unique client identifier
@@ -166,10 +197,11 @@ module Raptor
166
197
  # @param request_count [Integer] number of requests handled on this connection
167
198
  # @param remote_addr [String] client IP address
168
199
  # @param url_scheme [String] "http" or "https"
200
+ # @param persisted [Boolean] whether the connection has already completed at least one request
169
201
  # @return [void]
170
202
  #
171
- # @rbs (TCPSocket socket, Integer id, String buffer, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, Reactor reactor, Integer request_count, String remote_addr, String url_scheme) -> void
172
- def fallback_to_reactor: (TCPSocket socket, Integer id, String buffer, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, Reactor reactor, Integer request_count, String remote_addr, String url_scheme) -> void
203
+ # @rbs (TCPSocket socket, Integer id, String buffer, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, Reactor reactor, Integer request_count, String remote_addr, String url_scheme, persisted: bool) -> void
204
+ def fallback_to_reactor: (TCPSocket socket, Integer id, String buffer, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, Reactor reactor, Integer request_count, String remote_addr, String url_scheme, persisted: bool) -> void
173
205
 
174
206
  # Builds a Rack environment hash from parsed HTTP request data.
175
207
  #
@@ -10,16 +10,19 @@ module Raptor
10
10
  #
11
11
  # Supports TCP, Unix domain, and SSL listeners transparently. TCP_NODELAY is
12
12
  # applied only to TCP sockets, and SSL handshakes are performed synchronously
13
- # before handing the connection to the reactor.
13
+ # before the connection is dispatched.
14
14
  #
15
- # For SSL connections, ALPN negotiation determines the protocol. HTTP/2
16
- # connections are added to the reactor with initial SETTINGS and processed
17
- # through the same ractor pool pipeline as HTTP/1.1 connections.
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.
18
20
  #
19
21
  # @example
20
22
  # binder = Binder.new(["tcp://0.0.0.0:3000"])
21
23
  # reactor = Reactor.new(thread_pool, ractor_pool, client_options: {})
22
- # server = Server.new(binder, reactor, thread_pool)
24
+ # request = Request.new(app, 3000)
25
+ # server = Server.new(binder, reactor, thread_pool, request)
23
26
  # server.run
24
27
  # # ... later
25
28
  # server.shutdown
@@ -36,6 +39,8 @@ module Raptor
36
39
 
37
40
  @thread_pool: AtomicThreadPool
38
41
 
42
+ @request: Request
43
+
39
44
  @running: AtomicBoolean
40
45
 
41
46
  # Creates a new Server instance.
@@ -43,10 +48,11 @@ module Raptor
43
48
  # @param binder [Binder] the binder managing listening sockets
44
49
  # @param reactor [Reactor] the reactor for handling client connections
45
50
  # @param thread_pool [AtomicThreadPool] thread pool for application processing
51
+ # @param request [Request] the HTTP/1.1 request handler
46
52
  # @return [void]
47
53
  #
48
- # @rbs (Binder binder, Reactor reactor, AtomicThreadPool thread_pool) -> void
49
- def initialize: (Binder binder, Reactor reactor, AtomicThreadPool thread_pool) -> void
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
50
56
 
51
57
  # Starts the server's main accept loop in a new thread.
52
58
  #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: raptor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Young
@@ -114,6 +114,7 @@ files:
114
114
  - ext/raptor_http2/extconf.rb
115
115
  - ext/raptor_http2/huffman_table.h
116
116
  - ext/raptor_http2/raptor_http2.c
117
+ - lib/rackup/handler/raptor.rb
117
118
  - lib/raptor.rb
118
119
  - lib/raptor/binder.rb
119
120
  - lib/raptor/cli.rb
@@ -124,6 +125,7 @@ files:
124
125
  - lib/raptor/server.rb
125
126
  - lib/raptor/stats.rb
126
127
  - lib/raptor/version.rb
128
+ - sig/generated/rackup/handler/raptor.rbs
127
129
  - sig/generated/raptor.rbs
128
130
  - sig/generated/raptor/binder.rbs
129
131
  - sig/generated/raptor/cli.rbs