Remote reads (range-read M2/M3: open_storage, HTTP/S3) and near-complete in-place editing #18
@@ -3,7 +3,10 @@
|
|||||||
//! and Last-Modified with `If-Match` / `If-Unmodified-Since` (412), and
|
//! and Last-Modified with `If-Match` / `If-Unmodified-Since` (412), and
|
||||||
//! switches to misbehave — ignore ranges (200 with the whole file), cut
|
//! switches to misbehave — ignore ranges (200 with the whole file), cut
|
||||||
//! bodies short, answer 503, respond slowly, send no validators. It counts
|
//! 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)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
@@ -45,7 +48,8 @@ pub struct Shared {
|
|||||||
pub fail_next: AtomicU32,
|
pub fail_next: AtomicU32,
|
||||||
/// Sleep this long before answering each request.
|
/// Sleep this long before answering each request.
|
||||||
pub delay_ms: AtomicU64,
|
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,
|
pub requests: AtomicU64,
|
||||||
/// Body bytes sent.
|
/// Body bytes sent.
|
||||||
pub bytes: AtomicU64,
|
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());
|
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);
|
s.requests.fetch_add(1, Ordering::SeqCst);
|
||||||
let delay = s.delay_ms.load(Ordering::SeqCst);
|
let delay = s.delay_ms.load(Ordering::SeqCst);
|
||||||
if delay > 0 {
|
if delay > 0 {
|
||||||
std::thread::sleep(Duration::from_millis(delay));
|
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
|
if s.fail_next
|
||||||
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |n| n.checked_sub(1))
|
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |n| n.checked_sub(1))
|
||||||
.is_ok()
|
.is_ok()
|
||||||
@@ -241,16 +262,6 @@ fn serve(conn: TcpStream, s: &Shared) -> std::io::Result<()> {
|
|||||||
)?;
|
)?;
|
||||||
continue;
|
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 len = data.len() as u64;
|
||||||
let mut validators = String::new();
|
let mut validators = String::new();
|
||||||
if s.weak_etag.load(Ordering::SeqCst) {
|
if s.weak_etag.load(Ordering::SeqCst) {
|
||||||
|
|||||||
@@ -526,3 +526,21 @@ fn bad_urls_and_statuses_are_clean_errors() {
|
|||||||
"{err}"
|
"{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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user