use std::io::{Read as _, Write as _};
use std::net::{TcpListener, TcpStream};
use std::sync::mpsc::{Receiver, channel};
pub(crate) struct Fake {
pub(crate) port: u16,
pub(crate) request: Receiver<String>,
}
pub(crate) fn serve<F>(reply: F) -> Fake
where
F: FnOnce(&mut TcpStream) + Send + 'static,
{
let listener = TcpListener::bind(("127.0.0.1", 0)).expect("bind loopback");
let port = listener.local_addr().expect("local addr").port();
let (tx, request) = channel();
std::thread::spawn(move || {
let Ok((mut sock, _)) = listener.accept() else {
return;
};
let _ = tx.send(read_request(&mut sock));
reply(&mut sock);
});
Fake { port, request }
}
fn read_request(sock: &mut TcpStream) -> String {
let mut buf = Vec::new();
let mut tmp = [0u8; 1024];
loop {
if let Some(end) = super::find_subslice(&buf, b"\r\n\r\n") {
let head = String::from_utf8_lossy(&buf[..end]).into_owned();
let len = head
.lines()
.find_map(|l| l.strip_prefix("Content-Length: "))
.and_then(|v| v.trim().parse::<usize>().ok())
.unwrap_or(0);
if buf.len() >= end + 4 + len {
break;
}
}
match sock.read(&mut tmp) {
Ok(0) | Err(_) => break,
Ok(n) => buf.extend_from_slice(&tmp[..n]),
}
}
String::from_utf8_lossy(&buf).into_owned()
}
pub(crate) fn sse(sock: &mut TcpStream, chunks: &[&[u8]]) {
let _ = sock.write_all(b"HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\n\r\n");
let _ = sock.flush();
for c in chunks {
if sock.write_all(c).is_err() {
return;
}
let _ = sock.flush();
std::thread::sleep(std::time::Duration::from_millis(2));
}
}
pub(crate) fn refuse(sock: &mut TcpStream, status: &str, ctype: &str, body: &str) {
let _ = sock.write_all(
format!(
"HTTP/1.1 {status}\r\nContent-Type: {ctype}\r\nContent-Length: {}\r\n\r\n{body}",
body.len()
)
.as_bytes(),
);
let _ = sock.flush();
}
pub(crate) fn dead_port() -> u16 {
let l = TcpListener::bind(("127.0.0.1", 0)).expect("bind loopback");
l.local_addr().expect("local addr").port()
}