Skip to content

Commit

Permalink
Merge pull request twisted#1027 from chrisbarber/trunk
Browse files Browse the repository at this point in the history
Author: chrisbarber

Reviewer: glyph

Fixes: ticket:9468

twisted.web.twcgi.CGIProcessProtocol.processEnded(...) now handles an already-finished request, for example when request.connectionLost(...) was called previously.
  • Loading branch information
glyph authored Aug 7, 2020
2 parents 1b439fb + ec1025f commit 78032af
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 8 deletions.
1 change: 1 addition & 0 deletions src/twisted/newsfragments/9468.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
twisted.web.twcgi.CGIProcessProtocol.processEnded(...) now handles an already-finished request, for example when request.connectionLost(...) was called previously.
19 changes: 17 additions & 2 deletions src/twisted/web/test/test_cgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@

from twisted.trial import unittest
from twisted.internet import address, reactor, interfaces, error
from twisted.internet.error import ConnectionLost
from twisted.python import util, failure, log
from twisted.web.http import NOT_FOUND, INTERNAL_SERVER_ERROR
from twisted.web import client, twcgi, server, resource, http_headers
from twisted.web import client, http, twcgi, server, resource, http_headers
from twisted.web.test._util import _render
from twisted.web.test.test_web import DummyRequest
from twisted.web.test.requesthelper import DummyRequest, DummyChannel

DUMMY_CGI = '''\
print("Header: OK")
Expand Down Expand Up @@ -485,6 +486,20 @@ def test_prematureEndOfHeaders(self):
self.assertEqual(request.responseCode, INTERNAL_SERVER_ERROR)


def test_connectionLost(self):
"""
Ensure that the CGI process ends cleanly when the request connection
is lost.
"""
d = DummyChannel()
request = http.Request(d, True)
protocol = twcgi.CGIProcessProtocol(request)
request.connectionLost(
failure.Failure(ConnectionLost("Connection done"))
)
protocol.processEnded(failure.Failure(error.ProcessTerminated()))



def discardBody(response):
"""
Expand Down
26 changes: 20 additions & 6 deletions src/twisted/web/twcgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ class CGIProcessProtocol(protocol.ProcessProtocol, pb.Viewable):
headertext = b''
errortext = b''
_log = Logger()
_requestFinished = False

# Remotely relay producer interface.

Expand Down Expand Up @@ -231,6 +232,7 @@ def stopProducing(self):

def __init__(self, request):
self.request = request
self.request.notifyFinish().addBoth(self._finished)


def connectionMade(self):
Expand Down Expand Up @@ -311,12 +313,24 @@ def processEnded(self, reason):
if self.errortext:
self._log.error("Errors from CGI {uri}: {errorText}",
uri=self.request.uri, errorText=self.errortext)

if self.handling_headers:
self._log.error("Premature end of headers in {uri}: {headerText}",
uri=self.request.uri, headerText=self.headertext)
self.request.write(
resource.ErrorPage(http.INTERNAL_SERVER_ERROR,
"CGI Script Error",
"Premature end of script headers.").render(self.request))
self.request.unregisterProducer()
self.request.finish()
if not self._requestFinished:
self.request.write(
resource.ErrorPage(http.INTERNAL_SERVER_ERROR,
"CGI Script Error", "Premature end of script headers."
).render(self.request))

if not self._requestFinished:
self.request.unregisterProducer()
self.request.finish()


def _finished(self, ignored):
"""
Record the end of the response generation for the request being
serviced.
"""
self._requestFinished = True

0 comments on commit 78032af

Please sign in to comment.