fix: wrap full request+body read in timeout to catch slow response bodies
This commit is contained in:
parent
baea9f8e7e
commit
05c60db605
|
|
@ -97,9 +97,38 @@ async fn run_check(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Op
|
||||||
req = req.body(body.clone());
|
req = req.body(body.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = req.send().await;
|
// Wrap the entire request + body read in a single timeout so slow response
|
||||||
|
// bodies don't slip through after headers arrive within the window.
|
||||||
|
let timed = tokio::time::timeout(timeout, async {
|
||||||
|
let resp = req.send().await?;
|
||||||
|
let status = resp.status();
|
||||||
|
let headers: HashMap<String, String> = resp.headers().iter()
|
||||||
|
.filter_map(|(k, v)| Some((k.to_string(), v.to_str().ok()?.to_string())))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
const MAX_BODY_BYTES: usize = 10 * 1024 * 1024;
|
||||||
|
let body = {
|
||||||
|
let content_len = resp.content_length().unwrap_or(0) as usize;
|
||||||
|
if content_len > MAX_BODY_BYTES {
|
||||||
|
format!("[body truncated: Content-Length {} exceeds 10MB limit]", content_len)
|
||||||
|
} else {
|
||||||
|
let bytes = resp.bytes().await?;
|
||||||
|
let truncated = &bytes[..bytes.len().min(MAX_BODY_BYTES)];
|
||||||
|
String::from_utf8_lossy(truncated).into_owned()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok::<_, reqwest::Error>((status, headers, body))
|
||||||
|
}).await;
|
||||||
|
|
||||||
let latency_ms = start.elapsed().as_millis() as u64;
|
let latency_ms = start.elapsed().as_millis() as u64;
|
||||||
|
|
||||||
|
// Flatten timeout + reqwest errors into a single result
|
||||||
|
let result = match timed {
|
||||||
|
Err(_) => Err(format!("timed out after {}ms", timeout.as_millis())),
|
||||||
|
Ok(Err(e)) => Err(e.to_string()),
|
||||||
|
Ok(Ok(v)) => Ok(v),
|
||||||
|
};
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Err(e) => PingResult {
|
Err(e) => PingResult {
|
||||||
monitor_id: monitor.id.clone(),
|
monitor_id: monitor.id.clone(),
|
||||||
|
|
@ -108,29 +137,12 @@ async fn run_check(client: &reqwest::Client, monitor: &Monitor, scheduled_at: Op
|
||||||
status_code: None,
|
status_code: None,
|
||||||
latency_ms: Some(latency_ms),
|
latency_ms: Some(latency_ms),
|
||||||
up: false,
|
up: false,
|
||||||
error: Some(e.to_string()),
|
error: Some(e),
|
||||||
cert_expiry_days,
|
cert_expiry_days,
|
||||||
meta: None,
|
meta: None,
|
||||||
},
|
},
|
||||||
Ok(resp) => {
|
Ok((status_raw, headers, body)) => {
|
||||||
let status = resp.status().as_u16();
|
let status = status_raw.as_u16();
|
||||||
let headers: HashMap<String, String> = resp.headers().iter()
|
|
||||||
.filter_map(|(k, v)| Some((k.to_string(), v.to_str().ok()?.to_string())))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Limit response body to 10MB to prevent OOM from malicious targets
|
|
||||||
const MAX_BODY_BYTES: usize = 10 * 1024 * 1024;
|
|
||||||
let body = {
|
|
||||||
let content_len = resp.content_length().unwrap_or(0) as usize;
|
|
||||||
if content_len > MAX_BODY_BYTES {
|
|
||||||
// Skip reading body entirely if Content-Length exceeds limit
|
|
||||||
format!("[body truncated: Content-Length {} exceeds 10MB limit]", content_len)
|
|
||||||
} else {
|
|
||||||
let bytes = resp.bytes().await.unwrap_or_default();
|
|
||||||
let truncated = &bytes[..bytes.len().min(MAX_BODY_BYTES)];
|
|
||||||
String::from_utf8_lossy(truncated).into_owned()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Evaluate query if present
|
// Evaluate query if present
|
||||||
let (up, query_error) = if let Some(q) = &monitor.query {
|
let (up, query_error) = if let Some(q) = &monitor.query {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue