mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
Runs a rack app!
This commit is contained in:
parent
000deba3c1
commit
052c456d81
8 changed files with 374 additions and 215 deletions
|
@ -5,7 +5,7 @@ Manifest.txt
|
||||||
README.txt
|
README.txt
|
||||||
Rakefile
|
Rakefile
|
||||||
TODO
|
TODO
|
||||||
bin/puma_rails
|
bin/puma
|
||||||
examples/builder.rb
|
examples/builder.rb
|
||||||
examples/camping/README
|
examples/camping/README
|
||||||
examples/camping/blog.rb
|
examples/camping/blog.rb
|
||||||
|
|
|
@ -9,11 +9,9 @@
|
||||||
require 'yaml'
|
require 'yaml'
|
||||||
require 'etc'
|
require 'etc'
|
||||||
|
|
||||||
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
|
|
||||||
require 'puma'
|
require 'puma'
|
||||||
require 'puma/rails'
|
|
||||||
|
|
||||||
Puma::Gems.require 'gem_plugin'
|
Puma::Gems.require 'puma/gem_plugin'
|
||||||
|
|
||||||
# require 'ruby-debug'
|
# require 'ruby-debug'
|
||||||
# Debugger.start
|
# Debugger.start
|
||||||
|
@ -24,25 +22,19 @@ module Puma
|
||||||
|
|
||||||
def configure
|
def configure
|
||||||
options [
|
options [
|
||||||
["-e", "--environment ENV", "Rails environment to run as", :@environment, ENV['RAILS_ENV'] || "development"],
|
|
||||||
["-d", "--daemonize", "Run daemonized in the background", :@daemon, false],
|
|
||||||
['-p', '--port PORT', "Which port to bind to", :@port, 3000],
|
['-p', '--port PORT', "Which port to bind to", :@port, 3000],
|
||||||
['-a', '--address ADDR', "Address to bind to", :@address, "0.0.0.0"],
|
['-a', '--address ADDR', "Address to bind to", :@address, "0.0.0.0"],
|
||||||
['-l', '--log FILE', "Where to write log messages", :@log_file, "log/puma.log"],
|
['-l', '--log FILE', "Where to write log messages", :@log_file, "log/puma.log"],
|
||||||
['-P', '--pid FILE', "Where to write the PID", :@pid_file, "log/puma.pid"],
|
['-P', '--pid FILE', "Where to write the PID", :@pid_file, "log/puma.pid"],
|
||||||
['-n', '--num-processors INT', "Number of processors active before clients denied", :@num_processors, 1024],
|
['-n', '--concurrency INT', "Number of concurrent threads to use",
|
||||||
['-o', '--timeout TIME', "Time to wait (in seconds) before killing a stalled thread", :@timeout, 60],
|
:@concurrency, 16],
|
||||||
['-t', '--throttle TIME', "Time to pause (in hundredths of a second) between accepting clients", :@throttle, 0],
|
|
||||||
['-m', '--mime PATH', "A YAML file that lists additional MIME types", :@mime_map, nil],
|
|
||||||
['-c', '--chdir PATH', "Change to dir before starting (will be expanded)", :@cwd, Dir.pwd],
|
['-c', '--chdir PATH', "Change to dir before starting (will be expanded)", :@cwd, Dir.pwd],
|
||||||
['-r', '--root PATH', "Set the document root (default 'public')", :@docroot, "public"],
|
|
||||||
['-B', '--debug', "Enable debugging mode", :@debug, false],
|
|
||||||
['-C', '--config PATH', "Use a config file", :@config_file, nil],
|
['-C', '--config PATH', "Use a config file", :@config_file, nil],
|
||||||
['-S', '--script PATH', "Load the given file as an extra config script", :@config_script, nil],
|
['-S', '--script PATH', "Load the given file as an extra config script", :@config_script, nil],
|
||||||
['-G', '--generate PATH', "Generate a config file for use with -C", :@generate, nil],
|
['-G', '--generate PATH', "Generate a config file for use with -C", :@generate, nil],
|
||||||
|
['-r', '--rackup PATH', 'Load a specific rackup file', :@rackup_file, "config.ru"],
|
||||||
['', '--user USER', "User to run as", :@user, nil],
|
['', '--user USER', "User to run as", :@user, nil],
|
||||||
['', '--group GROUP', "Group to run as", :@group, nil],
|
['', '--group GROUP', "Group to run as", :@group, nil]
|
||||||
['', '--prefix PATH', "URL prefix for Rails app", :@prefix, nil]
|
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -61,99 +53,52 @@ module Puma
|
||||||
# Change there to start, then we'll have to come back after daemonize
|
# Change there to start, then we'll have to come back after daemonize
|
||||||
Dir.chdir(@cwd)
|
Dir.chdir(@cwd)
|
||||||
|
|
||||||
valid?(@prefix[0] == ?/ && @prefix[-1] != ?/, "Prefix must begin with / and not end in /") if @prefix
|
|
||||||
valid_dir? File.dirname(@log_file), "Path to log file not valid: #@log_file"
|
valid_dir? File.dirname(@log_file), "Path to log file not valid: #@log_file"
|
||||||
valid_dir? File.dirname(@pid_file), "Path to pid file not valid: #@pid_file"
|
valid_dir? File.dirname(@pid_file), "Path to pid file not valid: #@pid_file"
|
||||||
valid_dir? @docroot, "Path to docroot not valid: #@docroot"
|
|
||||||
valid_exists? @mime_map, "MIME mapping file does not exist: #@mime_map" if @mime_map
|
valid_exists? @mime_map, "MIME mapping file does not exist: #@mime_map" if @mime_map
|
||||||
valid_exists? @config_file, "Config file not there: #@config_file" if @config_file
|
valid_exists? @config_file, "Config file not there: #@config_file" if @config_file
|
||||||
valid_dir? File.dirname(@generate), "Problem accessing directory to #@generate" if @generate
|
valid_dir? File.dirname(@generate), "Problem accessing directory to #@generate" if @generate
|
||||||
valid_user? @user if @user
|
valid_user? @user if @user
|
||||||
valid_group? @group if @group
|
valid_group? @group if @group
|
||||||
|
|
||||||
|
@rackup = ARGV.shift || "config.ru"
|
||||||
|
|
||||||
|
valid? File.exists?(@rackup), "Unable to find rackup file"
|
||||||
|
|
||||||
return @valid
|
return @valid
|
||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run
|
||||||
if @generate
|
if @generate
|
||||||
@generate = File.expand_path(@generate)
|
@generate = File.expand_path(@generate)
|
||||||
@stderr.puts "** Writing config to \"#@generate\"."
|
@stderr.puts "** Writing config to \"#@generate\"."
|
||||||
open(@generate, "w") {|f| f.write(settings.to_yaml) }
|
open(@generate, "w") {|f| f.write(settings.to_yaml) }
|
||||||
@stderr.puts "** Finished. Run \"puma_rails start -C #@generate\" to use the config file."
|
@stderr.puts "** Finished. Run \"puma_rails start -C #@generate\" to use the config file."
|
||||||
exit 0
|
exit 0
|
||||||
end
|
end
|
||||||
|
|
||||||
config = Puma::Rails::RailsConfigurator.new(settings) do
|
rackup = @rackup
|
||||||
if defaults[:daemon]
|
|
||||||
if File.exist? defaults[:pid_file]
|
config = Puma::Configurator.new(settings) do |c|
|
||||||
log "!!! PID file #{defaults[:pid_file]} already exists. Puma could be running already. Check your #{defaults[:log_file]} for errors."
|
c.log "Starting Puma listening at #{c.defaults[:host]}:#{c.defaults[:port]}"
|
||||||
log "!!! Exiting with error. You must stop puma and clear the .pid before I'll attempt a start."
|
|
||||||
exit 1
|
c.listener do |l|
|
||||||
|
l.load_rackup rackup
|
||||||
|
|
||||||
|
l.load_plugins
|
||||||
|
|
||||||
|
if c.defaults[:config_script]
|
||||||
|
c.log "Loading #{c.defaults[:config_script]} external config script"
|
||||||
|
c.run_config(c.defaults[:config_script])
|
||||||
end
|
end
|
||||||
|
|
||||||
daemonize
|
|
||||||
write_pid_file
|
|
||||||
log "Daemonized, any open files are closed. Look at #{defaults[:pid_file]} and #{defaults[:log_file]} for info."
|
|
||||||
log "Settings loaded from #{@config_file} (they override command line)." if @config_file
|
|
||||||
end
|
|
||||||
|
|
||||||
log "Starting Puma listening at #{defaults[:host]}:#{defaults[:port]}"
|
|
||||||
|
|
||||||
listener do
|
|
||||||
mime = {}
|
|
||||||
if defaults[:mime_map]
|
|
||||||
log "Loading additional MIME types from #{defaults[:mime_map]}"
|
|
||||||
mime = load_mime_map(defaults[:mime_map], mime)
|
|
||||||
end
|
|
||||||
|
|
||||||
if defaults[:debug]
|
|
||||||
log "Installing debugging prefixed filters. Look in log/puma_debug for the files."
|
|
||||||
debug "/"
|
|
||||||
end
|
|
||||||
|
|
||||||
log "Starting Rails with #{defaults[:environment]} environment..."
|
|
||||||
log "Mounting Rails at #{defaults[:prefix]}..." if defaults[:prefix]
|
|
||||||
uri defaults[:prefix] || "/", :handler => rails(:mime => mime, :prefix => defaults[:prefix])
|
|
||||||
log "Rails loaded."
|
|
||||||
|
|
||||||
log "Loading any Rails specific GemPlugins"
|
|
||||||
load_plugins
|
|
||||||
|
|
||||||
if defaults[:config_script]
|
|
||||||
log "Loading #{defaults[:config_script]} external config script"
|
|
||||||
run_config(defaults[:config_script])
|
|
||||||
end
|
|
||||||
|
|
||||||
setup_rails_signals
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
config.run
|
config.run
|
||||||
config.log "Puma #{Puma::Const::PUMA_VERSION} available at #{@address}:#{@port}"
|
config.log "Puma #{Puma::Const::PUMA_VERSION} available at #{@address}:#{@port}"
|
||||||
|
config.log "Use CTRL-C to stop."
|
||||||
unless config.defaults[:daemon]
|
|
||||||
config.log "Use CTRL-C to stop."
|
|
||||||
end
|
|
||||||
|
|
||||||
config.join
|
config.join
|
||||||
|
|
||||||
if config.needs_restart
|
|
||||||
unless RbConfig::CONFIG['host_os'] =~ /mingw|mswin/
|
|
||||||
cmd = "ruby #{__FILE__} start #{original_args.join(' ')}"
|
|
||||||
config.log "Restarting with arguments: #{cmd}"
|
|
||||||
config.stop(false, true)
|
|
||||||
config.remove_pid_file
|
|
||||||
|
|
||||||
if config.defaults[:daemon]
|
|
||||||
system cmd
|
|
||||||
else
|
|
||||||
@stderr.puts "Can't restart unless in daemon mode."
|
|
||||||
exit 1
|
|
||||||
end
|
|
||||||
else
|
|
||||||
config.log "Win32 does not support restarts. Exiting."
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_config
|
def load_config
|
||||||
|
@ -161,7 +106,7 @@ module Puma
|
||||||
begin
|
begin
|
||||||
settings = YAML.load_file(@config_file)
|
settings = YAML.load_file(@config_file)
|
||||||
ensure
|
ensure
|
||||||
@stderr.puts "** Loading settings from #{@config_file} (they override command line)." unless @daemon || settings[:daemon]
|
@stderr.puts "** Loading settings from #{@config_file} (they override command line)."
|
||||||
end
|
end
|
||||||
|
|
||||||
settings[:includes] ||= ["puma"]
|
settings[:includes] ||= ["puma"]
|
||||||
|
@ -181,7 +126,7 @@ module Puma
|
||||||
|
|
||||||
def config_keys
|
def config_keys
|
||||||
@config_keys ||=
|
@config_keys ||=
|
||||||
%w(address host port cwd log_file pid_file environment docroot mime_map daemon debug includes config_script num_processors timeout throttle user group prefix)
|
%w(address host port cwd log_file pid_file config_script concurrency user group)
|
||||||
end
|
end
|
||||||
|
|
||||||
def settings
|
def settings
|
||||||
|
@ -194,7 +139,7 @@ module Puma
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def Puma::send_signal(signal, pid_file)
|
def Puma.send_signal(signal, pid_file)
|
||||||
pid = File.read(pid_file).to_i
|
pid = File.read(pid_file).to_i
|
||||||
print "Sending #{signal} to Puma at PID #{pid}..."
|
print "Sending #{signal} to Puma at PID #{pid}..."
|
||||||
begin
|
begin
|
||||||
|
@ -206,7 +151,6 @@ module Puma
|
||||||
puts "Done."
|
puts "Done."
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
class Stop < GemPlugin::Plugin "/commands"
|
class Stop < GemPlugin::Plugin "/commands"
|
||||||
include Puma::Command::Base
|
include Puma::Command::Base
|
||||||
|
|
||||||
|
@ -236,9 +180,9 @@ module Puma
|
||||||
sleep 1
|
sleep 1
|
||||||
end
|
end
|
||||||
|
|
||||||
Puma::send_signal("KILL", @pid_file) if File.exist? @pid_file
|
Puma.send_signal("KILL", @pid_file) if File.exist? @pid_file
|
||||||
else
|
else
|
||||||
Puma::send_signal("TERM", @pid_file)
|
Puma.send_signal("TERM", @pid_file)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -267,16 +211,15 @@ module Puma
|
||||||
|
|
||||||
def run
|
def run
|
||||||
if @soft
|
if @soft
|
||||||
Puma::send_signal("HUP", @pid_file)
|
Puma.send_signal("HUP", @pid_file)
|
||||||
else
|
else
|
||||||
Puma::send_signal("USR2", @pid_file)
|
Puma.send_signal("USR2", @pid_file)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
GemPlugin::Manager.instance.load "puma" => GemPlugin::INCLUDE,
|
GemPlugin::Manager.instance.load "puma" => GemPlugin::INCLUDE
|
||||||
"rails" => GemPlugin::EXCLUDE
|
|
||||||
|
|
||||||
exit 1 unless Puma::Command::Registry.instance.run(ARGV)
|
exit 1 unless Puma::Command::Registry.instance.run(ARGV)
|
|
@ -7,7 +7,7 @@ require 'singleton'
|
||||||
require 'optparse'
|
require 'optparse'
|
||||||
|
|
||||||
require 'puma/gems'
|
require 'puma/gems'
|
||||||
Puma::Gems.require 'gem_plugin'
|
Puma::Gems.require 'puma/gem_plugin'
|
||||||
|
|
||||||
module Puma
|
module Puma
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ module Puma
|
||||||
|
|
||||||
module Command
|
module Command
|
||||||
|
|
||||||
BANNER = "Usage: puma_rails <command> [options]"
|
BANNER = "Usage: puma <command> [options]"
|
||||||
|
|
||||||
# A Command pattern implementation used to create the set of command available to the user
|
# A Command pattern implementation used to create the set of command available to the user
|
||||||
# from Puma. The script uses objects which implement this interface to do the
|
# from Puma. The script uses objects which implement this interface to do the
|
||||||
|
@ -31,9 +31,10 @@ module Puma
|
||||||
def options(opts)
|
def options(opts)
|
||||||
# process the given options array
|
# process the given options array
|
||||||
opts.each do |short, long, help, variable, default|
|
opts.each do |short, long, help, variable, default|
|
||||||
self.instance_variable_set(variable, default)
|
instance_variable_set(variable, default)
|
||||||
|
|
||||||
@opt.on(short, long, help) do |arg|
|
@opt.on(short, long, help) do |arg|
|
||||||
self.instance_variable_set(variable, arg)
|
instance_variable_set(variable, arg)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -43,7 +44,7 @@ module Puma
|
||||||
def initialize(options={})
|
def initialize(options={})
|
||||||
argv = options[:argv] || []
|
argv = options[:argv] || []
|
||||||
@stderr = options[:stderr] || $stderr
|
@stderr = options[:stderr] || $stderr
|
||||||
@stdout = options[:stdout] || $stderr
|
@stdout = options[:stdout] || $stdout
|
||||||
|
|
||||||
@opt = OptionParser.new
|
@opt = OptionParser.new
|
||||||
@opt.banner = Puma::Command::BANNER
|
@opt.banner = Puma::Command::BANNER
|
||||||
|
@ -165,22 +166,30 @@ module Puma
|
||||||
@stdout.puts "#{Puma::Command::BANNER}\nAvailable commands are:\n\n"
|
@stdout.puts "#{Puma::Command::BANNER}\nAvailable commands are:\n\n"
|
||||||
|
|
||||||
self.commands.each do |name|
|
self.commands.each do |name|
|
||||||
if /puma::/ =~ name
|
if /puma::(.*)/ =~ name
|
||||||
name = name[9 .. -1]
|
name = $1
|
||||||
end
|
end
|
||||||
|
|
||||||
@stdout.puts " - #{name[1 .. -1]}\n"
|
@stdout.puts " - #{name}\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
@stdout.puts "\nEach command takes -h as an option to get help."
|
@stdout.puts "\nEach command takes -h as an option to get help."
|
||||||
end
|
end
|
||||||
|
|
||||||
|
BUILTIN_COMMANDS = ["start", "stop", "restart"]
|
||||||
|
|
||||||
# Runs the args against the first argument as the command name.
|
# Runs the args against the first argument as the command name.
|
||||||
# If it has any errors it returns a false, otherwise it return true.
|
# If it has any errors it returns a false, otherwise it return true.
|
||||||
def run(args)
|
def run(args)
|
||||||
# find the command
|
# find the command
|
||||||
cmd_name = args.shift
|
cmd_name = args.first
|
||||||
|
|
||||||
|
if !cmd_name or (!BUILTIN_COMMANDS.include?(cmd_name) and
|
||||||
|
File.exists?(cmd_name))
|
||||||
|
cmd_name = "start"
|
||||||
|
else
|
||||||
|
args.shift
|
||||||
|
end
|
||||||
|
|
||||||
if !cmd_name or cmd_name == "?" or cmd_name == "help"
|
if !cmd_name or cmd_name == "?" or cmd_name == "help"
|
||||||
print_command_list
|
print_command_list
|
||||||
|
@ -191,8 +200,10 @@ module Puma
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
# quick hack so that existing commands will keep working but the Puma:: ones can be moved
|
# quick hack so that existing commands will keep working but the
|
||||||
if ["start", "stop", "restart"].include? cmd_name
|
# Puma:: ones can be moved
|
||||||
|
|
||||||
|
if BUILTIN_COMMANDS.include? cmd_name
|
||||||
cmd_name = "puma::" + cmd_name
|
cmd_name = "puma::" + cmd_name
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
require 'yaml'
|
require 'yaml'
|
||||||
require 'etc'
|
require 'etc'
|
||||||
|
|
||||||
|
require 'rack/builder'
|
||||||
|
|
||||||
module Puma
|
module Puma
|
||||||
# Implements a simple DSL for configuring a Puma server for your
|
# Implements a simple DSL for configuring a Puma server for your
|
||||||
# purposes. More used by framework implementers to setup Puma
|
# purposes. More used by framework implementers to setup Puma
|
||||||
|
@ -47,7 +49,7 @@ module Puma
|
||||||
@pid_file = defaults[:pid_file]
|
@pid_file = defaults[:pid_file]
|
||||||
|
|
||||||
if block
|
if block
|
||||||
cloaker(&block).bind(self).call
|
yield self
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -129,14 +131,14 @@ module Puma
|
||||||
# * :user => User to change to, must have :group as well.
|
# * :user => User to change to, must have :group as well.
|
||||||
# * :group => Group to change to, must have :user as well.
|
# * :group => Group to change to, must have :user as well.
|
||||||
#
|
#
|
||||||
def listener(options={},&block)
|
def listener(options={})
|
||||||
raise "Cannot call listener inside another listener block." if (@listener or @listener_name)
|
raise "Cannot call listener inside another listener block." if (@listener or @listener_name)
|
||||||
ops = resolve_defaults(options)
|
ops = resolve_defaults(options)
|
||||||
ops[:num_processors] ||= 950
|
ops[:num_processors] ||= 950
|
||||||
ops[:throttle] ||= 0
|
ops[:throttle] ||= 0
|
||||||
ops[:timeout] ||= 60
|
ops[:timeout] ||= 60
|
||||||
|
|
||||||
@listener = Puma::HttpServer.new(ops[:host], ops[:port].to_i, ops[:num_processors].to_i, ops[:throttle].to_i, ops[:timeout].to_i)
|
@listener = Puma::Server.new(ops[:host], ops[:port].to_i, ops[:concurrency].to_i)
|
||||||
@listener_name = "#{ops[:host]}:#{ops[:port]}"
|
@listener_name = "#{ops[:host]}:#{ops[:port]}"
|
||||||
@listeners[@listener_name] = @listener
|
@listeners[@listener_name] = @listener
|
||||||
|
|
||||||
|
@ -145,8 +147,8 @@ module Puma
|
||||||
end
|
end
|
||||||
|
|
||||||
# Does the actual cloaking operation to give the new implicit self.
|
# Does the actual cloaking operation to give the new implicit self.
|
||||||
if block
|
if block_given?
|
||||||
cloaker(&block).bind(self).call
|
yield self
|
||||||
end
|
end
|
||||||
|
|
||||||
# all done processing this listener setup, reset implicit variables
|
# all done processing this listener setup, reset implicit variables
|
||||||
|
@ -154,60 +156,13 @@ module Puma
|
||||||
@listener_name = nil
|
@listener_name = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def load_rackup(file)
|
||||||
|
app, options = Rack::Builder.parse_file file
|
||||||
|
|
||||||
# Called inside a Configurator.listener block in order to
|
@listener.app = app
|
||||||
# add URI->handler mappings for that listener. Use this as
|
# Do something with options?
|
||||||
# many times as you like. It expects the following options
|
|
||||||
# or defaults:
|
|
||||||
#
|
|
||||||
# * :handler => HttpHandler -- Handler to use for this location.
|
|
||||||
# * :in_front => true/false -- Rather than appending, it prepends this handler.
|
|
||||||
def uri(location, options={})
|
|
||||||
ops = resolve_defaults(options)
|
|
||||||
@listener.register(location, ops[:handler], ops[:in_front])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# Daemonizes the current Ruby script turning all the
|
|
||||||
# listeners into an actual "server" or detached process.
|
|
||||||
# You must call this *before* frameworks that open files
|
|
||||||
# as otherwise the files will be closed by this function.
|
|
||||||
#
|
|
||||||
# Does not work for Win32 systems (the call is silently ignored).
|
|
||||||
#
|
|
||||||
# Requires the following options or defaults:
|
|
||||||
#
|
|
||||||
# * :cwd => Directory to change to.
|
|
||||||
# * :log_file => Where to write STDOUT and STDERR.
|
|
||||||
#
|
|
||||||
# It is safe to call this on win32 as it will only require the daemons
|
|
||||||
# gem/library if NOT win32.
|
|
||||||
def daemonize(options={})
|
|
||||||
ops = resolve_defaults(options)
|
|
||||||
# save this for later since daemonize will hose it
|
|
||||||
unless RbConfig::CONFIG['host_os'] =~ /mingw|mswin/
|
|
||||||
require 'daemons/daemonize'
|
|
||||||
|
|
||||||
logfile = ops[:log_file]
|
|
||||||
if logfile[0].chr != "/"
|
|
||||||
logfile = File.join(ops[:cwd],logfile)
|
|
||||||
if not File.exist?(File.dirname(logfile))
|
|
||||||
log "!!! Log file directory not found at full path #{File.dirname(logfile)}. Update your configuration to use a full path."
|
|
||||||
exit 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Daemonize.daemonize(logfile)
|
|
||||||
|
|
||||||
# change back to the original starting directory
|
|
||||||
Dir.chdir(ops[:cwd])
|
|
||||||
|
|
||||||
else
|
|
||||||
log "WARNING: Win32 does not support daemon mode."
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# Uses the GemPlugin system to easily load plugins based on their
|
# Uses the GemPlugin system to easily load plugins based on their
|
||||||
# gem dependencies. You pass in either an :includes => [] or
|
# gem dependencies. You pass in either an :includes => [] or
|
||||||
# :excludes => [] setting listing the names of plugins to include
|
# :excludes => [] setting listing the names of plugins to include
|
||||||
|
@ -237,24 +192,6 @@ module Puma
|
||||||
default.merge(YAML.load_file(file))
|
default.merge(YAML.load_file(file))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# Loads the MIME map file and checks that it is correct
|
|
||||||
# on loading. This is commonly passed to Puma::DirHandler
|
|
||||||
# or any framework handler that uses DirHandler to serve files.
|
|
||||||
# You can also include a set of default MIME types as additional
|
|
||||||
# settings. See Puma::DirHandler for how the MIME types map
|
|
||||||
# is organized.
|
|
||||||
def load_mime_map(file, mime={})
|
|
||||||
# configure any requested mime map
|
|
||||||
mime = load_yaml(file, mime)
|
|
||||||
|
|
||||||
# check all the mime types to make sure they are the right format
|
|
||||||
mime.each {|k,v| log "WARNING: MIME type #{k} must start with '.'" if k.index(".") != 0 }
|
|
||||||
|
|
||||||
return mime
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# Loads and creates a plugin for you based on the given
|
# Loads and creates a plugin for you based on the given
|
||||||
# name and configured with the selected options. The options
|
# name and configured with the selected options. The options
|
||||||
# are merged with the defaults prior to passing them in.
|
# are merged with the defaults prior to passing them in.
|
||||||
|
@ -281,8 +218,6 @@ module Puma
|
||||||
@listeners.each {|name,s|
|
@listeners.each {|name,s|
|
||||||
s.run
|
s.run
|
||||||
}
|
}
|
||||||
|
|
||||||
$puma_sleeper_thread = Thread.new { loop { sleep 1 } }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Calls .stop on all the configured listeners so they
|
# Calls .stop on all the configured listeners so they
|
||||||
|
@ -303,42 +238,6 @@ module Puma
|
||||||
@listeners.values.each {|s| s.acceptor.join }
|
@listeners.values.each {|s| s.acceptor.join }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# Calling this before you register your URIs to the given location
|
|
||||||
# will setup a set of handlers that log open files, objects, and the
|
|
||||||
# parameters for each request. This helps you track common problems
|
|
||||||
# found in Rails applications that are either slow or become unresponsive
|
|
||||||
# after a little while.
|
|
||||||
#
|
|
||||||
# You can pass an extra parameter *what* to indicate what you want to
|
|
||||||
# debug. For example, if you just want to dump rails stuff then do:
|
|
||||||
#
|
|
||||||
# debug "/", what = [:rails]
|
|
||||||
#
|
|
||||||
# And it will only produce the log/puma_debug/rails.log file.
|
|
||||||
# Available options are: :access, :files, :objects, :threads, :rails
|
|
||||||
#
|
|
||||||
# NOTE: Use [:files] to get accesses dumped to stderr like with WEBrick.
|
|
||||||
def debug(location, what = [:access, :files, :objects, :threads, :rails])
|
|
||||||
require 'puma/debug'
|
|
||||||
handlers = {
|
|
||||||
:access => "/handlers/requestlog::access",
|
|
||||||
:files => "/handlers/requestlog::files",
|
|
||||||
:objects => "/handlers/requestlog::objects",
|
|
||||||
:threads => "/handlers/requestlog::threads",
|
|
||||||
:rails => "/handlers/requestlog::params"
|
|
||||||
}
|
|
||||||
|
|
||||||
# turn on the debugging infrastructure, and ObjectTracker is a pig
|
|
||||||
PumaDbg.configure
|
|
||||||
|
|
||||||
# now we roll through each requested debug type, turn it on and load that plugin
|
|
||||||
what.each do |type|
|
|
||||||
PumaDbg.begin_trace type
|
|
||||||
uri location, :handler => plugin(handlers[type])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Used to allow you to let users specify their own configurations
|
# Used to allow you to let users specify their own configurations
|
||||||
# inside your Configurator setup. You pass it a script name and
|
# inside your Configurator setup. You pass it a script name and
|
||||||
# it reads it in and does an eval on the contents passing in the right
|
# it reads it in and does an eval on the contents passing in the right
|
||||||
|
|
302
lib/puma/gem_plugin.rb
Normal file
302
lib/puma/gem_plugin.rb
Normal file
|
@ -0,0 +1,302 @@
|
||||||
|
require 'singleton'
|
||||||
|
require 'rubygems'
|
||||||
|
|
||||||
|
# Implements a dynamic plugin loading, configuration, and discovery system
|
||||||
|
# based on RubyGems and a simple additional name space that looks like a URI.
|
||||||
|
#
|
||||||
|
# A plugin is created and put into a category with the following code:
|
||||||
|
#
|
||||||
|
# class MyThing < GemPlugin::Plugin "/things"
|
||||||
|
# ...
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# What this does is sets up your MyThing in the plugin registry via GemPlugin::Manager.
|
||||||
|
# You can then later get this plugin with GemPlugin::Manager.create("/things/mything")
|
||||||
|
# and can also pass in options as a second parameter.
|
||||||
|
#
|
||||||
|
# This isn't such a big deal, but the power is really from the GemPlugin::Manager.load
|
||||||
|
# method. This method will go through the installed gems and require_gem any
|
||||||
|
# that depend on the gem_plugin RubyGem. You can arbitrarily include or exclude
|
||||||
|
# gems based on what they also depend on, thus letting you load these gems when appropriate.
|
||||||
|
#
|
||||||
|
# Since this system was written originally for the Mongrel project that'll be the
|
||||||
|
# best example of using it.
|
||||||
|
#
|
||||||
|
# Imagine you have a neat plugin for Mongrel called snazzy_command that gives the
|
||||||
|
# mongrel_rails a new command snazzy (like: mongrel_rails snazzy). You'd like
|
||||||
|
# people to be able to grab this plugin if they want and use it, because it's snazzy.
|
||||||
|
#
|
||||||
|
# First thing you do is create a gem of your project and make sure that it depends
|
||||||
|
# on "mongrel" AND "gem_plugin". This signals to the GemPlugin system that this is
|
||||||
|
# a plugin for mongrel.
|
||||||
|
#
|
||||||
|
# Next you put this code into a file like lib/init.rb (can be anything really):
|
||||||
|
#
|
||||||
|
# class Snazzy < GemPlugin::Plugin "/commands"
|
||||||
|
# ...
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# Then when you create your gem you have the following bits in your Rakefile:
|
||||||
|
#
|
||||||
|
# spec.add_dependency('mongrel', '>= 0.3.9')
|
||||||
|
# spec.add_dependency('gem_plugin', '>= 0.1')
|
||||||
|
# spec.autorequire = 'init.rb'
|
||||||
|
#
|
||||||
|
# Finally, you just have to now publish this gem for people to install and Mongrel
|
||||||
|
# will "magically" be able to install it.
|
||||||
|
#
|
||||||
|
# The "magic" part though is pretty simple and done via the GemPlugin::Manager.load
|
||||||
|
# method. Read that to see how it is really done.
|
||||||
|
module GemPlugin
|
||||||
|
|
||||||
|
EXCLUDE = true
|
||||||
|
INCLUDE = false
|
||||||
|
|
||||||
|
class PluginNotLoaded < StandardError; end
|
||||||
|
|
||||||
|
|
||||||
|
# This class is used by people who use gem plugins (but don't necessarily make them)
|
||||||
|
# to add plugins to their own systems. It provides a way to load plugins, list them,
|
||||||
|
# and create them as needed.
|
||||||
|
#
|
||||||
|
# It is a singleton so you use like this: GemPlugins::Manager.instance.load
|
||||||
|
class Manager
|
||||||
|
include Singleton
|
||||||
|
attr_reader :plugins
|
||||||
|
attr_reader :gems
|
||||||
|
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
# plugins that have been loaded
|
||||||
|
@plugins = {}
|
||||||
|
|
||||||
|
# keeps track of gems which have been loaded already by the manager *and*
|
||||||
|
# where they came from so that they can be referenced later
|
||||||
|
@gems = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def every_gem
|
||||||
|
if Gem::VERSION >= '1.8.0'
|
||||||
|
gems = Gem::Specification.to_a
|
||||||
|
gems.each { |g| yield g }
|
||||||
|
else
|
||||||
|
gems = Gem::SourceIndex.from_gems_in(sdirs)
|
||||||
|
gems.each do |path, gem|
|
||||||
|
yield gem
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Responsible for going through the list of available gems and loading
|
||||||
|
# any plugins requested. It keeps track of what it's loaded already
|
||||||
|
# and won't load them again.
|
||||||
|
#
|
||||||
|
# It accepts one parameter which is a hash of gem depends that should include
|
||||||
|
# or exclude a gem from being loaded. A gem must depend on gem_plugin to be
|
||||||
|
# considered, but then each system has to add it's own INCLUDE to make sure
|
||||||
|
# that only plugins related to it are loaded.
|
||||||
|
#
|
||||||
|
# An example again comes from Mongrel. In order to load all Mongrel plugins:
|
||||||
|
#
|
||||||
|
# GemPlugin::Manager.instance.load "mongrel" => GemPlugin::INCLUDE
|
||||||
|
#
|
||||||
|
# Which will load all plugins that depend on mongrel AND gem_plugin. Now, one
|
||||||
|
# extra thing we do is we delay loading Rails Mongrel plugins until after rails
|
||||||
|
# is configured. Do do this the mongrel_rails script has:
|
||||||
|
#
|
||||||
|
# GemPlugin::Manager.instance.load "mongrel" => GemPlugin::INCLUDE, "rails" => GemPlugin::EXCLUDE
|
||||||
|
# The only thing to remember is that this is saying "include a plugin if it
|
||||||
|
# depends on gem_plugin, mongrel, but NOT rails". If a plugin also depends on other
|
||||||
|
# stuff then it's loaded just fine. Only gem_plugin, mongrel, and rails are
|
||||||
|
# ever used to determine if it should be included.
|
||||||
|
#
|
||||||
|
# NOTE: Currently RubyGems will run autorequire on gems that get required AND
|
||||||
|
# on their dependencies. This really messes with people running edge rails
|
||||||
|
# since activerecord or other stuff gets loaded for just touching a gem plugin.
|
||||||
|
# To prevent this load requires the full path to the "init.rb" file, which
|
||||||
|
# avoids the RubyGems autorequire magic.
|
||||||
|
def load(needs = {})
|
||||||
|
needs = needs.merge({"gem_plugin" => INCLUDE})
|
||||||
|
|
||||||
|
every_gem do |gem|
|
||||||
|
# don't load gems more than once
|
||||||
|
next if @gems.has_key? gem.name
|
||||||
|
check = needs.dup
|
||||||
|
|
||||||
|
# rolls through the depends and inverts anything it finds
|
||||||
|
gem.dependencies.each do |dep|
|
||||||
|
# this will fail if a gem is depended more than once
|
||||||
|
if check.has_key? dep.name
|
||||||
|
check[dep.name] = !check[dep.name]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# now since excluded gems start as true, inverting them
|
||||||
|
# makes them false so we'll skip this gem if any excludes are found
|
||||||
|
if (check.select {|name,test| !test}).length == 0
|
||||||
|
# looks like no needs were set to false, so it's good
|
||||||
|
|
||||||
|
# Previously was set wrong, we already have the correct gem path!
|
||||||
|
gem_dir = ""
|
||||||
|
Gem.path.each do |gem_path|
|
||||||
|
gem_dir = File.join(gem_path, "gems", path)
|
||||||
|
break if File.exists?(gem_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.join(gem_dir, "lib", gem.name, "init.rb")
|
||||||
|
@gems[gem.name] = gem_dir
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# Not necessary for you to call directly, but this is
|
||||||
|
# how GemPlugin::Base.inherited actually adds a
|
||||||
|
# plugin to a category.
|
||||||
|
def register(category, name, klass)
|
||||||
|
@plugins[category] ||= {}
|
||||||
|
@plugins[category][name.downcase] = klass
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# Resolves the given name (should include /category/name) to
|
||||||
|
# find the plugin class and create an instance. You can
|
||||||
|
# pass a second hash option that is then given to the Plugin
|
||||||
|
# to configure it.
|
||||||
|
def create(name, options = {})
|
||||||
|
last_slash = name.rindex("/")
|
||||||
|
category = name[0 ... last_slash]
|
||||||
|
plugin = name[last_slash .. -1]
|
||||||
|
|
||||||
|
map = @plugins[category]
|
||||||
|
if not map
|
||||||
|
raise "Plugin category #{category} does not exist"
|
||||||
|
elsif not map.has_key? plugin
|
||||||
|
raise "Plugin #{plugin} does not exist in category #{category}"
|
||||||
|
else
|
||||||
|
map[plugin].new(options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# Simply says whether the given gem has been loaded yet or not.
|
||||||
|
def loaded?(gem_name)
|
||||||
|
@gems.has_key? gem_name
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# GemPlugins can have a 'resources' directory which is packaged with them
|
||||||
|
# and can hold any data resources the plugin may need. The main problem
|
||||||
|
# is that it's difficult to figure out where these resources are
|
||||||
|
# actually located on the file system. The resource method tries to
|
||||||
|
# locate the real path for a given resource path.
|
||||||
|
#
|
||||||
|
# Let's say you have a 'resources/stylesheets/default.css' file in your
|
||||||
|
# gem distribution, then finding where this file really is involves:
|
||||||
|
#
|
||||||
|
# Manager.instance.resource("mygem", "/stylesheets/default.css")
|
||||||
|
#
|
||||||
|
# You either get back the full path to the resource or you get a nil
|
||||||
|
# if it doesn't exist.
|
||||||
|
#
|
||||||
|
# If you request a path for a GemPlugin that hasn't been loaded yet
|
||||||
|
# then it will throw an PluginNotLoaded exception. The gem may be
|
||||||
|
# present on your system in this case, but you just haven't loaded
|
||||||
|
# it with Manager.instance.load properly.
|
||||||
|
def resource(gem_name, path)
|
||||||
|
if not loaded? gem_name
|
||||||
|
raise PluginNotLoaded.new("Plugin #{gem_name} not loaded when getting resource #{path}")
|
||||||
|
end
|
||||||
|
|
||||||
|
file = File.join(@gems[gem_name], "resources", path)
|
||||||
|
|
||||||
|
if File.exist? file
|
||||||
|
return file
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# While Manager.resource will find arbitrary resources, a special
|
||||||
|
# case is when you need to load a set of configuration defaults.
|
||||||
|
# GemPlugin normalizes this to be if you have a file "resources/defaults.yaml"
|
||||||
|
# then you'll be able to load them via Manager.config.
|
||||||
|
#
|
||||||
|
# How you use the method is you get the options the user wants set, pass
|
||||||
|
# them to Manager.instance.config, and what you get back is a new Hash
|
||||||
|
# with the user's settings overriding the defaults.
|
||||||
|
#
|
||||||
|
# opts = Manager.instance.config "mygem", :age => 12, :max_load => .9
|
||||||
|
#
|
||||||
|
# In the above case, if defaults had {:age => 14} then it would be
|
||||||
|
# changed to 12.
|
||||||
|
#
|
||||||
|
# This loads the .yaml file on the fly every time, so doing it a
|
||||||
|
# whole lot is very stupid. If you need to make frequent calls to
|
||||||
|
# this, call it once with no options (Manager.instance.config) then
|
||||||
|
# use the returned defaults directly from then on.
|
||||||
|
def config(gem_name, options={})
|
||||||
|
config_file = Manager.instance.resource(gem_name, "/defaults.yaml")
|
||||||
|
if config_file
|
||||||
|
begin
|
||||||
|
defaults = YAML.load_file(config_file)
|
||||||
|
return defaults.merge(options)
|
||||||
|
rescue
|
||||||
|
raise "Error loading config #{config_file} for gem #{gem_name}"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return options
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# This base class for plugins really does nothing
|
||||||
|
# more than wire up the new class into the right category.
|
||||||
|
# It is not thread-safe yet but will be soon.
|
||||||
|
class Base
|
||||||
|
|
||||||
|
attr_reader :options
|
||||||
|
|
||||||
|
# See Mongrel::Plugin for an explanation.
|
||||||
|
def Base.inherited(klass)
|
||||||
|
name = "/" + klass.to_s.downcase
|
||||||
|
Manager.instance.register(@@category, name, klass)
|
||||||
|
@@category = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# See Mongrel::Plugin for an explanation.
|
||||||
|
def Base.category=(category)
|
||||||
|
@@category = category
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(options = {})
|
||||||
|
@options = options
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# This nifty function works with the GemPlugin::Base to give you
|
||||||
|
# the syntax:
|
||||||
|
#
|
||||||
|
# class MyThing < GemPlugin::Plugin "/things"
|
||||||
|
# ...
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# What it does is temporarily sets the GemPlugin::Base.category, and then
|
||||||
|
# returns GemPlugin::Base. Since the next immediate thing Ruby does is
|
||||||
|
# use this returned class to create the new class, GemPlugin::Base.inherited
|
||||||
|
# gets called. GemPlugin::Base.inherited then uses the set category, class name,
|
||||||
|
# and class to register the plugin in the right way.
|
||||||
|
def GemPlugin.Plugin(c)
|
||||||
|
Base.category = c
|
||||||
|
Base
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
require 'puma/gems'
|
require 'puma/gems'
|
||||||
Puma::Gems.require 'gem_plugin'
|
Puma::Gems.require 'puma/gem_plugin'
|
||||||
|
|
||||||
# File is just a stub that makes sure the puma_plugins gem is loaded and ready
|
# File is just a stub that makes sure the puma_plugins gem is loaded and ready
|
||||||
|
|
|
@ -5,7 +5,7 @@ HOE = Hoe.spec 'puma' do
|
||||||
developer 'Evan Phoenix', 'evan@phx.io'
|
developer 'Evan Phoenix', 'evan@phx.io'
|
||||||
|
|
||||||
spec_extras[:extensions] = ["ext/http11/extconf.rb"]
|
spec_extras[:extensions] = ["ext/http11/extconf.rb"]
|
||||||
spec_extras[:executables] = ['puma_rails']
|
spec_extras[:executables] = ['puma']
|
||||||
|
|
||||||
extra_rdoc_files << 'LICENSE'
|
extra_rdoc_files << 'LICENSE'
|
||||||
|
|
||||||
|
|
4
test/lobster.ru
Normal file
4
test/lobster.ru
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
require 'rack/lobster'
|
||||||
|
|
||||||
|
use Rack::ShowExceptions
|
||||||
|
run Rack::Lobster.new
|
Loading…
Reference in a new issue