Fix: is_reachable() akzeptiert 401/403, aber nicht dauerhafte 5xx-Fehler
Der HTTP-Zweig von is_reachable() nutzte curl --fail, was einen HTTP-Server, der auf das HEAD mit 401/403 (Auth erforderlich, aber erreichbar) antwortet, faelschlich als nicht erreichbar meldete - relevant fuer WebDAV-Cloud-Seiten, die Auth verlangen. Der tatsaechliche Statuscode wird jetzt ausgelesen: nur ein Serverfehler (5xx) gilt als nicht erreichbar, da ein dauerhaft 5xx-antwortender Server (z. B. ein kaputter Reverse-Proxy) sonst bei jedem watch-Durchlauf einen vollen, letztlich erfolglosen Umschaltversuch (inkl. MOUNT_TIMEOUT_SECS-Wartezeit) ausloesen wuerde. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+64
-5
@@ -9,15 +9,37 @@ use std::process::{Command, Stdio};
|
||||
/// (HTTP HEAD via `curl`) oder eine reine Host-/IP-Adresse (ICMP-Ping) handelt.
|
||||
///
|
||||
/// Portiert aus dem alten `src/network/utils.rs` (v0.2.0).
|
||||
///
|
||||
/// Für HTTP wird bewusst NICHT `curl --fail` verwendet: ein WebDAV-Server, der auf das HEAD
|
||||
/// mit 401/403 (Auth erforderlich) antwortet, ist trotzdem erreichbar - `--fail` würde das als
|
||||
/// Fehler werten. Der tatsächliche Statuscode wird stattdessen ausgelesen und geprüft: nur ein
|
||||
/// Serverfehler (5xx) gilt als NICHT erreichbar, da ein dauerhaft 5xx-antwortender Server
|
||||
/// (z. B. ein kaputter Reverse-Proxy) sonst bei jedem `watch`-Durchlauf einen vollen,
|
||||
/// letztlich erfolglosen Umschaltversuch (inkl. `MOUNT_TIMEOUT_SECS`-Wartezeit) auslösen würde.
|
||||
pub fn is_reachable(addr: &str) -> bool {
|
||||
if addr.starts_with("http://") || addr.starts_with("https://") {
|
||||
Command::new("curl")
|
||||
.args(["--head", "--silent", "--fail", "--max-time", "5", addr])
|
||||
.stdout(Stdio::null())
|
||||
.args([
|
||||
"--head",
|
||||
"--silent",
|
||||
"--max-time",
|
||||
"5",
|
||||
"--output",
|
||||
"/dev/null",
|
||||
"--write-out",
|
||||
"%{http_code}",
|
||||
addr,
|
||||
])
|
||||
.stderr(Stdio::null())
|
||||
.status()
|
||||
.map(|s| s.success())
|
||||
.unwrap_or(false)
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|o| {
|
||||
String::from_utf8_lossy(&o.stdout)
|
||||
.trim()
|
||||
.parse::<u16>()
|
||||
.ok()
|
||||
})
|
||||
.is_some_and(|code| (100..500).contains(&code))
|
||||
} else {
|
||||
Command::new("ping")
|
||||
.args(["-c", "1", "-W", "2", addr])
|
||||
@@ -28,3 +50,40 @@ pub fn is_reachable(addr: &str) -> bool {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::Write;
|
||||
use std::net::TcpListener;
|
||||
|
||||
#[test]
|
||||
fn http_401_endpoint_is_reported_as_reachable() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").expect("bind");
|
||||
let port = listener.local_addr().expect("port").port();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if let Ok((mut stream, _)) = listener.accept() {
|
||||
let response = "HTTP/1.1 401 Unauthorized\r\nContent-Length: 0\r\n\r\n";
|
||||
let _ = stream.write_all(response.as_bytes());
|
||||
}
|
||||
});
|
||||
|
||||
assert!(is_reachable(&format!("http://127.0.0.1:{port}")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_500_endpoint_is_reported_as_unreachable() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").expect("bind");
|
||||
let port = listener.local_addr().expect("port").port();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if let Ok((mut stream, _)) = listener.accept() {
|
||||
let response = "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n";
|
||||
let _ = stream.write_all(response.as_bytes());
|
||||
}
|
||||
});
|
||||
|
||||
assert!(!is_reachable(&format!("http://127.0.0.1:{port}")));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user