Issue #16220: wsgiref now always calls close() on an iterable response.

Patch by Brent Tubbs.
This commit is contained in:
Antoine Pitrou 2012-10-21 14:15:06 +02:00
commit d3a3e640b6
4 changed files with 29 additions and 93 deletions

View file

@ -41,9 +41,6 @@ class MockHandler(WSGIRequestHandler):
pass pass
def hello_app(environ,start_response): def hello_app(environ,start_response):
start_response("200 OK", [ start_response("200 OK", [
('Content-Type','text/plain'), ('Content-Type','text/plain'),
@ -65,28 +62,6 @@ def run_amock(app=hello_app, data=b"GET / HTTP/1.0\n\n"):
return out.getvalue(), err.getvalue() return out.getvalue(), err.getvalue()
def compare_generic_iter(make_it,match): def compare_generic_iter(make_it,match):
"""Utility to compare a generic 2.1/2.2+ iterator with an iterable """Utility to compare a generic 2.1/2.2+ iterator with an iterable
@ -124,10 +99,6 @@ def compare_generic_iter(make_it,match):
raise AssertionError("Too many items from .__next__()", it) raise AssertionError("Too many items from .__next__()", it)
class IntegrationTests(TestCase): class IntegrationTests(TestCase):
def check_hello(self, out, has_length=True): def check_hello(self, out, has_length=True):
@ -201,8 +172,6 @@ class IntegrationTests(TestCase):
out) out)
class UtilityTests(TestCase): class UtilityTests(TestCase):
def checkShift(self,sn_in,pi_in,part,sn_out,pi_out): def checkShift(self,sn_in,pi_in,part,sn_out,pi_out):
@ -241,11 +210,6 @@ class UtilityTests(TestCase):
util.setup_testing_defaults(kw) util.setup_testing_defaults(kw)
self.assertEqual(util.request_uri(kw,query),uri) self.assertEqual(util.request_uri(kw,query),uri)
def checkFW(self,text,size,match): def checkFW(self,text,size,match):
def make_it(text=text,size=size): def make_it(text=text,size=size):
@ -264,7 +228,6 @@ class UtilityTests(TestCase):
it.close() it.close()
self.assertTrue(it.filelike.closed) self.assertTrue(it.filelike.closed)
def testSimpleShifts(self): def testSimpleShifts(self):
self.checkShift('','/', '', '/', '') self.checkShift('','/', '', '/', '')
self.checkShift('','/x', 'x', '/x', '') self.checkShift('','/x', 'x', '/x', '')
@ -272,7 +235,6 @@ class UtilityTests(TestCase):
self.checkShift('/a','/x/y', 'x', '/a/x', '/y') self.checkShift('/a','/x/y', 'x', '/a/x', '/y')
self.checkShift('/a','/x/', 'x', '/a/x', '/') self.checkShift('/a','/x/', 'x', '/a/x', '/')
def testNormalizedShifts(self): def testNormalizedShifts(self):
self.checkShift('/a/b', '/../y', '..', '/a', '/y') self.checkShift('/a/b', '/../y', '..', '/a', '/y')
self.checkShift('', '/../y', '..', '', '/y') self.checkShift('', '/../y', '..', '', '/y')
@ -286,7 +248,6 @@ class UtilityTests(TestCase):
self.checkShift('/a/b', '/x//', 'x', '/a/b/x', '/') self.checkShift('/a/b', '/x//', 'x', '/a/b/x', '/')
self.checkShift('/a/b', '/.', None, '/a/b', '') self.checkShift('/a/b', '/.', None, '/a/b', '')
def testDefaults(self): def testDefaults(self):
for key, value in [ for key, value in [
('SERVER_NAME','127.0.0.1'), ('SERVER_NAME','127.0.0.1'),
@ -306,7 +267,6 @@ class UtilityTests(TestCase):
]: ]:
self.checkDefault(key,value) self.checkDefault(key,value)
def testCrossDefaults(self): def testCrossDefaults(self):
self.checkCrossDefault('HTTP_HOST',"foo.bar",SERVER_NAME="foo.bar") self.checkCrossDefault('HTTP_HOST',"foo.bar",SERVER_NAME="foo.bar")
self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="on") self.checkCrossDefault('wsgi.url_scheme',"https",HTTPS="on")
@ -316,7 +276,6 @@ class UtilityTests(TestCase):
self.checkCrossDefault('SERVER_PORT',"80",HTTPS="foo") self.checkCrossDefault('SERVER_PORT',"80",HTTPS="foo")
self.checkCrossDefault('SERVER_PORT',"443",HTTPS="on") self.checkCrossDefault('SERVER_PORT',"443",HTTPS="on")
def testGuessScheme(self): def testGuessScheme(self):
self.assertEqual(util.guess_scheme({}), "http") self.assertEqual(util.guess_scheme({}), "http")
self.assertEqual(util.guess_scheme({'HTTPS':"foo"}), "http") self.assertEqual(util.guess_scheme({'HTTPS':"foo"}), "http")
@ -324,10 +283,6 @@ class UtilityTests(TestCase):
self.assertEqual(util.guess_scheme({'HTTPS':"yes"}), "https") self.assertEqual(util.guess_scheme({'HTTPS':"yes"}), "https")
self.assertEqual(util.guess_scheme({'HTTPS':"1"}), "https") self.assertEqual(util.guess_scheme({'HTTPS':"1"}), "https")
def testAppURIs(self): def testAppURIs(self):
self.checkAppURI("http://127.0.0.1/") self.checkAppURI("http://127.0.0.1/")
self.checkAppURI("http://127.0.0.1/spam", SCRIPT_NAME="/spam") self.checkAppURI("http://127.0.0.1/spam", SCRIPT_NAME="/spam")
@ -452,15 +407,6 @@ class TestHandler(ErrorHandler):
raise # for testing, we want to see what's happening raise # for testing, we want to see what's happening
class HandlerTests(TestCase): class HandlerTests(TestCase):
def checkEnvironAttrs(self, handler): def checkEnvironAttrs(self, handler):
@ -501,7 +447,6 @@ class HandlerTests(TestCase):
h=TestHandler(); h.setup_environ() h=TestHandler(); h.setup_environ()
self.assertEqual(h.environ['wsgi.url_scheme'],'http') self.assertEqual(h.environ['wsgi.url_scheme'],'http')
def testAbstractMethods(self): def testAbstractMethods(self):
h = BaseHandler() h = BaseHandler()
for name in [ for name in [
@ -510,7 +455,6 @@ class HandlerTests(TestCase):
self.assertRaises(NotImplementedError, getattr(h,name)) self.assertRaises(NotImplementedError, getattr(h,name))
self.assertRaises(NotImplementedError, h._write, "test") self.assertRaises(NotImplementedError, h._write, "test")
def testContentLength(self): def testContentLength(self):
# Demo one reason iteration is better than write()... ;) # Demo one reason iteration is better than write()... ;)
@ -602,7 +546,6 @@ class HandlerTests(TestCase):
"\r\n".encode("iso-8859-1")+MSG)) "\r\n".encode("iso-8859-1")+MSG))
self.assertIn("AssertionError", h.stderr.getvalue()) self.assertIn("AssertionError", h.stderr.getvalue())
def testHeaderFormats(self): def testHeaderFormats(self):
def non_error_app(e,s): def non_error_app(e,s):
@ -662,40 +605,27 @@ class HandlerTests(TestCase):
b"data", b"data",
h.stdout.getvalue()) h.stdout.getvalue())
# This epilogue is needed for compatibility with the Python 2.5 regrtest module def testCloseOnError(self):
side_effects = {'close_called': False}
MSG = b"Some output has been sent"
def error_app(e,s):
s("200 OK",[])(MSG)
class CrashyIterable(object):
def __iter__(self):
while True:
yield b'blah'
raise AssertionError("This should be caught by handler")
def close(self):
side_effects['close_called'] = True
return CrashyIterable()
h = ErrorHandler()
h.run(error_app)
self.assertEqual(side_effects['close_called'], True)
def test_main(): def test_main():
support.run_unittest(__name__) support.run_unittest(__name__)
if __name__ == "__main__": if __name__ == "__main__":
test_main() test_main()
# the above lines intentionally left blank

View file

@ -174,11 +174,13 @@ class BaseHandler:
in the event loop to iterate over the data, and to call in the event loop to iterate over the data, and to call
'self.close()' once the response is finished. 'self.close()' once the response is finished.
""" """
if not self.result_is_file() or not self.sendfile(): try:
for data in self.result: if not self.result_is_file() or not self.sendfile():
self.write(data) for data in self.result:
self.finish_content() self.write(data)
self.close() self.finish_content()
finally:
self.close()
def get_scheme(self): def get_scheme(self):

View file

@ -1178,6 +1178,7 @@ Alberto Trevino
Matthias Troffaes Matthias Troffaes
John Tromp John Tromp
Jason Trowbridge Jason Trowbridge
Brent Tubbs
Anthony Tuininga Anthony Tuininga
Erno Tukia Erno Tukia
David Turner David Turner

View file

@ -59,6 +59,9 @@ Core and Builtins
Library Library
------- -------
- Issue #16220: wsgiref now always calls close() on an iterable response.
Patch by Brent Tubbs.
- Issue #16270: urllib may hang when used for retrieving files via FTP by using - Issue #16270: urllib may hang when used for retrieving files via FTP by using
a context manager. Patch by Giampaolo Rodola'. a context manager. Patch by Giampaolo Rodola'.