diff --git a/experiments/tiny_webservers/.gitignore b/experiments/tiny_webservers/.gitignore new file mode 100644 index 0000000..1ec7ed7 --- /dev/null +++ b/experiments/tiny_webservers/.gitignore @@ -0,0 +1,3 @@ +target/ +Cargo.lock +.vscode/ \ No newline at end of file diff --git a/experiments/tiny_webservers/Cargo.toml b/experiments/tiny_webservers/Cargo.toml new file mode 100644 index 0000000..052ccd3 --- /dev/null +++ b/experiments/tiny_webservers/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "tiny_webserver" +version = "0.1.0" +edition = "2024" + +[dependencies] +axum = "0.8.4" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.140" +tokio = { version = "1.45.1", features = ["macros", "rt-multi-thread"] } diff --git a/experiments/tiny_webservers/src/bin/async.rs b/experiments/tiny_webservers/src/bin/async.rs new file mode 100644 index 0000000..c56fd23 --- /dev/null +++ b/experiments/tiny_webservers/src/bin/async.rs @@ -0,0 +1,34 @@ +// use axum::{ +// routing::{get,post}, +// Router, +// }; +// use serde_json::Result; +// use serde::{Deserialize, Serialize}; + +// #[derive(Serialize, Deserialize, Copy)] +// struct State { +// value: bool +// } + +// static STATE: bool = false; + +// async fn get_state() -> State { +// &STATE +// } + + +// #[tokio::main] +// async fn main() { + + + + +// // build our application with a single route +// let app = Router::new() +// .route("/get", get(get_state)) +// .route("/set", post(set_state)); + +// // run our app with hyper, listening globally on port 3000 +// let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); +// axum::serve(listener, app).await.unwrap(); +// } \ No newline at end of file diff --git a/experiments/tiny_webservers/src/bin/multi_threaded.rs b/experiments/tiny_webservers/src/bin/multi_threaded.rs new file mode 100644 index 0000000..8735587 --- /dev/null +++ b/experiments/tiny_webservers/src/bin/multi_threaded.rs @@ -0,0 +1,62 @@ +use std::env; +use std::io; +use std::io::prelude::*; +use std::net::TcpListener; +use std::net::TcpStream; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; + +fn main() { + // The only shared state of this tiny webserver + let mut hitcount: Arc = Arc::new(AtomicUsize::new(0)); + + // Bind to address given as argument or a default value. + let address = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string()); + println!("Listening on {}", address); + let listener = TcpListener::bind(address).unwrap(); + + // Handle new connections in an infinite loop. + std::thread::scope(|scope| { + for stream in listener.incoming() { + let mut stream = stream.unwrap(); + let hitcount_clone = Arc::clone(&hitcount); + let _ = scope.spawn(move || { + let re = handle_connection(&mut stream, hitcount_clone); + + // Print out any errors + if re.is_err() { + println!("Error: {:?}", re); + } + }); + + } + }) +} + +fn handle_connection(stream: &mut TcpStream, hitcount: Arc) -> io::Result<()> { + let mut buf = [0; 512]; + stream.read(&mut buf).expect("Failed to read from socket."); + let request = str::from_utf8(&buf).expect("Request is not valid utf8"); + + println!("Got request: {}", request.split("\r\n").nth(0).unwrap_or(&request)); + + let (status, content) = if request.starts_with("GET / HTTP/1.1\r\n") { + let current_count = hitcount.fetch_add(1, Ordering::Relaxed) + 1; + ("200 OK", format!("{{\"hits\": {}}}\n", current_count)) + } else { + ("404 NOT FOUND", "404 not found".to_string()) + }; + + let length = content.len(); + let response = format!( + "HTTP/1.1 {status}\r\n\ + Content-Type: application/json\r\n\ + Content-Length: {length} + \r\n\r\n\ + {content}" + ); + + stream.write(response.as_bytes())?; + stream.flush()?; + Ok(()) +} diff --git a/experiments/tiny_webservers/src/bin/single_threaded.rs b/experiments/tiny_webservers/src/bin/single_threaded.rs new file mode 100644 index 0000000..ea58eee --- /dev/null +++ b/experiments/tiny_webservers/src/bin/single_threaded.rs @@ -0,0 +1,54 @@ +use std::env; +use std::io; +use std::io::prelude::*; +use std::net::TcpListener; +use std::net::TcpStream; + +fn main() { + // The only shared state of this tiny webserver + let mut hitcount: usize = 0; + + // Bind to address given as argument or a default value. + let address = env::args().nth(1).unwrap_or("127.0.0.1:8080".to_string()); + println!("Listening on {}", address); + let listener = TcpListener::bind(address).unwrap(); + + // Handle new connections in an infinite loop. + for stream in listener.incoming() { + let mut stream = stream.unwrap(); + let re = handle_connection(&mut stream, &mut hitcount); + + // Print out any errors + if re.is_err() { + println!("Error: {:?}", re); + } + } +} + +fn handle_connection(stream: &mut TcpStream, hitcount: &mut usize) -> io::Result<()> { + let mut buf = [0; 512]; + stream.read(&mut buf).expect("Failed to read from socket."); + let request = str::from_utf8(&buf).expect("Request is not valid utf8"); + + println!("Got request: {}", request.split("\r\n").nth(0).unwrap_or(&request)); + + let (status, content) = if request.starts_with("GET / HTTP/1.1\r\n") { + *hitcount = (*hitcount).saturating_add(1); + ("200 OK", format!("{{\"hits\": {}}}\n", hitcount)) + } else { + ("404 NOT FOUND", "404 not found".to_string()) + }; + + let length = content.len(); + let response = format!( + "HTTP/1.1 {status}\r\n\ + Content-Type: application/json\r\n\ + Content-Length: {length} + \r\n\r\n\ + {content}" + ); + + stream.write(response.as_bytes())?; + stream.flush()?; + Ok(()) +} diff --git a/experiments/tiny_webservers/src/main.rs b/experiments/tiny_webservers/src/main.rs new file mode 100644 index 0000000..e69de29