Files
sausage/userland/git/tests/http-server/src/main.rs
T
surma 4b27e9afd5 userland/git: git-compatible CLI on v2's gix code (T10)
Local workflow, smart-HTTP clone/fetch/push and public HTTPS clone/fetch match
native git in stdout and exit codes. Only WASI imports (wasi:http, exit-with-code).
2026-11-06 16:07:57 +00:00

87 lines
2.8 KiB
Rust

//! A local HTTP CGI server for integration tests against native git http-backend.
use std::{
io::{Read, Write},
net::TcpListener,
process::{Command, Stdio},
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<_> = std::env::args().collect();
let listener = TcpListener::bind("127.0.0.1:0")?;
std::fs::write(&args[2], listener.local_addr()?.port().to_string())?;
for stream in listener.incoming() {
let mut stream = stream?;
if let Err(error) = serve(&mut stream, &args[1]) {
eprintln!("request failed: {error}");
}
}
Ok(())
}
fn serve(io: &mut (impl Read + Write), root: &str) -> Result<(), Box<dyn std::error::Error>> {
let mut header = Vec::new();
while !header.ends_with(b"\r\n\r\n") {
let mut byte = [0];
io.read_exact(&mut byte)?;
header.push(byte[0]);
}
let text = std::str::from_utf8(&header)?;
let mut lines = text.split("\r\n");
let request: Vec<_> = lines
.next()
.ok_or("no request")?
.split_whitespace()
.collect();
let mut length = 0;
let mut content_type = "";
for line in lines {
if let Some((key, value)) = line.split_once(':') {
if key.eq_ignore_ascii_case("content-length") {
length = value.trim().parse()?;
}
if key.eq_ignore_ascii_case("content-type") {
content_type = value.trim();
}
}
}
let mut body = vec![0; length];
io.read_exact(&mut body)?;
let (path, query) = request[1].split_once('?').unwrap_or((request[1], ""));
let mut child = Command::new("git")
.arg("http-backend")
.env("GIT_PROJECT_ROOT", root)
.env("GIT_HTTP_EXPORT_ALL", "1")
.env("PATH_INFO", path)
.env("QUERY_STRING", query)
.env("REQUEST_METHOD", request[0])
.env("CONTENT_TYPE", content_type)
.env("CONTENT_LENGTH", length.to_string())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
child.stdin.take().ok_or("no CGI stdin")?.write_all(&body)?;
let response = child.wait_with_output()?;
let split = response
.stdout
.windows(4)
.position(|w| w == b"\r\n\r\n")
.ok_or("invalid CGI response")?;
let headers = std::str::from_utf8(&response.stdout[..split])?;
let status = headers
.lines()
.find_map(|l| l.strip_prefix("Status: "))
.unwrap_or("200 OK");
write!(
io,
"HTTP/1.1 {status}\r\nConnection: close\r\nContent-Length: {}\r\n",
response.stdout.len() - split - 4
)?;
for line in headers.split("\r\n") {
if !line.starts_with("Status:") {
write!(io, "{line}\r\n")?;
}
}
io.write_all(b"\r\n")?;
io.write_all(&response.stdout[split + 4..])?;
io.flush()?;
Ok(())
}