Remote reads (range-read M2/M3: open_storage, HTTP/S3) and near-complete in-place editing #18

Merged
osobh merged 52 commits from feat/p3-remote-editor into main 2026-09-27 11:15:23 +00:00
2 changed files with 44 additions and 15 deletions
Showing only changes of commit 5062b907bd - Show all commits
+26 -15
View File
@@ -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) {
+18
View File
@@ -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);
}