diff --git a/lib/puma/server.rb b/lib/puma/server.rb index 87da8aa45a..807213a008 100644 --- a/lib/puma/server.rb +++ b/lib/puma/server.rb @@ -476,7 +476,7 @@ def process_client(client) end true rescue StandardError => e - client_error(e, client) + client_error(e, client, requests) # The ensure tries to close +client+ down requests > 0 ensure @@ -504,22 +504,22 @@ def with_force_shutdown(client, &block) # :nocov: # Handle various error types thrown by Client I/O operations. - def client_error(e, client) + def client_error(e, client, requests = 1) # Swallow, do not log return if [ConnectionError, EOFError].include?(e.class) - lowlevel_error(e, client.env) case e when MiniSSL::SSLError + lowlevel_error(e, client.env) @log_writer.ssl_error e, client.io when HttpParserError - client.write_error(400) + response_to_error(client, requests, e, 400) @log_writer.parse_error e, client when HttpParserError501 - client.write_error(501) + response_to_error(client, requests, e, 501) @log_writer.parse_error e, client else - client.write_error(500) + response_to_error(client, requests, e, 500) @log_writer.unknown_error e, nil, "Read" end end @@ -541,10 +541,17 @@ def lowlevel_error(e, env, status=500) backtrace = e.backtrace.nil? ? '' : e.backtrace.join("\n") [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]] else - [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]] + [status, {}, [""]] end end + def response_to_error(client, requests, err, status_code) + status, headers, res_body = lowlevel_error(err, client.env, status_code) + prepare_response(status, headers, res_body, requests, client) + client.write_error(status_code) + end + private :response_to_error + # Wait for all outstanding requests to finish. # def graceful_shutdown diff --git a/test/test_puma_server.rb b/test/test_puma_server.rb index 297d44ab28..fd6cbc800a 100644 --- a/test/test_puma_server.rb +++ b/test/test_puma_server.rb @@ -1695,4 +1695,19 @@ def spawn_cmd(env = {}, cmd) [out_w, err_w].each(&:close) [out_r, err_r, pid] end + + def test_lowlevel_error_handler_response + options = { + lowlevel_error_handler: ->(_error) do + [500, {}, ["something wrong happened"]] + end + } + broken_app = ->(_env) { [200, nil, []] } + + server_run(**options, &broken_app) + + data = send_http_and_read "GET / HTTP/1.1\r\n\r\n" + + assert_match(/something wrong happened/, data) + end end diff --git a/test/test_response_header.rb b/test/test_response_header.rb index f3615394c7..2f33c846c6 100644 --- a/test/test_response_header.rb +++ b/test/test_response_header.rb @@ -49,7 +49,7 @@ def test_integer_key server_run app: ->(env) { [200, { 1 => 'Boo'}, []] } data = send_http_and_read "GET / HTTP/1.0\r\n\r\n" - assert_match(/HTTP\/1.1 500 Internal Server Error/, data) + assert_match(/Puma caught this error/, data) end # The header must respond to each @@ -57,7 +57,7 @@ def test_nil_header server_run app: ->(env) { [200, nil, []] } data = send_http_and_read "GET / HTTP/1.0\r\n\r\n" - assert_match(/HTTP\/1.1 500 Internal Server Error/, data) + assert_match(/Puma caught this error/, data) end # The values of the header must be Strings