diff --git a/Cargo.toml b/Cargo.toml index 0bf2447..a1ee9ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "http-debugger" -version = "0.1.0" +version = "0.2.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/main.rs b/src/main.rs index cd76bfd..17c0928 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,53 +1,97 @@ -use std::collections::HashMap; +use std::collections::{HashMap, BTreeMap}; use std::net::SocketAddr; +use std::time::{SystemTime, UNIX_EPOCH, Duration}; -use hyper::http::HeaderValue; +use hyper::header::HeaderName; +use hyper::http::{HeaderValue}; use hyper::server::conn::Http; use hyper::service::service_fn; -use hyper::{Request, Response, Body}; +use hyper::{Request, Response, Body, StatusCode, body}; +use serde_json::Value; use tokio::net::TcpListener; -use serde::Serialize; +use serde::{Serialize, Serializer}; -fn str_or_repr(v: &HeaderValue) -> String { - match v.to_str() { +/// excessive parsing of headers, because I want to use all json types... +fn parse_header(k: &HeaderName, v: &HeaderValue) -> (String, Value) { + let key = k.to_string(); + let value = match v.to_str() { Ok(str) => { - str.to_string() + match key.as_str() { + "accept" | "accept-encoding" | + "accept-language" => { // lists + Value::from(str.split(",").map(|x| x.trim()).collect::>()) + }, + "upgrade-insecure-requests" => { // booleans + Value::from( + str == "1" || str.to_lowercase() == "true" || + str.to_lowercase() == "t" + ) + }, + "x-real-port" => { // numbers + if let Ok(int) = str.parse::() { + Value::from(int) + } else if let Ok(float) = str.parse::() { + Value::from(float) + } else { + Value::from(str.to_string()) + } + }, + _ => { + Value::from(str.to_string()) + }, + } }, Err(e) => { - format!("{:?} ({})", v, e) + Value::from(format!("{:?} ({})", v, e)) }, - } + }; + + (key, value) +} + +/// yoinked off stackoverflow #42723065 +fn ordered_map(value: &HashMap, serializer: S) -> Result { + value.iter().collect::>().serialize(serializer) } #[derive(Serialize)] struct InspectRequest { - pub host: String, + pub path: String, pub method: String, - pub headers: HashMap, - pub body: Option, pub version: String, + #[serde(serialize_with = "ordered_map")] + pub headers: HashMap, + pub body: Option, + pub time: u128, } -async fn hello(req: Request) -> Result, serde_json::error::Error> { - let response = InspectRequest { - host: req.uri().to_string(), +async fn echo_json(mut req: Request) -> Result, hyper::http::Error> { + let inspect = InspectRequest { + path: req.uri().to_string(), method: req.method().to_string(), version: format!("{:?}", req.version()), - headers: req.headers().iter().map(|x| (x.0.to_string(), str_or_repr(x.1))).collect(), - body: Some(format!("{:?}", req.body())), + headers: req.headers().iter().map(|(k, v)| parse_header(k, v)).collect(), + body: String::from_utf8(body::to_bytes(req.body_mut()).await.unwrap().to_vec()).ok(), + time: SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or(Duration::from_secs(0)).as_millis(), }; - println!(" * {}", serde_json::to_string_pretty(&response)?); + let serialized = serde_json::to_string(&inspect).unwrap_or("[]".to_string()); - Ok(Response::new(serde_json::to_string(&response)?.into())) + // println!(" * {}", serde_json::to_string_pretty(&response)?); + println!(" * {}", serialized); + + Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "application/json") + .body(serialized.into()) } #[tokio::main] pub async fn main() -> Result<(), Box> { pretty_env_logger::init(); - let addr: SocketAddr = ([127, 0, 0, 1], 3000).into(); + let addr: SocketAddr = ([127, 0, 0, 1], 10000).into(); let listener = TcpListener::bind(addr).await?; println!("Listening on http://{}", addr); @@ -56,7 +100,7 @@ pub async fn main() -> Result<(), Box> { tokio::task::spawn(async move { if let Err(err) = Http::new() - .serve_connection(stream, service_fn(hello)) + .serve_connection(stream, service_fn(echo_json)) .await { println!("Error serving connection: {:?}", err);