use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
pub struct StubServer {
pub base_url: String,
hits: Arc<AtomicUsize>,
}
impl StubServer {
pub async fn serve_raw(response: impl Into<Vec<u8>>) -> Self {
let response = response.into();
Self::spawn(move |_n| Some(response.clone())).await
}
pub async fn spawn<F>(responder: F) -> Self
where
F: Fn(usize) -> Option<Vec<u8>> + Send + Sync + 'static,
{
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let hits = Arc::new(AtomicUsize::new(0));
let counter = hits.clone();
let responder = Arc::new(responder);
tokio::spawn(async move {
loop {
let Ok((mut sock, _)) = listener.accept().await else {
break;
};
let n = counter.fetch_add(1, Ordering::SeqCst);
let responder = responder.clone();
tokio::spawn(async move {
let mut buf = [0u8; 2048];
let _ = sock.read(&mut buf).await;
if let Some(resp) = responder(n) {
let _ = sock.write_all(&resp).await;
let _ = sock.flush().await;
}
});
}
});
Self {
base_url: format!("http://{addr}"),
hits,
}
}
pub fn hits(&self) -> usize {
self.hits.load(Ordering::SeqCst)
}
}
pub async fn serve_never_responds() -> StubServer {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let hits = Arc::new(AtomicUsize::new(0));
let counter = hits.clone();
tokio::spawn(async move {
let mut held = Vec::new();
loop {
let Ok((mut sock, _)) = listener.accept().await else {
break;
};
counter.fetch_add(1, Ordering::SeqCst);
let mut buf = [0u8; 2048];
let _ = sock.read(&mut buf).await;
held.push(sock);
}
});
StubServer {
base_url: format!("http://{addr}"),
hits,
}
}
pub async fn serve_endless_stream(chunk: usize, delay_ms: u64) -> StubServer {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let hits = Arc::new(AtomicUsize::new(0));
let counter = hits.clone();
tokio::spawn(async move {
loop {
let Ok((mut sock, _)) = listener.accept().await else {
break;
};
counter.fetch_add(1, Ordering::SeqCst);
tokio::spawn(async move {
let mut buf = [0u8; 2048];
let _ = sock.read(&mut buf).await;
let head = "HTTP/1.1 200 OK\r\n\
Content-Type: application/octet-stream\r\n\
Content-Length: 1073741824\r\n\
\r\n";
if sock.write_all(head.as_bytes()).await.is_err() {
return;
}
let payload = vec![b'x'; chunk];
loop {
if sock.write_all(&payload).await.is_err() {
break;
}
if sock.flush().await.is_err() {
break;
}
tokio::time::sleep(std::time::Duration::from_millis(delay_ms)).await;
}
});
}
});
StubServer {
base_url: format!("http://{addr}"),
hits,
}
}
pub fn http_response(status: u16, content_type: &str, body: &str) -> Vec<u8> {
format!(
"HTTP/1.1 {status} OK\r\n\
Content-Type: {content_type}\r\n\
Content-Length: {}\r\n\
Connection: close\r\n\
\r\n\
{body}",
body.len()
)
.into_bytes()
}