use core::fmt; use std::{borrow::Cow, fmt::format, net::SocketAddr, sync::Arc}; use httparse::{Request, Status}; use smol::{ LocalExecutor, future, io::{AsyncReadExt, AsyncWriteExt}, net::{TcpListener, TcpStream}, }; fn main() { let exec = LocalExecutor::new(); let arxc = Arc::new(exec); let moving_arxc = Arc::clone(&arxc); future::block_on(arxc.run(async move { async_main(moving_arxc).await })); } async fn async_main(ex: Arc>) { let addr = SocketAddr::from(([0, 0, 0, 0], 12345)); let listen = TcpListener::bind(addr).await.unwrap(); loop { let (stream, caddr) = listen.accept().await.unwrap(); println!("accepted!"); ex.spawn(async move { handle_request(stream, caddr).await }) .detach(); } } async fn handle_request(mut stream: TcpStream, caddr: SocketAddr) { let mut buffer = vec![0; 1024]; let mut data_end = 0; loop { println!("Waiting on read"); match stream.read(&mut buffer[data_end..]).await { Err(_err) => { eprintln!("Error in handle_request during read. Bailing."); return; } Ok(bytes_read) => { data_end += bytes_read; println!("read {bytes_read}"); } } let mut headers = [httparse::EMPTY_HEADER; 64]; let mut req = Request::new(&mut headers); match req.parse(&buffer[..data_end]) { Err(err) => { eprintln!("Error in handle_request during parsing. Bailing.\nerror: {err}"); return; } Ok(Status::Partial) => { // Going around again. // Make sure we have room in the buffer. 512 seems good :) if buffer.len() == data_end { buffer.resize(buffer.len() + 512, 0); } println!("partial!"); continue; } Ok(Status::Complete(body_offset)) => { println!("Got entire request!"); println!("method: {}", req.method.unwrap()); println!("path: {}", req.path.unwrap()); println!("body_offset: {body_offset}"); } } // Fall through the match. We should guarantee that, if we end up here, // we got a Status::Complete from the parser. let response = form_response(req, caddr).await; if let Err(err) = stream.write_all(&response).await { eprintln!("Error in handle_request sending response back. Bailing.\nError: {err}"); return; } else { return; } } } fn error(code: u16, msg: &'static str) -> Vec { format!( "HTTP/1.1 {code} {msg}\n\ server: piper/0.1\n\ content-length: 0\n\ cache-control: no-store\n\n" ) .as_bytes() .to_vec() } fn get_header<'h>( headers: &'h [httparse::Header<'h>], key: &'static str, ) -> Result, Vec> { headers .iter() .find(|h| h.name.to_lowercase() == key) .map(|v| str::from_utf8(v.value).map_err(|_| error(400, "malformed header"))) .transpose() } /// Create a response, whatever it is. This function forms responses for /// success, and errors. async fn form_response<'req>(request: Request<'req, 'req>, caddr: SocketAddr) -> Vec { let is_head = match request.method.unwrap() { "GET" => false, "HEAD" => true, // This is not strictly correct, but it's correct enough? :) // 405 is "The request method is known by the server but // is not supported by the target resource" _ => return error(405, "we do not support that request method"), }; let minor = request.version.unwrap(); let status = 200; let message = "Gotcha"; let requested_type = match get_header(request.headers, "content-type") { Err(e) => return e, Ok(None) | Ok(Some("text/plain")) => ContentType::Plain, Ok(Some("application/json")) => ContentType::Json, Ok(Some(_)) => return error(415, "unsupported content-type"), }; // Make our response body! let body = match requested_type { ContentType::Plain => format!("{}", caddr.ip()), ContentType::Json => format!(r#"{{"ip": "{}"}}"#, caddr.ip()), }; let length = body.len(); // And now we form the request :D let mut response = format!("HTTP/1.{minor} {status} {message}\n"); macro_rules! header { ($content:expr) => { response.push_str(&format!($content)); response.push('\n'); }; } header!("Content-Length: {length}"); header!("Content-Type: {requested_type}"); header!("Cache-Control: no-store"); if !is_head { response.push('\n'); response.push_str(&body); } response.into_bytes() } enum ContentType { Plain, Json, } impl fmt::Display for ContentType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ContentType::Plain => write!(f, "text/plain"), ContentType::Json => write!(f, "application/json"), } } }