120 lines
3.5 KiB
Rust
120 lines
3.5 KiB
Rust
use hyper::{Body, Request, Response, Server};
|
|
use hyper::service::{make_service_fn, service_fn};
|
|
use hyper::{Method, StatusCode};
|
|
use std::convert::Infallible;
|
|
use std::net::SocketAddr;
|
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
use tokio::process::Command;
|
|
use httparse;
|
|
|
|
async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
|
// Serialize request into raw HTTP format
|
|
let raw_request = build_raw_http_request(req).await;
|
|
|
|
// Spawn the binary
|
|
let mut child = Command::new("./target/debug/calendar")
|
|
.stdin(std::process::Stdio::piped())
|
|
.stdout(std::process::Stdio::piped())
|
|
.spawn()
|
|
.expect("failed to spawn binary");
|
|
|
|
// Feed raw HTTP request to binary's stdin
|
|
if let Some(mut stdin) = child.stdin.take() {
|
|
let _ = stdin.write_all(&raw_request).await;
|
|
}
|
|
|
|
// Read response from binary's stdout
|
|
let mut stdout = child.stdout.take().expect("no stdout");
|
|
let mut output = Vec::new();
|
|
let _ = stdout.read_to_end(&mut output).await;
|
|
|
|
let _ = child.wait().await;
|
|
|
|
// Parse raw HTTP response
|
|
match parse_raw_http_response(&output) {
|
|
Ok(res) => Ok(res),
|
|
Err(e) => {
|
|
let mut resp = Response::new(Body::from(format!("Failed to parse response: {}", e)));
|
|
*resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
|
|
Ok(resp)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build raw HTTP request string
|
|
async fn build_raw_http_request(req: Request<Body>) -> Vec<u8> {
|
|
let mut raw = String::new();
|
|
|
|
let method = req.method();
|
|
let path = req.uri().path();
|
|
let version = match req.version() {
|
|
hyper::Version::HTTP_11 => "HTTP/1.1",
|
|
_ => "HTTP/1.1",
|
|
};
|
|
|
|
raw.push_str(&format!("{method} {path} {version}\r\n"));
|
|
|
|
for (key, value) in req.headers() {
|
|
if let Ok(val_str) = value.to_str() {
|
|
raw.push_str(&format!("{}: {}\r\n", key, val_str));
|
|
}
|
|
}
|
|
|
|
raw.push_str("\r\n");
|
|
|
|
let body_bytes = hyper::body::to_bytes(req.into_body()).await.unwrap_or_default();
|
|
let mut full = raw.into_bytes();
|
|
full.extend_from_slice(&body_bytes);
|
|
|
|
full
|
|
}
|
|
|
|
// Parse raw HTTP response into hyper::Response<Body>
|
|
fn parse_raw_http_response(bytes: &[u8]) -> Result<Response<Body>, Box<dyn std::error::Error>> {
|
|
let mut headers = [httparse::EMPTY_HEADER; 64];
|
|
let mut res = httparse::Response::new(&mut headers);
|
|
let parsed_len = res.parse(bytes)?.unwrap(); // returns offset
|
|
|
|
let status = res.code.unwrap_or(200);
|
|
let mut builder = Response::builder().status(status);
|
|
|
|
for h in res.headers.iter() {
|
|
builder = builder.header(h.name, std::str::from_utf8(h.value)?);
|
|
}
|
|
|
|
// Get body and append LiveReload script
|
|
let mut body = bytes[parsed_len..].to_vec();
|
|
let livereload_snippet = br#"<script>
|
|
const ws = new WebSocket("ws://localhost:35729");
|
|
ws.onmessage = (msg) => {
|
|
if (msg.data === "reload") location.reload();
|
|
};
|
|
</script>
|
|
<link
|
|
rel="stylesheet"
|
|
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
|
|
>
|
|
"#;
|
|
body.extend_from_slice(b"\n");
|
|
body.extend_from_slice(livereload_snippet);
|
|
|
|
Ok(builder.body(Body::from(body))?)
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
|
|
|
|
let make_svc = make_service_fn(|_conn| async {
|
|
Ok::<_, Infallible>(service_fn(handle_request))
|
|
});
|
|
|
|
println!("Proxy server running at http://{}", addr);
|
|
|
|
if let Err(e) = Server::bind(&addr).serve(make_svc).await {
|
|
eprintln!("server error: {}", e);
|
|
}
|
|
}
|
|
|
|
|