diff --git a/core/http/src/handlers.rs b/core/http/src/handlers.rs new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/core/http/src/lib.rs b/core/http/src/lib.rs index 7d12d9af8195bf5e19d10c7b592b359ccd014149..1d8e24d1c71a2f52a2a6c5619060251ba16678af 100644 --- a/core/http/src/lib.rs +++ b/core/http/src/lib.rs @@ -1,14 +1,74 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right +use std::{ + fs, + io::{BufRead, BufReader, Write}, + net::{TcpListener, TcpStream}, + thread::available_parallelism, +}; + +use threading::ThreadPool; + +mod handlers; +mod routing; +mod threading; + +pub struct Config { + address: TcpListener, + workers: usize, + threadpool: ThreadPool, } -#[cfg(test)] -mod tests { - use super::*; +impl Config { + pub fn mount(self, mountpoint: &str, routes: Vec<routing::Route>) {} + pub fn launch(self) { + for stream in self.address.incoming() { + let stream = stream.unwrap(); + self.threadpool.execute(|| handle_connection(stream)) + } + } +} + +fn handle_connection(mut stream: TcpStream) { + let buf_reader = BufReader::new(&mut stream); + let http_request: Vec<_> = buf_reader + .lines() + .map(|result| result.unwrap()) + .take_while(|line| !line.is_empty()) + .collect(); - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + let status_line = "HTTP/1.1 200 OK"; + let contents = fs::read_to_string("hello.html").unwrap(); + let length = contents.len(); + + let response = format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"); + + stream.write_all(response.as_bytes()).unwrap(); +} + +pub fn build(ip: &str) -> Config { + let listener = TcpListener::bind(ip).unwrap(); + let ip = ip.splitn(2, ":").collect::<Vec<&str>>(); + if ip.len() != 2 { + panic!("Invalid IP Address"); + } + let port = ip[1]; + let ip = ip[0]; + let workers = available_parallelism().unwrap().get(); + let threadpool = ThreadPool::new(workers); + println!( + "\x1b[34mâš™ Configuration\x1b[0m + >> \x1b[34mIp\x1b[0m: {ip} + >> \x1b[34mPort\x1b[0m: {port} + >> \x1b[34mWorkers\x1b[0m: {workers} +\n +Server has launched from {ip}:{port}. +" + ); + Config { + address: listener, + workers, + threadpool, } } + +#[cfg(test)] +mod tests {} diff --git a/core/http/src/routing.rs b/core/http/src/routing.rs new file mode 100644 index 0000000000000000000000000000000000000000..6ae9a7398c015629ecf7c07d90206e0302747856 --- /dev/null +++ b/core/http/src/routing.rs @@ -0,0 +1,2 @@ +pub struct Route; +pub struct RoutInfo; diff --git a/core/http/src/threading.rs b/core/http/src/threading.rs new file mode 100644 index 0000000000000000000000000000000000000000..b59bec8320b39123b7c486cd2c9d6b1373e11a97 --- /dev/null +++ b/core/http/src/threading.rs @@ -0,0 +1,90 @@ +use std::{ + sync::{mpsc, Arc, Mutex}, + thread, +}; + +pub struct ThreadPool { + workers: Vec<Worker>, + sender: Option<mpsc::Sender<Job>>, +} + +type Job = Box<dyn FnOnce() + Send + 'static>; + +impl ThreadPool { + /// Create a new ThreadPool. + /// + /// The size is the number of threads in the pool. + /// + /// # Panics + /// + /// The `new` function will panic if the size is zero. + pub fn new(size: usize) -> ThreadPool { + assert!(size > 0); + + let (sender, receiver) = mpsc::channel(); + + let receiver = Arc::new(Mutex::new(receiver)); + + let mut workers = Vec::with_capacity(size); + + for id in 0..size { + workers.push(Worker::new(id, Arc::clone(&receiver))); + } + ThreadPool { + workers, + sender: Some(sender), + } + } + + pub fn execute<F>(&self, f: F) + where + F: FnOnce() + Send + 'static, + { + let job = Box::new(f); + + self.sender.as_ref().unwrap().send(job).unwrap(); + } +} + +impl Drop for ThreadPool { + fn drop(&mut self) { + drop(self.sender.take()); + + for worker in &mut self.workers { + println!("Shutting down worker {}", worker.id); + + if let Some(thread) = worker.thread.take() { + thread.join().unwrap(); + } + } + } +} +struct Worker { + id: usize, + thread: Option<thread::JoinHandle<()>>, +} + +impl Worker { + fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker { + let thread = thread::spawn(move || loop { + let message = receiver.lock().unwrap().recv(); + + match message { + Ok(job) => { + println!("Worker {id} got a job; executing."); + + job(); + } + Err(_) => { + println!("Worker {id} disconnected; shutting down."); + break; + } + } + }); + + Worker { + id, + thread: Some(thread), + } + } +} diff --git a/q b/q new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/site/Cargo.lock b/site/Cargo.lock index d1bf5078f09d5e69784d6dfd70bfab679737d362..fac1130d38de5e4962dadd74bae9f23f1cf1e92b 100644 --- a/site/Cargo.lock +++ b/site/Cargo.lock @@ -2,6 +2,13 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "http" +version = "0.1.0" + [[package]] name = "site" version = "0.1.0" +dependencies = [ + "http", +] diff --git a/site/Cargo.toml b/site/Cargo.toml index e6a315340d89f3e8aa591389c7be8147b0c83995..d4e9a4aa28539c317f6e90f0c703bdc4ce96d155 100644 --- a/site/Cargo.toml +++ b/site/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +http = { path = "../core/http" } diff --git a/site/hello.html b/site/hello.html new file mode 100644 index 0000000000000000000000000000000000000000..ff4f652fb0a10afb80abf769b19610998e15d290 --- /dev/null +++ b/site/hello.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <title>Hello!</title> + </head> + <body> + <h1>Hello!</h1> + <p>Hi from Rust</p> + </body> +</html> diff --git a/site/src/main.rs b/site/src/main.rs index e7a11a969c037e00a796aafeff6258501ec15e9a..435d748cd9a50dabeeef5a540582d5d5b13b71ba 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -1,3 +1,3 @@ fn main() { - println!("Hello, world!"); + http::build("192.168.179.2:8000").launch(); }