Ensure Content-Length in backend container/account HEAD response
A failing CORS test in the gate discovered that, when running with eventlet==0.38.0, container and account HEAD requests returned Content-Type of application/json to clients regardless of the requested format. This was due to the backend HEAD response no longer having a Content-Length header, causing the listing_formats middleware to not modify the returned Content-Type (see Related-Change). The Related-Change fixed the client facing issue by making listing_formats middleware ensure the correct Content-Type is returned to clients even when Content-Length is absent in the backend response. The Related-Change also ensured that the 204 response to clients always has a Content-Length header. This patch directly fixes the problem of backend account and container server HEADs no longer having 'Content-Length: 0' by adding it explicitly. This violates the RFC prohibition of a 204 response having a Content-Length header [1], but preserves Swift's historic behavior and is consistent with the proxy-server's 204 response to clients. [1] https://httpwg.org/specs/rfc7230.html#header.content-length Related-Change: If724485e1425d1481d10b9255436301e346f07e8 Change-Id: Idacc59c5f43367926eff5221ee7fc417a9bc2d50
This commit is contained in:
parent
fa889358ac
commit
4aadb54025
@ -230,6 +230,7 @@ class AccountController(BaseStorageServer):
|
||||
return self._deleted_response(broker, req, HTTPNotFound)
|
||||
headers = get_response_headers(broker)
|
||||
headers['Content-Type'] = out_content_type
|
||||
headers['Content-Length'] = 0
|
||||
return HTTPNoContent(request=req, headers=headers, charset='utf-8')
|
||||
|
||||
@public
|
||||
|
@ -610,6 +610,7 @@ class ContainerController(BaseStorageServer):
|
||||
if value != '' and (key.lower() in self.save_headers or
|
||||
is_sys_or_user_meta('container', key)))
|
||||
headers['Content-Type'] = out_content_type
|
||||
headers['Content-Length'] = 0
|
||||
resp = HTTPNoContent(request=req, headers=headers, charset='utf-8')
|
||||
resp.last_modified = Timestamp(headers['X-PUT-Timestamp']).ceil()
|
||||
return resp
|
||||
|
@ -324,6 +324,31 @@ class TestAccountController(unittest.TestCase):
|
||||
self.assertEqual(resp.status_int, 404)
|
||||
self.assertEqual(resp.headers['X-Account-Status'], 'Deleted')
|
||||
|
||||
def test_HEAD_has_content_length(self):
|
||||
# create the account
|
||||
put_timestamp = next(self.ts)
|
||||
req = Request.blank('/sda1/p/a', method='PUT', headers={
|
||||
'x-timestamp': put_timestamp.normal})
|
||||
created_at_timestamp = next(self.ts)
|
||||
with mock.patch('swift.account.backend.Timestamp.now',
|
||||
return_value=created_at_timestamp):
|
||||
resp = req.get_response(self.controller)
|
||||
self.assertEqual(resp.status_int, 201)
|
||||
# do a HEAD
|
||||
req = Request.blank('/sda1/p/a', method='HEAD')
|
||||
status, headers, body_iter = req.call_application(self.controller)
|
||||
self.assertEqual('204 No Content', status)
|
||||
self.assertEqual({
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
'Content-Length': '0',
|
||||
'X-Account-Container-Count': '0',
|
||||
'X-Account-Object-Count': '0',
|
||||
'X-Account-Bytes-Used': '0',
|
||||
'X-Timestamp': created_at_timestamp.normal,
|
||||
'X-Put-Timestamp': put_timestamp.normal,
|
||||
}, dict(headers))
|
||||
self.assertEqual(b'', b''.join(body_iter))
|
||||
|
||||
def test_HEAD_empty_account(self):
|
||||
req = Request.blank('/sda1/p/a', environ={'REQUEST_METHOD': 'PUT',
|
||||
'HTTP_X_TIMESTAMP': '0'})
|
||||
|
@ -37,7 +37,7 @@ from six.moves.urllib.parse import quote
|
||||
from swift import __version__ as swift_version
|
||||
from swift.common.header_key_dict import HeaderKeyDict
|
||||
from swift.common.swob import (Request, WsgiBytesIO, HTTPNoContent,
|
||||
bytes_to_wsgi)
|
||||
bytes_to_wsgi, Response)
|
||||
import swift.container
|
||||
from swift.container import server as container_server
|
||||
from swift.common import constraints
|
||||
@ -203,6 +203,39 @@ class TestContainerController(unittest.TestCase):
|
||||
self.assertEqual(response.headers.get('x-container-write'),
|
||||
'account:user')
|
||||
|
||||
def test_HEAD_has_content_length(self):
|
||||
# create a container
|
||||
put_timestamp = next(self.ts)
|
||||
expected_last_modified = Response(
|
||||
last_modified=put_timestamp.ceil()).headers['Last-Modified']
|
||||
created_at_timestamp = next(self.ts)
|
||||
req = Request.blank('/sda1/p/a/c', method='PUT', headers={
|
||||
'x-timestamp': put_timestamp.normal})
|
||||
with mock.patch('swift.container.backend.Timestamp.now',
|
||||
return_value=created_at_timestamp):
|
||||
resp = req.get_response(self.controller)
|
||||
self.assertEqual(resp.status_int, 201)
|
||||
# do a HEAD
|
||||
req = Request.blank('/sda1/p/a/c', method='HEAD')
|
||||
status, headers, body_iter = req.call_application(self.controller)
|
||||
self.assertEqual('204 No Content', status)
|
||||
self.assertEqual({
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
'Content-Length': '0',
|
||||
'Last-Modified': expected_last_modified,
|
||||
'X-Backend-Delete-Timestamp': '0000000000.00000',
|
||||
'X-Backend-Put-Timestamp': put_timestamp.normal,
|
||||
'X-Backend-Sharding-State': 'unsharded',
|
||||
'X-Backend-Status-Changed-At': put_timestamp.normal,
|
||||
'X-Backend-Storage-Policy-Index': str(int(POLICIES.default)),
|
||||
'X-Backend-Timestamp': created_at_timestamp.normal,
|
||||
'X-Container-Bytes-Used': '0',
|
||||
'X-Container-Object-Count': '0',
|
||||
'X-Put-Timestamp': put_timestamp.normal,
|
||||
'X-Timestamp': created_at_timestamp.normal,
|
||||
}, dict(headers))
|
||||
self.assertEqual(b'', b''.join(body_iter))
|
||||
|
||||
def _test_head(self, start, ts):
|
||||
req = Request.blank('/sda1/p/a/c', method='HEAD')
|
||||
response = req.get_response(self.controller)
|
||||
|
Loading…
x
Reference in New Issue
Block a user