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 c81a71ceb7..1433b207da 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 cfb1c2a840..4e187b5144 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 5e7b39af63..716633eacd 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 c179211279..54656b3f16 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");