S3: fix Range request handling (#6778)
This commit is contained in:
parent
4ea51d8795
commit
3e928bcad6
@ -1169,25 +1169,35 @@ class S3Response(BaseResponse):
|
||||
) -> TYPE_RESPONSE:
|
||||
length = len(response_content)
|
||||
last = length - 1
|
||||
|
||||
_, rspec = request.headers.get("range").split("=")
|
||||
if "," in rspec:
|
||||
raise NotImplementedError("Multiple range specifiers not supported")
|
||||
return 200, response_headers, response_content
|
||||
|
||||
def toint(i: Any) -> Optional[int]:
|
||||
return int(i) if i else None
|
||||
try:
|
||||
begin, end = [int(i) if i else None for i in rspec.split("-")]
|
||||
except ValueError:
|
||||
# if we can't parse the Range header, S3 just treat the request as a non-range request
|
||||
return 200, response_headers, response_content
|
||||
|
||||
if (begin is None and end == 0) or (begin is not None and begin > last):
|
||||
raise InvalidRange(
|
||||
actual_size=str(length), range_requested=request.headers.get("range")
|
||||
)
|
||||
|
||||
begin, end = map(toint, rspec.split("-"))
|
||||
if begin is not None: # byte range
|
||||
end = last if end is None else min(end, last)
|
||||
elif end is not None: # suffix byte range
|
||||
begin = length - min(end, length)
|
||||
end = last
|
||||
else:
|
||||
return 400, response_headers, ""
|
||||
if begin < 0 or end > last or begin > min(end, last):
|
||||
raise InvalidRange(
|
||||
actual_size=str(length), range_requested=request.headers.get("range")
|
||||
)
|
||||
# Treat as non-range request
|
||||
return 200, response_headers, response_content
|
||||
|
||||
if begin > min(end, last):
|
||||
# Treat as non-range request if after the logic is applied, the start of the range is greater than the end
|
||||
return 200, response_headers, response_content
|
||||
|
||||
response_headers["content-range"] = f"bytes {begin}-{end}/{length}"
|
||||
content = response_content[begin : end + 1]
|
||||
response_headers["content-length"] = len(content)
|
||||
|
@ -1004,6 +1004,51 @@ def test_ranged_get():
|
||||
|
||||
assert key.content_length == 100
|
||||
|
||||
assert key.get(Range="bytes=0-0")["Body"].read() == b"0"
|
||||
assert key.get(Range="bytes=1-1")["Body"].read() == b"1"
|
||||
|
||||
range_req = key.get(Range="bytes=1-0")
|
||||
assert range_req["Body"].read() == rep * 10
|
||||
# assert that the request was not treated as a range request
|
||||
assert range_req["ResponseMetadata"]["HTTPStatusCode"] == 200
|
||||
assert "ContentRange" not in range_req
|
||||
|
||||
range_req = key.get(Range="bytes=-1-")
|
||||
assert range_req["Body"].read() == rep * 10
|
||||
assert range_req["ResponseMetadata"]["HTTPStatusCode"] == 200
|
||||
|
||||
range_req = key.get(Range="bytes=0--1")
|
||||
assert range_req["Body"].read() == rep * 10
|
||||
assert range_req["ResponseMetadata"]["HTTPStatusCode"] == 200
|
||||
|
||||
range_req = key.get(Range="bytes=0-1,3-4,7-9")
|
||||
assert range_req["Body"].read() == rep * 10
|
||||
assert range_req["ResponseMetadata"]["HTTPStatusCode"] == 200
|
||||
|
||||
range_req = key.get(Range="bytes=-")
|
||||
assert range_req["Body"].read() == rep * 10
|
||||
assert range_req["ResponseMetadata"]["HTTPStatusCode"] == 200
|
||||
|
||||
with pytest.raises(ClientError) as ex:
|
||||
key.get(Range="bytes=-0")
|
||||
assert ex.value.response["Error"]["Code"] == "InvalidRange"
|
||||
assert (
|
||||
ex.value.response["Error"]["Message"]
|
||||
== "The requested range is not satisfiable"
|
||||
)
|
||||
assert ex.value.response["Error"]["ActualObjectSize"] == "100"
|
||||
assert ex.value.response["Error"]["RangeRequested"] == "bytes=-0"
|
||||
|
||||
with pytest.raises(ClientError) as ex:
|
||||
key.get(Range="bytes=101-200")
|
||||
assert ex.value.response["Error"]["Code"] == "InvalidRange"
|
||||
assert (
|
||||
ex.value.response["Error"]["Message"]
|
||||
== "The requested range is not satisfiable"
|
||||
)
|
||||
assert ex.value.response["Error"]["ActualObjectSize"] == "100"
|
||||
assert ex.value.response["Error"]["RangeRequested"] == "bytes=101-200"
|
||||
|
||||
|
||||
@mock_s3
|
||||
def test_policy():
|
||||
|
Loading…
Reference in New Issue
Block a user