use std::io::{Read, Write};
use std::net::TcpListener;
pub struct HttpServer {
addr: String,
}
impl HttpServer {
pub fn new(addr: &str) -> Self {
Self {
addr: addr.to_string(),
}
}
pub fn bind_and_serve(
&self,
handler: &dyn Fn(&str, &str, &str) -> HttpResponse,
) -> std::io::Result<()> {
let listener = TcpListener::bind(&self.addr)?;
for stream in listener.incoming() {
let mut stream = stream?;
let mut buf = [0u8; 8192];
let n = stream.read(&mut buf)?;
let raw = String::from_utf8_lossy(&buf[..n]);
let (method, path, body) = parse_request(&raw);
let response = handler(&method, &path, &body);
let header = format!(
"HTTP/1.1 {} {}\r\nContent-Type: {}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
response.status_code,
response.status_text,
response.content_type,
response.body.len()
);
stream.write_all(header.as_bytes())?;
stream.write_all(response.body.as_bytes())?;
}
Ok(())
}
}
pub struct HttpResponse {
pub status_code: u16,
pub status_text: String,
pub content_type: String,
pub body: String,
}
impl HttpResponse {
pub fn ok(body: &str) -> Self {
Self {
status_code: 200,
status_text: "OK".into(),
content_type: "application/json".into(),
body: body.to_string(),
}
}
pub fn bad_request(body: &str) -> Self {
Self {
status_code: 400,
status_text: "Bad Request".into(),
content_type: "application/json".into(),
body: body.to_string(),
}
}
pub fn not_found() -> Self {
Self {
status_code: 404,
status_text: "Not Found".into(),
content_type: "application/json".into(),
body: r#"{"error":"not found"}"#.to_string(),
}
}
pub fn internal_error(msg: &str) -> Self {
Self {
status_code: 500,
status_text: "Internal Server Error".into(),
content_type: "application/json".into(),
body: format!(r#"{{"error":"{msg}"}}"#),
}
}
}
fn parse_request(raw: &str) -> (String, String, String) {
let mut lines = raw.lines();
let first = lines.next().unwrap_or("");
let parts: Vec<&str> = first.split_whitespace().collect();
let method = parts.first().unwrap_or(&"GET").to_string();
let path = parts.get(1).unwrap_or(&"/").to_string();
let body = if let Some(pos) = raw.find("\r\n\r\n") {
raw[pos + 4..].to_string()
} else {
String::new()
};
(method, path, body)
}