lib/puma/binder.rb in piesync-puma-3.12.6.1 vs lib/puma/binder.rb in piesync-puma-5.4.0.1
- old
+ new
@@ -3,30 +3,47 @@
require 'uri'
require 'socket'
require 'puma/const'
require 'puma/util'
+require 'puma/configuration'
module Puma
+
+ if HAS_SSL
+ require 'puma/minissl'
+ require 'puma/minissl/context_builder'
+
+ # Odd bug in 'pure Ruby' nio4r version 2.5.2, which installs with Ruby 2.3.
+ # NIO doesn't create any OpenSSL objects, but it rescues an OpenSSL error.
+ # The bug was that it did not require openssl.
+ # @todo remove when Ruby 2.3 support is dropped
+ #
+ if windows? && RbConfig::CONFIG['ruby_version'] == '2.3.0'
+ require 'openssl'
+ end
+ end
+
class Binder
include Puma::Const
- RACK_VERSION = [1,3].freeze
+ RACK_VERSION = [1,6].freeze
- def initialize(events)
+ def initialize(events, conf = Configuration.new)
@events = events
@listeners = []
@inherited_fds = {}
@activated_sockets = {}
@unix_paths = []
@proto_env = {
"rack.version".freeze => RACK_VERSION,
"rack.errors".freeze => events.stderr,
- "rack.multithread".freeze => true,
- "rack.multiprocess".freeze => false,
+ "rack.multithread".freeze => conf.options[:max_threads] > 1,
+ "rack.multiprocess".freeze => conf.options[:workers] >= 1,
"rack.run_once".freeze => false,
+ RACK_URL_SCHEME => conf.options[:rack_url_scheme],
"SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
# I'd like to set a default CONTENT_TYPE here but some things
# depend on their not being a default set and inferring
# it from the content. And so if i set it here, it won't
@@ -40,55 +57,102 @@
@envs = {}
@ios = []
end
- attr_reader :listeners, :ios
+ attr_reader :ios
+ # @version 5.0.0
+ attr_reader :activated_sockets, :envs, :inherited_fds, :listeners, :proto_env, :unix_paths
+
+ # @version 5.0.0
+ attr_writer :ios, :listeners
+
def env(sock)
@envs.fetch(sock, @proto_env)
end
def close
@ios.each { |i| i.close }
- @unix_paths.each { |i| File.unlink i }
end
- def import_from_env
- remove = []
+ # @!attribute [r] connected_ports
+ # @version 5.0.0
+ def connected_ports
+ ios.map { |io| io.addr[1] }.uniq
+ end
- ENV.each do |k,v|
- if k =~ /PUMA_INHERIT_\d+/
- fd, url = v.split(":", 2)
- @inherited_fds[url] = fd.to_i
- remove << k
- elsif k == 'LISTEN_FDS' && ENV['LISTEN_PID'].to_i == $$
- v.to_i.times do |num|
- fd = num + 3
- sock = TCPServer.for_fd(fd)
- begin
- key = [ :unix, Socket.unpack_sockaddr_un(sock.getsockname) ]
- rescue ArgumentError
- port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
- if addr =~ /\:/
- addr = "[#{addr}]"
- end
- key = [ :tcp, addr, port ]
- end
- @activated_sockets[key] = sock
- @events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
- end
- remove << k << 'LISTEN_PID'
+ # @version 5.0.0
+ def create_inherited_fds(env_hash)
+ env_hash.select {|k,v| k =~ /PUMA_INHERIT_\d+/}.each do |_k, v|
+ fd, url = v.split(":", 2)
+ @inherited_fds[url] = fd.to_i
+ end.keys # pass keys back for removal
+ end
+
+ # systemd socket activation.
+ # LISTEN_FDS = number of listening sockets. e.g. 2 means accept on 2 sockets w/descriptors 3 and 4.
+ # LISTEN_PID = PID of the service process, aka us
+ # @see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
+ # @version 5.0.0
+ #
+ def create_activated_fds(env_hash)
+ @events.debug "ENV['LISTEN_FDS'] #{ENV['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
+ return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
+ env_hash['LISTEN_FDS'].to_i.times do |index|
+ sock = TCPServer.for_fd(socket_activation_fd(index))
+ key = begin # Try to parse as a path
+ [:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
+ rescue ArgumentError # Try to parse as a port/ip
+ port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
+ addr = "[#{addr}]" if addr =~ /\:/
+ [:tcp, addr, port]
end
+ @activated_sockets[key] = sock
+ @events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
end
+ ["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
+ end
- remove.each do |k|
- ENV.delete k
+ # Synthesize binds from systemd socket activation
+ #
+ # When systemd socket activation is enabled, it can be tedious to keep the
+ # binds in sync. This method can synthesize any binds based on the received
+ # activated sockets. Any existing matching binds will be respected.
+ #
+ # When only_matching is true in, all binds that do not match an activated
+ # socket is removed in place.
+ #
+ # It's a noop if no activated sockets were received.
+ def synthesize_binds_from_activated_fs(binds, only_matching)
+ return binds unless activated_sockets.any?
+
+ activated_binds = []
+
+ activated_sockets.keys.each do |proto, addr, port|
+ if port
+ tcp_url = "#{proto}://#{addr}:#{port}"
+ ssl_url = "ssl://#{addr}:#{port}"
+ ssl_url_prefix = "#{ssl_url}?"
+
+ existing = binds.find { |bind| bind == tcp_url || bind == ssl_url || bind.start_with?(ssl_url_prefix) }
+
+ activated_binds << (existing || tcp_url)
+ else
+ # TODO: can there be a SSL bind without a port?
+ activated_binds << "#{proto}://#{addr}"
+ end
end
+
+ if only_matching
+ activated_binds
+ else
+ binds | activated_binds
+ end
end
- def parse(binds, logger)
+ def parse(binds, logger, log_msg = 'Listening')
binds.each do |str|
uri = URI.parse str
case uri.scheme
when "tcp"
if fd = @inherited_fds.delete(str)
@@ -96,27 +160,41 @@
logger.log "* Inherited #{str}"
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
io = inherit_tcp_listener uri.host, uri.port, sock
logger.log "* Activated #{str}"
else
+ ios_len = @ios.length
params = Util.parse_query uri.query
- opt = params.key?('low_latency')
+ opt = params.key?('low_latency') && params['low_latency'] != 'false'
bak = params.fetch('backlog', 1024).to_i
io = add_tcp_listener uri.host, uri.port, opt, bak
- logger.log "* Listening on #{str}"
+
+ @ios[ios_len..-1].each do |i|
+ addr = loc_addr_str i
+ logger.log "* #{log_msg} on http://#{addr}"
+ end
end
@listeners << [str, io] if io
when "unix"
path = "#{uri.host}#{uri.path}".gsub("%20", " ")
+ abstract = false
+ if str.start_with? 'unix://@'
+ raise "OS does not support abstract UNIXSockets" unless Puma.abstract_unix_socket?
+ abstract = true
+ path = "@#{path}"
+ end
if fd = @inherited_fds.delete(str)
+ @unix_paths << path unless abstract
io = inherit_unix_listener path, fd
logger.log "* Inherited #{str}"
- elsif sock = @activated_sockets.delete([ :unix, path ])
+ elsif sock = @activated_sockets.delete([ :unix, path ]) ||
+ @activated_sockets.delete([ :unix, File.realdirpath(path) ])
+ @unix_paths << path unless abstract || File.exist?(path)
io = inherit_unix_listener path, sock
logger.log "* Activated #{str}"
else
umask = nil
mode = nil
@@ -136,88 +214,37 @@
if u = params['backlog']
backlog = Integer(u)
end
end
+ @unix_paths << path unless abstract || File.exist?(path)
io = add_unix_listener path, umask, mode, backlog
- logger.log "* Listening on #{str}"
+ logger.log "* #{log_msg} on #{str}"
end
@listeners << [str, io]
when "ssl"
- params = Util.parse_query uri.query
- require 'puma/minissl'
- MiniSSL.check
+ raise "Puma compiled without SSL support" unless HAS_SSL
- ctx = MiniSSL::Context.new
+ params = Util.parse_query uri.query
+ ctx = MiniSSL::ContextBuilder.new(params, @events).context
- if defined?(JRUBY_VERSION)
- unless params['keystore']
- @events.error "Please specify the Java keystore via 'keystore='"
- end
-
- ctx.keystore = params['keystore']
-
- unless params['keystore-pass']
- @events.error "Please specify the Java keystore password via 'keystore-pass='"
- end
-
- ctx.keystore_pass = params['keystore-pass']
- ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
- else
- unless params['key']
- @events.error "Please specify the SSL key via 'key='"
- end
-
- ctx.key = params['key']
-
- unless params['cert']
- @events.error "Please specify the SSL cert via 'cert='"
- end
-
- ctx.cert = params['cert']
-
- if ['peer', 'force_peer'].include?(params['verify_mode'])
- unless params['ca']
- @events.error "Please specify the SSL ca via 'ca='"
- end
- end
-
- ctx.ca = params['ca'] if params['ca']
- ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
- end
-
- if params['verify_mode']
- ctx.verify_mode = case params['verify_mode']
- when "peer"
- MiniSSL::VERIFY_PEER
- when "force_peer"
- MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
- when "none"
- MiniSSL::VERIFY_NONE
- else
- @events.error "Please specify a valid verify_mode="
- MiniSSL::VERIFY_NONE
- end
- end
-
- if params['verification_flags']
- ctx.verification_flags = Array(params['verification_flags']).
- map { |flag| MiniSSL::VERIFICATION_FLAGS.fetch(flag) }.
- inject { |sum, flag| sum ? sum | flag : flag }
- end
-
if fd = @inherited_fds.delete(str)
logger.log "* Inherited #{str}"
io = inherit_ssl_listener fd, ctx
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
io = inherit_ssl_listener sock, ctx
logger.log "* Activated #{str}"
else
+ ios_len = @ios.length
io = add_ssl_listener uri.host, uri.port, ctx
- logger.log "* Listening on #{str}"
+
+ @ios[ios_len..-1].each do |i|
+ addr = loc_addr_str i
+ logger.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
+ end
end
@listeners << [str, io] if io
else
logger.error "Invalid URI: #{str}"
@@ -241,27 +268,25 @@
File.unlink path
end
end
# Also close any unused activated sockets
- @activated_sockets.each do |key, sock|
- logger.log "* Closing unused activated socket: #{key.join ':'}"
- begin
- sock.close
- rescue SystemCallError
+ unless @activated_sockets.empty?
+ fds = @ios.map(&:to_i)
+ @activated_sockets.each do |key, sock|
+ next if fds.include? sock.to_i
+ logger.log "* Closing unused activated socket: #{key.first}://#{key[1..-1].join ':'}"
+ begin
+ sock.close
+ rescue SystemCallError
+ end
+ # We have to unlink a unix socket path that's not being used
+ File.unlink key[1] if key.first == :unix
end
- # We have to unlink a unix socket path that's not being used
- File.unlink key[1] if key[0] == :unix
end
end
- def loopback_addresses
- Socket.ip_address_list.select do |addrinfo|
- addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
- end.map { |addrinfo| addrinfo.ip_address }.uniq
- end
-
# Tell the server to listen on host +host+, port +port+.
# If +optimize_for_latency+ is true (the default) then clients connecting
# will be optimized for latency over throughput.
#
# +backlog+ indicates how many unaccepted connections the kernel should
@@ -274,40 +299,32 @@
end
return
end
host = host[1..-2] if host and host[0..0] == '['
- s = TCPServer.new(host, port)
+ tcp_server = TCPServer.new(host, port)
if optimize_for_latency
- s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
+ tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
end
- s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
- s.listen backlog
- @connected_port = s.addr[1]
+ tcp_server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
+ tcp_server.listen backlog
- @ios << s
- s
+ @ios << tcp_server
+ tcp_server
end
- attr_reader :connected_port
-
def inherit_tcp_listener(host, port, fd)
- if fd.kind_of? TCPServer
- s = fd
- else
- s = TCPServer.for_fd(fd)
- end
+ s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
@ios << s
s
end
def add_ssl_listener(host, port, ctx,
optimize_for_latency=true, backlog=1024)
- require 'puma/minissl'
- MiniSSL.check
+ raise "Puma compiled without SSL support" unless HAS_SSL
if host == "localhost"
loopback_addresses.each do |addr|
add_ssl_listener addr, port, ctx, optimize_for_latency, backlog
end
@@ -320,29 +337,24 @@
s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
end
s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
s.listen backlog
-
ssl = MiniSSL::Server.new s, ctx
env = @proto_env.dup
env[HTTPS_KEY] = HTTPS
@envs[ssl] = env
@ios << ssl
s
end
def inherit_ssl_listener(fd, ctx)
- require 'puma/minissl'
- MiniSSL.check
+ raise "Puma compiled without SSL support" unless HAS_SSL
- if fd.kind_of? TCPServer
- s = fd
- else
- s = TCPServer.for_fd(fd)
- end
+ s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
+
ssl = MiniSSL::Server.new(s, ctx)
env = @proto_env.dup
env[HTTPS_KEY] = HTTPS
@envs[ssl] = env
@@ -353,12 +365,10 @@
end
# Tell the server to listen on +path+ as a UNIX domain socket.
#
def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
- @unix_paths << path
-
# Let anyone connect by default
umask ||= 0
begin
old_mask = File.umask(umask)
@@ -371,12 +381,11 @@
else
old.close
raise "There is already a server bound to: #{path}"
end
end
-
- s = UNIXServer.new(path)
+ s = UNIXServer.new path.sub(/\A@/, "\0") # check for abstract UNIXSocket
s.listen backlog
@ios << s
ensure
File.umask old_mask
end
@@ -391,23 +400,63 @@
s
end
def inherit_unix_listener(path, fd)
- @unix_paths << path
+ s = fd.kind_of?(::TCPServer) ? fd : ::UNIXServer.for_fd(fd)
- if fd.kind_of? TCPServer
- s = fd
- else
- s = UNIXServer.for_fd fd
- end
@ios << s
env = @proto_env.dup
env[REMOTE_ADDR] = "127.0.0.1"
@envs[s] = env
s
end
+ def close_listeners
+ @listeners.each do |l, io|
+ io.close unless io.closed?
+ uri = URI.parse l
+ next unless uri.scheme == 'unix'
+ unix_path = "#{uri.host}#{uri.path}"
+ File.unlink unix_path if @unix_paths.include?(unix_path) && File.exist?(unix_path)
+ end
+ end
+
+ def redirects_for_restart
+ redirects = @listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
+ redirects[:close_others] = true
+ redirects
+ end
+
+ # @version 5.0.0
+ def redirects_for_restart_env
+ @listeners.each_with_object({}).with_index do |(listen, memo), i|
+ memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
+ end
+ end
+
+ private
+
+ # @!attribute [r] loopback_addresses
+ def loopback_addresses
+ Socket.ip_address_list.select do |addrinfo|
+ addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
+ end.map { |addrinfo| addrinfo.ip_address }.uniq
+ end
+
+ def loc_addr_str(io)
+ loc_addr = io.to_io.local_address
+ if loc_addr.ipv6?
+ "[#{loc_addr.ip_unpack[0]}]:#{loc_addr.ip_unpack[1]}"
+ else
+ loc_addr.ip_unpack.join(':')
+ end
+ end
+
+ # @version 5.0.0
+ def socket_activation_fd(int)
+ int + 3 # 3 is the magic number you add to follow the SA protocol
+ end
end
end