mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
Add unix socket support
This commit is contained in:
parent
a3fc7ec951
commit
597218e59d
6 changed files with 122 additions and 102 deletions
|
@ -1,17 +1,13 @@
|
|||
require 'optparse'
|
||||
require 'uri'
|
||||
|
||||
require 'puma/configurator'
|
||||
require 'puma/const'
|
||||
|
||||
module Puma
|
||||
class CLI
|
||||
Options = [
|
||||
['-p', '--port PORT', "Which port to bind to", :@port, 3000],
|
||||
['-a', '--address ADDR', "Address to bind to", :@address, "0.0.0.0"],
|
||||
['-n', '--concurrency INT', "Number of concurrent threads to use",
|
||||
:@concurrency, 16],
|
||||
]
|
||||
|
||||
Banner = "puma <options> <rackup file>"
|
||||
DefaultTCPHost = "0.0.0.0"
|
||||
DefaultTCPPort = 3000
|
||||
|
||||
def initialize(argv, stdout=STDOUT)
|
||||
@argv = argv
|
||||
|
@ -21,26 +17,37 @@ module Puma
|
|||
end
|
||||
|
||||
def setup_options
|
||||
@options = OptionParser.new do |o|
|
||||
Options.each do |short, long, help, variable, default|
|
||||
instance_variable_set(variable, default)
|
||||
@options = {
|
||||
:concurrency => 16
|
||||
}
|
||||
|
||||
o.on(short, long, help) do |arg|
|
||||
instance_variable_set(variable, arg)
|
||||
end
|
||||
@binds = []
|
||||
|
||||
@parser = OptionParser.new do |o|
|
||||
o.on '-n', '--concurrency INT', "Number of concurrent threads to use" do |arg|
|
||||
@options[:concurrency] = arg.to_i
|
||||
end
|
||||
|
||||
o.on "-b", "--bind URI", "URI to bind to (tcp:// and unix:// only)" do |arg|
|
||||
@binds << arg
|
||||
end
|
||||
end
|
||||
|
||||
@options.banner = Banner
|
||||
@parser.banner = "puma <options> <rackup file>"
|
||||
|
||||
@options.on_tail "-h", "--help", "Show help" do
|
||||
@stdout.puts @options
|
||||
@parser.on_tail "-h", "--help", "Show help" do
|
||||
@stdout.puts @parser
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
def load_rackup
|
||||
@app, options = Rack::Builder.parse_file @rackup
|
||||
@options.merge! options
|
||||
end
|
||||
|
||||
def run
|
||||
@options.parse! @argv
|
||||
@parser.parse! @argv
|
||||
|
||||
@rackup = ARGV.shift || "config.ru"
|
||||
|
||||
|
@ -48,24 +55,46 @@ module Puma
|
|||
raise "Missing rackup file '#{@rackup}'"
|
||||
end
|
||||
|
||||
settings = {
|
||||
:host => @address,
|
||||
:port => @port,
|
||||
:concurrency => @concurrency,
|
||||
:stdout => @stdout
|
||||
}
|
||||
load_rackup
|
||||
|
||||
config = Puma::Configurator.new(settings) do |c|
|
||||
c.listener do |l|
|
||||
l.load_rackup @rackup
|
||||
if @binds.empty?
|
||||
@options[:Host] ||= DefaultTCPHost
|
||||
@options[:Port] ||= DefaultTCPPort
|
||||
end
|
||||
|
||||
server = Puma::Server.new @app, @options[:concurrency]
|
||||
|
||||
@stdout.puts "Puma #{Puma::Const::PUMA_VERSION} starting..."
|
||||
|
||||
if @options[:Host]
|
||||
@stdout.puts "Listening on tcp://#{@options[:Host]}:#{@options[:Port]}"
|
||||
server.add_tcp_listener @options[:Host], @options[:Port]
|
||||
end
|
||||
|
||||
@binds.each do |str|
|
||||
uri = URI.parse str
|
||||
case uri.scheme
|
||||
when "tcp"
|
||||
@stdout.puts "Listening on #{str}"
|
||||
server.add_tcp_listener uri.host, uri.port
|
||||
when "unix"
|
||||
@stdout.puts "Listening on #{str}"
|
||||
if uri.host
|
||||
path = "#{uri.host}/#{uri.path}"
|
||||
else
|
||||
path = uri.path
|
||||
end
|
||||
|
||||
server.add_unix_listener path
|
||||
else
|
||||
@stdout.puts "Invalid URI: #{str}"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
config.run
|
||||
config.log "Puma #{Puma::Const::PUMA_VERSION} available at #{@address}:#{@port}"
|
||||
config.log "Use CTRL-C to stop."
|
||||
@stdout.puts "Use Ctrl-C to stop"
|
||||
|
||||
config.join
|
||||
server.run.join
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
require 'yaml'
|
||||
require 'etc'
|
||||
|
||||
require 'rubygems'
|
||||
|
@ -8,9 +7,7 @@ require 'puma/server'
|
|||
|
||||
module Puma
|
||||
# Implements a simple DSL for configuring a Puma server for your
|
||||
# purposes. More used by framework implementers to setup Puma
|
||||
# how they like, but could be used by regular folks to add more things
|
||||
# to an existing puma configuration.
|
||||
# purposes.
|
||||
#
|
||||
# It is used like this:
|
||||
#
|
||||
|
@ -40,15 +37,13 @@ module Puma
|
|||
class Configurator
|
||||
attr_reader :listeners
|
||||
attr_reader :defaults
|
||||
attr_reader :needs_restart
|
||||
|
||||
# You pass in initial defaults and then a block to continue configuring.
|
||||
def initialize(defaults={}, &block)
|
||||
@listener = nil
|
||||
@listener_name = nil
|
||||
@listeners = {}
|
||||
@listeners = []
|
||||
@defaults = defaults
|
||||
@needs_restart = false
|
||||
|
||||
if block
|
||||
yield self
|
||||
|
@ -98,7 +93,7 @@ module Puma
|
|||
|
||||
@listener = Puma::Server.new(ops[:host], ops[:port].to_i, ops[:concurrency].to_i)
|
||||
@listener_name = "#{ops[:host]}:#{ops[:port]}"
|
||||
@listeners[@listener_name] = @listener
|
||||
@listeners << @listener
|
||||
|
||||
if ops[:user] and ops[:group]
|
||||
change_privilege(ops[:user], ops[:group])
|
||||
|
@ -118,28 +113,20 @@ module Puma
|
|||
# Do something with options?
|
||||
end
|
||||
|
||||
# Easy way to load a YAML file and apply default settings.
|
||||
def load_yaml(file, default={})
|
||||
default.merge(YAML.load_file(file))
|
||||
end
|
||||
|
||||
# Works like a meta run method which goes through all the
|
||||
# configured listeners. Use the Configurator.join method
|
||||
# to prevent Ruby from exiting until each one is done.
|
||||
def run
|
||||
@listeners.each {|name,s|
|
||||
s.run
|
||||
}
|
||||
@listeners.each { |s| s.run }
|
||||
end
|
||||
|
||||
# Calls .stop on all the configured listeners so they
|
||||
# stop processing requests (gracefully). By default it
|
||||
# assumes that you don't want to restart.
|
||||
def stop(needs_restart=false, synchronous=false)
|
||||
@listeners.each do |name,s|
|
||||
def stop(synchronous=false)
|
||||
@listeners.each do |s|
|
||||
s.stop(synchronous)
|
||||
end
|
||||
@needs_restart = needs_restart
|
||||
end
|
||||
|
||||
|
||||
|
@ -147,7 +134,7 @@ module Puma
|
|||
# Configurator block so that you can control it. In other words
|
||||
# do it like: config.join.
|
||||
def join
|
||||
@listeners.values.each {|s| s.acceptor.join }
|
||||
@listeners.each { |s| s.acceptor.join }
|
||||
end
|
||||
|
||||
# Used to allow you to let users specify their own configurations
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require 'rack'
|
||||
require 'stringio'
|
||||
|
||||
require 'puma/thread_pool'
|
||||
require 'puma/const'
|
||||
|
@ -21,8 +22,6 @@ module Puma
|
|||
|
||||
attr_accessor :stderr, :stdout
|
||||
|
||||
Default = lambda { |e| raise "no rack app configured" }
|
||||
|
||||
# Creates a working server on host:port (strange things happen if port
|
||||
# isn't a Number).
|
||||
#
|
||||
|
@ -33,14 +32,13 @@ module Puma
|
|||
# the same time. Any requests over this ammount are queued and handled
|
||||
# as soon as a thread is available.
|
||||
#
|
||||
def initialize(host, port, concurrent=10, app=Default)
|
||||
@socket = TCPServer.new(host, port)
|
||||
|
||||
@host = host
|
||||
@port = port
|
||||
def initialize(app, concurrent=10)
|
||||
@concurrent = concurrent
|
||||
|
||||
@check, @notify = IO.pipe
|
||||
|
||||
@ios = [@check]
|
||||
|
||||
@running = true
|
||||
|
||||
@thread_pool = ThreadPool.new(0, concurrent) do |client|
|
||||
|
@ -53,24 +51,24 @@ module Puma
|
|||
@app = app
|
||||
end
|
||||
|
||||
def add_tcp_listener(host, port)
|
||||
@ios << TCPServer.new(host, port)
|
||||
end
|
||||
|
||||
def add_unix_listener(path)
|
||||
@ios << UNIXServer.new(path)
|
||||
end
|
||||
|
||||
# Runs the server. It returns the thread used so you can "join" it.
|
||||
# You can also access the HttpServer#acceptor attribute to get the
|
||||
# thread later.
|
||||
def run
|
||||
BasicSocket.do_not_reverse_lookup = true
|
||||
|
||||
configure_socket_options
|
||||
|
||||
if @tcp_defer_accept_opts
|
||||
@socket.setsockopt(*@tcp_defer_accept_opts)
|
||||
end
|
||||
|
||||
tcp_cork_opts = @tcp_cork_opts
|
||||
|
||||
@acceptor = Thread.new do
|
||||
begin
|
||||
check = @check
|
||||
sockets = [check, @socket]
|
||||
sockets = @ios
|
||||
pool = @thread_pool
|
||||
|
||||
while @running
|
||||
|
@ -80,11 +78,7 @@ module Puma
|
|||
if sock == check
|
||||
break if handle_check
|
||||
else
|
||||
client = sock.accept
|
||||
|
||||
client.setsockopt(*tcp_cork_opts) if tcp_cork_opts
|
||||
|
||||
pool << client
|
||||
pool << sock.accept
|
||||
end
|
||||
end
|
||||
rescue Errno::ECONNABORTED
|
||||
|
@ -97,35 +91,13 @@ module Puma
|
|||
end
|
||||
graceful_shutdown
|
||||
ensure
|
||||
@socket.close
|
||||
# @stderr.puts "#{Time.now}: Closed socket."
|
||||
@ios.each { |i| i.close }
|
||||
end
|
||||
end
|
||||
|
||||
return @acceptor
|
||||
end
|
||||
|
||||
def configure_socket_options
|
||||
@tcp_defer_accept_opts = nil
|
||||
@tcp_cork_opts = nil
|
||||
|
||||
case RUBY_PLATFORM
|
||||
when /linux/
|
||||
# 9 is currently TCP_DEFER_ACCEPT
|
||||
@tcp_defer_accept_opts = [Socket::SOL_TCP, 9, 1]
|
||||
@tcp_cork_opts = [Socket::SOL_TCP, 3, 1]
|
||||
|
||||
when /freebsd(([1-4]\..{1,2})|5\.[0-4])/
|
||||
# Do nothing, just closing a bug when freebsd <= 5.4
|
||||
when /freebsd/
|
||||
# Use the HTTP accept filter if available.
|
||||
# The struct made by pack() is defined in /usr/include/sys/socket.h as accept_filter_arg
|
||||
unless `/sbin/sysctl -nq net.inet.accf.http`.empty?
|
||||
@tcp_defer_accept_opts = [Socket::SOL_SOCKET, Socket::SO_ACCEPTFILTER, ['httpready', nil].pack('a16a240')]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def handle_check
|
||||
cmd = @check.read(1)
|
||||
|
||||
|
@ -351,6 +323,4 @@ module Puma
|
|||
@acceptor.join if sync
|
||||
end
|
||||
end
|
||||
|
||||
HttpServer = Server
|
||||
end
|
||||
|
|
|
@ -42,9 +42,9 @@ class TestRackServer < Test::Unit::TestCase
|
|||
def setup
|
||||
@valid_request = "GET / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\n\r\n"
|
||||
|
||||
@server = Puma::Server.new("127.0.0.1", 9998)
|
||||
@simple = lambda { |env| [200, { "X-Header" => "Works" }, "Hello"] }
|
||||
@server.app = @simple
|
||||
@server = Puma::Server.new @simple
|
||||
@server.add_tcp_listener "127.0.0.1", 9998
|
||||
end
|
||||
|
||||
def teardown
|
||||
|
|
32
test/test_unix_socket.rb
Normal file
32
test/test_unix_socket.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
require 'test/unit'
|
||||
require 'puma/server'
|
||||
|
||||
require 'socket'
|
||||
|
||||
class TestPumaUnixSocket < Test::Unit::TestCase
|
||||
|
||||
App = lambda { |env| [200, {}, ["Works"]] }
|
||||
|
||||
Path = "test/puma.sock"
|
||||
|
||||
def setup
|
||||
@server = Puma::Server.new App, 2
|
||||
end
|
||||
|
||||
def teardown
|
||||
@server.stop(true)
|
||||
File.unlink Path if File.exists? Path
|
||||
end
|
||||
|
||||
def test_server
|
||||
@server.add_unix_listener Path
|
||||
@server.run
|
||||
|
||||
sock = UNIXSocket.new Path
|
||||
|
||||
sock << "GET / HTTP/1.0\r\nHost: blah.com\r\n\r\n"
|
||||
|
||||
assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\nWorks",
|
||||
sock.read
|
||||
end
|
||||
end
|
|
@ -20,11 +20,13 @@ class WebServerTest < Test::Unit::TestCase
|
|||
def setup
|
||||
@valid_request = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\n\r\n"
|
||||
|
||||
@server = HttpServer.new("127.0.0.1", 9998)
|
||||
@server.stderr = StringIO.new
|
||||
|
||||
@tester = TestHandler.new
|
||||
|
||||
@server = Server.new @tester
|
||||
@server.add_tcp_listener "127.0.0.1", 9998
|
||||
|
||||
@server.stderr = StringIO.new
|
||||
|
||||
@server.app = @tester
|
||||
|
||||
redirect_test_io do
|
||||
|
|
Loading…
Add table
Reference in a new issue