From 5b369e6370814a7652180eec0a5f0feeac5fb235 Mon Sep 17 00:00:00 2001 From: konsti Date: Wed, 1 Apr 2026 12:03:31 +0200 Subject: [PATCH] Handle broken range request implementations (#18780) This updates async_http_range_reader to v0.11.0 to add the missing range request bounds validation: https://github.com/astral-sh/async_http_range_reader/pull/8. An open question is how we want to behave when the server has an incorrect range request implementation (while advertising range request support). In the current implementation, it warns with the index URL, so that the user is aware that the massive slowdown is caused by a server advertising broken features. Also removes a dependency where the corresponding repo was deleted. Fixes https://github.com/astral-sh/uv/issues/18316 --- Cargo.lock | 11 +---- Cargo.toml | 2 +- crates/uv-client/src/error.rs | 64 ++++++++++++++++++++----- crates/uv-client/src/registry_client.rs | 2 +- 4 files changed, 56 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c81a71ceb..1433b207d 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 cfb1c2a84..4e187b514 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 5e7b39af6..716633eac 100644 --- a/crates/uv-client/src/error.rs +++ b/crates/uv-client/src/error.rs @@ -9,13 +9,13 @@ use reqwest::Response; use serde::Deserialize; use tracing::warn; -use uv_cache::Error as CacheError; -use uv_distribution_filename::{WheelFilename, WheelFilenameError}; -use uv_normalize::PackageName; -use uv_redacted::DisplaySafeUrl; - 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; /// RFC 9457 Problem Details for HTTP APIs /// @@ -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 c17921127..54656b3f1 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");