Fix HEAD request response when request not given to response.

If a middleware (swift3, I'm looking at you), doesn't pass a Request
object into the Response constructor, Response._response_iter cannot
know to send zero bytes in the body of the HEAD response.

This patch fixes this usage of swob by making Response.__call__
helpfully reify self.request from env if it wasn't already set by the
Response object's constructor.

This fixes a bug in swift3 + swob-enabled-Swift where HEAD requests to
swift3 resulted in a response with a body in violation of the relevant
RFC and confusing clients.

Thanks to kostecky for finding the bug and describing it accurately.

Change-Id: I2bdb098052b161e1cddf1e4e482ab4dfafeb18c0
This commit is contained in:
Darrell Bishop 2013-01-10 13:08:22 -08:00
parent b8626f9667
commit e2929ec58a
2 changed files with 20 additions and 0 deletions

View File

@ -997,6 +997,8 @@ class Response(object):
return self.host_url() + self.location
def __call__(self, env, start_response):
if not self.request:
self.request = Request(env)
self.environ = env
app_iter = self._response_iter(self.app_iter, self._body)
if 'location' in self.headers:

View File

@ -462,6 +462,24 @@ class TestResponse(unittest.TestCase):
resp.body = u'\N{SNOWMAN}'
self.assertEquals(resp.body, u'\N{SNOWMAN}'.encode('utf-8'))
def test_call_reifies_request_if_necessary(self):
"""
The actual bug was a HEAD response coming out with a body because the
Request object wasn't passed into the Response object's constructor.
The Response object's __call__ method should be able to reify a
Request object from the env it gets passed.
"""
def test_app(environ, start_response):
start_response('200 OK', [])
return ['hi']
req = swift.common.swob.Request.blank('/')
req.method = 'HEAD'
status, headers, app_iter = req.call_application(test_app)
resp = swift.common.swob.Response(status=status, headers=dict(headers),
app_iter=app_iter)
output_iter = resp(req.environ, lambda *_: None)
self.assertEquals(list(output_iter), [''])
def test_location_rewrite(self):
def start_response(env, headers):
pass