use reqwest::Url;
use std::io::{BufRead, BufReader, Write};
use std::net::{TcpListener, TcpStream};
#[cfg(feature = "image-downloader")]
use std::sync::Arc;
pub fn serve(max_requests: usize, respond: impl Fn(&str, &mut TcpStream) + Send + 'static) -> Url {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let base = Url::parse(&format!("http://{}/", listener.local_addr().unwrap())).unwrap();
std::thread::spawn(move || {
for stream in listener.incoming().take(max_requests) {
let Ok(mut stream) = stream else { continue };
let request_line = read_request(&stream);
respond(&request_line, &mut stream);
}
});
base
}
#[cfg(feature = "image-downloader")]
pub fn serve_parallel(
max_requests: usize,
respond: impl Fn(&str, &mut TcpStream) + Send + Sync + 'static,
) -> Url {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let base = Url::parse(&format!("http://{}/", listener.local_addr().unwrap())).unwrap();
let respond = Arc::new(respond);
std::thread::spawn(move || {
for stream in listener.incoming().take(max_requests) {
let Ok(mut stream) = stream else { continue };
let respond = respond.clone();
std::thread::spawn(move || {
let request_line = read_request(&stream);
respond(&request_line, &mut stream);
});
}
});
base
}
fn read_request(stream: &TcpStream) -> String {
let mut reader = BufReader::new(stream.try_clone().unwrap());
let mut request_line = String::new();
_ = reader.read_line(&mut request_line);
let mut line = String::new();
while reader.read_line(&mut line).is_ok_and(|n| n > 2) {
line.clear();
}
request_line
}
pub fn write_response(stream: &mut TcpStream, content_type: &str, body: &str) {
write_status_response(stream, "200 OK", content_type, body);
}
pub fn write_status_response(stream: &mut TcpStream, status: &str, content_type: &str, body: &str) {
_ = write!(
stream,
"HTTP/1.1 {status}\r\nContent-Type: {content_type}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}",
body.len()
);
}
pub fn write_huge_content_length(stream: &mut TcpStream, content_type: &str) {
_ = write!(
stream,
"HTTP/1.1 200 OK\r\nContent-Type: {content_type}\r\nContent-Length: 1000000000000\r\nConnection: close\r\n\r\n"
);
}
#[cfg(feature = "image-downloader")]
pub fn write_bytes_response(stream: &mut TcpStream, content_type: &str, body: &[u8]) {
_ = write!(
stream,
"HTTP/1.1 200 OK\r\nContent-Type: {content_type}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
body.len()
);
_ = stream.write_all(body);
}
#[cfg(feature = "image-downloader")]
pub fn write_chunked_response(stream: &mut TcpStream, content_type: &str, body: &[u8]) {
_ = write!(
stream,
"HTTP/1.1 200 OK\r\nContent-Type: {content_type}\r\nTransfer-Encoding: chunked\r\nConnection: close\r\n\r\n{:x}\r\n",
body.len()
);
_ = stream.write_all(body);
_ = stream.write_all(b"\r\n0\r\n\r\n");
}
pub fn write_endless_body(stream: &mut TcpStream, content_type: &str) {
if write!(
stream,
"HTTP/1.1 200 OK\r\nContent-Type: {content_type}\r\nTransfer-Encoding: chunked\r\nConnection: close\r\n\r\n"
)
.is_err()
{
return;
}
let chunk = vec![b'a'; 64 * 1024];
while write!(stream, "{:x}\r\n", chunk.len()).is_ok()
&& stream.write_all(&chunk).is_ok()
&& stream.write_all(b"\r\n").is_ok()
{}
}