mirror of
https://github.com/puma/puma.git
synced 2022-11-09 13:48:40 -05:00
Refactor drain on shutdown (#2600)
* Refactor test_puma_server#server_run for option arguments * Refactor drain_on_shutdown into main server accept loop Also add test coverage for drain_on_shutdown option.
This commit is contained in:
parent
3e80f7c704
commit
ff17194228
3 changed files with 118 additions and 127 deletions
|
@ -5,22 +5,22 @@ module Puma
|
|||
# Add a simple implementation for earlier Ruby versions.
|
||||
#
|
||||
module QueueClose
|
||||
def initialize
|
||||
@closed = false
|
||||
super
|
||||
end
|
||||
def close
|
||||
num_waiting.times {push nil}
|
||||
@closed = true
|
||||
end
|
||||
def closed?
|
||||
@closed
|
||||
@closed ||= false
|
||||
end
|
||||
def push(object)
|
||||
@closed ||= false
|
||||
raise ClosedQueueError if @closed
|
||||
raise ClosedQueueError if closed?
|
||||
super
|
||||
end
|
||||
alias << push
|
||||
def pop(non_block=false)
|
||||
return nil if !non_block && closed? && empty?
|
||||
super
|
||||
end
|
||||
end
|
||||
::Queue.prepend QueueClose
|
||||
end
|
||||
|
|
|
@ -311,6 +311,7 @@ module Puma
|
|||
sockets = [check] + @binder.ios
|
||||
pool = @thread_pool
|
||||
queue_requests = @queue_requests
|
||||
drain = @options[:drain_on_shutdown] ? 0 : nil
|
||||
|
||||
remote_addr_value = nil
|
||||
remote_addr_header = nil
|
||||
|
@ -322,9 +323,10 @@ module Puma
|
|||
remote_addr_header = @options[:remote_address_header]
|
||||
end
|
||||
|
||||
while @status == :run
|
||||
while @status == :run || (drain && shutting_down?)
|
||||
begin
|
||||
ios = IO.select sockets
|
||||
ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil)
|
||||
break unless ios
|
||||
ios.first.each do |sock|
|
||||
if sock == check
|
||||
break if handle_check
|
||||
|
@ -337,6 +339,7 @@ module Puma
|
|||
rescue IO::WaitReadable
|
||||
next
|
||||
end
|
||||
drain += 1 if shutting_down?
|
||||
client = Client.new io, @binder.env(sock)
|
||||
if remote_addr_value
|
||||
client.peerip = remote_addr_value
|
||||
|
@ -351,6 +354,7 @@ module Puma
|
|||
end
|
||||
end
|
||||
|
||||
@events.debug "Drained #{drain} additional connections." if drain
|
||||
@events.fire :state, @status
|
||||
|
||||
if queue_requests
|
||||
|
@ -553,28 +557,6 @@ module Puma
|
|||
$stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
|
||||
end
|
||||
|
||||
if @options[:drain_on_shutdown]
|
||||
count = 0
|
||||
|
||||
while true
|
||||
ios = IO.select @binder.ios, nil, nil, 0
|
||||
break unless ios
|
||||
|
||||
ios.first.each do |sock|
|
||||
begin
|
||||
if io = sock.accept_nonblock
|
||||
count += 1
|
||||
client = Client.new io, @binder.env(sock)
|
||||
@thread_pool << client
|
||||
end
|
||||
rescue SystemCallError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@events.debug "Drained #{count} additional connections."
|
||||
end
|
||||
|
||||
if @status != :restart
|
||||
@binder.close
|
||||
end
|
||||
|
|
|
@ -22,10 +22,9 @@ class TestPumaServer < Minitest::Test
|
|||
@ios.each { |io| io.close if io && !io.closed? }
|
||||
end
|
||||
|
||||
def server_run(app: @app, early_hints: false)
|
||||
@server.app = app
|
||||
def server_run(**options, &block)
|
||||
@server = Puma::Server.new block || @app, @events, options
|
||||
@port = (@server.add_tcp_listener @host, 0).addr[1]
|
||||
@server.early_hints = true if early_hints
|
||||
@server.run
|
||||
sleep 0.15 if Puma.jruby?
|
||||
end
|
||||
|
@ -54,7 +53,7 @@ class TestPumaServer < Minitest::Test
|
|||
end
|
||||
|
||||
def test_normalize_host_header_missing
|
||||
server_run app: ->(env) do
|
||||
server_run do |env|
|
||||
[200, {}, [env["SERVER_NAME"], "\n", env["SERVER_PORT"]]]
|
||||
end
|
||||
|
||||
|
@ -63,7 +62,7 @@ class TestPumaServer < Minitest::Test
|
|||
end
|
||||
|
||||
def test_normalize_host_header_hostname
|
||||
server_run app: ->(env) do
|
||||
server_run do |env|
|
||||
[200, {}, [env["SERVER_NAME"], "\n", env["SERVER_PORT"]]]
|
||||
end
|
||||
|
||||
|
@ -75,7 +74,7 @@ class TestPumaServer < Minitest::Test
|
|||
end
|
||||
|
||||
def test_normalize_host_header_ipv4
|
||||
server_run app: ->(env) do
|
||||
server_run do |env|
|
||||
[200, {}, [env["SERVER_NAME"], "\n", env["SERVER_PORT"]]]
|
||||
end
|
||||
|
||||
|
@ -87,7 +86,7 @@ class TestPumaServer < Minitest::Test
|
|||
end
|
||||
|
||||
def test_normalize_host_header_ipv6
|
||||
server_run app: ->(env) do
|
||||
server_run do |env|
|
||||
[200, {}, [env["SERVER_NAME"], "\n", env["SERVER_PORT"]]]
|
||||
end
|
||||
|
||||
|
@ -104,7 +103,7 @@ class TestPumaServer < Minitest::Test
|
|||
def test_proper_stringio_body
|
||||
data = nil
|
||||
|
||||
server_run app: ->(env) do
|
||||
server_run do |env|
|
||||
data = env['rack.input'].read
|
||||
[200, {}, ["ok"]]
|
||||
end
|
||||
|
@ -123,7 +122,7 @@ class TestPumaServer < Minitest::Test
|
|||
|
||||
def test_puma_socket
|
||||
body = "HTTP/1.1 750 Upgraded to Awesome\r\nDone: Yep!\r\n"
|
||||
server_run app: ->(env) do
|
||||
server_run do |env|
|
||||
io = env['puma.socket']
|
||||
io.write body
|
||||
io.close
|
||||
|
@ -138,7 +137,7 @@ class TestPumaServer < Minitest::Test
|
|||
def test_very_large_return
|
||||
giant = "x" * 2056610
|
||||
|
||||
server_run app: ->(env) do
|
||||
server_run do
|
||||
[200, {}, [giant]]
|
||||
end
|
||||
|
||||
|
@ -179,7 +178,7 @@ class TestPumaServer < Minitest::Test
|
|||
end
|
||||
|
||||
def test_default_server_port
|
||||
server_run app: ->(env) do
|
||||
server_run do |env|
|
||||
[200, {}, [env['SERVER_PORT']]]
|
||||
end
|
||||
|
||||
|
@ -194,7 +193,7 @@ class TestPumaServer < Minitest::Test
|
|||
end
|
||||
|
||||
def test_default_server_port_respects_x_forwarded_proto
|
||||
server_run app: ->(env) do
|
||||
server_run do |env|
|
||||
[200, {}, [env['SERVER_PORT']]]
|
||||
end
|
||||
|
||||
|
@ -210,7 +209,7 @@ class TestPumaServer < Minitest::Test
|
|||
end
|
||||
|
||||
def test_HEAD_has_no_body
|
||||
server_run app: ->(env) { [200, {"Foo" => "Bar"}, ["hello"]] }
|
||||
server_run { [200, {"Foo" => "Bar"}, ["hello"]] }
|
||||
|
||||
data = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n"
|
||||
|
||||
|
@ -218,7 +217,7 @@ class TestPumaServer < Minitest::Test
|
|||
end
|
||||
|
||||
def test_GET_with_empty_body_has_sane_chunking
|
||||
server_run app: ->(env) { [200, {}, [""]] }
|
||||
server_run { [200, {}, [""]] }
|
||||
|
||||
data = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n"
|
||||
|
||||
|
@ -226,7 +225,7 @@ class TestPumaServer < Minitest::Test
|
|||
end
|
||||
|
||||
def test_early_hints_works
|
||||
server_run early_hints: true, app: ->(env) do
|
||||
server_run(early_hints: true) do |env|
|
||||
env['rack.early_hints'].call("Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload")
|
||||
[200, { "X-Hello" => "World" }, ["Hello world!"]]
|
||||
end
|
||||
|
@ -250,15 +249,15 @@ EOF
|
|||
|
||||
def test_early_hints_are_ignored_if_connection_lost
|
||||
|
||||
def @server.fast_write(*args)
|
||||
raise Puma::ConnectionError
|
||||
end
|
||||
|
||||
server_run early_hints: true, app: ->(env) do
|
||||
server_run(early_hints: true) do |env|
|
||||
env['rack.early_hints'].call("Link" => "</script.js>; rel=preload")
|
||||
[200, { "X-Hello" => "World" }, ["Hello world!"]]
|
||||
end
|
||||
|
||||
def @server.fast_write(*args)
|
||||
raise Puma::ConnectionError
|
||||
end
|
||||
|
||||
# This request will cause the server to try and send early hints
|
||||
_ = send_http "HEAD / HTTP/1.0\r\n\r\n"
|
||||
|
||||
|
@ -270,7 +269,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_early_hints_is_off_by_default
|
||||
server_run app: ->(env) do
|
||||
server_run do |env|
|
||||
assert_nil env['rack.early_hints']
|
||||
[200, { "X-Hello" => "World" }, ["Hello world!"]]
|
||||
end
|
||||
|
@ -289,7 +288,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_GET_with_no_body_has_sane_chunking
|
||||
server_run app: ->(env) { [200, {}, []] }
|
||||
server_run { [200, {}, []] }
|
||||
|
||||
data = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n"
|
||||
|
||||
|
@ -297,8 +296,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_doesnt_print_backtrace_in_production
|
||||
@server.leak_stack_on_error = false
|
||||
server_run app: ->(env) { raise "don't leak me bro" }
|
||||
server_run(environment: :production) { raise "don't leak me bro" }
|
||||
|
||||
data = send_http_and_read "GET / HTTP/1.0\r\n\r\n"
|
||||
|
||||
|
@ -319,9 +317,7 @@ EOF
|
|||
|
||||
def test_force_shutdown_custom_error_message
|
||||
handler = lambda {|err, env, status| [500, {"Content-Type" => "application/json"}, ["{}\n"]]}
|
||||
@server = Puma::Server.new @app, @events, {:lowlevel_error_handler => handler, :force_shutdown_after => 2}
|
||||
|
||||
server_run app: ->(env) do
|
||||
server_run(lowlevel_error_handler: handler, force_shutdown_after: 2) do
|
||||
@server.stop
|
||||
sleep 5
|
||||
end
|
||||
|
@ -337,7 +333,7 @@ EOF
|
|||
skip_if :windows
|
||||
@server = Puma::Server.new @app, @events, {:force_shutdown_after => 2}
|
||||
|
||||
server_run app: ->(env) do
|
||||
server_run do
|
||||
require 'json'
|
||||
|
||||
# will raise fatal: machine stack overflow in critical region
|
||||
|
@ -353,9 +349,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_force_shutdown_error_default
|
||||
@server = Puma::Server.new @app, @events, {:force_shutdown_after => 2}
|
||||
|
||||
server_run app: ->(env) do
|
||||
server_run(force_shutdown_after: 2) do
|
||||
@server.stop
|
||||
sleep 5
|
||||
end
|
||||
|
@ -368,9 +362,7 @@ EOF
|
|||
|
||||
def test_prints_custom_error
|
||||
re = lambda { |err| [302, {'Content-Type' => 'text', 'Location' => 'foo.html'}, ['302 found']] }
|
||||
@server = Puma::Server.new @app, @events, {:lowlevel_error_handler => re}
|
||||
|
||||
server_run app: ->(env) { raise "don't leak me bro" }
|
||||
server_run(lowlevel_error_handler: re) { raise "don't leak me bro" }
|
||||
|
||||
data = send_http_and_read "GET / HTTP/1.0\r\n\r\n"
|
||||
|
||||
|
@ -383,9 +375,7 @@ EOF
|
|||
[302, {'Content-Type' => 'text', 'Location' => 'foo.html'}, ['302 found']]
|
||||
}
|
||||
|
||||
@server = Puma::Server.new @app, @events, {:lowlevel_error_handler => re}
|
||||
|
||||
server_run app: ->(env) { raise "don't leak me bro" }
|
||||
server_run(lowlevel_error_handler: re) { raise "don't leak me bro" }
|
||||
|
||||
data = send_http_and_read "GET / HTTP/1.0\r\n\r\n"
|
||||
|
||||
|
@ -398,9 +388,7 @@ EOF
|
|||
[302, {'Content-Type' => 'text', 'Location' => 'foo.html'}, ['302 found']]
|
||||
}
|
||||
|
||||
@server = Puma::Server.new @app, @events, {:lowlevel_error_handler => re}
|
||||
|
||||
server_run app: ->(env) { raise "don't leak me bro" }
|
||||
server_run(lowlevel_error_handler: re) { raise "don't leak me bro" }
|
||||
|
||||
data = send_http_and_read "GET / HTTP/1.0\r\n\r\n"
|
||||
|
||||
|
@ -408,7 +396,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_custom_http_codes_10
|
||||
server_run app: ->(env) { [449, {}, [""]] }
|
||||
server_run { [449, {}, [""]] }
|
||||
|
||||
data = send_http_and_read "GET / HTTP/1.0\r\n\r\n"
|
||||
|
||||
|
@ -416,7 +404,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_custom_http_codes_11
|
||||
server_run app: ->(env) { [449, {}, [""]] }
|
||||
server_run { [449, {}, [""]] }
|
||||
|
||||
data = send_http_and_read "GET / HTTP/1.1\r\nConnection: close\r\n\r\n"
|
||||
|
||||
|
@ -424,7 +412,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_HEAD_returns_content_headers
|
||||
server_run app: ->(env) { [200, {"Content-Type" => "application/pdf",
|
||||
server_run { [200, {"Content-Type" => "application/pdf",
|
||||
"Content-Length" => "4242"}, []] }
|
||||
|
||||
data = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n"
|
||||
|
@ -438,7 +426,7 @@ EOF
|
|||
|
||||
@events.register(:state) { |s| states << s }
|
||||
|
||||
server_run app: ->(env) { [200, {}, [""]] }
|
||||
server_run { [200, {}, [""]] }
|
||||
|
||||
_ = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n"
|
||||
|
||||
|
@ -449,9 +437,8 @@ EOF
|
|||
assert_equal [:booting, :running, :stop, :done], states
|
||||
end
|
||||
|
||||
def test_timeout_in_data_phase
|
||||
@server.first_data_timeout = 1
|
||||
server_run
|
||||
def test_timeout_in_data_phase(**options)
|
||||
server_run(first_data_timeout: 1, **options)
|
||||
|
||||
sock = send_http "POST / HTTP/1.1\r\nHost: test.com\r\nContent-Type: text/plain\r\nContent-Length: 5\r\n\r\n"
|
||||
|
||||
|
@ -463,8 +450,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_timeout_data_no_queue
|
||||
@server = Puma::Server.new @app, @events, queue_requests: false
|
||||
test_timeout_in_data_phase
|
||||
test_timeout_in_data_phase(queue_requests: false)
|
||||
end
|
||||
|
||||
# https://github.com/puma/puma/issues/2574
|
||||
|
@ -492,7 +478,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_http_11_keep_alive_with_body
|
||||
server_run app: ->(env) { [200, {"Content-Type" => "plain/text"}, ["hello\n"]] }
|
||||
server_run { [200, {"Content-Type" => "plain/text"}, ["hello\n"]] }
|
||||
|
||||
sock = send_http "GET / HTTP/1.1\r\nConnection: Keep-Alive\r\n\r\n"
|
||||
|
||||
|
@ -507,7 +493,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_http_11_close_with_body
|
||||
server_run app: ->(env) { [200, {"Content-Type" => "plain/text"}, ["hello"]] }
|
||||
server_run { [200, {"Content-Type" => "plain/text"}, ["hello"]] }
|
||||
|
||||
data = send_http_and_read "GET / HTTP/1.1\r\nConnection: close\r\n\r\n"
|
||||
|
||||
|
@ -515,7 +501,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_http_11_keep_alive_without_body
|
||||
server_run app: ->(env) { [204, {}, []] }
|
||||
server_run { [204, {}, []] }
|
||||
|
||||
sock = send_http "GET / HTTP/1.1\r\nConnection: Keep-Alive\r\n\r\n"
|
||||
|
||||
|
@ -525,7 +511,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_http_11_close_without_body
|
||||
server_run app: ->(env) { [204, {}, []] }
|
||||
server_run { [204, {}, []] }
|
||||
|
||||
sock = send_http "GET / HTTP/1.1\r\nConnection: close\r\n\r\n"
|
||||
|
||||
|
@ -535,7 +521,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_http_10_keep_alive_with_body
|
||||
server_run app: ->(env) { [200, {"Content-Type" => "plain/text"}, ["hello\n"]] }
|
||||
server_run { [200, {"Content-Type" => "plain/text"}, ["hello\n"]] }
|
||||
|
||||
sock = send_http "GET / HTTP/1.0\r\nConnection: Keep-Alive\r\n\r\n"
|
||||
|
||||
|
@ -548,7 +534,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_http_10_close_with_body
|
||||
server_run app: ->(env) { [200, {"Content-Type" => "plain/text"}, ["hello"]] }
|
||||
server_run { [200, {"Content-Type" => "plain/text"}, ["hello"]] }
|
||||
|
||||
data = send_http_and_read "GET / HTTP/1.0\r\nConnection: close\r\n\r\n"
|
||||
|
||||
|
@ -558,7 +544,7 @@ EOF
|
|||
def test_http_10_partial_hijack_with_content_length
|
||||
body_parts = ['abc', 'de']
|
||||
|
||||
server_run app: ->(env) do
|
||||
server_run do
|
||||
hijack_lambda = proc do | io |
|
||||
io.write(body_parts[0])
|
||||
io.write(body_parts[1])
|
||||
|
@ -573,7 +559,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_http_10_keep_alive_without_body
|
||||
server_run app: ->(env) { [204, {}, []] }
|
||||
server_run { [204, {}, []] }
|
||||
|
||||
sock = send_http "GET / HTTP/1.0\r\nConnection: Keep-Alive\r\n\r\n"
|
||||
|
||||
|
@ -583,7 +569,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_http_10_close_without_body
|
||||
server_run app: ->(env) { [204, {}, []] }
|
||||
server_run { [204, {}, []] }
|
||||
|
||||
data = send_http_and_read "GET / HTTP/1.0\r\nConnection: close\r\n\r\n"
|
||||
|
||||
|
@ -591,7 +577,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_Expect_100
|
||||
server_run app: ->(env) { [200, {}, [""]] }
|
||||
server_run { [200, {}, [""]] }
|
||||
|
||||
data = send_http_and_read "GET / HTTP/1.1\r\nConnection: close\r\nExpect: 100-continue\r\n\r\n"
|
||||
|
||||
|
@ -601,7 +587,7 @@ EOF
|
|||
def test_chunked_request
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
server_run { |env|
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
|
@ -617,7 +603,7 @@ EOF
|
|||
def test_large_chunked_request
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
server_run { |env|
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
|
@ -647,7 +633,7 @@ EOF
|
|||
def test_chunked_request_pause_before_value
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
server_run { |env|
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
|
@ -668,7 +654,7 @@ EOF
|
|||
def test_chunked_request_pause_between_chunks
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
server_run { |env|
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
|
@ -689,7 +675,7 @@ EOF
|
|||
def test_chunked_request_pause_mid_count
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
server_run { |env|
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
|
@ -710,7 +696,7 @@ EOF
|
|||
def test_chunked_request_pause_before_count_newline
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
server_run { |env|
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
|
@ -731,7 +717,7 @@ EOF
|
|||
def test_chunked_request_pause_mid_value
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
server_run { |env|
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
|
@ -752,7 +738,7 @@ EOF
|
|||
def test_chunked_request_pause_between_cr_lf_after_size_of_second_chunk
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
server_run { |env|
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
|
@ -782,7 +768,7 @@ EOF
|
|||
def test_chunked_request_pause_between_closing_cr_lf
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
server_run { |env|
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
|
@ -804,7 +790,7 @@ EOF
|
|||
def test_chunked_request_pause_before_closing_cr_lf
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
server_run { |env|
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
|
@ -826,7 +812,7 @@ EOF
|
|||
def test_chunked_request_header_case
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
server_run { |env|
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
|
@ -842,7 +828,7 @@ EOF
|
|||
def test_chunked_keep_alive
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
server_run { |env|
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
|
@ -862,7 +848,7 @@ EOF
|
|||
def test_chunked_keep_alive_two_back_to_back
|
||||
body = nil
|
||||
content_length = nil
|
||||
server_run app: ->(env) {
|
||||
server_run { |env|
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
[200, {}, [""]]
|
||||
|
@ -903,8 +889,7 @@ EOF
|
|||
body = nil
|
||||
content_length = nil
|
||||
remote_addr =nil
|
||||
@server = Puma::Server.new @app, @events, { remote_address: :header, remote_address_header: 'HTTP_X_FORWARDED_FOR'}
|
||||
server_run app: ->(env) {
|
||||
server_run(remote_address: :header, remote_address_header: 'HTTP_X_FORWARDED_FOR') { |env|
|
||||
body = env['rack.input'].read
|
||||
content_length = env['CONTENT_LENGTH']
|
||||
remote_addr = env['REMOTE_ADDR']
|
||||
|
@ -936,7 +921,7 @@ EOF
|
|||
enc = Encoding::UTF_16LE
|
||||
str = "──иї_テスト──\n".encode enc
|
||||
|
||||
server_run app: ->(env) {
|
||||
server_run {
|
||||
hdrs = {}
|
||||
hdrs['Content-Type'] = "text; charset=#{enc.to_s.downcase}"
|
||||
|
||||
|
@ -958,7 +943,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_empty_header_values
|
||||
server_run app: ->(env) { [200, {"X-Empty-Header" => ""}, []] }
|
||||
server_run { [200, {"X-Empty-Header" => ""}, []] }
|
||||
|
||||
data = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n"
|
||||
|
||||
|
@ -967,7 +952,7 @@ EOF
|
|||
|
||||
def test_request_body_wait
|
||||
request_body_wait = nil
|
||||
server_run app: ->(env) {
|
||||
server_run { |env|
|
||||
request_body_wait = env['puma.request_body_wait']
|
||||
[204, {}, []]
|
||||
}
|
||||
|
@ -985,7 +970,7 @@ EOF
|
|||
|
||||
def test_request_body_wait_chunked
|
||||
request_body_wait = nil
|
||||
server_run app: ->(env) {
|
||||
server_run { |env|
|
||||
request_body_wait = env['puma.request_body_wait']
|
||||
[204, {}, []]
|
||||
}
|
||||
|
@ -1001,8 +986,8 @@ EOF
|
|||
assert_operator request_body_wait, :>=, 900
|
||||
end
|
||||
|
||||
def test_open_connection_wait
|
||||
server_run app: ->(_) { [200, {}, ["Hello"]] }
|
||||
def test_open_connection_wait(**options)
|
||||
server_run(**options) { [200, {}, ["Hello"]] }
|
||||
s = send_http nil
|
||||
sleep 0.1
|
||||
s << "GET / HTTP/1.0\r\n\r\n"
|
||||
|
@ -1010,13 +995,12 @@ EOF
|
|||
end
|
||||
|
||||
def test_open_connection_wait_no_queue
|
||||
@server = Puma::Server.new @app, @events, queue_requests: false
|
||||
test_open_connection_wait
|
||||
test_open_connection_wait(queue_requests: false)
|
||||
end
|
||||
|
||||
# Rack may pass a newline in a header expecting us to split it.
|
||||
def test_newline_splits
|
||||
server_run app: ->(_) { [200, {'X-header' => "first line\nsecond line"}, ["Hello"]] }
|
||||
server_run { [200, {'X-header' => "first line\nsecond line"}, ["Hello"]] }
|
||||
|
||||
data = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n"
|
||||
|
||||
|
@ -1024,7 +1008,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_newline_splits_in_early_hint
|
||||
server_run early_hints: true, app: ->(env) do
|
||||
server_run(early_hints: true) do |env|
|
||||
env['rack.early_hints'].call({'X-header' => "first line\nsecond line"})
|
||||
[200, {}, ["Hello world!"]]
|
||||
end
|
||||
|
@ -1037,7 +1021,7 @@ EOF
|
|||
# To comply with the Rack spec, we have to split header field values
|
||||
# containing newlines into multiple headers.
|
||||
def assert_does_not_allow_http_injection(app, opts = {})
|
||||
server_run(early_hints: opts[:early_hints], app: app)
|
||||
server_run(early_hints: opts[:early_hints], &app)
|
||||
|
||||
data = send_http_and_read "HEAD / HTTP/1.0\r\n\r\n"
|
||||
|
||||
|
@ -1080,10 +1064,9 @@ EOF
|
|||
|
||||
# Perform a server shutdown while requests are pending (one in app-server response, one still sending client request).
|
||||
def shutdown_requests(s1_complete: true, s1_response: nil, post: false, s2_response: nil, **options)
|
||||
@server = Puma::Server.new @app, @events, options
|
||||
mutex = Mutex.new
|
||||
app_finished = ConditionVariable.new
|
||||
server_run app: ->(env) {
|
||||
server_run(**options) { |env|
|
||||
path = env['REQUEST_PATH']
|
||||
mutex.synchronize do
|
||||
app_finished.signal
|
||||
|
@ -1157,7 +1140,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_http11_connection_header_queue
|
||||
server_run app: ->(_) { [200, {}, [""]] }
|
||||
server_run { [200, {}, [""]] }
|
||||
|
||||
sock = send_http "GET / HTTP/1.1\r\n\r\n"
|
||||
assert_equal ["HTTP/1.1 200 OK", "Content-Length: 0"], header(sock)
|
||||
|
@ -1169,7 +1152,7 @@ EOF
|
|||
end
|
||||
|
||||
def test_http10_connection_header_queue
|
||||
server_run app: ->(_) { [200, {}, [""]] }
|
||||
server_run { [200, {}, [""]] }
|
||||
|
||||
sock = send_http "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n"
|
||||
assert_equal ["HTTP/1.0 200 OK", "Connection: Keep-Alive", "Content-Length: 0"], header(sock)
|
||||
|
@ -1180,16 +1163,14 @@ EOF
|
|||
end
|
||||
|
||||
def test_http11_connection_header_no_queue
|
||||
@server = Puma::Server.new @app, @events, queue_requests: false
|
||||
server_run app: ->(_) { [200, {}, [""]] }
|
||||
server_run(queue_requests: false) { [200, {}, [""]] }
|
||||
sock = send_http "GET / HTTP/1.1\r\n\r\n"
|
||||
assert_equal ["HTTP/1.1 200 OK", "Connection: close", "Content-Length: 0"], header(sock)
|
||||
sock.close
|
||||
end
|
||||
|
||||
def test_http10_connection_header_no_queue
|
||||
@server = Puma::Server.new @app, @events, queue_requests: false
|
||||
server_run app: ->(_) { [200, {}, [""]] }
|
||||
server_run(queue_requests: false) { [200, {}, [""]] }
|
||||
sock = send_http "GET / HTTP/1.0\r\n\r\n"
|
||||
assert_equal ["HTTP/1.0 200 OK", "Content-Length: 0"], header(sock)
|
||||
sock.close
|
||||
|
@ -1233,9 +1214,7 @@ EOF
|
|||
[500, {"Content-Type" => "application/json"}, ["{}\n"]]
|
||||
}
|
||||
|
||||
@server = Puma::Server.new @app, @events, {:lowlevel_error_handler => handler}
|
||||
|
||||
server_run app: ->(env) { [200, {}, ['Hello World']] }
|
||||
server_run(lowlevel_error_handler: handler) { [200, {}, ['Hello World']] }
|
||||
|
||||
# valid req & read, close
|
||||
sock = TCPSocket.new @host, @port
|
||||
|
@ -1303,4 +1282,34 @@ EOF
|
|||
|
||||
assert_equal selector.backend, backend
|
||||
end
|
||||
|
||||
def test_drain_on_shutdown(drain=true)
|
||||
num_connections = 10
|
||||
|
||||
wait = Queue.new
|
||||
server_run(drain_on_shutdown: drain, max_threads: 1) do
|
||||
wait.pop
|
||||
[200, {}, ["DONE"]]
|
||||
end
|
||||
connections = Array.new(num_connections) {send_http "GET / HTTP/1.0\r\n\r\n"}
|
||||
@server.stop
|
||||
wait.close
|
||||
bad = 0
|
||||
connections.each do |s|
|
||||
begin
|
||||
assert_match 'DONE', s.read
|
||||
rescue Errno::ECONNRESET
|
||||
bad += 1
|
||||
end
|
||||
end
|
||||
if drain
|
||||
assert_equal 0, bad
|
||||
else
|
||||
refute_equal 0, bad
|
||||
end
|
||||
end
|
||||
|
||||
def test_not_drain_on_shutdown
|
||||
test_drain_on_shutdown false
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue