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

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.
This commit is contained in:
schneems 2017-03-03 14:04:56 -08:00
parent 24f12579bf
commit 85dfe8edcf
4 changed files with 138 additions and 45 deletions

View file

@ -77,9 +77,10 @@ module Puma
return cfg
end
def initialize(options={}, &blk)
def initialize(options={}, default_options = {}, &blk)
default_options = self.puma_default_options.merge(default_options)
@options = UserFileDefaultOptions.new(options, self.default_options)
@options = UserFileDefaultOptions.new(options, default_options)
@plugins = PluginLoader.new
@user_dsl = DSL.new(@options.user_options, self)
@file_dsl = DSL.new(@options.file_options, self)
@ -115,7 +116,7 @@ module Puma
self
end
def default_options
def puma_default_options
{
:min_threads => 0,
:max_threads => 16,

View file

@ -13,9 +13,20 @@ module Rack
require 'puma/events'
require 'puma/launcher'
options = DEFAULT_OPTIONS.merge(options)
default_options = DEFAULT_OPTIONS.dup
conf = ::Puma::Configuration.new(options) do |user_config, file_config, default_config|
# Libraries pass in values such as :Port and there is no way to determine
# if it is a default provided by the library or a special value provided
# by the user. A special key `user_supplied_options` can be passed. This
# contains an array of all explicitly defined user options. We then
# know that all other values are defaults
if user_supplied_options = options.delete(:user_supplied_options)
(options.keys - user_supplied_options).each do |k, v|
default_options[k] = options.delete(k)
end
end
conf = ::Puma::Configuration.new(options, default_options) do |user_config, file_config, default_config|
user_config.quiet
if options.delete(:Verbose)
@ -31,30 +42,16 @@ module Rack
user_config.threads min, max
end
host = options[:Host]
if host && (host[0,1] == '.' || host[0,1] == '/')
user_config.bind "unix://#{host}"
elsif host && host =~ /^ssl:\/\//
uri = URI.parse(host)
uri.port ||= options[:Port] || ::Puma::Configuration::DefaultTCPPort
user_config.bind uri.to_s
else
if host
options[:Port] ||= ::Puma::Configuration::DefaultTCPPort
end
if port = options[:Port]
host ||= ::Puma::Configuration::DefaultTCPHost
user_config.port port, host
end
end
self.set_host_port_to_config(options[:Host], options[:Port], user_config)
self.set_host_port_to_config(default_options[:Host], default_options[:Port], default_config)
user_config.app app
end
conf
end
def self.run(app, options = {})
conf = self.config(app, options)
@ -80,6 +77,26 @@ module Rack
"Verbose" => "Don't report each request (default: false)"
}
end
private
def self.set_host_port_to_config(host, port, config)
if host && (host[0,1] == '.' || host[0,1] == '/')
config.bind "unix://#{host}"
elsif host && host =~ /^ssl:\/\//
uri = URI.parse(host)
uri.port ||= port || ::Puma::Configuration::DefaultTCPPort
config.bind uri.to_s
else
if host
port ||= ::Puma::Configuration::DefaultTCPPort
end
if port
host ||= ::Puma::Configuration::DefaultTCPHost
config.port port, host
end
end
end
end
register :puma, Puma

View file

@ -13,6 +13,8 @@ require "minitest/pride"
require "puma"
require "puma/detect"
$LOAD_PATH << File.expand_path("../../lib", __FILE__)
# Either takes a string to do a get request against, or a tuple of [URI, HTTP] where
# HTTP is some kind of Net::HTTP request object (POST, HEAD, etc.)
def hit(uris)

View file

@ -53,37 +53,50 @@ class TestPathHandler < Minitest::Test
assert_equal("/test", @input["PATH_INFO"])
end
end
end
# def test_user_supplied_port_wins_over_config_file
# user_port = 5001
# file_port = 6001
# options = {}
class TestUserSuppliedOptionsPortIsSet < Minitest::Test
def setup
@options = {}
@options[:user_supplied_options] = [:Port]
end
# Tempfile.open("puma.rb") do |f|
# f.puts "port #{file_port}"
# f.close
# options[:config_files] = [f.path]
# options[:user_supplied_options] = [:Port]
# options[:Port] = user_port
# conf = Rack::Handler::Puma.config(app, options)
# conf.load
# assert_equal ["tcp://0.0.0.0:#{user_port}"], conf.options[:binds]
# end
# end
def test_default_port_loses_to_config_file
def test_port_wins_over_config
user_port = 5001
file_port = 6001
options = {}
Dir.mktmpdir do |d|
Dir.chdir(d) do
FileUtils.mkdir("config")
File.open("config/puma.rb", "w") { |f| f << "port #{6001}" }
File.open("config/puma.rb", "w") { |f| f << "port #{file_port}" }
conf = Rack::Handler::Puma.config(app, options)
@options[:Port] = user_port
conf = Rack::Handler::Puma.config(->{}, @options)
conf.load
assert_equal ["tcp://0.0.0.0:#{user_port}"], conf.options[:binds]
end
end
end
end
class TestUserSuppliedOptionsIsEmpty < Minitest::Test
def setup
@options = {}
@options[:user_supplied_options] = []
end
def test_config_file_wins_over_port
user_port = 5001
file_port = 6001
Dir.mktmpdir do |d|
Dir.chdir(d) do
FileUtils.mkdir("config")
File.open("config/puma.rb", "w") { |f| f << "port #{file_port}" }
@options[:Port] = user_port
conf = Rack::Handler::Puma.config(->{}, @options)
conf.load
assert_equal ["tcp://0.0.0.0:#{file_port}"], conf.options[:binds]
@ -91,3 +104,63 @@ class TestPathHandler < Minitest::Test
end
end
end
class TestUserSuppliedOptionsIsNotPresent < Minitest::Test
def setup
@options = {}
end
def test_default_port_when_no_config_file
options = {}
conf = Rack::Handler::Puma.config(->{}, options)
conf.load
assert_equal ["tcp://0.0.0.0:9292"], conf.options[:binds]
end
def test_config_wins_over_default
file_port = 6001
@options = {}
Dir.mktmpdir do |d|
Dir.chdir(d) do
FileUtils.mkdir("config")
File.open("config/puma.rb", "w") { |f| f << "port #{file_port}" }
conf = Rack::Handler::Puma.config(-> {}, @options)
conf.load
assert_equal ["tcp://0.0.0.0:#{file_port}"], conf.options[:binds]
end
end
end
def test_user_port_wins_over_default
user_port = 5001
@options[:Port] = user_port
conf = Rack::Handler::Puma.config(->{}, @options)
conf.load
assert_equal ["tcp://0.0.0.0:#{user_port}"], conf.options[:binds]
end
def test_user_port_wins_over_config
user_port = 5001
file_port = 6001
@options = {}
Dir.mktmpdir do |d|
Dir.chdir(d) do
FileUtils.mkdir("config")
File.open("config/puma.rb", "w") { |f| f << "port #{file_port}" }
@options[:Port] = user_port
conf = Rack::Handler::Puma.config(->{}, @options)
conf.load
assert_equal ["tcp://0.0.0.0:#{user_port}"], conf.options[:binds]
end
end
end
end