2018-09-17 12:41:14 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-02-07 01:28:02 -05:00
|
|
|
require 'uri'
|
2016-09-05 14:29:16 -04:00
|
|
|
require 'socket'
|
|
|
|
|
|
|
|
require 'puma/const'
|
|
|
|
require 'puma/util'
|
2020-06-01 03:17:46 -04:00
|
|
|
require 'puma/configuration'
|
2012-08-02 18:03:52 -04:00
|
|
|
|
|
|
|
module Puma
|
2020-07-03 16:40:13 -04:00
|
|
|
|
|
|
|
if HAS_SSL
|
|
|
|
require 'puma/minissl'
|
|
|
|
require 'puma/minissl/context_builder'
|
|
|
|
require 'puma/accept_nonblock'
|
|
|
|
end
|
|
|
|
|
2012-08-02 18:03:52 -04:00
|
|
|
class Binder
|
|
|
|
include Puma::Const
|
|
|
|
|
2020-03-19 16:25:09 -04:00
|
|
|
RACK_VERSION = [1,6].freeze
|
2015-07-14 13:28:59 -04:00
|
|
|
|
2020-06-01 03:17:46 -04:00
|
|
|
def initialize(events, conf = Configuration.new)
|
2012-08-02 18:03:52 -04:00
|
|
|
@events = events
|
|
|
|
@listeners = []
|
|
|
|
@inherited_fds = {}
|
2016-04-01 15:45:48 -04:00
|
|
|
@activated_sockets = {}
|
2012-08-02 18:03:52 -04:00
|
|
|
@unix_paths = []
|
|
|
|
|
|
|
|
@proto_env = {
|
2015-07-14 13:28:59 -04:00
|
|
|
"rack.version".freeze => RACK_VERSION,
|
2012-08-02 18:03:52 -04:00
|
|
|
"rack.errors".freeze => events.stderr,
|
2020-06-01 21:53:08 -04:00
|
|
|
"rack.multithread".freeze => conf.options[:max_threads] > 1,
|
|
|
|
"rack.multiprocess".freeze => conf.options[:workers] >= 1,
|
2013-03-08 11:57:25 -05:00
|
|
|
"rack.run_once".freeze => false,
|
2012-08-02 18:03:52 -04:00
|
|
|
"SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
|
|
|
|
|
2015-07-14 14:45:33 -04:00
|
|
|
# I'd like to set a default CONTENT_TYPE here but some things
|
2016-04-07 14:22:15 -04:00
|
|
|
# depend on their not being a default set and inferring
|
2015-07-14 14:45:33 -04:00
|
|
|
# it from the content. And so if i set it here, it won't
|
|
|
|
# infer properly.
|
2012-08-02 18:03:52 -04:00
|
|
|
|
|
|
|
"QUERY_STRING".freeze => "",
|
|
|
|
SERVER_PROTOCOL => HTTP_11,
|
2016-02-27 12:32:18 -05:00
|
|
|
SERVER_SOFTWARE => PUMA_SERVER_STRING,
|
2012-08-02 18:03:52 -04:00
|
|
|
GATEWAY_INTERFACE => CGI_VER
|
|
|
|
}
|
|
|
|
|
|
|
|
@envs = {}
|
|
|
|
@ios = []
|
|
|
|
end
|
|
|
|
|
2020-03-07 13:44:52 -05:00
|
|
|
attr_reader :ios, :listeners, :unix_paths, :proto_env, :envs, :activated_sockets, :inherited_fds
|
|
|
|
attr_writer :ios, :listeners
|
2012-08-02 18:03:52 -04:00
|
|
|
|
|
|
|
def env(sock)
|
|
|
|
@envs.fetch(sock, @proto_env)
|
|
|
|
end
|
|
|
|
|
|
|
|
def close
|
|
|
|
@ios.each { |i| i.close }
|
|
|
|
end
|
|
|
|
|
2020-03-08 12:06:42 -04:00
|
|
|
def connected_ports
|
|
|
|
ios.map { |io| io.addr[1] }.uniq
|
|
|
|
end
|
2012-08-02 18:03:52 -04:00
|
|
|
|
2020-03-11 14:50:44 -04:00
|
|
|
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
|
|
|
|
def create_activated_fds(env_hash)
|
|
|
|
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]
|
2013-04-04 19:41:22 -04:00
|
|
|
end
|
2020-03-11 14:50:44 -04:00
|
|
|
@activated_sockets[key] = sock
|
|
|
|
@events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
|
2012-08-02 18:03:52 -04:00
|
|
|
end
|
2020-03-11 14:50:44 -04:00
|
|
|
["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
|
2012-08-02 18:03:52 -04:00
|
|
|
end
|
|
|
|
|
2020-02-20 07:40:17 -05:00
|
|
|
def parse(binds, logger, log_msg = 'Listening')
|
2012-08-02 18:03:52 -04:00
|
|
|
binds.each do |str|
|
|
|
|
uri = URI.parse str
|
|
|
|
case uri.scheme
|
|
|
|
when "tcp"
|
|
|
|
if fd = @inherited_fds.delete(str)
|
|
|
|
io = inherit_tcp_listener uri.host, uri.port, fd
|
2018-07-14 21:09:58 -04:00
|
|
|
logger.log "* Inherited #{str}"
|
2016-04-01 15:45:48 -04:00
|
|
|
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
|
|
|
io = inherit_tcp_listener uri.host, uri.port, sock
|
2018-07-14 21:09:58 -04:00
|
|
|
logger.log "* Activated #{str}"
|
2012-08-02 18:03:52 -04:00
|
|
|
else
|
2015-07-14 13:28:59 -04:00
|
|
|
params = Util.parse_query uri.query
|
2014-01-25 20:02:32 -05:00
|
|
|
|
|
|
|
opt = params.key?('low_latency')
|
|
|
|
bak = params.fetch('backlog', 1024).to_i
|
|
|
|
|
|
|
|
io = add_tcp_listener uri.host, uri.port, opt, bak
|
2019-09-11 08:01:24 -04:00
|
|
|
|
|
|
|
@ios.each do |i|
|
2019-10-01 05:33:00 -04:00
|
|
|
next unless TCPServer === i
|
2019-09-11 08:01:24 -04:00
|
|
|
addr = if i.local_address.ipv6?
|
|
|
|
"[#{i.local_address.ip_unpack[0]}]:#{i.local_address.ip_unpack[1]}"
|
|
|
|
else
|
|
|
|
i.local_address.ip_unpack.join(':')
|
|
|
|
end
|
|
|
|
|
2020-03-09 15:29:34 -04:00
|
|
|
logger.log "* #{log_msg} on http://#{addr}"
|
2019-09-11 08:01:24 -04:00
|
|
|
end
|
2012-08-02 18:03:52 -04:00
|
|
|
end
|
|
|
|
|
2016-11-21 09:40:56 -05:00
|
|
|
@listeners << [str, io] if io
|
2012-08-02 18:03:52 -04:00
|
|
|
when "unix"
|
2015-01-20 13:38:09 -05:00
|
|
|
path = "#{uri.host}#{uri.path}".gsub("%20", " ")
|
2012-09-09 18:14:56 -04:00
|
|
|
|
2012-08-02 18:03:52 -04:00
|
|
|
if fd = @inherited_fds.delete(str)
|
2012-09-09 18:14:56 -04:00
|
|
|
io = inherit_unix_listener path, fd
|
2018-07-14 21:09:58 -04:00
|
|
|
logger.log "* Inherited #{str}"
|
2016-04-01 15:45:48 -04:00
|
|
|
elsif sock = @activated_sockets.delete([ :unix, path ])
|
|
|
|
io = inherit_unix_listener path, sock
|
2018-07-14 21:09:58 -04:00
|
|
|
logger.log "* Activated #{str}"
|
2012-08-02 18:03:52 -04:00
|
|
|
else
|
|
|
|
umask = nil
|
2015-01-20 13:14:50 -05:00
|
|
|
mode = nil
|
2017-11-28 12:11:59 -05:00
|
|
|
backlog = 1024
|
2012-08-02 18:03:52 -04:00
|
|
|
|
|
|
|
if uri.query
|
2015-07-14 13:28:59 -04:00
|
|
|
params = Util.parse_query uri.query
|
2012-08-02 18:03:52 -04:00
|
|
|
if u = params['umask']
|
|
|
|
# Use Integer() to respect the 0 prefix as octal
|
|
|
|
umask = Integer(u)
|
|
|
|
end
|
2015-01-20 13:14:50 -05:00
|
|
|
|
|
|
|
if u = params['mode']
|
|
|
|
mode = Integer('0'+u)
|
|
|
|
end
|
2016-02-17 17:55:32 -05:00
|
|
|
|
|
|
|
if u = params['backlog']
|
|
|
|
backlog = Integer(u)
|
|
|
|
end
|
2012-08-02 18:03:52 -04:00
|
|
|
end
|
|
|
|
|
2016-02-17 17:55:32 -05:00
|
|
|
io = add_unix_listener path, umask, mode, backlog
|
2020-02-20 07:40:17 -05:00
|
|
|
logger.log "* #{log_msg} on #{str}"
|
2012-08-02 18:03:52 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
@listeners << [str, io]
|
|
|
|
when "ssl"
|
2020-07-03 16:40:13 -04:00
|
|
|
|
|
|
|
raise "Puma compiled without SSL support" unless HAS_SSL
|
|
|
|
|
2015-07-14 13:28:59 -04:00
|
|
|
params = Util.parse_query uri.query
|
2019-10-20 21:35:15 -04:00
|
|
|
ctx = MiniSSL::ContextBuilder.new(params, @events).context
|
2012-08-02 18:03:52 -04:00
|
|
|
|
|
|
|
if fd = @inherited_fds.delete(str)
|
|
|
|
logger.log "* Inherited #{str}"
|
2016-04-01 15:59:22 -04:00
|
|
|
io = inherit_ssl_listener fd, ctx
|
2016-04-01 15:45:48 -04:00
|
|
|
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
|
|
|
io = inherit_ssl_listener sock, ctx
|
2018-07-14 21:09:58 -04:00
|
|
|
logger.log "* Activated #{str}"
|
2012-08-02 18:03:52 -04:00
|
|
|
else
|
|
|
|
io = add_ssl_listener uri.host, uri.port, ctx
|
2018-07-14 21:09:58 -04:00
|
|
|
logger.log "* Listening on #{str}"
|
2012-08-02 18:03:52 -04:00
|
|
|
end
|
|
|
|
|
2016-11-21 09:40:56 -05:00
|
|
|
@listeners << [str, io] if io
|
2012-08-02 18:03:52 -04:00
|
|
|
else
|
|
|
|
logger.error "Invalid URI: #{str}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# If we inherited fds but didn't use them (because of a
|
|
|
|
# configuration change), then be sure to close them.
|
|
|
|
@inherited_fds.each do |str, fd|
|
|
|
|
logger.log "* Closing unused inherited connection: #{str}"
|
|
|
|
|
|
|
|
begin
|
2016-04-01 15:45:48 -04:00
|
|
|
IO.for_fd(fd).close
|
2012-08-02 18:03:52 -04:00
|
|
|
rescue SystemCallError
|
|
|
|
end
|
|
|
|
|
|
|
|
# We have to unlink a unix socket path that's not being used
|
|
|
|
uri = URI.parse str
|
|
|
|
if uri.scheme == "unix"
|
|
|
|
path = "#{uri.host}#{uri.path}"
|
|
|
|
File.unlink path
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-04-07 14:22:15 -04:00
|
|
|
# Also close any unused activated sockets
|
2016-04-01 15:45:48 -04:00
|
|
|
@activated_sockets.each do |key, sock|
|
|
|
|
logger.log "* Closing unused activated socket: #{key.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[0] == :unix
|
|
|
|
end
|
2016-07-19 00:42:29 -04:00
|
|
|
end
|
2016-04-01 15:45:48 -04:00
|
|
|
|
2012-08-02 18:03:52 -04:00
|
|
|
# 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
|
|
|
|
# allow to accumulate before returning connection refused.
|
|
|
|
#
|
|
|
|
def add_tcp_listener(host, port, optimize_for_latency=true, backlog=1024)
|
2016-07-19 00:42:29 -04:00
|
|
|
if host == "localhost"
|
2017-08-02 20:50:50 -04:00
|
|
|
loopback_addresses.each do |addr|
|
2016-07-19 00:42:29 -04:00
|
|
|
add_tcp_listener addr, port, optimize_for_latency, backlog
|
|
|
|
end
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2015-08-15 12:55:37 -04:00
|
|
|
host = host[1..-2] if host and host[0..0] == '['
|
2019-11-19 13:36:15 -05:00
|
|
|
tcp_server = TCPServer.new(host, port)
|
2012-08-02 18:03:52 -04:00
|
|
|
if optimize_for_latency
|
2019-11-19 13:36:15 -05:00
|
|
|
tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
2012-08-02 18:03:52 -04:00
|
|
|
end
|
2019-11-19 13:36:15 -05:00
|
|
|
tcp_server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
|
|
|
tcp_server.listen backlog
|
2016-02-04 11:15:41 -05:00
|
|
|
|
2019-11-19 13:36:15 -05:00
|
|
|
@ios << tcp_server
|
|
|
|
tcp_server
|
2012-08-02 18:03:52 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def inherit_tcp_listener(host, port, fd)
|
|
|
|
if fd.kind_of? TCPServer
|
|
|
|
s = fd
|
|
|
|
else
|
|
|
|
s = TCPServer.for_fd(fd)
|
|
|
|
end
|
|
|
|
|
|
|
|
@ios << s
|
|
|
|
s
|
|
|
|
end
|
|
|
|
|
|
|
|
def add_ssl_listener(host, port, ctx,
|
|
|
|
optimize_for_latency=true, backlog=1024)
|
2012-09-10 11:41:43 -04:00
|
|
|
|
2020-07-03 16:40:13 -04:00
|
|
|
raise "Puma compiled without SSL support" unless HAS_SSL
|
2015-09-18 12:43:51 -04:00
|
|
|
|
2016-07-19 00:42:29 -04:00
|
|
|
if host == "localhost"
|
2017-08-02 20:50:50 -04:00
|
|
|
loopback_addresses.each do |addr|
|
2017-02-09 09:32:50 -05:00
|
|
|
add_ssl_listener addr, port, ctx, optimize_for_latency, backlog
|
2016-07-19 00:42:29 -04:00
|
|
|
end
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2014-11-23 18:59:20 -05:00
|
|
|
host = host[1..-2] if host[0..0] == '['
|
2012-08-02 18:03:52 -04:00
|
|
|
s = TCPServer.new(host, port)
|
|
|
|
if optimize_for_latency
|
|
|
|
s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
|
|
|
end
|
|
|
|
s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
|
|
|
s.listen backlog
|
|
|
|
|
2012-09-10 11:41:43 -04:00
|
|
|
ssl = MiniSSL::Server.new s, ctx
|
2012-08-02 18:03:52 -04:00
|
|
|
env = @proto_env.dup
|
|
|
|
env[HTTPS_KEY] = HTTPS
|
|
|
|
@envs[ssl] = env
|
|
|
|
|
|
|
|
@ios << ssl
|
|
|
|
s
|
|
|
|
end
|
|
|
|
|
2016-04-01 15:59:22 -04:00
|
|
|
def inherit_ssl_listener(fd, ctx)
|
2020-07-03 16:40:13 -04:00
|
|
|
raise "Puma compiled without SSL support" unless HAS_SSL
|
2015-09-18 12:43:51 -04:00
|
|
|
|
2016-04-01 14:09:48 -04:00
|
|
|
if fd.kind_of? TCPServer
|
|
|
|
s = fd
|
|
|
|
else
|
|
|
|
s = TCPServer.for_fd(fd)
|
|
|
|
end
|
2015-01-20 13:35:22 -05:00
|
|
|
ssl = MiniSSL::Server.new(s, ctx)
|
|
|
|
|
|
|
|
env = @proto_env.dup
|
|
|
|
env[HTTPS_KEY] = HTTPS
|
|
|
|
@envs[ssl] = env
|
|
|
|
|
|
|
|
@ios << ssl
|
|
|
|
|
2012-08-02 18:03:52 -04:00
|
|
|
s
|
|
|
|
end
|
|
|
|
|
|
|
|
# Tell the server to listen on +path+ as a UNIX domain socket.
|
|
|
|
#
|
2017-11-28 12:11:59 -05:00
|
|
|
def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
|
2019-10-01 09:50:38 -04:00
|
|
|
@unix_paths << path unless File.exist? path
|
2012-08-02 18:03:52 -04:00
|
|
|
|
|
|
|
# Let anyone connect by default
|
|
|
|
umask ||= 0
|
|
|
|
|
|
|
|
begin
|
|
|
|
old_mask = File.umask(umask)
|
2013-07-04 18:57:49 -04:00
|
|
|
|
2014-01-25 17:50:40 -05:00
|
|
|
if File.exist? path
|
2013-07-04 18:57:49 -04:00
|
|
|
begin
|
|
|
|
old = UNIXSocket.new path
|
2014-01-25 21:34:12 -05:00
|
|
|
rescue SystemCallError, IOError
|
2013-07-04 18:57:49 -04:00
|
|
|
File.unlink path
|
|
|
|
else
|
|
|
|
old.close
|
|
|
|
raise "There is already a server bound to: #{path}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-08-02 18:03:52 -04:00
|
|
|
s = UNIXServer.new(path)
|
2017-11-28 12:11:59 -05:00
|
|
|
s.listen backlog
|
2012-08-02 18:03:52 -04:00
|
|
|
@ios << s
|
|
|
|
ensure
|
|
|
|
File.umask old_mask
|
|
|
|
end
|
|
|
|
|
2015-01-20 13:14:50 -05:00
|
|
|
if mode
|
|
|
|
File.chmod mode, path
|
|
|
|
end
|
|
|
|
|
2015-01-20 13:34:14 -05:00
|
|
|
env = @proto_env.dup
|
|
|
|
env[REMOTE_ADDR] = "127.0.0.1"
|
|
|
|
@envs[s] = env
|
|
|
|
|
2012-08-02 18:03:52 -04:00
|
|
|
s
|
|
|
|
end
|
|
|
|
|
|
|
|
def inherit_unix_listener(path, fd)
|
2019-10-01 09:50:38 -04:00
|
|
|
@unix_paths << path unless File.exist? path
|
2012-08-02 18:03:52 -04:00
|
|
|
|
2013-04-04 19:41:22 -04:00
|
|
|
if fd.kind_of? TCPServer
|
|
|
|
s = fd
|
|
|
|
else
|
|
|
|
s = UNIXServer.for_fd fd
|
|
|
|
end
|
2012-08-02 18:03:52 -04:00
|
|
|
@ios << s
|
|
|
|
|
2015-01-20 13:34:14 -05:00
|
|
|
env = @proto_env.dup
|
|
|
|
env[REMOTE_ADDR] = "127.0.0.1"
|
|
|
|
@envs[s] = env
|
|
|
|
|
2012-08-02 18:03:52 -04:00
|
|
|
s
|
|
|
|
end
|
|
|
|
|
2019-09-11 06:49:35 -04:00
|
|
|
def close_listeners
|
2020-03-07 13:44:52 -05:00
|
|
|
listeners.each do |l, io|
|
2020-03-04 12:17:38 -05:00
|
|
|
io.close unless io.closed? # Ruby 2.2 issue
|
2019-09-11 06:49:35 -04:00
|
|
|
uri = URI.parse(l)
|
|
|
|
next unless uri.scheme == 'unix'
|
2019-10-01 09:50:38 -04:00
|
|
|
unix_path = "#{uri.host}#{uri.path}"
|
2020-03-07 13:44:52 -05:00
|
|
|
File.unlink unix_path if unix_paths.include? unix_path
|
2019-09-11 06:49:35 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def redirects_for_restart
|
2020-03-08 12:06:42 -04:00
|
|
|
redirects = listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
|
|
|
|
redirects[:close_others] = true
|
2019-09-11 06:49:35 -04:00
|
|
|
redirects
|
|
|
|
end
|
2020-03-07 13:44:52 -05:00
|
|
|
|
2020-03-08 12:06:42 -04:00
|
|
|
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
|
|
|
|
|
2020-03-07 13:44:52 -05:00
|
|
|
private
|
|
|
|
|
|
|
|
def loopback_addresses
|
|
|
|
Socket.ip_address_list.select do |addrinfo|
|
|
|
|
addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
|
|
|
|
end.map { |addrinfo| addrinfo.ip_address }.uniq
|
|
|
|
end
|
2020-03-08 12:06:42 -04:00
|
|
|
|
2020-03-11 14:50:44 -04:00
|
|
|
def socket_activation_fd(int)
|
|
|
|
int + 3 # 3 is the magic number you add to follow the SA protocol
|
2020-03-08 12:06:42 -04:00
|
|
|
end
|
2012-08-02 18:03:52 -04:00
|
|
|
end
|
|
|
|
end
|