Fix suffix-byte-range responses for zero-byte replicated objects.

Previously, given a GET request like "Range: bytes=-12345" for a
zero-byte object, Swift would return a 206 response with the header
"Content-Range: bytes 0--1/0". This is clearly incorrect. Now Swift
returns a 200 with the whole (zero-byte) object.

Note: this does not fix the bug for EC objects, only for replicated
ones. The fix for EC objects will follow in a separate commit.

Change-Id: If1edb665b0ae000da78c4efff6faddd94d75da6b
Partial-Bug: 1736840
This commit is contained in:
Samuel Merritt 2017-12-07 12:10:02 -08:00
parent c53d5bfe9f
commit dd9bc82826
2 changed files with 30 additions and 0 deletions

View File

@ -1256,6 +1256,20 @@ class Response(object):
# body text from RESPONSE_REASONS.
body = None
app_iter = None
elif self.content_length == 0:
# If ranges_for_length found ranges but our content length
# is 0, then that means we got a suffix-byte-range request
# (e.g. "bytes=-512"). This is asking for *up to* the last N
# bytes of the file. If we had any bytes to send at all,
# we'd return a 206 with an apropriate Content-Range header,
# but we can't construct a Content-Range header because we
# have no byte indices because we have no bytes.
#
# The only reasonable thing to do is to return a 200 with
# the whole object (all zero bytes of it). This is also what
# Apache and Nginx do, so if we're wrong, at least we're in
# good company.
pass
elif ranges:
range_size = len(ranges)
if range_size > 0:

View File

@ -2918,6 +2918,22 @@ class TestObjectController(unittest.TestCase):
self.assertEqual(resp.headers['X-Backend-Timestamp'],
utils.Timestamp(timestamp).internal)
def test_GET_range_zero_byte_object(self):
timestamp = normalize_timestamp(time())
req = Request.blank('/sda1/p/a/c/zero-byte',
environ={'REQUEST_METHOD': 'PUT'},
headers={'X-Timestamp': timestamp,
'Content-Type': 'application/x-test'})
req.body = ''
resp = req.get_response(self.object_controller)
self.assertEqual(resp.status_int, 201)
req = Request.blank('/sda1/p/a/c/zero-byte',
environ={'REQUEST_METHOD': 'GET'},
headers={'Range': 'bytes=-10'})
resp = req.get_response(self.object_controller)
self.assertEqual(resp.status_int, 200)
def test_GET_if_match(self):
req = Request.blank('/sda1/p/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
headers={