Class: Raptor::Binder
- Inherits:
-
Object
- Object
- Raptor::Binder
- Defined in:
- lib/raptor/binder.rb,
sig/generated/raptor/binder.rbs
Overview
Binds tcp://, unix://, and ssl:// 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: 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>
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. -
#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>
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>
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.
66 67 68 69 70 71 72 73 |
# File 'lib/raptor/binder.rb', line 66 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.
43 44 45 |
# File 'lib/raptor/binder.rb', line 43 def bind_uris @bind_uris end |
#listeners ⇒ Array<TCPServer, UNIXServer, SslListener> (readonly)
Returns the array of listening sockets.
53 54 55 |
# File 'lib/raptor/binder.rb', line 53 def listeners @listeners end |
#socket_backlog ⇒ Integer (readonly)
Returns the kernel listen() queue depth for TCP/SSL listeners.
48 49 50 |
# File 'lib/raptor/binder.rb', line 48 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.
81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/raptor/binder.rb', line 81 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}" 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.
293 294 295 296 297 298 299 300 301 302 303 |
# File 'lib/raptor/binder.rb', line 293 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.
142 143 144 |
# File 'lib/raptor/binder.rb', line 142 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.
123 124 125 |
# File 'lib/raptor/binder.rb', line 123 def close @listeners.each(&:close) end |
#create_listeners(uri) ⇒ Array<TCPServer, UNIXServer, SslListener>
Creates fresh listeners for the given URI.
171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/raptor/binder.rb', line 171 def create_listeners(uri) case uri.scheme when "tcp" create_tcp_listeners(uri.host, uri.port) 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.
280 281 282 283 284 |
# File 'lib/raptor/binder.rb', line 280 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.
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/raptor/binder.rb', line 215 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.
244 245 246 247 248 249 250 251 252 253 254 255 256 257 |
# File 'lib/raptor/binder.rb', line 244 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.
114 115 116 |
# File 'lib/raptor/binder.rb', line 114 def http2? @listeners.any? { |listener| listener.is_a?(SslListener) } end |
#inheritable_fds ⇒ Hash{String => Array<Integer>}
Returns the file descriptors of every listener, grouped by bind URI.
132 133 134 |
# File 'lib/raptor/binder.rb', line 132 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.
310 311 312 313 314 315 316 |
# File 'lib/raptor/binder.rb', line 310 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.
155 156 157 158 159 160 161 162 |
# File 'lib/raptor/binder.rb', line 155 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.
267 268 269 270 |
# File 'lib/raptor/binder.rb', line 267 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>
Reconstructs listeners for the given URI from inherited file descriptors.
193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/raptor/binder.rb', line 193 def restore_listeners(uri, filenos) case uri.scheme when "tcp" filenos.map { |fileno| 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.
102 103 104 105 106 107 |
# File 'lib/raptor/binder.rb', line 102 def server_port tcp_listener = @listeners.find { |listener| listener.is_a?(TCPServer) || listener.is_a?(SslListener) } return 0 unless tcp_listener tcp_listener.local_address.ip_port end |