puma: Puma 6.2 does not respond correctly when Rails app responds with empty body
Describe the bug
In one of my Rails 6.1 apps, I have code that boils down to:
class ThingsController < ApplicationController
def show
head :not_found
end
end
With Puma 6.1.1 I get the following response:
curl
$ curl -i localhost:3000/thing
HTTP/1.1 404 Not Found
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Content-Type: text/html
Cache-Control: no-cache
X-Request-Id: 15463eac-5912-4c17-8044-5d884fde576b
X-Runtime: 0.003416
Transfer-Encoding: chunked
Rails log
=> Booting Puma
=> Rails 6.1.7.3 application starting in development
=> Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 6.1.1 (ruby 3.0.5-p211) ("The Way Up")
* Min threads: 5
* Max threads: 5
* Environment: development
* PID: 104668
* Listening on http://[::]:3000
Use Ctrl-C to stop
Started GET "/thing" for ::1 at 2023-03-30 16:39:55 +0200
Processing by ThingsController#show as */*
Completed 404 Not Found in 0ms (Allocations: 160)
After upgrading to Puma 6.2, this is the result:
curl
$ curl -i localhost:3000/thing
curl: (1) Received HTTP/0.9 when not allowed
Rails log
=> Booting Puma
=> Rails 6.1.7.3 application starting in development
=> Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 6.2.0 (ruby 3.0.5-p211) ("Speaking of Now")
* Min threads: 5
* Max threads: 5
* Environment: development
* PID: 105699
* Listening on http://[::]:3000
Use Ctrl-C to stop
Started GET "/thing" for ::1 at 2023-03-30 16:40:18 +0200
Processing by ThingsController#show as */*
Completed 404 Not Found in 0ms (Allocations: 231)
When pointing Firefox to http://localhost:3000/thing, the browser hangs for 20s, then prints a 0.
I’ve captured the network traffic: puma.pcap.gz
Puma config:
config/puma.rb
This is the unaltered config file generated by rails new
:
# Puma can serve each request in a thread from an internal thread pool.
# The `threads` method setting takes two numbers: a minimum and maximum.
# Any libraries that use thread pools should be configured to match
# the maximum value specified for Puma. Default is set to 5 threads for minimum
# and maximum; this matches the default thread size of Active Record.
#
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count
# Specifies the `worker_timeout` threshold that Puma will use to wait before
# terminating a worker in development environments.
#
worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
#
port ENV.fetch("PORT") { 3000 }
# Specifies the `environment` that Puma will run in.
#
environment ENV.fetch("RAILS_ENV") { "development" }
# Specifies the `pidfile` that Puma will use.
pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
# Specifies the number of `workers` to boot in clustered mode.
# Workers are forked web server processes. If using threads and workers together
# the concurrency of the application would be max `threads` * `workers`.
# Workers do not work on JRuby or Windows (both of which do not support
# processes).
#
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }
# Use the `preload_app!` method when specifying a `workers` number.
# This directive tells Puma to first boot the application and load code
# before forking the application. This takes advantage of Copy On Write
# process behavior so workers use less memory.
#
# preload_app!
# Allow puma to be restarted by `rails restart` command.
plugin :tmp_restart
The Rails app is started with bundle exec rails server -b '::'
.
To Reproduce
I’ve prepared a repro-repo. You’ll need Ruby 3.0.5:
$ git clone https://github.com/dmke/puma-repro
$ cd puma-repro
$ git checkout puma-6.1.1
$ bundle install
$ bundle exec rails server -b '::'
To switch to Puma 6.2:
$ git checkout puma-6.2
$ bundle install
$ bundle exec rails server -b '::'
Expected behavior
head(:not_found)
should work as expected.
Desktop (please complete the following information):
- OS: Debian Linux (11/bullseye)
- Puma Version: 6.2
About this issue
- Original URL
- State: closed
- Created a year ago
- Reactions: 8
- Comments: 20 (10 by maintainers)
I’m also experiencing this. I’ve narrowed it down to any response (regardless of status code) that returns an empty body. You can work around it by doing something like
render(plain: "Not found", status: :not_found)
, butrender(plain: "", status: :not_found)
will trigger the issue in Puma. Reverting to 6.1.1 fixes it.@collinsauve
If you’d like to, but I’ve already got a test for it, or at least an app response for it. I’ll post in your PR.
#3113 seems to fix this issue.