//! Deterministic native HTTP fixture for curl comparisons. use std::{ collections::BTreeMap, io::{BufRead, BufReader, Read, Write}, net::TcpListener, }; fn main() { let ready = std::env::args().nth(1).expect("readiness path"); let listener = TcpListener::bind("127.0.0.1:0").unwrap(); std::fs::write(ready, listener.local_addr().unwrap().port().to_string()).unwrap(); for stream in listener.incoming() { let mut stream = stream.unwrap(); let mut read = BufReader::new(stream.try_clone().unwrap()); let mut first = String::new(); read.read_line(&mut first).unwrap(); let words: Vec<_> = first.split_whitespace().collect(); if words.len() < 2 { continue; } let method = words[0]; let path = words[1]; let mut headers: BTreeMap> = BTreeMap::new(); let mut length = 0; loop { let mut line = String::new(); read.read_line(&mut line).unwrap(); if line == "\r\n" || line.is_empty() { break; } let (name, value) = line.split_once(':').unwrap(); let name = name.to_ascii_lowercase(); let value = value.trim(); if name == "content-length" { length = value.parse().unwrap(); } if [ "accept", "content-type", "authorization", "user-agent", "x-test", ] .contains(&name.as_str()) { headers.entry(name).or_default().push(value.into()); } } let mut body = vec![0; length]; read.read_exact(&mut body).unwrap(); let echo = serde_json::json!({"method":method,"path":path,"body":String::from_utf8_lossy(&body),"headers":headers}).to_string(); let (status, reason, response, extra) = match path { "/plain" => (200, "OK", "hello\n".into(), "".into()), "/404" => (404, "Not Found", "missing\n".into(), "".into()), "/redirect" => ( 302, "Found", "redirect\n".into(), "location: /echo\r\n".into(), ), "/303" => ( 303, "See Other", "redirect\n".into(), "location: /echo\r\n".into(), ), "/307" => ( 307, "Temporary Redirect", "redirect\n".into(), "location: /echo\r\n".into(), ), "/loop" => ( 302, "Found", "redirect\n".into(), "location: /loop\r\n".into(), ), "/slow" => { std::thread::sleep(std::time::Duration::from_millis(100)); (200, "OK", "slow\n".into(), "".into()) } _ => (200, "OK", echo, "".into()), }; let response: String = response; let extra: String = extra; let _ = write!( stream, "HTTP/1.1 {status} {reason}\r\ncontent-type: text/plain\r\ncontent-length: {}\r\n{extra}\r\n{}", response.len(), if method == "HEAD" { "" } else { &response } ); } }