Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Convert setup.py to pyproject.toml ([#920](https://github.com/Open-EO/openeo-python-client/issues/920))
- Make `_DerivedFrom._from_url` more resilient against unresolvable/unparsable `derived_from` links ([#928](https://github.com/Open-EO/openeo-python-client/issues/928), eu-cdse/openeo-cdse-infra#1338)
- Use GET with a 0-0 range request instead of HEAD when downloading ([#939](https://github.com/Open-EO/openeo-python-client/issues/939))

### Removed

Expand Down
10 changes: 7 additions & 3 deletions openeo/rest/_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,13 @@ def download_url(
chunk_size: int = DEFAULT_DOWNLOAD_CHUNK_SIZE,
range_size: int = DEFAULT_DOWNLOAD_RANGE_SIZE,
) -> None:
head = self.head(url, stream=True)
if head.ok and head.headers.get("Accept-Ranges") == "bytes" and "Content-Length" in head.headers:
file_size = int(head.headers["Content-Length"])
# URL might be pre-signed S3 URL, so use a GET with a 0-0 range request
# to figure out if the server supports range requests.
# Trying to GET a 0-byte file will give a 416-response, so accept that
# since the download will succeed.
head = self.get(url, headers={"Range": "bytes=0-0"}, expected_status=[200, 206, 416], stream=True)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this doing a GET with 0-range headers is a good idea: this will only work if the server supports range. If the server doesn't then the user will download the same, possibly large, payload twice (once for this head-emulation and once for doing the actual download a couple of lines lower in _download_all_at_once

if head.ok and head.headers.get("Accept-Ranges") == "bytes" and "Content-Range" in head.headers:
file_size = int(head.headers["Content-Range"].split("/")[1])
self._download_ranged(
url=url, target=target, file_size=file_size, chunk_size=chunk_size, range_size=range_size
)
Expand Down
9 changes: 7 additions & 2 deletions tests/rest/test_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -734,8 +734,13 @@ def handle_content(request, context):
assert search
from_bytes = int(search.group(1))
to_bytes = int(search.group(2))
assert from_bytes < to_bytes
return TIFF_CONTENT[from_bytes : to_bytes + 1]
assert from_bytes <= to_bytes

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change isn't directly related to this PR, except that these tests fail because the previous assert is too strict.

sliced_content = TIFF_CONTENT[from_bytes : to_bytes + 1]
context.status_code = 206

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm misunderstanding something, the mock didn't respond with the right headers/status code for range requests earlier.

context.headers["Accept-Ranges"] = "bytes"
context.headers["Content-Range"] = f"bytes {from_bytes}-{to_bytes}/{len(TIFF_CONTENT)}"
context.headers["Content-Length"] = str(len(sliced_content))
return sliced_content

requests_mock.get(
API_URL + "/jobs/jj1/results",
Expand Down
Loading