mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
Add support for Linux's abstract sockets (#2564)
* Support Linux's abstract sockets Closes #2526 * Add two simple UNIXSocket tests
This commit is contained in:
parent
7a68835545
commit
fac83ae35a
5 changed files with 78 additions and 22 deletions
14
lib/puma.rb
14
lib/puma.rb
|
@ -39,6 +39,20 @@ module Puma
|
||||||
HAS_SSL
|
HAS_SSL
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.abstract_unix_socket?
|
||||||
|
@abstract_unix ||=
|
||||||
|
if HAS_UNIX_SOCKET
|
||||||
|
begin
|
||||||
|
::UNIXServer.new("\0puma.temp.unix").close
|
||||||
|
true
|
||||||
|
rescue ArgumentError # darwin
|
||||||
|
false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# @!attribute [rw] stats_object=
|
# @!attribute [rw] stats_object=
|
||||||
def self.stats_object=(val)
|
def self.stats_object=(val)
|
||||||
@get_stats = val
|
@get_stats = val
|
||||||
|
|
|
@ -177,11 +177,19 @@ module Puma
|
||||||
@listeners << [str, io] if io
|
@listeners << [str, io] if io
|
||||||
when "unix"
|
when "unix"
|
||||||
path = "#{uri.host}#{uri.path}".gsub("%20", " ")
|
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)
|
if fd = @inherited_fds.delete(str)
|
||||||
|
@unix_paths << path unless abstract
|
||||||
io = inherit_unix_listener path, fd
|
io = inherit_unix_listener path, fd
|
||||||
logger.log "* Inherited #{str}"
|
logger.log "* Inherited #{str}"
|
||||||
elsif sock = @activated_sockets.delete([ :unix, path ])
|
elsif sock = @activated_sockets.delete([ :unix, path ])
|
||||||
|
@unix_paths << path unless abstract || File.exist?(path)
|
||||||
io = inherit_unix_listener path, sock
|
io = inherit_unix_listener path, sock
|
||||||
logger.log "* Activated #{str}"
|
logger.log "* Activated #{str}"
|
||||||
else
|
else
|
||||||
|
@ -205,6 +213,7 @@ module Puma
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@unix_paths << path unless abstract || File.exist?(path)
|
||||||
io = add_unix_listener path, umask, mode, backlog
|
io = add_unix_listener path, umask, mode, backlog
|
||||||
logger.log "* #{log_msg} on #{str}"
|
logger.log "* #{log_msg} on #{str}"
|
||||||
end
|
end
|
||||||
|
@ -355,8 +364,6 @@ module Puma
|
||||||
# Tell the server to listen on +path+ as a UNIX domain socket.
|
# Tell the server to listen on +path+ as a UNIX domain socket.
|
||||||
#
|
#
|
||||||
def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
|
def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
|
||||||
@unix_paths << path unless File.exist? path
|
|
||||||
|
|
||||||
# Let anyone connect by default
|
# Let anyone connect by default
|
||||||
umask ||= 0
|
umask ||= 0
|
||||||
|
|
||||||
|
@ -373,8 +380,7 @@ module Puma
|
||||||
raise "There is already a server bound to: #{path}"
|
raise "There is already a server bound to: #{path}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
s = UNIXServer.new path.sub(/\A@/, "\0") # check for abstract UNIXSocket
|
||||||
s = UNIXServer.new(path)
|
|
||||||
s.listen backlog
|
s.listen backlog
|
||||||
@ios << s
|
@ios << s
|
||||||
ensure
|
ensure
|
||||||
|
@ -393,8 +399,6 @@ module Puma
|
||||||
end
|
end
|
||||||
|
|
||||||
def inherit_unix_listener(path, fd)
|
def inherit_unix_listener(path, fd)
|
||||||
@unix_paths << path unless File.exist? path
|
|
||||||
|
|
||||||
s = fd.kind_of?(::TCPServer) ? fd : ::UNIXServer.for_fd(fd)
|
s = fd.kind_of?(::TCPServer) ? fd : ::UNIXServer.for_fd(fd)
|
||||||
|
|
||||||
@ios << s
|
@ios << s
|
||||||
|
@ -407,24 +411,24 @@ module Puma
|
||||||
end
|
end
|
||||||
|
|
||||||
def close_listeners
|
def close_listeners
|
||||||
listeners.each do |l, io|
|
@listeners.each do |l, io|
|
||||||
io.close unless io.closed? # Ruby 2.2 issue
|
io.close unless io.closed?
|
||||||
uri = URI.parse(l)
|
uri = URI.parse l
|
||||||
next unless uri.scheme == 'unix'
|
next unless uri.scheme == 'unix'
|
||||||
unix_path = "#{uri.host}#{uri.path}"
|
unix_path = "#{uri.host}#{uri.path}"
|
||||||
File.unlink unix_path if unix_paths.include? unix_path
|
File.unlink unix_path if @unix_paths.include?(unix_path) && File.exist?(unix_path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def redirects_for_restart
|
def redirects_for_restart
|
||||||
redirects = listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
|
redirects = @listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
|
||||||
redirects[:close_others] = true
|
redirects[:close_others] = true
|
||||||
redirects
|
redirects
|
||||||
end
|
end
|
||||||
|
|
||||||
# @version 5.0.0
|
# @version 5.0.0
|
||||||
def redirects_for_restart_env
|
def redirects_for_restart_env
|
||||||
listeners.each_with_object({}).with_index do |(listen, memo), i|
|
@listeners.each_with_object({}).with_index do |(listen, memo), i|
|
||||||
memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
|
memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -176,7 +176,9 @@ module Puma
|
||||||
when 'tcp'
|
when 'tcp'
|
||||||
TCPSocket.new uri.host, uri.port
|
TCPSocket.new uri.host, uri.port
|
||||||
when 'unix'
|
when 'unix'
|
||||||
UNIXSocket.new "#{uri.host}#{uri.path}"
|
# check for abstract UNIXSocket
|
||||||
|
UNIXSocket.new(@control_url.start_with?('unix://@') ?
|
||||||
|
"\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
|
||||||
else
|
else
|
||||||
raise "Invalid scheme: #{uri.scheme}"
|
raise "Invalid scheme: #{uri.scheme}"
|
||||||
end
|
end
|
||||||
|
|
|
@ -175,6 +175,30 @@ class TestPumaControlCli < TestConfigFileBase
|
||||||
assert_kind_of Thread, t.join, "server didn't stop"
|
assert_kind_of Thread, t.join, "server didn't stop"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_control_aunix
|
||||||
|
skip_unless :aunix
|
||||||
|
|
||||||
|
url = "unix://@test_control_aunix.unix"
|
||||||
|
|
||||||
|
opts = [
|
||||||
|
"--control-url", url,
|
||||||
|
"--control-token", "ctrl",
|
||||||
|
"--config-file", "test/config/app.rb",
|
||||||
|
]
|
||||||
|
|
||||||
|
control_cli = Puma::ControlCLI.new (opts + ["start"]), @ready, @ready
|
||||||
|
t = Thread.new do
|
||||||
|
control_cli.run
|
||||||
|
end
|
||||||
|
|
||||||
|
wait_booted
|
||||||
|
|
||||||
|
assert_command_cli_output opts + ["status"], "Puma is started"
|
||||||
|
assert_command_cli_output opts + ["stop"], "Command stop sent success"
|
||||||
|
|
||||||
|
assert_kind_of Thread, t.join, "server didn't stop"
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def assert_command_cli_output(options, expected_out)
|
def assert_command_cli_output(options, expected_out)
|
||||||
|
|
|
@ -8,21 +8,21 @@ class TestPumaUnixSocket < Minitest::Test
|
||||||
|
|
||||||
App = lambda { |env| [200, {}, ["Works"]] }
|
App = lambda { |env| [200, {}, ["Works"]] }
|
||||||
|
|
||||||
def setup
|
def teardown
|
||||||
return unless UNIX_SKT_EXIST
|
return if skipped?
|
||||||
@tmp_socket_path = tmp_path('.sock')
|
@server.stop(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def server_unix(type)
|
||||||
|
@tmp_socket_path = type == :unix ? tmp_path('.sock') : "@TestPumaUnixSocket"
|
||||||
@server = Puma::Server.new App
|
@server = Puma::Server.new App
|
||||||
@server.add_unix_listener @tmp_socket_path
|
@server.add_unix_listener @tmp_socket_path
|
||||||
@server.run
|
@server.run
|
||||||
end
|
end
|
||||||
|
|
||||||
def teardown
|
def test_server_unix
|
||||||
return unless UNIX_SKT_EXIST
|
|
||||||
@server.stop(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_server
|
|
||||||
skip_unless :unix
|
skip_unless :unix
|
||||||
|
server_unix :unix
|
||||||
sock = UNIXSocket.new @tmp_socket_path
|
sock = UNIXSocket.new @tmp_socket_path
|
||||||
|
|
||||||
sock << "GET / HTTP/1.0\r\nHost: blah.com\r\n\r\n"
|
sock << "GET / HTTP/1.0\r\nHost: blah.com\r\n\r\n"
|
||||||
|
@ -31,4 +31,16 @@ class TestPumaUnixSocket < Minitest::Test
|
||||||
|
|
||||||
assert_equal expected, sock.read(expected.size)
|
assert_equal expected, sock.read(expected.size)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_server_aunix
|
||||||
|
skip_unless :aunix
|
||||||
|
server_unix :aunix
|
||||||
|
sock = UNIXSocket.new @tmp_socket_path.sub(/\A@/, "\0")
|
||||||
|
|
||||||
|
sock << "GET / HTTP/1.0\r\nHost: blah.com\r\n\r\n"
|
||||||
|
|
||||||
|
expected = "HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nWorks"
|
||||||
|
|
||||||
|
assert_equal expected, sock.read(expected.size)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue