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, SSL, or cleartext HTTP/2 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.
83 84 85 86 87 88 89 90 |
# File 'lib/raptor/binder.rb', line 83 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.
60 61 62 |
# File 'lib/raptor/binder.rb', line 60 def bind_uris @bind_uris end |
#listeners ⇒ Array<TCPServer, UNIXServer, SslListener, H2cListener> (readonly)
Returns the array of listening sockets.
70 71 72 |
# File 'lib/raptor/binder.rb', line 70 def listeners @listeners end |
#socket_backlog ⇒ Integer (readonly)
Returns the kernel listen() queue depth for TCP/SSL listeners.
65 66 67 |
# File 'lib/raptor/binder.rb', line 65 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.
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/raptor/binder.rb', line 99 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.
321 322 323 324 325 326 327 328 329 |
# File 'lib/raptor/binder.rb', line 321 def build_ssl_context(ssl_params) 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.
165 166 167 |
# File 'lib/raptor/binder.rb', line 165 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.
146 147 148 |
# File 'lib/raptor/binder.rb', line 146 def close @listeners.each(&:close) end |
#create_listeners(uri) ⇒ Array<TCPServer, UNIXServer, SslListener, H2cListener>
Creates fresh listeners for the given URI.
194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/raptor/binder.rb', line 194 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.
308 309 310 311 312 |
# File 'lib/raptor/binder.rb', line 308 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.
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/raptor/binder.rb', line 242 def create_tcp_listeners(host, port) if host == "localhost" return loopback_addresses.flat_map { |address| create_tcp_listeners(address, port) } 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.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_DEFER_ACCEPT, 1) if Socket.const_defined?(:TCP_DEFER_ACCEPT) 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.
272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/raptor/binder.rb', line 272 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.
137 138 139 |
# File 'lib/raptor/binder.rb', line 137 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.
155 156 157 |
# File 'lib/raptor/binder.rb', line 155 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.
178 179 180 181 182 183 184 185 |
# File 'lib/raptor/binder.rb', line 178 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.
295 296 297 298 |
# File 'lib/raptor/binder.rb', line 295 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.
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/raptor/binder.rb', line 218 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, SSL, or cleartext HTTP/2 listener, or 0 when none is configured.
123 124 125 126 127 128 129 130 |
# File 'lib/raptor/binder.rb', line 123 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 |