use crate::Error;
pub(super) fn is_retriable_status(status: reqwest::StatusCode) -> bool {
status.is_server_error() || status == reqwest::StatusCode::TOO_MANY_REQUESTS
}
pub(super) async fn read_body_capped(
mut resp: reqwest::Response,
cap: u64,
label: &str,
) -> Result<bytes::Bytes, Error> {
if cap == 0 {
return Ok(resp.bytes().await?);
}
const STREAM_INITIAL: usize = 64 * 1024;
let initial = resp
.content_length()
.map(|len| len.min(cap) as usize)
.unwrap_or(STREAM_INITIAL);
let mut buf = bytes::BytesMut::with_capacity(initial);
while let Some(chunk) = resp.chunk().await? {
if (buf.len() as u64).saturating_add(chunk.len() as u64) > cap {
return Err(Error::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("{label}: response body exceeds cap {cap}"),
)));
}
buf.extend_from_slice(&chunk);
}
Ok(buf.freeze())
}
pub(super) async fn read_body_capped_streaming_sha512(
mut resp: reqwest::Response,
cap: u64,
label: &str,
) -> Result<(bytes::Bytes, [u8; 64]), Error> {
use sha2::Digest;
const STREAM_INITIAL: usize = 64 * 1024;
let initial = match (resp.content_length(), cap) {
(Some(len), 0) => len as usize,
(Some(len), cap) => len.min(cap) as usize,
(None, _) => STREAM_INITIAL,
};
let mut buf = bytes::BytesMut::with_capacity(initial);
let mut hasher = sha2::Sha512::new();
while let Some(chunk) = resp.chunk().await? {
if cap > 0 && (buf.len() as u64).saturating_add(chunk.len() as u64) > cap {
return Err(Error::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("{label}: response body exceeds cap {cap}"),
)));
}
hasher.update(&chunk);
buf.extend_from_slice(&chunk);
}
let mut digest = [0u8; 64];
digest.copy_from_slice(&hasher.finalize()[..]);
Ok((buf.freeze(), digest))
}
pub(super) fn check_body_cap(resp: &reqwest::Response, cap: u64, label: &str) -> Result<(), Error> {
if cap == 0 {
return Ok(());
}
if let Some(len) = resp.content_length()
&& len > cap
{
return Err(Error::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("{label}: response Content-Length {len} exceeds cap {cap}"),
)));
}
Ok(())
}
pub(super) fn warn_slow_tarball(
threshold_kibps: u64,
url: &str,
len: usize,
elapsed: std::time::Duration,
) {
if threshold_kibps == 0 {
return;
}
if len == 0 || elapsed <= std::time::Duration::from_secs(1) {
return;
}
let elapsed_ms = elapsed.as_millis() as u64;
let kibps = ((len as u64).saturating_mul(1000)) / elapsed_ms / 1024;
if kibps < threshold_kibps {
let safe_url = aube_util::url::redact_url(url);
tracing::warn!(
kibps,
threshold_kibps,
bytes = len,
elapsed_ms,
url = %safe_url,
code = aube_codes::warnings::WARN_AUBE_SLOW_TARBALL,
"slow tarball download fell below fetchMinSpeedKiBps",
);
}
}
pub(super) fn retry_after_from(resp: &reqwest::Response) -> Option<std::time::Duration> {
let raw = resp
.headers()
.get(reqwest::header::RETRY_AFTER)?
.to_str()
.ok()?;
let secs: u64 = raw.trim().parse().ok()?;
Some(std::time::Duration::from_secs(
secs.min(RETRY_AFTER_CAP_SECS),
))
}
const RETRY_AFTER_CAP_SECS: u64 = 60;
pub(super) const TIMEOUT_RETRY_CAP: u32 = 1;