Skip to content

Instantly share code, notes, and snippets.

@0x24a
Created July 15, 2024 07:06
Show Gist options
  • Save 0x24a/1cf4e9c5597ef48c621607a3e9ce3b8c to your computer and use it in GitHub Desktop.
Save 0x24a/1cf4e9c5597ef48c621607a3e9ce3b8c to your computer and use it in GitHub Desktop.
Yet Another Rust HTTP Server
use std::{collections::HashMap, io::{BufRead, BufReader, Write, Read}, net::TcpListener, thread};
struct HTTP101Request {
method: String,
path: String,
headers: HashMap<String, String>,
body: Vec<u8>,
}
struct HTTP101Response {
response_code: i16,
response_text: String,
headers: HashMap<String, String>,
body: Vec<u8>,
}
struct YARHSError {
message: String,
}
impl std::fmt::Display for YARHSError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
fn resolve_http101_request(request: &str) -> Result<HTTP101Request, YARHSError> {
let mut lines = request.lines();
let headline = lines.next().ok_or(YARHSError { message: String::from("Invalid headline: Empty") })?;
let headline_parts: Vec<&str> = headline.split_whitespace().collect();
if headline_parts.len() != 3 {
return Err(YARHSError { message: String::from("Invalid headline: Invalid length") });
}
let method = headline_parts[0].to_string();
let path = headline_parts[1].to_string();
let mut headers = HashMap::new();
let mut body = Vec::new();
for line in lines {
if line.is_empty() {
break;
}
let elements: Vec<&str> = line.splitn(2, ": ").collect();
if elements.len() == 2 {
headers.insert(elements[0].to_string(), elements[1].to_string());
}
}
// Collect the body
let body_start = request.find("\r\n\r\n").unwrap_or(request.len()) + 4;
if body_start < request.len() {
body.extend_from_slice(&request.as_bytes()[body_start..]);
}
Ok(HTTP101Request {
method,
path,
headers,
body,
})
}
fn construct_http101_response(response: HTTP101Response) -> Vec<u8> {
let mut data = String::new();
data.push_str("HTTP/1.1 ");
data.push_str(&response.response_code.to_string());
data.push_str(" ");
data.push_str(&response.response_text);
data.push_str("\r\n");
for (key, value) in &response.headers {
data.push_str(key);
data.push_str(": ");
data.push_str(value);
data.push_str("\r\n");
}
data.push_str("\r\n");
let mut head = data.as_bytes().to_vec();
head.extend(response.body);
head
}
fn create_app(addr: String, handler: fn(HTTP101Request) -> HTTP101Response) {
println!("Listening YetAnotherRustHTTPServer on http://{}", addr);
let server = TcpListener::bind(addr).expect("Failed to start HTTP server");
for connection in server.incoming() {
match connection {
Ok(stream) => {
thread::spawn(move || {
handle_connection(stream, handler);
});
}
Err(e) => eprintln!("Failed to accept connection: {}", e),
}
}
}
fn handle_connection(mut stream: std::net::TcpStream, handler: fn(HTTP101Request) -> HTTP101Response) {
let mut buf_reader = BufReader::new(&mut stream);
let mut request = String::new();
// Read headers
loop {
let mut line = String::new();
match buf_reader.read_line(&mut line) {
Ok(0) => break, // End of stream
Ok(_) => {
if line == "\r\n" {
break; // End of headers
}
request.push_str(&line);
},
Err(e) => {
eprintln!("Failed to read request header: {}", e);
return;
}
}
}
// Read the body
let content_length = request.find("Content-Length: ")
.and_then(|start| {
let end = request[start..].find("\r\n")?;
request[start + 16..start + end].trim().parse::<usize>().ok()
})
.unwrap_or(0);
let mut body = vec![0; content_length];
if let Err(e) = buf_reader.read_exact(&mut body) {
eprintln!("Failed to read request body: {}", e);
return;
}
request.push_str("\r\n");
request.push_str(&String::from_utf8_lossy(&body));
if request.is_empty() {
return;
}
let request = match resolve_http101_request(&request) {
Ok(req) => req,
Err(e) => {
eprintln!("Failed to resolve request: {}", e);
return;
},
};
let req_method = request.method.clone();
let req_path = request.path.clone();
let mut response = handler(request);
let resp_code = response.response_code;
let resp_text = response.response_text.clone();
response.headers.insert("Server".to_string(), "YetAnotherRustHTTPServer".to_string());
let response = construct_http101_response(response);
if let Err(e) = stream.write_all(&response) {
eprintln!("Failed to write response: {}", e);
}
if let Err(e) = stream.flush() {
eprintln!("Failed to flush stream: {}", e);
}
println!("{} {} - {} {} - {}", req_method, req_path, resp_code, resp_text, stream.peer_addr().expect("Failed to read remote address"));
}
fn main() {
fn handler(request: HTTP101Request) -> HTTP101Response {
let mut headers_processed = String::new();
for item in request.headers.iter(){
headers_processed.push_str(format!("{}: {}<br>", item.0, item.1).as_str());
}
let _ = request.body;
let body=format!("<h1>YetAnotherRustHTTPServer</h1>This is a demo HTTP server by 0x24a using Rust.<h2>Request Infomations</h2>Method: {}<br>Path: {}<br>Headers:<br>{}", request.method, request.path, headers_processed);
let mut headers = HashMap::new();
headers.insert("Content-Type".to_string(), "text/html".to_string());
HTTP101Response {
response_code: 200,
response_text: "OK".to_string(),
headers: headers,
body: body.as_bytes().to_vec(),
}
}
create_app("127.0.0.1:10087".to_string(), handler);
}
@0x24a
Copy link
Author

0x24a commented Jul 15, 2024

I'm new to rust - just 2 days ago. So this may be crappy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment