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
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 15 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "async_http_range_reader"
authors = ["Bas Zalmstra <zalmstra.bas@gmail.com>"]
version = "0.9.1"
version = "0.9.2"
edition = "2021"
description = "A library for streaming reading of files over HTTP using range requests"
license = "MIT"
Expand All @@ -15,7 +15,9 @@ http-content-range = "0.2.0"
itertools = "0.13.0"
bisection = "0.1.0"
memmap2 = "0.9.0"
reqwest = { version = "0.12.3", default-features = false, features = ["stream"] }
reqwest = { version = "0.12.3", default-features = false, features = [
"stream",
] }
reqwest-middleware = "0.4.0"
tokio = { version = "1.33.0", default-features = false }
tokio-stream = { version = "0.1.14", features = ["sync"] }
Expand All @@ -24,10 +26,18 @@ thiserror = "1.0.50"
tracing = "0.1.40"

[dev-dependencies]
axum = { version = "0.7.5", default-features = false, features = ["tokio", "http1"] }
tokio = { version = "1.33.0", default-features = false, features = ["macros", "test-util"] }
axum = { version = "0.7.5", default-features = false, features = [
"tokio",
"http1",
] }
tokio = { version = "1.33.0", default-features = false, features = [
"macros",
"test-util",
] }
tower-http = { version = "0.6.1", default-features = false, features = ["fs"] }
async_zip = { version = "0.0.17", default-features = false, features = ["tokio"] }
async_zip = { version = "0.0.17", default-features = false, features = [
"tokio",
] }
assert_matches = "1.5.0"
rstest = { version = "0.23.0" }
url = { version = "2.4.1" }
43 changes: 42 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,14 @@ impl AsyncHttpRangeReader {
.headers(extra_headers)
.send()
.await
.and_then(error_for_status)
.map_err(Arc::new)
.map_err(AsyncHttpRangeReaderError::HttpError)?;

if tail_response.status() == reqwest::StatusCode::RANGE_NOT_SATISFIABLE {
return Err(AsyncHttpRangeReaderError::HttpRangeRequestUnsupported);
Copy link
Member

Choose a reason for hiding this comment

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

Is that correct? We could also return the a "RangeNotSatisfiable" error here?

Copy link
Author

@pavelzw pavelzw Mar 9, 2026

Choose a reason for hiding this comment

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

yeah... probably you're right... might make more sense if we handle this in rattler itself :/ if jfrog actually provided sensible products and adhered to the RFC, we wouldn't be needing this 🫠

Copy link
Author

Choose a reason for hiding this comment

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

will handle this in rattler itself: conda/rattler@58574d1 (#2199)

}

let tail_response = error_for_status(tail_response)
.map_err(Arc::new)
.map_err(AsyncHttpRangeReaderError::HttpError)?;
Ok(tail_response)
Expand Down Expand Up @@ -655,6 +662,11 @@ mod static_directory_server;
#[cfg(test)]
mod test {
use super::*;
use axum::{
http::{HeaderMap as AxumHeaderMap, StatusCode as AxumStatusCode},
routing::get,
Router,
};
use crate::static_directory_server::StaticDirectoryServer;
use assert_matches::assert_matches;
use async_zip::tokio::read::seek::ZipFileReader;
Expand Down Expand Up @@ -854,4 +866,33 @@ mod test {
err, AsyncHttpRangeReaderError::HttpError(err) if err.status() == Some(StatusCode::NOT_FOUND)
);
}

#[tokio::test]
async fn test_negative_range_416_is_unsupported() {
async fn handler(headers: AxumHeaderMap) -> AxumStatusCode {
if headers.contains_key(reqwest::header::RANGE) {
AxumStatusCode::RANGE_NOT_SATISFIABLE
} else {
AxumStatusCode::OK
}
}

let app = Router::new().route("/", get(handler));
let listener = tokio::net::TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move {
let _ = axum::serve(listener, app).await;
});

let err = AsyncHttpRangeReader::new(
Client::new(),
Url::parse(&format!("http://127.0.0.1:{}/", addr.port())).unwrap(),
CheckSupportMethod::NegativeRangeRequest(8192),
HeaderMap::default(),
)
.await
.expect_err("expected an unsupported range request error");

assert_matches!(err, AsyncHttpRangeReaderError::HttpRangeRequestUnsupported);
}
}