94 lines
3.3 KiB
Rust
94 lines
3.3 KiB
Rust
//! 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<String, Vec<String>> = 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 }
|
|
);
|
|
}
|
|
}
|