Class: Raptor::Binder
- Inherits:
-
Object
- Object
- Raptor::Binder
- Defined in:
- lib/raptor/binder.rb,
sig/generated/raptor/binder.rbs
Overview
Binds TCP, Unix, SSL, and cleartext HTTP/2 URIs to listening sockets and holds them for the server. Reconstructs listeners from inherited file descriptors when provided (systemd socket activation, hot restart).
Defined Under Namespace
Classes: H2cListener, SslListener, UnknownBindSchemeError
Constant Summary collapse
- SOCKET_BACKLOG =
1024
Instance Attribute Summary collapse
-
#bind_uris ⇒ Array<String>
readonly
Returns the array of bind URIs.
-
#listeners ⇒ Array<TCPServer, UNIXServer, SslListener, H2cListener>
readonly
Returns the array of listening sockets.
-
#socket_backlog ⇒ Integer
readonly
Returns the kernel
listen()queue depth for TCP/SSL listeners.
Instance Method Summary collapse
-
#addresses ⇒ Array<String>
Returns the bound addresses as strings: TCP as
host:port, Unix as the socket path, SSL asssl://host:port, and cleartext HTTP/2 ash2c://host:port. -
#build_ssl_context(ssl_params) ⇒ OpenSSL::SSL::SSLContext
Builds a frozen
OpenSSL::SSL::SSLContextconfigured for HTTP/2 and HTTP/1.1 ALPN negotiation. -
#clear_close_on_exec ⇒ void
Clears the close-on-exec flag on every listener so the file descriptors survive
Kernel.exec. -
#close ⇒ void
Closes all listening sockets.
-
#create_listeners(uri) ⇒ Array<TCPServer, UNIXServer, SslListener, H2cListener>
Creates fresh listeners for the given URI.
-
#create_ssl_listeners(host, port, ssl_params) ⇒ Array<SslListener>
Creates SSL server sockets for the given host and port.
-
#create_tcp_listeners(host, port) ⇒ Array<TCPServer>
Creates TCP server sockets for the given host and port.
-
#create_unix_listeners(path) ⇒ Array<UNIXServer>
Creates a Unix domain server socket at the given path.
-
#http2? ⇒ Boolean
Returns whether any listener can negotiate HTTP/2.
-
#inheritable_fds ⇒ Hash{String => Array<Integer>}
Returns the file descriptors of every listener, grouped by bind URI.
-
#initialize(bind_uris, socket_backlog: SOCKET_BACKLOG, inherited_fds: {}) ⇒ Binder
constructor
Creates a new Binder and binds each URI.
-
#loopback_addresses ⇒ Array<String>
Returns all available loopback IP addresses.
-
#parse ⇒ void
Parses bind URIs and creates listening sockets, reusing inherited file descriptors for URIs supplied in
@inherited_fds. -
#register_unix_socket_cleanup(path) ⇒ void
Registers an
at_exithook that removes the Unix socket file on the owning master's clean exit. -
#restore_listeners(uri, filenos) ⇒ Array<TCPServer, UNIXServer, SslListener, H2cListener>
Reconstructs listeners for the given URI from inherited file descriptors.
-
#server_port ⇒ Integer
Returns the port of the first TCP or SSL listener, or 0 when none is configured.
Constructor Details
#initialize(bind_uris, socket_backlog: SOCKET_BACKLOG, inherited_fds: {}) ⇒ Binder
Creates a new Binder and binds each URI. localhost expands to both
IPv4 and IPv6 loopback; when inherited_fds supplies file descriptors
for a URI the listener is rebuilt from those instead of binding fresh.
82 83 84 85 86 87 88 89 |
# File 'lib/raptor/binder.rb', line 82 def initialize(bind_uris, socket_backlog: SOCKET_BACKLOG, inherited_fds: {}) @bind_uris = bind_uris @socket_backlog = socket_backlog @inherited_fds = inherited_fds @listeners = nil @uri_listeners = nil parse end |
Instance Attribute Details
#bind_uris ⇒ Array<String> (readonly)
Returns the array of bind URIs.
59 60 61 |
# File 'lib/raptor/binder.rb', line 59 def bind_uris @bind_uris end |
#listeners ⇒ Array<TCPServer, UNIXServer, SslListener, H2cListener> (readonly)
Returns the array of listening sockets.
69 70 71 |
# File 'lib/raptor/binder.rb', line 69 def listeners @listeners end |
#socket_backlog ⇒ Integer (readonly)
Returns the kernel listen() queue depth for TCP/SSL listeners.
64 65 66 |
# File 'lib/raptor/binder.rb', line 64 def socket_backlog @socket_backlog end |
Instance Method Details
#addresses ⇒ Array<String>
Returns the bound addresses as strings: TCP as host:port, Unix as
the socket path, SSL as ssl://host:port, and cleartext HTTP/2 as
h2c://host:port.
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/raptor/binder.rb', line 98 def addresses @listeners.map do |listener| case listener when UNIXServer listener.path when SslListener address = listener.local_address "ssl://#{address.ip_address}:#{address.ip_port}" when H2cListener address = listener.local_address "h2c://#{address.ip_address}:#{address.ip_port}" else address = listener.local_address "#{address.ip_address}:#{address.ip_port}" end end end |
#build_ssl_context(ssl_params) ⇒ OpenSSL::SSL::SSLContext
Builds a frozen OpenSSL::SSL::SSLContext configured for HTTP/2 and
HTTP/1.1 ALPN negotiation.
319 320 321 322 323 324 325 326 327 328 329 |
# File 'lib/raptor/binder.rb', line 319 def build_ssl_context(ssl_params) require "openssl" OpenSSL::SSL::SSLContext.new.tap do |ssl_context| ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(ssl_params["cert"])) ssl_context.key = OpenSSL::PKey.read(File.read(ssl_params["key"])) ssl_context.alpn_protocols = ["h2", "http/1.1"] ssl_context.alpn_select_cb = ->(protocols) { protocols.include?("h2") ? "h2" : "http/1.1" } ssl_context.freeze end end |
#clear_close_on_exec ⇒ void
This method returns an undefined value.
Clears the close-on-exec flag on every listener so the file descriptors
survive Kernel.exec.
164 165 166 |
# File 'lib/raptor/binder.rb', line 164 def clear_close_on_exec @listeners.each { |listener| listener.to_io.close_on_exec = false } end |
#close ⇒ void
This method returns an undefined value.
Closes all listening sockets.
145 146 147 |
# File 'lib/raptor/binder.rb', line 145 def close @listeners.each(&:close) end |
#create_listeners(uri) ⇒ Array<TCPServer, UNIXServer, SslListener, H2cListener>
Creates fresh listeners for the given URI.
193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/raptor/binder.rb', line 193 def create_listeners(uri) case uri.scheme when "tcp" create_tcp_listeners(uri.host, uri.port) when "h2c" create_tcp_listeners(uri.host, uri.port).map { |listener| H2cListener.new(tcp_server: listener) } when "unix" create_unix_listeners(uri.path) when "ssl" create_ssl_listeners(uri.host, uri.port, URI.decode_www_form(uri.query || "").to_h) else raise UnknownBindSchemeError.new(uri.scheme) end end |
#create_ssl_listeners(host, port, ssl_params) ⇒ Array<SslListener>
Creates SSL server sockets for the given host and port.
306 307 308 309 310 |
# File 'lib/raptor/binder.rb', line 306 def create_ssl_listeners(host, port, ssl_params) tcp_servers = create_tcp_listeners(host, port) ssl_context = build_ssl_context(ssl_params) tcp_servers.map { |tcp_server| SslListener.new(tcp_server: tcp_server, ssl_context: ssl_context) } end |
#create_tcp_listeners(host, port) ⇒ Array<TCPServer>
Creates TCP server sockets for the given host and port.
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
# File 'lib/raptor/binder.rb', line 241 def create_tcp_listeners(host, port) if host == "localhost" return loopback_addresses.map { |address| create_tcp_listeners(address, port) }.flatten end host = host[1..-2] if host&.start_with?("[") addrinfo = Addrinfo.tcp(host, port) socket = Socket.new(addrinfo.afamily, Socket::SOCK_STREAM, 0) socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true) socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, true) if Socket.const_defined?(:SO_REUSEPORT) socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) socket.bind(addrinfo) socket.listen(@socket_backlog) tcp_server = TCPServer.for_fd(socket.fileno) socket.autoclose = false [tcp_server] end |
#create_unix_listeners(path) ⇒ Array<UNIXServer>
Creates a Unix domain server socket at the given path. Removes stale socket files left by crashed processes, and cleans the file up on the master's clean exit.
270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/raptor/binder.rb', line 270 def create_unix_listeners(path) if File.exist?(path) begin UNIXSocket.new(path).close raise "Socket #{path.inspect} is already in use" rescue Errno::ECONNREFUSED File.delete(path) end end register_unix_socket_cleanup(path) [UNIXServer.new(path)] end |
#http2? ⇒ Boolean
Returns whether any listener can negotiate HTTP/2.
136 137 138 |
# File 'lib/raptor/binder.rb', line 136 def http2? @listeners.any? { |listener| listener.is_a?(SslListener) || listener.is_a?(H2cListener) } end |
#inheritable_fds ⇒ Hash{String => Array<Integer>}
Returns the file descriptors of every listener, grouped by bind URI.
154 155 156 |
# File 'lib/raptor/binder.rb', line 154 def inheritable_fds @uri_listeners.transform_values { |listeners| listeners.map { |listener| listener.to_io.fileno } } end |
#loopback_addresses ⇒ Array<String>
Returns all available loopback IP addresses.
336 337 338 339 340 341 342 |
# File 'lib/raptor/binder.rb', line 336 def loopback_addresses Socket.ip_address_list.filter_map do |addrinfo| next unless addrinfo.ipv4_loopback? || addrinfo.ipv6_loopback? addrinfo.ip_address end.tap(&:uniq!) end |
#parse ⇒ void
This method returns an undefined value.
Parses bind URIs and creates listening sockets, reusing inherited file
descriptors for URIs supplied in @inherited_fds.
177 178 179 180 181 182 183 184 |
# File 'lib/raptor/binder.rb', line 177 def parse @uri_listeners = @bind_uris.to_h do |bind_uri| uri = URI.parse(bind_uri) filenos = @inherited_fds[bind_uri] [bind_uri, filenos ? restore_listeners(uri, filenos) : create_listeners(uri)] end @listeners = @uri_listeners.values.flatten end |
#register_unix_socket_cleanup(path) ⇒ void
This method returns an undefined value.
Registers an at_exit hook that removes the Unix socket file on the
owning master's clean exit. Each call records the current process so
forked workers won't delete a socket their master still owns.
293 294 295 296 |
# File 'lib/raptor/binder.rb', line 293 def register_unix_socket_cleanup(path) master_pid = Process.pid at_exit { File.delete(path) rescue nil if Process.pid == master_pid } end |
#restore_listeners(uri, filenos) ⇒ Array<TCPServer, UNIXServer, SslListener, H2cListener>
Reconstructs listeners for the given URI from inherited file descriptors.
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/raptor/binder.rb', line 217 def restore_listeners(uri, filenos) case uri.scheme when "tcp" filenos.map { |fileno| TCPServer.for_fd(fileno) } when "h2c" filenos.map { |fileno| H2cListener.new(tcp_server: TCPServer.for_fd(fileno)) } when "unix" register_unix_socket_cleanup(uri.path) filenos.map { |fileno| UNIXServer.for_fd(fileno) } when "ssl" ssl_context = build_ssl_context(URI.decode_www_form(uri.query || "").to_h) filenos.map { |fileno| SslListener.new(tcp_server: TCPServer.for_fd(fileno), ssl_context: ssl_context) } else raise UnknownBindSchemeError.new(uri.scheme) end end |
#server_port ⇒ Integer
Returns the port of the first TCP or SSL listener, or 0 when none is configured.
122 123 124 125 126 127 128 129 |
# File 'lib/raptor/binder.rb', line 122 def server_port tcp_listener = @listeners.find do |listener| listener.is_a?(TCPServer) || listener.is_a?(SslListener) || listener.is_a?(H2cListener) end return 0 unless tcp_listener tcp_listener.local_address.ip_port end |