#![allow(
dead_code,
reason = "each test target uses a different subset of these helpers"
)]
use std::io::{Read, Write};
use std::os::unix::net::{UnixListener, UnixStream};
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
const MAX_HEAD: usize = 64 * 1024;
#[derive(Debug)]
pub struct Request {
pub method: String,
pub target: String,
pub body: Vec<u8>,
}
pub fn serve<H>(socket: &Path, stop: Arc<AtomicBool>, handle: H)
where
H: Fn(UnixStream) + Send + 'static,
{
let listener = UnixListener::bind(socket).expect("bind mock docker socket");
std::thread::spawn(move || {
for conn in listener.incoming() {
if stop.load(Ordering::Relaxed) {
break;
}
match conn {
Ok(stream) => handle(stream),
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {}
Err(_) => break,
}
}
});
}
pub fn stop(socket: &Path, flag: &AtomicBool) {
flag.store(true, Ordering::Relaxed);
let _ = UnixStream::connect(socket);
let _ = std::fs::remove_file(socket);
}
pub fn read_request(stream: &mut UnixStream) -> Option<Request> {
let mut buf = Vec::new();
let mut chunk = [0u8; 8192];
let head_end = loop {
if let Some(i) = find(&buf, b"\r\n\r\n") {
break i + 4;
}
if buf.len() > MAX_HEAD {
return None;
}
let n = stream.read(&mut chunk).ok()?;
if n == 0 {
return None;
}
buf.extend_from_slice(&chunk[..n]);
};
let head = String::from_utf8_lossy(&buf[..head_end]).into_owned();
let mut lines = head.lines();
let mut request_line = lines.next()?.split_whitespace();
let method = request_line.next()?.to_string();
let target = request_line.next()?.to_string();
let mut content_length = 0usize;
let mut expect_continue = false;
for line in lines {
let Some((name, value)) = line.split_once(':') else {
continue;
};
let value = value.trim();
if name.eq_ignore_ascii_case("content-length") {
content_length = value.parse().ok()?;
} else if name.eq_ignore_ascii_case("expect") && value.eq_ignore_ascii_case("100-continue")
{
expect_continue = true;
}
}
let mut body = buf.split_off(head_end);
if expect_continue && content_length > body.len() {
stream.write_all(b"HTTP/1.1 100 Continue\r\n\r\n").ok()?;
stream.flush().ok()?;
}
while body.len() < content_length {
let n = stream.read(&mut chunk).ok()?;
if n == 0 {
return None;
}
body.extend_from_slice(&chunk[..n]);
}
body.truncate(content_length);
Some(Request {
method,
target,
body,
})
}
fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack.windows(needle.len()).position(|w| w == needle)
}