raptor 0.7.0 → 0.8.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.
data/lib/raptor/http2.rb CHANGED
@@ -6,7 +6,7 @@ require "stringio"
6
6
  require "atomic-ruby/atom"
7
7
  require "rack"
8
8
 
9
- require_relative "request"
9
+ require_relative "http"
10
10
  require_relative "raptor_http2"
11
11
 
12
12
  module Raptor
@@ -27,12 +27,17 @@ module Raptor
27
27
  IDLE = :idle
28
28
 
29
29
  # @rbs @state: Atom
30
+ # @rbs @write_timeout: Integer
30
31
 
31
32
  # Creates a new Writer.
32
33
  #
33
- # @rbs () -> void
34
- def initialize
34
+ # @param write_timeout [Integer] per-write socket timeout passed through to {Http.socket_write}
35
+ # @return [void]
36
+ #
37
+ # @rbs (write_timeout: Integer) -> void
38
+ def initialize(write_timeout:)
35
39
  @state = Atom.new(IDLE)
40
+ @write_timeout = write_timeout
36
41
  end
37
42
 
38
43
  # Writes frames to the socket, coordinating with concurrent writers
@@ -69,7 +74,7 @@ module Raptor
69
74
  break if pending.empty?
70
75
 
71
76
  pending.each do |frame|
72
- Request.socket_write(socket, frame) rescue nil
77
+ Http.socket_write(socket, frame, timeout: @write_timeout) rescue nil
73
78
  end
74
79
  end
75
80
  end
@@ -78,7 +83,7 @@ module Raptor
78
83
  # Per-connection outbound flow-control accounting.
79
84
  #
80
85
  # Tracks the peer's connection-level and per-stream receive windows so
81
- # outbound DATA frames respect RFC 7540 §5.2. Threads dispatching stream
86
+ # outbound DATA frames respect RFC 7540 section 5.2. Threads dispatching stream
82
87
  # responses call `acquire` to reserve send capacity; threads applying
83
88
  # inbound WINDOW_UPDATE or SETTINGS frames call the mutating methods to
84
89
  # replenish it. The connection window and per-stream windows live in
@@ -146,9 +151,9 @@ module Raptor
146
151
  end
147
152
 
148
153
  if granted > 0
149
- @stream_windows.swap do |s|
150
- current = s[stream_id] || initial
151
- s.merge(stream_id => current - granted)
154
+ @stream_windows.swap do |windows|
155
+ current = windows[stream_id] || initial
156
+ windows.merge(stream_id => current - granted)
152
157
  end
153
158
  return granted
154
159
  end
@@ -178,14 +183,14 @@ module Raptor
178
183
  # @rbs (Integer stream_id, Integer increment) -> void
179
184
  def add_stream_window(stream_id, increment)
180
185
  initial = @initial_stream_window.value
181
- @stream_windows.swap do |s|
182
- current = s[stream_id] || initial
183
- s.merge(stream_id => current + increment)
186
+ @stream_windows.swap do |windows|
187
+ current = windows[stream_id] || initial
188
+ windows.merge(stream_id => current + increment)
184
189
  end
185
190
  end
186
191
 
187
192
  # Updates the peer's `SETTINGS_INITIAL_WINDOW_SIZE`. Shifts every
188
- # existing stream window by the delta as required by RFC 7540 §6.9.2.
193
+ # existing stream window by the delta as required by RFC 7540 section 6.9.2.
189
194
  #
190
195
  # @param new_size [Integer] the peer's new initial window size
191
196
  # @return [void]
@@ -197,8 +202,8 @@ module Raptor
197
202
  delta = new_size - old
198
203
  return if delta.zero?
199
204
 
200
- @stream_windows.swap do |s|
201
- s.transform_values { |size| size + delta }
205
+ @stream_windows.swap do |windows|
206
+ windows.transform_values { |size| size + delta }
202
207
  end
203
208
  end
204
209
 
@@ -213,12 +218,12 @@ module Raptor
213
218
  def discard_stream(stream_id)
214
219
  return unless @stream_windows.value.key?(stream_id)
215
220
 
216
- @stream_windows.swap do |s|
217
- next s unless s.key?(stream_id)
221
+ @stream_windows.swap do |windows|
222
+ next windows unless windows.key?(stream_id)
218
223
 
219
- new = s.dup
220
- new.delete(stream_id)
221
- new
224
+ pruned = windows.dup
225
+ pruned.delete(stream_id)
226
+ pruned
222
227
  end
223
228
  end
224
229
  end
@@ -244,34 +249,51 @@ module Raptor
244
249
 
245
250
  # @rbs @app: ^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped]
246
251
  # @rbs @server_port: Integer
252
+ # @rbs @write_timeout: Integer
253
+ # @rbs @access_log_io: IO?
247
254
  # @rbs @on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
255
+ # @rbs @initial_settings_frame: String
256
+
257
+ # The initial server SETTINGS frame sent on every new HTTP/2 connection.
258
+ #
259
+ # @return [String] the encoded SETTINGS frame
260
+ attr_reader :initial_settings_frame
248
261
 
249
262
  # Creates a new Http2 handler.
250
263
  #
251
264
  # @param app [#call] the Rack application to dispatch requests to
252
265
  # @param server_port [Integer] port number used to populate SERVER_PORT in the Rack env
266
+ # @param connection_options [Hash] per-connection settings shared across protocols
267
+ # @option connection_options [Integer] :write_timeout per-write socket timeout in seconds
268
+ # @param http2_options [Hash] HTTP/2-specific settings
269
+ # @option http2_options [Integer] :max_concurrent_streams maximum HTTP/2 concurrent streams per connection
270
+ # @param access_log_io [IO, nil] IO to write Common Log Format access entries to, or nil to disable
253
271
  # @param on_error [#call, nil] callback invoked with (env, exception) when the Rack app raises
254
272
  # @return [void]
255
273
  #
256
- # @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Integer server_port, ?on_error: ^(Hash[String, untyped]?, Exception) -> void | nil) -> void
257
- def initialize(app, server_port, on_error: nil)
274
+ # @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Integer server_port, ?connection_options: Hash[Symbol, untyped], ?http2_options: Hash[Symbol, untyped], ?access_log_io: IO?, ?on_error: ^(Hash[String, untyped]?, Exception) -> void | nil) -> void
275
+ def initialize(app, server_port, connection_options: {}, http2_options: {}, access_log_io: nil, on_error: nil)
258
276
  @app = app
259
277
  @server_port = server_port
278
+ @write_timeout = connection_options[:write_timeout] || Http::WRITE_TIMEOUT
279
+ @access_log_io = access_log_io
260
280
  @on_error = on_error
261
- end
262
281
 
263
- # Builds the initial server SETTINGS frame to send on connection establishment.
264
- #
265
- # @return [String] the encoded SETTINGS frame
266
- #
267
- # @rbs () -> String
268
- def self.build_server_settings_frame
269
282
  parser = Http2Parser.new
270
283
  settings_payload = parser.build_settings(
271
- max_concurrent_streams: 100,
284
+ max_concurrent_streams: http2_options[:max_concurrent_streams],
272
285
  initial_window_size: DEFAULT_WINDOW_SIZE
273
286
  )
274
- parser.build_frame(:settings, 0, 0, settings_payload)
287
+ @initial_settings_frame = parser.build_frame(:settings, 0, 0, settings_payload).freeze
288
+ end
289
+
290
+ # Creates a per-connection {Writer} configured with the handler's write timeout.
291
+ #
292
+ # @return [Writer] a new per-connection frame writer
293
+ #
294
+ # @rbs () -> Writer
295
+ def create_writer
296
+ Writer.new(write_timeout: @write_timeout)
275
297
  end
276
298
 
277
299
  # Processes HTTP/2 frames from the connection buffer.
@@ -626,7 +648,8 @@ module Raptor
626
648
  env = build_rack_env(headers, body, remote_addr: remote_addr)
627
649
  status, response_headers, response_body = @app.call(env)
628
650
 
629
- write_http2_response(socket, writer, flow_control, stream_id, status, response_headers, response_body)
651
+ response_size = write_http2_response(socket, writer, flow_control, stream_id, status, response_headers, response_body)
652
+ write_access_log(env, status, response_size, remote_addr) if @access_log_io
630
653
  rescue => error
631
654
  write_http2_error_response(socket, writer, stream_id)
632
655
 
@@ -652,9 +675,9 @@ module Raptor
652
675
  # @param status [Integer] HTTP status code
653
676
  # @param headers [Hash] response headers from the Rack application
654
677
  # @param body [Object] response body responding to each
655
- # @return [void]
678
+ # @return [String] the response body size in bytes
656
679
  #
657
- # @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> void
680
+ # @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> String
658
681
  def write_http2_response(socket, writer, flow_control, stream_id, status, headers, body)
659
682
  parser = Http2Parser.new
660
683
 
@@ -673,11 +696,16 @@ module Raptor
673
696
 
674
697
  encoded_headers = parser.encode_headers(header_pairs)
675
698
  body_chunks = []
676
- body.each { |chunk| body_chunks << chunk unless chunk.empty? }
699
+ body_bytes = 0
700
+ body.each do |chunk|
701
+ next if chunk.empty?
702
+ body_chunks << chunk
703
+ body_bytes += chunk.bytesize
704
+ end
677
705
 
678
706
  if body_chunks.empty?
679
707
  writer.write_frames(socket, [parser.build_frame(:headers, FLAG_END_STREAM | FLAG_END_HEADERS, stream_id, encoded_headers)])
680
- return
708
+ return "0"
681
709
  end
682
710
 
683
711
  frames = [parser.build_frame(:headers, FLAG_END_HEADERS, stream_id, encoded_headers)]
@@ -697,6 +725,7 @@ module Raptor
697
725
  end
698
726
 
699
727
  writer.write_frames(socket, frames)
728
+ body_bytes.to_s
700
729
  end
701
730
 
702
731
  # Writes a 500 error response as HTTP/2 frames.
@@ -717,6 +746,20 @@ module Raptor
717
746
  )
718
747
  end
719
748
 
749
+ # Instance-level wrapper around {Http.write_access_log} that routes to
750
+ # the configured `@access_log_io`.
751
+ #
752
+ # @param env [Hash] the Rack environment
753
+ # @param status [Integer] the response status code
754
+ # @param size [String] the response body size in bytes, or `-` if unknown
755
+ # @param remote_addr [String] the client IP address
756
+ # @return [void]
757
+ #
758
+ # @rbs (Hash[String, untyped] env, Integer status, String size, String remote_addr) -> void
759
+ def write_access_log(env, status, size, remote_addr)
760
+ Http.write_access_log(@access_log_io, env, status, size, remote_addr)
761
+ end
762
+
720
763
  # Builds a Rack environment hash from HTTP/2 headers and body.
721
764
  #
722
765
  # Translates HTTP/2 pseudo-headers into Rack-compatible environment keys
@@ -743,9 +786,9 @@ module Raptor
743
786
  when ":authority" then env[Rack::HTTP_HOST] = value
744
787
  end
745
788
  elsif name == "content-type"
746
- env["CONTENT_TYPE"] = value
789
+ env[Http::CONTENT_TYPE] = value
747
790
  elsif name == "content-length"
748
- env["CONTENT_LENGTH"] = value
791
+ env[Http::CONTENT_LENGTH] = value
749
792
  else
750
793
  rack_key = "HTTP_#{name.upcase.tr("-", "_")}"
751
794
  env[rack_key] = value
@@ -763,11 +806,13 @@ module Raptor
763
806
  env[Rack::PATH_INFO] = "" unless env.key?(Rack::PATH_INFO)
764
807
  env[Rack::QUERY_STRING] = "" unless env.key?(Rack::QUERY_STRING)
765
808
 
766
- if body.bytesize.positive? && !env.key?("CONTENT_LENGTH")
767
- env["CONTENT_LENGTH"] = body.bytesize.to_s
809
+ if body.bytesize.positive? && !env.key?(Http::CONTENT_LENGTH)
810
+ env[Http::CONTENT_LENGTH] = body.bytesize.to_s
768
811
  end
769
812
 
770
- env["REMOTE_ADDR"] = remote_addr
813
+ env[Http::REMOTE_ADDR] = remote_addr
814
+ env[Http::SERVER_SOFTWARE] = Http::SERVER_SOFTWARE_VALUE
815
+ env[Http::HTTP_VERSION] = SERVER_PROTOCOL
771
816
 
772
817
  populate_server_name_and_port(env)
773
818
 
@@ -14,10 +14,12 @@ module Raptor
14
14
  # the server uses for backpressure control to prevent overload.
15
15
  #
16
16
  # @example
17
- # reactor = Reactor.new(ractor_pool, thread_pool, client_options: {
18
- # first_data_timeout: 30,
19
- # chunk_data_timeout: 10
20
- # })
17
+ # reactor = Reactor.new(
18
+ # ractor_pool,
19
+ # thread_pool,
20
+ # connection_options: { first_data_timeout: 30, chunk_data_timeout: 10 },
21
+ # http1_options: { persistent_data_timeout: 65 }
22
+ # )
21
23
  # reactor.run
22
24
  # reactor.add(id: client.object_id, socket: client)
23
25
  # # ... later
@@ -68,7 +70,9 @@ module Raptor
68
70
 
69
71
  # @rbs @thread_pool: untyped
70
72
  # @rbs @ractor_pool: untyped
71
- # @rbs @client_options: Hash[Symbol, Integer]
73
+ # @rbs @first_data_timeout: Integer
74
+ # @rbs @chunk_data_timeout: Integer
75
+ # @rbs @persistent_data_timeout: Integer
72
76
  # @rbs @selector: NIO::Selector
73
77
  # @rbs @queue: Queue[TCPSocket]
74
78
  # @rbs @timeouts: RedBlackTree[TimeoutClient]
@@ -82,17 +86,20 @@ module Raptor
82
86
  #
83
87
  # @param ractor_pool [RactorPool] ractor pool for HTTP parsing
84
88
  # @param thread_pool [AtomicThreadPool] thread pool for application processing
85
- # @param client_options [Hash] timeout configuration options
86
- # @option client_options [Integer] :first_data_timeout timeout for initial data
87
- # @option client_options [Integer] :chunk_data_timeout timeout for subsequent chunks
88
- # @option client_options [Integer] :persistent_data_timeout timeout for keep-alive connections
89
+ # @param connection_options [Hash] per-connection timeout configuration
90
+ # @option connection_options [Integer] :first_data_timeout timeout for initial data
91
+ # @option connection_options [Integer] :chunk_data_timeout timeout for subsequent chunks
92
+ # @param http1_options [Hash] HTTP/1.1-specific configuration
93
+ # @option http1_options [Integer] :persistent_data_timeout timeout for keep-alive idle connections
89
94
  # @return [void]
90
95
  #
91
- # @rbs (untyped ractor_pool, untyped thread_pool, client_options: Hash[Symbol, Integer]) -> void
92
- def initialize(ractor_pool, thread_pool, client_options:)
96
+ # @rbs (untyped ractor_pool, untyped thread_pool, connection_options: Hash[Symbol, untyped], http1_options: Hash[Symbol, untyped]) -> void
97
+ def initialize(ractor_pool, thread_pool, connection_options:, http1_options:)
93
98
  @ractor_pool = ractor_pool
94
99
  @thread_pool = thread_pool
95
- @client_options = client_options
100
+ @first_data_timeout = connection_options[:first_data_timeout]
101
+ @chunk_data_timeout = connection_options[:chunk_data_timeout]
102
+ @persistent_data_timeout = http1_options[:persistent_data_timeout]
96
103
 
97
104
  @selector = NIO::Selector.new
98
105
  @queue = Queue.new
@@ -354,11 +361,11 @@ module Raptor
354
361
  state = @socket_to_state[socket]
355
362
  client = TimeoutClient.new(state)
356
363
  timeout = if state[:persisted]
357
- @client_options[:persistent_data_timeout]
364
+ @persistent_data_timeout
358
365
  elsif first_data_received?(state)
359
- @client_options[:chunk_data_timeout]
366
+ @chunk_data_timeout
360
367
  else
361
- @client_options[:first_data_timeout]
368
+ @first_data_timeout
362
369
  end
363
370
  client.timeout_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
364
371
  @timeouts << client
data/lib/raptor/server.rb CHANGED
@@ -19,9 +19,10 @@ module Raptor
19
19
  #
20
20
  # @example
21
21
  # binder = Binder.new(["tcp://0.0.0.0:3000"])
22
- # reactor = Reactor.new(ractor_pool, thread_pool, client_options: {})
23
- # request = Request.new(app, 3000)
24
- # server = Server.new(binder, reactor, thread_pool, request, client_options: { first_data_timeout: 30 })
22
+ # reactor = Reactor.new(ractor_pool, thread_pool, connection_options: {}, http1_options: {})
23
+ # http1 = Http1.new(app, 3000)
24
+ # http2 = Http2.new(app, 3000)
25
+ # server = Server.new(binder, reactor, thread_pool, http1, http2, connection_options: { first_data_timeout: 30 })
25
26
  # server.run
26
27
  # # ... later
27
28
  # server.shutdown
@@ -40,8 +41,10 @@ module Raptor
40
41
  # @rbs @binder: Binder
41
42
  # @rbs @reactor: Reactor
42
43
  # @rbs @thread_pool: AtomicThreadPool
43
- # @rbs @request: Request
44
- # @rbs @client_options: Hash[Symbol, untyped]
44
+ # @rbs @http1: Http1
45
+ # @rbs @http2: Http2
46
+ # @rbs @first_data_timeout: Integer
47
+ # @rbs @drain_accept_queue: bool
45
48
  # @rbs @running: AtomicBoolean
46
49
 
47
50
  # Creates a new Server instance.
@@ -49,17 +52,21 @@ module Raptor
49
52
  # @param binder [Binder] the binder managing listening sockets
50
53
  # @param reactor [Reactor] the reactor for handling client connections
51
54
  # @param thread_pool [AtomicThreadPool] thread pool for application processing
52
- # @param request [Request] the HTTP/1.1 request handler
53
- # @param client_options [Hash] client timeout configuration, used to bound TLS handshakes
55
+ # @param http1 [Http1] the HTTP/1.1 handler
56
+ # @param http2 [Http2] the HTTP/2 handler (provides the initial SETTINGS frame)
57
+ # @param connection_options [Hash] per-connection timeout configuration, used to bound TLS handshakes
58
+ # @param drain_accept_queue [Boolean] whether to drain the kernel accept queue on shutdown
54
59
  # @return [void]
55
60
  #
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:)
61
+ # @rbs (Binder binder, Reactor reactor, AtomicThreadPool thread_pool, Http1 http1, Http2 http2, connection_options: Hash[Symbol, untyped], ?drain_accept_queue: bool) -> void
62
+ def initialize(binder, reactor, thread_pool, http1, http2, connection_options:, drain_accept_queue: false)
58
63
  @binder = binder
59
64
  @reactor = reactor
60
65
  @thread_pool = thread_pool
61
- @request = request
62
- @client_options = client_options
66
+ @http1 = http1
67
+ @http2 = http2
68
+ @first_data_timeout = connection_options[:first_data_timeout]
69
+ @drain_accept_queue = drain_accept_queue
63
70
  @running = AtomicBoolean.new(true)
64
71
  end
65
72
 
@@ -74,12 +81,12 @@ module Raptor
74
81
  #
75
82
  # @rbs () -> Thread
76
83
  def run
77
- Thread.new(@binder.listeners, @reactor, @running) do |server_sockets, reactor, running|
84
+ Thread.new do
78
85
  Thread.current.name = "Server"
79
86
 
80
- while running.true?
87
+ while @running.true?
81
88
  begin
82
- ready_servers, _, _ = IO.select(server_sockets, nil, nil, 1)
89
+ ready_servers, _, _ = IO.select(@binder.listeners, nil, nil, 1)
83
90
  rescue IOError, Errno::EBADF
84
91
  break
85
92
  end
@@ -87,28 +94,42 @@ module Raptor
87
94
  next unless ready_servers
88
95
  next if @reactor.backlog >= [(@thread_pool.size * 1.2).ceil, MIN_BACKPRESSURE_THRESHOLD].max
89
96
 
90
- ready_servers.each do |listener|
91
- accept_connection(listener, reactor)
92
- end
97
+ ready_servers.each { |listener| accept_connection(listener) }
93
98
  end
94
99
  end
95
100
  end
96
101
 
97
102
  # Gracefully shuts down the server.
98
103
  #
99
- # Stops accepting new connections and closes all listening sockets.
100
- # The server thread will exit after handling any in-flight accept operations.
104
+ # Stops accepting new connections and closes all listening sockets. When
105
+ # `drain_accept_queue` is enabled, dispatches every connection already in
106
+ # the kernel accept queue before closing the listeners.
101
107
  #
102
108
  # @return [void]
103
109
  #
104
110
  # @rbs () -> void
105
111
  def shutdown
106
112
  @running.make_false
113
+ drain_accept_queue if @drain_accept_queue
107
114
  @binder.close
108
115
  end
109
116
 
110
117
  private
111
118
 
119
+ # Dispatches every connection already in the kernel accept queue for each
120
+ # listener until all are drained.
121
+ #
122
+ # @return [void]
123
+ #
124
+ # @rbs () -> void
125
+ def drain_accept_queue
126
+ loop do
127
+ accepted = false
128
+ @binder.listeners.each { |listener| accepted = true if accept_connection(listener) }
129
+ break unless accepted
130
+ end
131
+ end
132
+
112
133
  # Accepts a connection from the given listener and dispatches it.
113
134
  #
114
135
  # For SSL listeners the TLS handshake is offloaded to the thread pool so
@@ -118,15 +139,14 @@ module Raptor
118
139
  # follow the HTTP/1.1 path.
119
140
  #
120
141
  # @param listener [TCPServer, UNIXServer, Binder::SslListener] the ready listener
121
- # @param reactor [Reactor] the reactor to dispatch connections to
122
- # @return [void]
142
+ # @return [Boolean] true if a connection was accepted, false if the listener had nothing to dispatch
123
143
  #
124
- # @rbs (TCPServer | UNIXServer | Binder::SslListener listener, Reactor reactor) -> void
125
- def accept_connection(listener, reactor)
144
+ # @rbs (TCPServer | UNIXServer | Binder::SslListener listener) -> bool
145
+ def accept_connection(listener)
126
146
  tcp_client = begin
127
147
  listener.is_a?(Binder::SslListener) ? listener.tcp_server.accept_nonblock : listener.accept_nonblock
128
148
  rescue IO::WaitReadable
129
- return
149
+ return false
130
150
  end
131
151
 
132
152
  if tcp_client.is_a?(TCPSocket)
@@ -138,19 +158,20 @@ module Raptor
138
158
 
139
159
  if listener.is_a?(Binder::SslListener)
140
160
  @thread_pool << proc do
141
- dispatch_ssl_connection(listener, tcp_client, remote_addr, reactor)
161
+ dispatch_ssl_connection(listener, tcp_client, remote_addr)
142
162
  end
143
- return
163
+ return true
144
164
  end
145
165
 
146
- @request.eager_accept(
166
+ @http1.eager_accept(
147
167
  tcp_client,
148
168
  tcp_client.object_id,
149
- reactor,
169
+ @reactor,
150
170
  @thread_pool,
151
171
  remote_addr,
152
172
  HTTP_SCHEME
153
173
  )
174
+ true
154
175
  end
155
176
 
156
177
  # Performs the TLS handshake for an accepted SSL connection and dispatches
@@ -160,35 +181,34 @@ module Raptor
160
181
  # @param listener [Binder::SslListener] the SSL listener that accepted the connection
161
182
  # @param tcp_client [TCPSocket] the accepted TCP socket
162
183
  # @param remote_addr [String] the client's IP address
163
- # @param reactor [Reactor] the reactor to dispatch the connection to
164
184
  # @return [void]
165
185
  #
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)
186
+ # @rbs (Binder::SslListener listener, TCPSocket tcp_client, String remote_addr) -> void
187
+ def dispatch_ssl_connection(listener, tcp_client, remote_addr)
168
188
  ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_client, listener.ssl_context)
169
189
  ssl_socket.sync_close = true
170
190
  return unless perform_ssl_handshake(ssl_socket)
171
191
 
172
192
  if ssl_socket.alpn_protocol == H2_PROTOCOL
173
- ssl_socket.write(Http2.build_server_settings_frame) rescue nil
193
+ ssl_socket.write(@http2.initial_settings_frame) rescue nil
174
194
 
175
- reactor.add(
195
+ @reactor.add(
176
196
  id: ssl_socket.object_id,
177
197
  socket: ssl_socket,
178
198
  remote_addr: remote_addr,
179
199
  url_scheme: HTTPS_SCHEME,
180
200
  protocol: :http2,
181
- writer: Http2::Writer.new,
201
+ writer: @http2.create_writer,
182
202
  flow_control: Http2::FlowControl.new
183
203
  )
184
204
 
185
205
  return
186
206
  end
187
207
 
188
- @request.eager_accept(
208
+ @http1.eager_accept(
189
209
  ssl_socket,
190
210
  ssl_socket.object_id,
191
- reactor,
211
+ @reactor,
192
212
  @thread_pool,
193
213
  remote_addr,
194
214
  HTTPS_SCHEME
@@ -204,7 +224,7 @@ module Raptor
204
224
  #
205
225
  # @rbs (OpenSSL::SSL::SSLSocket ssl_socket) -> bool
206
226
  def perform_ssl_handshake(ssl_socket)
207
- deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @client_options[:first_data_timeout]
227
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @first_data_timeout
208
228
 
209
229
  begin
210
230
  ssl_socket.accept_nonblock
data/lib/raptor/stats.rb CHANGED
@@ -60,7 +60,7 @@ module Raptor
60
60
  # @rbs (Integer index, pid: Integer, phase: Integer, requests: Integer, backlog: Integer, busy_threads: Integer, thread_capacity: Integer, started_at: Float, last_checkin: Float, booted: bool) -> void
61
61
  def write(index, pid:, phase:, requests:, backlog:, busy_threads:, thread_capacity:, started_at:, last_checkin:, booted:)
62
62
  data = [pid, index, phase, requests, backlog, busy_threads, thread_capacity, started_at, last_checkin, booted ? 1 : 0].pack(SLOT_FORMAT)
63
- @mmap.semlock { @mmap[index * SLOT_SIZE, SLOT_SIZE] = data }
63
+ @mmap[index * SLOT_SIZE, SLOT_SIZE] = data
64
64
  end
65
65
 
66
66
  # Returns stats for all worker slots.
@@ -0,0 +1,69 @@
1
+ # rbs_inline: enabled
2
+ # frozen_string_literal: true
3
+
4
+ require "socket"
5
+
6
+ module Raptor
7
+ # Integration with systemd's service notification protocol and
8
+ # socket-activation file descriptors.
9
+ #
10
+ module Systemd
11
+ LISTEN_FDS_START = 3
12
+
13
+ LISTEN_FDNAMES_ENV = "LISTEN_FDNAMES"
14
+ LISTEN_FDS_ENV = "LISTEN_FDS"
15
+ LISTEN_PID_ENV = "LISTEN_PID"
16
+ NOTIFY_SOCKET_ENV = "NOTIFY_SOCKET"
17
+
18
+ # Sends `message` to the systemd notification socket, returning true on
19
+ # success and false when the socket is unset or the send fails.
20
+ #
21
+ # @param message [String] notify protocol payload, e.g. "READY=1"
22
+ # @return [Boolean]
23
+ #
24
+ # @rbs (String message) -> bool
25
+ def self.notify(message)
26
+ socket_path = ENV[NOTIFY_SOCKET_ENV]
27
+ return false if socket_path.nil? || socket_path.empty?
28
+
29
+ address = if socket_path.start_with?("@")
30
+ Socket.pack_sockaddr_un("\0#{socket_path[1..]}")
31
+ else
32
+ Socket.pack_sockaddr_un(socket_path)
33
+ end
34
+
35
+ socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM, 0)
36
+ socket.send(message, 0, address)
37
+ true
38
+ rescue SystemCallError, IOError
39
+ false
40
+ ensure
41
+ socket&.close
42
+ end
43
+
44
+ # Returns the file descriptors passed in via socket activation, or an
45
+ # empty array when systemd has not exported any.
46
+ #
47
+ # @return [Array<Integer>]
48
+ #
49
+ # @rbs () -> Array[Integer]
50
+ def self.listen_fds
51
+ return [] unless ENV[LISTEN_PID_ENV]&.to_i == Process.pid
52
+
53
+ count = ENV[LISTEN_FDS_ENV]&.to_i || 0
54
+ Array.new(count) { |index| LISTEN_FDS_START + index }
55
+ end
56
+
57
+ # Clears the socket-activation environment variables so children don't
58
+ # act on stale values.
59
+ #
60
+ # @return [void]
61
+ #
62
+ # @rbs () -> void
63
+ def self.clear_listen_env
64
+ ENV.delete(LISTEN_FDNAMES_ENV)
65
+ ENV.delete(LISTEN_FDS_ENV)
66
+ ENV.delete(LISTEN_PID_ENV)
67
+ end
68
+ end
69
+ end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Raptor
5
- VERSION = "0.7.0"
5
+ VERSION = "0.8.0"
6
6
  end