1
0
Fork 0
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:
Evan Phoenix 2011-09-27 10:53:45 -07:00
parent a3fc7ec951
commit 597218e59d
6 changed files with 122 additions and 102 deletions

View file

@ -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)
@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

View file

@ -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

View file

@ -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

View file

@ -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
View 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

View file

@ -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