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.
-
#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.
284 285 286 287 288 289 290 291 292 293 294 |
# File 'lib/raptor/binder.rb', line 284 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.
133 134 135 |
# File 'lib/raptor/binder.rb', line 133 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.
114 115 116 |
# File 'lib/raptor/binder.rb', line 114 def close @listeners.each(&:close) end |
#create_listeners(uri) ⇒ Array<TCPServer, UNIXServer, SslListener>
Creates fresh listeners for the given URI.
162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/raptor/binder.rb', line 162 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.
271 272 273 274 275 |
# File 'lib/raptor/binder.rb', line 271 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.
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/raptor/binder.rb', line 206 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.
235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/raptor/binder.rb', line 235 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 |
#inheritable_fds ⇒ Hash{String => Array<Integer>}
Returns the file descriptors of every listener, grouped by bind URI.
123 124 125 |
# File 'lib/raptor/binder.rb', line 123 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.
301 302 303 304 305 306 307 |
# File 'lib/raptor/binder.rb', line 301 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.
146 147 148 149 150 151 152 153 |
# File 'lib/raptor/binder.rb', line 146 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.
258 259 260 261 |
# File 'lib/raptor/binder.rb', line 258 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.
184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/raptor/binder.rb', line 184 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 |