diff --git a/Cargo.lock b/Cargo.lock index c81a71ceb79b8..1433b207dacd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -326,12 +326,11 @@ dependencies = [ [[package]] name = "astral_async_http_range_reader" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9a05148c7b3e85c17806fef4b2889c44f1a60575c5da9e9be80ce13227185" +checksum = "4a8647866aee8d9707ae6ccc35205803a6df47c0ba83c5339ea6061b79131e4f" dependencies = [ "astral-reqwest-middleware", - "bisection", "futures", "http-content-range", "itertools 0.14.0", @@ -548,12 +547,6 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" -[[package]] -name = "bisection" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "021e079a1bab0ecce6cf4b4b74c0c37afa4a697136eb3b127875c84a8f04a8c3" - [[package]] name = "bitflags" version = "1.3.2" diff --git a/Cargo.toml b/Cargo.toml index cfb1c2a84013c..4e187b51441ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,7 +104,7 @@ async-compression = { version = "0.4.12", features = [ "zstd", ] } async-trait = { version = "0.1.82" } -async_http_range_reader = { version = "0.10.0", package = "astral_async_http_range_reader" } +async_http_range_reader = { version = "0.11.0", package = "astral_async_http_range_reader" } async_zip = { version = "0.0.17", package = "astral_async_zip", features = [ "bzip2", "deflate", diff --git a/crates/uv-client/src/error.rs b/crates/uv-client/src/error.rs index 5e7b39af63c18..716633eacd2a7 100644 --- a/crates/uv-client/src/error.rs +++ b/crates/uv-client/src/error.rs @@ -9,14 +9,14 @@ use reqwest::Response; use serde::Deserialize; use tracing::warn; +use crate::middleware::OfflineError; +use crate::{FlatIndexError, html}; use uv_cache::Error as CacheError; use uv_distribution_filename::{WheelFilename, WheelFilenameError}; +use uv_distribution_types::IndexUrl; use uv_normalize::PackageName; use uv_redacted::DisplaySafeUrl; -use crate::middleware::OfflineError; -use crate::{FlatIndexError, html}; - /// RFC 9457 Problem Details for HTTP APIs /// /// This structure represents the standard format for machine-readable details @@ -242,7 +242,11 @@ impl Error { } /// Returns `true` if the error is due to the server not supporting HTTP range requests. - pub fn is_http_range_requests_unsupported(&self) -> bool { + pub fn is_http_range_requests_unsupported( + &self, + url: &DisplaySafeUrl, + index: Option<&IndexUrl>, + ) -> bool { match &*self.kind { // The server doesn't support range requests (as reported by the `HEAD` check). ErrorKind::AsyncHttpRangeReader( @@ -261,6 +265,25 @@ impl Error { return true; } + // The server advertises range request support, but doesn't implement it correctly. + ErrorKind::AsyncHttpRangeReader( + _, + AsyncHttpRangeReaderError::RangeMismatch { .. } + | AsyncHttpRangeReaderError::ResponseTooShort { .. } + | AsyncHttpRangeReaderError::ResponseTooLong { .. }, + ) => { + let url = if let Some(index) = index { + index.url() + } else { + url + }; + warn!( + "Invalid range request response from server that declares HTTP range request \ + support, falling back to streaming: {url}" + ); + return true; + } + // The server returned a "Method Not Allowed" error, indicating it doesn't support // HEAD requests, so we can't check for range requests. ErrorKind::WrappedReqwestError(_, err) => { @@ -295,14 +318,31 @@ impl Error { // The server doesn't support range requests, but we only discovered this while // unzipping due to erroneous server behavior. ErrorKind::Zip(_, ZipError::UpstreamReadError(err)) => { - if let Some(inner) = err.get_ref() { - if let Some(inner) = inner.downcast_ref::() { - if matches!( - inner, - AsyncHttpRangeReaderError::HttpRangeRequestUnsupported - ) { + if let Some(inner) = err.get_ref() + && let Some(range_reader_error) = + inner.downcast_ref::() + { + match range_reader_error { + AsyncHttpRangeReaderError::HttpRangeRequestUnsupported + | AsyncHttpRangeReaderError::ContentLengthMissing + | AsyncHttpRangeReaderError::ContentRangeMissing => { + return true; + } + AsyncHttpRangeReaderError::RangeMismatch { .. } + | AsyncHttpRangeReaderError::ResponseTooShort { .. } + | AsyncHttpRangeReaderError::ResponseTooLong { .. } => { + let url = if let Some(index) = index { + index.url() + } else { + url + }; + warn!( + "Invalid range request response from server that declares HTTP \ + range request support, falling back to streaming: {url}" + ); return true; } + _ => {} } } } diff --git a/crates/uv-client/src/registry_client.rs b/crates/uv-client/src/registry_client.rs index c1792112795e9..54656b3f16af7 100644 --- a/crates/uv-client/src/registry_client.rs +++ b/crates/uv-client/src/registry_client.rs @@ -1191,7 +1191,7 @@ impl RegistryClient { match result { Ok(metadata) => return Ok(metadata), Err(err) => { - if err.is_http_range_requests_unsupported() { + if err.is_http_range_requests_unsupported(url, index) { // The range request version failed. Fall back to streaming the file to search // for the METADATA file. warn!("Range requests not supported for {filename}; streaming wheel");