diff --git a/crates/clawhdf5-remote/tests/common/server.rs b/crates/clawhdf5-remote/tests/common/server.rs index 4749f50..a060a9d 100644 --- a/crates/clawhdf5-remote/tests/common/server.rs +++ b/crates/clawhdf5-remote/tests/common/server.rs @@ -3,7 +3,10 @@ //! and Last-Modified with `If-Match` / `If-Unmodified-Since` (412), and //! switches to misbehave — ignore ranges (200 with the whole file), cut //! bodies short, answer 503, respond slowly, send no validators. It counts -//! requests and body bytes, and logs every range asked for. +//! requests and body bytes, and logs every range asked for — only for the +//! paths it serves: a request for any other path (a local port scanner's +//! `GET /`, say) is answered 404 and not counted, so request budgets in +//! tests stay exact. #![allow(dead_code)] @@ -45,7 +48,8 @@ pub struct Shared { pub fail_next: AtomicU32, /// Sleep this long before answering each request. pub delay_ms: AtomicU64, - /// Requests served (every status). + /// Requests for a served path (every status); requests for other + /// paths are not counted. pub requests: AtomicU64, /// Body bytes sent. pub bytes: AtomicU64, @@ -223,14 +227,31 @@ fn serve(conn: TcpStream, s: &Shared) -> std::io::Result<()> { headers.insert(k.trim().to_ascii_lowercase(), v.trim().to_string()); } } + // The query string (a presigned URL's signature, say) is not part + // of the file's name. + let path = path.split('?').next().unwrap_or("").to_string(); + let close = headers + .get("connection") + .is_some_and(|v| v.eq_ignore_ascii_case("close")); + let res = { + let files = s.files.read().unwrap(); + files + .get(&path) + .map(|r| (r.data.clone(), r.etag.clone(), r.last_modified.clone())) + }; + let Some((data, etag, lm)) = res else { + // Not ours: not counted, not delayed, no failure injected. + write!(out, "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n")?; + if close { + return Ok(()); + } + continue; + }; s.requests.fetch_add(1, Ordering::SeqCst); let delay = s.delay_ms.load(Ordering::SeqCst); if delay > 0 { std::thread::sleep(Duration::from_millis(delay)); } - let close = headers - .get("connection") - .is_some_and(|v| v.eq_ignore_ascii_case("close")); if s.fail_next .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |n| n.checked_sub(1)) .is_ok() @@ -241,16 +262,6 @@ fn serve(conn: TcpStream, s: &Shared) -> std::io::Result<()> { )?; continue; } - let res = { - let files = s.files.read().unwrap(); - files - .get(&path) - .map(|r| (r.data.clone(), r.etag.clone(), r.last_modified.clone())) - }; - let Some((data, etag, lm)) = res else { - write!(out, "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n")?; - continue; - }; let len = data.len() as u64; let mut validators = String::new(); if s.weak_etag.load(Ordering::SeqCst) { diff --git a/crates/clawhdf5-remote/tests/http.rs b/crates/clawhdf5-remote/tests/http.rs index 5a68072..2c95cd1 100644 --- a/crates/clawhdf5-remote/tests/http.rs +++ b/crates/clawhdf5-remote/tests/http.rs @@ -526,3 +526,21 @@ fn bad_urls_and_statuses_are_clean_errors() { "{err}" ); } + +/// A request for a path the server does not serve (a local port scanner's +/// `GET /`) does not count against a test's request budget. +#[test] +fn requests_for_other_paths_are_not_counted() { + use std::io::{Read, Write}; + let server = Server::start(vec![("/m.h5".into(), multi_block_file())]); + let mut probe = std::net::TcpStream::connect(server.addr).unwrap(); + probe + .write_all(b"GET / HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n") + .unwrap(); + let mut answer = String::new(); + probe.read_to_string(&mut answer).unwrap(); + assert!(answer.starts_with("HTTP/1.1 404"), "{answer}"); + storage_for_url(&server.url("/m.h5"), &quick()).unwrap(); + assert_eq!(server.requests(), 1, "only the open counts"); + assert_eq!(server.log().len(), 1); +}