mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
![Julik Tarkhanov](/assets/img/avatar_default.png)
When you need to have a switch in an infrastructure middleware (streaming, events and sockets) you might have to make runtime decisions based on the server that is being used. The Rack env header is the only way for the underlying Rack app to see which server it is being run on. Previously it would only contain the puma version string, which is insufficient.
362 lines
9.2 KiB
Ruby
362 lines
9.2 KiB
Ruby
require 'puma/const'
|
|
require 'uri'
|
|
|
|
module Puma
|
|
class Binder
|
|
include Puma::Const
|
|
|
|
RACK_VERSION = [1,3].freeze
|
|
|
|
def initialize(events)
|
|
@events = events
|
|
@listeners = []
|
|
@inherited_fds = {}
|
|
@unix_paths = []
|
|
|
|
@proto_env = {
|
|
"rack.version".freeze => RACK_VERSION,
|
|
"rack.errors".freeze => events.stderr,
|
|
"rack.multithread".freeze => true,
|
|
"rack.multiprocess".freeze => false,
|
|
"rack.run_once".freeze => false,
|
|
"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 infering
|
|
# it from the content. And so if i set it here, it won't
|
|
# infer properly.
|
|
|
|
"QUERY_STRING".freeze => "",
|
|
SERVER_PROTOCOL => HTTP_11,
|
|
SERVER_SOFTWARE => PUMA_SERVER_STRING,
|
|
GATEWAY_INTERFACE => CGI_VER
|
|
}
|
|
|
|
@envs = {}
|
|
@ios = []
|
|
end
|
|
|
|
attr_reader :listeners, :ios
|
|
|
|
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 = []
|
|
|
|
ENV.each do |k,v|
|
|
if k =~ /PUMA_INHERIT_\d+/
|
|
fd, url = v.split(":", 2)
|
|
@inherited_fds[url] = fd.to_i
|
|
remove << k
|
|
end
|
|
if k =~ /LISTEN_FDS/ && ENV['LISTEN_PID'].to_i == $$
|
|
v.to_i.times do |num|
|
|
fd = num + 3
|
|
sock = TCPServer.for_fd(fd)
|
|
begin
|
|
url = "unix://" + Socket.unpack_sockaddr_un(sock.getsockname)
|
|
rescue ArgumentError
|
|
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
|
|
if addr =~ /\:/
|
|
addr = "[#{addr}]"
|
|
end
|
|
url = "tcp://#{addr}:#{port}"
|
|
end
|
|
@inherited_fds[url] = sock
|
|
end
|
|
ENV.delete k
|
|
ENV.delete 'LISTEN_PID'
|
|
end
|
|
end
|
|
|
|
remove.each do |k|
|
|
ENV.delete k
|
|
end
|
|
end
|
|
|
|
def parse(binds, logger)
|
|
binds.each do |str|
|
|
uri = URI.parse str
|
|
case uri.scheme
|
|
when "tcp"
|
|
if fd = @inherited_fds.delete(str)
|
|
logger.log "* Inherited #{str}"
|
|
io = inherit_tcp_listener uri.host, uri.port, fd
|
|
else
|
|
params = Util.parse_query uri.query
|
|
|
|
opt = params.key?('low_latency')
|
|
bak = params.fetch('backlog', 1024).to_i
|
|
|
|
logger.log "* Listening on #{str}"
|
|
io = add_tcp_listener uri.host, uri.port, opt, bak
|
|
end
|
|
|
|
@listeners << [str, io]
|
|
when "unix"
|
|
path = "#{uri.host}#{uri.path}".gsub("%20", " ")
|
|
|
|
if fd = @inherited_fds.delete(str)
|
|
logger.log "* Inherited #{str}"
|
|
io = inherit_unix_listener path, fd
|
|
else
|
|
logger.log "* Listening on #{str}"
|
|
|
|
umask = nil
|
|
mode = nil
|
|
backlog = nil
|
|
|
|
if uri.query
|
|
params = Util.parse_query uri.query
|
|
if u = params['umask']
|
|
# Use Integer() to respect the 0 prefix as octal
|
|
umask = Integer(u)
|
|
end
|
|
|
|
if u = params['mode']
|
|
mode = Integer('0'+u)
|
|
end
|
|
|
|
if u = params['backlog']
|
|
backlog = Integer(u)
|
|
end
|
|
end
|
|
|
|
io = add_unix_listener path, umask, mode, backlog
|
|
end
|
|
|
|
@listeners << [str, io]
|
|
when "ssl"
|
|
MiniSSL.check
|
|
|
|
params = Util.parse_query uri.query
|
|
require 'puma/minissl'
|
|
|
|
ctx = MiniSSL::Context.new
|
|
|
|
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']
|
|
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']
|
|
|
|
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
|
|
end
|
|
|
|
if fd = @inherited_fds.delete(str)
|
|
logger.log "* Inherited #{str}"
|
|
io = inherited_ssl_listener fd, ctx
|
|
else
|
|
logger.log "* Listening on #{str}"
|
|
io = add_ssl_listener uri.host, uri.port, ctx
|
|
end
|
|
|
|
@listeners << [str, io]
|
|
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
|
|
if fd.kind_of? TCPServer
|
|
fd.close
|
|
else
|
|
IO.for_fd(fd).close
|
|
end
|
|
|
|
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
|
|
|
|
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
|
|
# allow to accumulate before returning connection refused.
|
|
#
|
|
def add_tcp_listener(host, port, optimize_for_latency=true, backlog=1024)
|
|
host = host[1..-2] if host and host[0..0] == '['
|
|
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
|
|
@connected_port = s.addr[1]
|
|
|
|
@ios << s
|
|
s
|
|
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
|
|
|
|
@ios << s
|
|
s
|
|
end
|
|
|
|
def add_ssl_listener(host, port, ctx,
|
|
optimize_for_latency=true, backlog=1024)
|
|
require 'puma/minissl'
|
|
|
|
MiniSSL.check
|
|
|
|
host = host[1..-2] if host[0..0] == '['
|
|
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
|
|
|
|
ssl = MiniSSL::Server.new s, ctx
|
|
env = @proto_env.dup
|
|
env[HTTPS_KEY] = HTTPS
|
|
@envs[ssl] = env
|
|
|
|
@ios << ssl
|
|
s
|
|
end
|
|
|
|
def inherited_ssl_listener(fd, ctx)
|
|
require 'puma/minissl'
|
|
MiniSSL.check
|
|
|
|
s = TCPServer.for_fd(fd)
|
|
ssl = MiniSSL::Server.new(s, ctx)
|
|
|
|
env = @proto_env.dup
|
|
env[HTTPS_KEY] = HTTPS
|
|
@envs[ssl] = env
|
|
|
|
@ios << ssl
|
|
|
|
s
|
|
end
|
|
|
|
# Tell the server to listen on +path+ as a UNIX domain socket.
|
|
#
|
|
def add_unix_listener(path, umask=nil, mode=nil, backlog=nil)
|
|
@unix_paths << path
|
|
|
|
# Let anyone connect by default
|
|
umask ||= 0
|
|
|
|
begin
|
|
old_mask = File.umask(umask)
|
|
|
|
if File.exist? path
|
|
begin
|
|
old = UNIXSocket.new path
|
|
rescue SystemCallError, IOError
|
|
File.unlink path
|
|
else
|
|
old.close
|
|
raise "There is already a server bound to: #{path}"
|
|
end
|
|
end
|
|
|
|
s = UNIXServer.new(path)
|
|
s.listen backlog if backlog
|
|
@ios << s
|
|
ensure
|
|
File.umask old_mask
|
|
end
|
|
|
|
if mode
|
|
File.chmod mode, path
|
|
end
|
|
|
|
env = @proto_env.dup
|
|
env[REMOTE_ADDR] = "127.0.0.1"
|
|
@envs[s] = env
|
|
|
|
s
|
|
end
|
|
|
|
def inherit_unix_listener(path, fd)
|
|
@unix_paths << path
|
|
|
|
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
|
|
|
|
end
|
|
end
|