Skip to content

Commit 2c12146

Browse files
Merge pull request #178 from composerinteralia/raise-after-response-written
Avoid double response on post-response exception
2 parents 18869d2 + ef32635 commit 2c12146

File tree

2 files changed

+27
-3
lines changed

2 files changed

+27
-3
lines changed

lib/pitchfork/http_server.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -803,7 +803,7 @@ def restart_outdated_workers
803803
# assuming we haven't closed the socket, but don't get hung up
804804
# if the socket is already closed or broken. We'll always ensure
805805
# the socket is closed at the end of this function
806-
def handle_error(client, e)
806+
def handle_error(client, e, response_written)
807807
code = case e
808808
when EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::ENOTCONN
809809
# client disconnected on us and there's nothing we can do
@@ -817,7 +817,7 @@ def handle_error(client, e)
817817
Pitchfork.log_error(@logger, "app error", e)
818818
500
819819
end
820-
if code
820+
if code && !response_written
821821
client.write_nonblock(err_response(code, @request.response_start_sent), exception: false)
822822
end
823823
client.close
@@ -842,6 +842,7 @@ def e100_response_write(client, env)
842842
# once a client is accepted, it is processed in its entirety here
843843
# in 3 easy steps: read request, call app, write app response
844844
def process_client(client, worker, timeout_handler)
845+
response_written = false
845846
env = nil
846847
@request = Pitchfork::HttpParser.new
847848
env = @request.read(client)
@@ -876,6 +877,7 @@ def process_client(client, worker, timeout_handler)
876877
end
877878
@request.headers? or headers = nil
878879
http_response_write(client, status, headers, body, @request)
880+
response_written = true
879881
ensure
880882
body.respond_to?(:close) and body.close
881883
end
@@ -889,7 +891,7 @@ def process_client(client, worker, timeout_handler)
889891
end
890892
env
891893
rescue => application_error
892-
handle_error(client, application_error)
894+
handle_error(client, application_error, response_written)
893895
env
894896
ensure
895897
if env

test/slow/test_server.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ def check_arguments(env, status, headers, application_error)
6363
end
6464
end
6565

66+
class TestRaiseAfterResponseWritten
67+
def call(env)
68+
body = Rack::BodyProxy.new(['hello!\n']) { raise "response already written" }
69+
[200, { 'content-type' => 'text/plain' }, body]
70+
end
71+
end
72+
6673
def setup
6774
@valid_request = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\n\r\n"
6875
@port = unused_port
@@ -181,6 +188,21 @@ def test_broken_app
181188
assert_match %r{\AHTTP/1.[01] 500\b}, sock.sysread(4096)
182189
assert_nil sock.close
183190
end
191+
192+
def test_raise_after_response_written
193+
redirect_test_io do
194+
@server = Pitchfork::HttpServer.new(TestRaiseAfterResponseWritten.new, :listeners => [ "127.0.0.1:#@port"])
195+
@server.start
196+
end
197+
198+
sock = tcp_socket('127.0.0.1', @port)
199+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
200+
201+
responses = sock.read(4096)
202+
203+
assert_match %r{^HTTP/1.[01] 200\b}, responses
204+
refute_match %r{HTTP/1.[01] 500\b}, responses
205+
end
184206
end
185207

186208
class WebServerTest < Pitchfork::Test

0 commit comments

Comments
 (0)