Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ gem "minitest-proveit"
gem "minitest-stub-const"

gem "rack", (ENV['PUMA_CI_RACK_2'] ? "~> 2.2" : ">= 2.2")
gem "rackup" unless ENV['PUMA_CI_RACK_2']

gem "jruby-openssl", :platform => "jruby"

Expand Down
21 changes: 18 additions & 3 deletions lib/puma/rack_default.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,23 @@

require_relative '../rack/handler/puma'

module Rack::Handler
def self.default(options = {})
Rack::Handler::Puma
# rackup was removed in Rack 3, it is now a separate gem
if Object.const_defined? :Rackup
module Rackup
module Handler
def self.default(options = {})
::Rackup::Handler::Puma
end
end
end
elsif Object.const_defined?(:Rack) && Rack::RELEASE < '3'
module Rack
module Handler
def self.default(options = {})
::Rack::Handler::Puma
end
end
end
else
raise "Rack 3 must be used with the Rackup gem"
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message should be more actionable and consistent with the error message in lib/rack/handler/puma.rb. Consider changing to: 'Rack 3 must be used with the rackup gem. Run: gem install rackup'

Suggested change
raise "Rack 3 must be used with the Rackup gem"
raise "Rack 3 must be used with the rackup gem. Run: gem install rackup"

Copilot uses AI. Check for mistakes.
end
210 changes: 116 additions & 94 deletions lib/rack/handler/puma.rb
Original file line number Diff line number Diff line change
@@ -1,114 +1,136 @@
# frozen_string_literal: true

require 'rack/handler'

module Rack
module Handler
module Puma
DEFAULT_OPTIONS = {
:Verbose => false,
:Silent => false
}

def self.config(app, options = {})
require_relative '../../puma'
require_relative '../../puma/configuration'
require_relative '../../puma/log_writer'
require_relative '../../puma/launcher'

default_options = DEFAULT_OPTIONS.dup

# 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|
default_options[k] = options.delete(k)
end
# This module is used as an 'include' file in code at bottom of file
module Puma
module RackHandler
DEFAULT_OPTIONS = {
:Verbose => false,
:Silent => false
}

def config(app, options = {})
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Methods in the Puma::RackHandler module should be defined with self. prefix or use module_function to ensure they work correctly when included via class << self. Currently these are instance methods, but they're being included into singleton classes and called as class methods. Consider using module_function :config, :run, :valid_options, :set_host_port_to_config after the method definitions to make them work properly as both instance and module methods.

Copilot uses AI. Check for mistakes.
require_relative '../../puma'
require_relative '../../puma/configuration'
require_relative '../../puma/log_writer'
require_relative '../../puma/launcher'

default_options = DEFAULT_OPTIONS.dup

# 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|
default_options[k] = options.delete(k)
end
end

conf = ::Puma::Configuration.new(options, default_options) do |user_config, file_config, default_config|
if options.delete(:Verbose)
require 'rack/common_logger'
app = Rack::CommonLogger.new(app, STDOUT)
end

if options[:environment]
user_config.environment options[:environment]
end

if options[:Threads]
min, max = options.delete(:Threads).split(':', 2)
user_config.threads min, max
end

if options[:Host] || options[:Port]
host = options[:Host] || default_options[:Host]
port = options[:Port] || default_options[:Port]
self.set_host_port_to_config(host, port, user_config)
end

if default_options[:Host]
file_config.set_default_host(default_options[:Host])
end
self.set_host_port_to_config(default_options[:Host], default_options[:Port], default_config)

user_config.app app
conf = ::Puma::Configuration.new(options, default_options) do |user_config, file_config, default_config|
if options.delete(:Verbose)
require 'rack/common_logger'
app = Rack::CommonLogger.new(app, STDOUT)
end
conf
end

def self.run(app, **options)
conf = self.config(app, options)
if options[:environment]
user_config.environment options[:environment]
end

log_writer = options.delete(:Silent) ? ::Puma::LogWriter.strings : ::Puma::LogWriter.stdio
if options[:Threads]
min, max = options.delete(:Threads).split(':', 2)
user_config.threads min, max
end

launcher = ::Puma::Launcher.new(conf, :log_writer => log_writer)
if options[:Host] || options[:Port]
host = options[:Host] || default_options[:Host]
port = options[:Port] || default_options[:Port]
self.set_host_port_to_config(host, port, user_config)
end

yield launcher if block_given?
begin
launcher.run
rescue Interrupt
puts "* Gracefully stopping, waiting for requests to finish"
launcher.stop
puts "* Goodbye!"
if default_options[:Host]
file_config.set_default_host(default_options[:Host])
end
self.set_host_port_to_config(default_options[:Host], default_options[:Port], default_config)

user_config.app app
end
conf
end

def run(app, **options)
conf = self.config(app, options)

log_writer = options.delete(:Silent) ? ::Puma::LogWriter.strings : ::Puma::LogWriter.stdio

launcher = ::Puma::Launcher.new(conf, :log_writer => log_writer)

def self.valid_options
{
"Host=HOST" => "Hostname to listen on (default: localhost)",
"Port=PORT" => "Port to listen on (default: 8080)",
"Threads=MIN:MAX" => "min:max threads to use (default 0:16)",
"Verbose" => "Don't report each request (default: false)"
}
yield launcher if block_given?
begin
launcher.run
rescue Interrupt
puts "* Gracefully stopping, waiting for requests to finish"
launcher.stop
puts "* Goodbye!"
end
end

def self.set_host_port_to_config(host, port, config)
config.clear_binds! if host || port

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::DEFAULTS[:tcp_port]
config.bind uri.to_s
else

if host
port ||= ::Puma::Configuration::DEFAULTS[:tcp_port]
end

if port
host ||= ::Puma::Configuration::DEFAULTS[:tcp_host]
config.port port, host
end
def valid_options
{
"Host=HOST" => "Hostname to listen on (default: localhost)",
"Port=PORT" => "Port to listen on (default: 8080)",
"Threads=MIN:MAX" => "min:max threads to use (default 0:16)",
"Verbose" => "Don't report each request (default: false)"
}
end

def set_host_port_to_config(host, port, config)
config.clear_binds! if host || port

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::DEFAULTS[:tcp_port]
config.bind uri.to_s
else

if host
port ||= ::Puma::Configuration::DEFAULTS[:tcp_port]
end

if port
host ||= ::Puma::Configuration::DEFAULTS[:tcp_host]
config.port port, host
end
end
end
end
end

register :puma, Puma
# rackup was removed in Rack 3, it is now a separate gem
if Object.const_defined? :Rackup
module Rackup
module Handler
module Puma
class << self
include ::Puma::RackHandler
end
end
register :puma, Puma
end
end
elsif Object.const_defined?(:Rack) && Rack::RELEASE < '3'
module Rack
module Handler
module Puma
class << self
include ::Puma::RackHandler
end
end
register :puma, Puma
end
end
else
raise "You must install the rackup gem when using Rack 3"
Copy link

Copilot AI Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message should be more actionable by specifying how to install the rackup gem. Consider changing to: 'You must install the rackup gem when using Rack 3. Run: gem install rackup'

Suggested change
raise "You must install the rackup gem when using Rack 3"
raise "You must install the rackup gem when using Rack 3. Run: gem install rackup"

Copilot uses AI. Check for mistakes.
end
Loading