1
0
Fork 0
mirror of https://github.com/puma/puma.git synced 2022-11-09 13:48:40 -05:00
puma--puma/lib/puma/configuration.rb

363 lines
9.5 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
require 'puma/rack/builder'
2016-02-07 17:51:54 -05:00
require 'puma/plugin'
require 'puma/const'
2015-05-05 08:06:30 -04:00
2011-12-07 16:42:53 -05:00
module Puma
module ConfigDefault
2011-12-07 16:42:53 -05:00
DefaultRackup = "config.ru"
DefaultTCPHost = "0.0.0.0"
DefaultTCPPort = 9292
DefaultWorkerTimeout = 60
DefaultWorkerShutdownTimeout = 30
end
2017-03-06 13:32:12 -05:00
# A class used for storing "leveled" configuration options.
#
2017-03-06 13:32:12 -05:00
# In this class any "user" specified options take precedence over any
# "file" specified options, take precedence over any "default" options.
#
# User input is prefered over "defaults":
# user_options = { foo: "bar" }
# default_options = { foo: "zoo" }
# options = UserFileDefaultOptions.new(user_options, default_options)
# puts options[:foo]
# # => "bar"
#
# All values can be accessed via `all_of`
#
# puts options.all_of(:foo)
# # => ["bar", "zoo"]
#
# A "file" option can be set. This config will be prefered over "default" options
# but will defer to any available "user" specified options.
#
# user_options = { foo: "bar" }
# default_options = { rackup: "zoo.rb" }
# options = UserFileDefaultOptions.new(user_options, default_options)
# options.file_options[:rackup] = "sup.rb"
# puts options[:rackup]
# # => "sup.rb"
#
# The "default" options can be set via procs. These are resolved during runtime
# via calls to `finalize_values`
2017-03-03 15:30:28 -05:00
class UserFileDefaultOptions
def initialize(user_options, default_options)
@user_options = user_options
@file_options = {}
@default_options = default_options
end
attr_reader :user_options, :file_options, :default_options
def [](key)
2017-03-03 15:30:28 -05:00
return user_options[key] if user_options.key?(key)
return file_options[key] if file_options.key?(key)
return default_options[key] if default_options.key?(key)
end
2017-03-03 15:30:28 -05:00
def []=(key, value)
user_options[key] = value
2016-03-05 19:14:54 -05:00
end
def fetch(key, default_value = nil)
self[key] || default_value
end
def all_of(key)
2017-03-03 15:30:28 -05:00
user = user_options[key]
file = file_options[key]
default = default_options[key]
2017-03-03 15:41:42 -05:00
2017-03-03 15:30:28 -05:00
user = [user] unless user.is_a?(Array)
file = [file] unless file.is_a?(Array)
default = [default] unless default.is_a?(Array)
2016-02-07 17:51:54 -05:00
2017-03-03 15:41:42 -05:00
user.compact!
file.compact!
default.compact!
2017-03-03 15:30:28 -05:00
user + file + default
2016-02-19 20:05:59 -05:00
end
2017-03-03 15:41:42 -05:00
def finalize_values
2017-03-03 15:41:42 -05:00
@default_options.each do |k,v|
if v.respond_to? :call
@default_options[k] = v.call
end
end
end
end
2017-03-06 13:32:12 -05:00
# The main configuration class of Puma.
#
# It can be initialized with a set of "user" options and "default" options.
# Defaults will be merged with `Configuration.puma_default_options`.
#
# This class works together with 2 main other classes the `UserFileDefaultOptions`
# which stores configuration options in order so the precedence is that user
# set configuration wins over "file" based configuration wins over "default"
# configuration. These configurations are set via the `DSL` class. This
# class powers the Puma config file syntax and does double duty as a configuration
# DSL used by the `Puma::CLI` and Puma rack handler.
#
# It also handles loading plugins.
#
# > Note: `:port` and `:host` are not valid keys. By they time they make it to the
# configuration options they are expected to be incorporated into a `:binds` key.
# Under the hood the DSL maps `port` and `host` calls to `:binds`
#
# config = Configuration.new({}) do |user_config, file_config, default_config|
# user_config.port 3003
# end
# config.load
# puts config.options[:port]
# # => 3003
#
# It is expected that `load` is called on the configuration instance after setting
# config. This method expands any values in `config_file` and puts them into the
# correct configuration option hash.
#
# Once all configuration is complete it is expected that `clamp` will be called
# on the instance. This will expand any procs stored under "default" values. This
# is done because an environment variable may have been modified while loading
# configuration files.
class Configuration
include ConfigDefault
2011-12-07 16:42:53 -05:00
2017-03-09 12:34:49 -05:00
def initialize(user_options={}, default_options = {}, &block)
Implement user_supplied_options behavior. When options are passed to the Puma rack handler it is unknown if the options were set via a framework as a default or via a user. Puma currently has 3 different sources of configuration, the user via command line, the config files, and defaults. Rails 5.1+ will record the values actually specified by the user versus the values specified by the frameworks. It passes these values to the Rack handler and now it's up to Puma to do something with that information. When only framework defaults are passed it will set ``` options[:user_supplied_options] = [] ``` When one or more options are specified by the user such as `:Port` then those keys will be in the array. In that example it will look like this ``` options[:user_supplied_options] = [:Port] ``` This change is 100% backwards compatible. If the framework is older and does not pass this information then the `user_supplied_options` will not be set, in that case we assume all values are user supplied. Internally we accomplish this separation by replacing `LeveledOptions` which was a generic way of specifying options with different priorities with a more explicit `UserFileDefaultOptions` this assumes only 3 levels of options and it will use them in the order supplied (user config wins over file based config wins over defaults). Now instead of using 1 dsl to set all values, we use 3. A user dsl, a file dsl and a Configuration.new` will return all 3 DSLs to the block. It's up to the person using the block to use the correct dsl corresponding to the source of data they are getting.
2017-03-03 17:04:56 -05:00
default_options = self.puma_default_options.merge(default_options)
2017-03-06 13:30:38 -05:00
@options = UserFileDefaultOptions.new(user_options, default_options)
2017-03-06 11:46:11 -05:00
@plugins = PluginLoader.new
@user_dsl = DSL.new(@options.user_options, self)
@file_dsl = DSL.new(@options.file_options, self)
@default_dsl = DSL.new(@options.default_options, self)
2017-03-09 12:34:49 -05:00
if block
configure(&block)
end
end
attr_reader :options, :plugins
2017-03-09 12:34:49 -05:00
def configure
yield @user_dsl, @file_dsl, @default_dsl
ensure
@user_dsl._offer_plugins
@file_dsl._offer_plugins
@default_dsl._offer_plugins
2011-12-07 16:42:53 -05:00
end
def initialize_copy(other)
2017-03-03 15:41:42 -05:00
@conf = nil
@cli_options = nil
2017-03-03 15:41:42 -05:00
@options = @options.dup
end
def flatten
dup.flatten!
end
def flatten!
@options = @options.flatten
self
end
Implement user_supplied_options behavior. When options are passed to the Puma rack handler it is unknown if the options were set via a framework as a default or via a user. Puma currently has 3 different sources of configuration, the user via command line, the config files, and defaults. Rails 5.1+ will record the values actually specified by the user versus the values specified by the frameworks. It passes these values to the Rack handler and now it's up to Puma to do something with that information. When only framework defaults are passed it will set ``` options[:user_supplied_options] = [] ``` When one or more options are specified by the user such as `:Port` then those keys will be in the array. In that example it will look like this ``` options[:user_supplied_options] = [:Port] ``` This change is 100% backwards compatible. If the framework is older and does not pass this information then the `user_supplied_options` will not be set, in that case we assume all values are user supplied. Internally we accomplish this separation by replacing `LeveledOptions` which was a generic way of specifying options with different priorities with a more explicit `UserFileDefaultOptions` this assumes only 3 levels of options and it will use them in the order supplied (user config wins over file based config wins over defaults). Now instead of using 1 dsl to set all values, we use 3. A user dsl, a file dsl and a Configuration.new` will return all 3 DSLs to the block. It's up to the person using the block to use the correct dsl corresponding to the source of data they are getting.
2017-03-03 17:04:56 -05:00
def puma_default_options
{
:min_threads => 0,
:max_threads => 16,
:log_requests => false,
:debug => false,
:binds => ["tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"],
:workers => 0,
:daemon => false,
:mode => :http,
:worker_timeout => DefaultWorkerTimeout,
:worker_boot_timeout => DefaultWorkerTimeout,
:worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
:remote_address => :socket,
:tag => method(:infer_tag),
:environment => -> { ENV['RACK_ENV'] || "development" },
:rackup => DefaultRackup,
:logger => STDOUT,
:persistent_timeout => Const::PERSISTENT_TIMEOUT,
:first_data_timeout => Const::FIRST_DATA_TIMEOUT,
:raise_exception_on_sigterm => true
}
end
2011-12-07 16:42:53 -05:00
def load
config_files.each { |config_file| @file_dsl._load_from(config_file) }
@options
end
def config_files
files = @options.all_of(:config_files)
return [] if files == ['-']
return files if files.any?
first_default_file = %W(config/puma/#{environment_str}.rb config/puma.rb).find do |f|
File.exist?(f)
end
[first_default_file]
end
# Call once all configuration (included from rackup files)
# is loaded to flesh out any defaults
def clamp
@options.finalize_values
2011-12-07 16:42:53 -05:00
end
# Injects the Configuration object into the env
class ConfigMiddleware
def initialize(config, app)
@config = config
@app = app
end
def call(env)
env[Const::PUMA_CONFIG] = @config
@app.call(env)
end
end
# Indicate if there is a properly configured app
#
def app_configured?
2014-01-25 17:50:40 -05:00
@options[:app] || File.exist?(rackup)
end
def rackup
@options[:rackup]
end
2014-11-01 19:41:26 -04:00
# Load the specified rackup file, pull options from
2011-12-07 16:42:53 -05:00
# the rackup file, and set @app.
#
def app
2015-03-13 21:23:09 -04:00
found = options[:app] || load_rackup
2011-12-07 16:42:53 -05:00
2015-03-13 21:23:09 -04:00
if @options[:mode] == :tcp
require 'puma/tcp_logger'
logger = @options[:logger]
quiet = !@options[:log_requests]
return TCPLogger.new(logger, found, quiet)
2015-03-13 21:23:09 -04:00
end
2011-12-07 16:42:53 -05:00
if @options[:log_requests]
require 'puma/commonlogger'
logger = @options[:logger]
found = CommonLogger.new(found, logger)
2015-03-13 21:23:09 -04:00
end
2011-12-07 16:42:53 -05:00
2015-03-13 21:23:09 -04:00
ConfigMiddleware.new(self, found)
end
# Return which environment we're running in
def environment
@options[:environment]
end
def environment_str
environment.respond_to?(:call) ? environment.call : environment
end
def load_plugin(name)
@plugins.create name
end
2016-02-07 17:51:54 -05:00
def run_hooks(key, arg)
@options.all_of(key).each { |b| b.call arg }
end
2015-03-13 21:23:09 -04:00
def self.temp_path
require 'tmpdir'
2015-03-13 21:23:09 -04:00
t = (Time.now.to_f * 1000).to_i
"#{Dir.tmpdir}/puma-status-#{t}-#{$$}"
end
private
def infer_tag
File.basename(Dir.getwd)
end
# Load and use the normal Rack builder if we can, otherwise
# fallback to our minimal version.
def rack_builder
# Load bundler now if we can so that we can pickup rack from
# a Gemfile
if ENV.key? 'PUMA_BUNDLER_PRUNED'
begin
require 'bundler/setup'
rescue LoadError
end
end
begin
require 'rack'
require 'rack/builder'
rescue LoadError
# ok, use builtin version
return Puma::Rack::Builder
else
return ::Rack::Builder
end
end
2015-03-13 21:23:09 -04:00
def load_rackup
raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
rack_app, rack_options = rack_builder.parse_file(rackup)
2017-03-03 15:41:42 -05:00
@options.file_options.merge!(rack_options)
2015-03-13 21:23:09 -04:00
config_ru_binds = []
rack_options.each do |k, v|
config_ru_binds << v if k.to_s.start_with?("bind")
2011-12-07 16:42:53 -05:00
end
2017-03-03 15:41:42 -05:00
@options.file_options[:binds] = config_ru_binds unless config_ru_binds.empty?
2011-12-07 16:42:53 -05:00
2015-03-13 21:23:09 -04:00
rack_app
end
def self.random_token
2011-12-07 16:42:53 -05:00
begin
require 'openssl'
rescue LoadError
end
count = 16
bytes = nil
if defined? OpenSSL::Random
bytes = OpenSSL::Random.random_bytes(count)
2014-01-25 17:50:40 -05:00
elsif File.exist?("/dev/urandom")
2015-03-13 21:23:09 -04:00
File.open('/dev/urandom') { |f| bytes = f.read(count) }
2011-12-07 16:42:53 -05:00
end
if bytes
2019-06-10 14:02:46 -04:00
token = +""
2011-12-07 16:42:53 -05:00
bytes.each_byte { |b| token << b.to_s(16) }
else
token = (0..count).to_a.map { rand(255).to_s(16) }.join
end
return token
2011-12-07 16:42:53 -05:00
end
end
end
require 'puma/dsl'