From 02acb04f650b494de1dfb78474bf7e397e0f2995 Mon Sep 17 00:00:00 2001 From: mtgmonkey Date: Mon, 16 Jun 2025 15:48:31 -0400 Subject: [PATCH] init --- 404.html | 10 ++ Cargo.lock | 289 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 12 +++ flake.lock | 26 +++++ flake.nix | 12 +++ index.html | 11 ++ package.nix | 16 +++ src/lib.rs | 116 +++++++++++++++++++++ src/main.rs | 90 ++++++++++++++++ 9 files changed, 582 insertions(+) create mode 100644 404.html create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 index.html create mode 100644 package.nix create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/404.html b/404.html new file mode 100644 index 0000000..2997a14 --- /dev/null +++ b/404.html @@ -0,0 +1,10 @@ + + + + + 404 NOT_FOUND + + + Page not found. Check you typed the address correctly, or contact the site owner. + + diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3fb98ef --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,289 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "clap" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rust_http_server" +version = "0.1.0" +dependencies = [ + "clap", + "regex", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..bb4357b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rust_http_server" +version = "0.1.0" +authors = ["Andromeda"] +edition = "2024" + +[dependencies] +regex = "1.11.1" +clap = { version = "4.5.40", features = ["derive"] } + +[profile.release] +opt-level = 3 diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..46db729 --- /dev/null +++ b/flake.lock @@ -0,0 +1,26 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1749794982, + "narHash": "sha256-Kh9K4taXbVuaLC0IL+9HcfvxsSUx8dPB5s5weJcc9pc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "ee930f9755f58096ac6e8ca94a1887e0534e2d81", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-unstable", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..9cebb14 --- /dev/null +++ b/flake.nix @@ -0,0 +1,12 @@ +{ + inputs = { + nixpkgs.url = "nixpkgs/nixos-unstable"; + }; + outputs = {nixpkgs, ...}: let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; + in { + packages.${system}.default = pkgs.callPackage ./package.nix {}; + # nixosModules.${system}.default = ./module.nix; + }; +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..4c5ab50 --- /dev/null +++ b/index.html @@ -0,0 +1,11 @@ + + + + + Test + + +

Test

+

Does it work?

+ + diff --git a/package.nix b/package.nix new file mode 100644 index 0000000..73b03ba --- /dev/null +++ b/package.nix @@ -0,0 +1,16 @@ +{rustPlatform, ...}: +rustPlatform.buildRustPackage { + name = "rust_http_server"; + src = ./.; + cargoLock.lockFile = ./Cargo.lock; + nativeBuildInputs = []; + buildInputs = []; + configurePhase = ''''; + buildPhase = ''''; + installPhase = ''''; + meta = { + mainProgram = "rust_http_server"; + description = "bare minimum, serves the current directory"; + homepage = "https://mtgmonkey.net"; + }; +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..98b705d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,116 @@ +use std::sync::{Arc, Mutex, mpsc}; +use std::thread; + +pub enum HTTP_RESPONSE_CODE { + HTTP_200_OK, + HTTP_400_BAD_REQUEST, + HTTP_404_NOT_FOUND, + HTTP_501_NOT_IMPLEMENTED, + HTTP_505_HTTP_VERSION_NOT_SUPPORTED, +} +impl std::fmt::Debug for HTTP_RESPONSE_CODE { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + HTTP_RESPONSE_CODE::HTTP_200_OK => write!(f, "HTTP_200_OK"), + HTTP_RESPONSE_CODE::HTTP_400_BAD_REQUEST => write!(f, "HTTP_400_BAD_REQUEST"), + HTTP_RESPONSE_CODE::HTTP_404_NOT_FOUND => write!(f, "HTTP_404_NOT_FOUND"), + HTTP_RESPONSE_CODE::HTTP_501_NOT_IMPLEMENTED => write!(f, "HTTP_501_NOT_IMPLEMENTED"), + HTTP_RESPONSE_CODE::HTTP_505_HTTP_VERSION_NOT_SUPPORTED => { + write!(f, "HTTP_505_HTTP_VERSION_NOT_SUPPORTED") + } + } + } +} +impl std::fmt::Display for HTTP_RESPONSE_CODE { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + HTTP_RESPONSE_CODE::HTTP_200_OK => write!(f, "200 OK"), + HTTP_RESPONSE_CODE::HTTP_400_BAD_REQUEST => write!(f, "400 BAD REQUEST"), + HTTP_RESPONSE_CODE::HTTP_404_NOT_FOUND => write!(f, "404 NOT FOUND"), + HTTP_RESPONSE_CODE::HTTP_501_NOT_IMPLEMENTED => write!(f, "501 NOT IMPLEMENTED"), + HTTP_RESPONSE_CODE::HTTP_505_HTTP_VERSION_NOT_SUPPORTED => { + write!(f, "HTTP_505_HTTP_VERSION_NOT_SUPPORTED") + } + } + } +} + +pub struct ThreadPool { + workers: Vec, + sender: Option>, +} +impl ThreadPool { + 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(&self, f: F) + where + F: FnOnce() + Send + 'static, + { + let job = Box::new(f); + match self.sender.as_ref() { + Some(sender) => match sender.send(job) { + Ok(_) => {} + Err(e) => println!("[ERROR]: {e}"), + }, + None => println!("[ERROR] no sender to execute!"), + } + } +} +impl Drop for ThreadPool { + fn drop(&mut self) { + drop(self.sender.take()); + for worker in self.workers.drain(..) { + println!("Shutting down worker {}", worker.id); + match worker.thread.join() { + Ok(join) => join, + Err(_) => println!( + "Failed to join thread while shutting down worker {}", + worker.id + ), + } + } + } +} + +type Job = Box; + +struct Worker { + id: usize, + thread: thread::JoinHandle<()>, +} +impl Worker { + fn new(id: usize, receiver: Arc>>) -> Worker { + let thread = thread::spawn(move || { + loop { + match receiver.lock() { + Ok(receiver) => { + let message = receiver.recv(); + match message { + Ok(job) => { + println!("Worker {id} got a job, executing."); + job(); + } + Err(_) => { + println!("Worker {id} disconnected, shutting down."); + break; + } + } + } + Err(e) => println!("[ERROR]: {e}"), + } + } + }); + Worker { id, thread } + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..1654c83 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,90 @@ +use clap::Parser; +use regex::Regex; +use rust_http_server::HTTP_RESPONSE_CODE::*; +use rust_http_server::*; +use std::fs; +use std::io::{BufReader, prelude::*}; +use std::net::{SocketAddr, TcpListener, TcpStream}; + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Args { + #[arg(short, long, default_value_t = 8383)] + port: u16, +} + +fn main() { + let args = Args::parse(); + let listener = TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], args.port))) + .expect(&format!("Failed to bind to port {}", args.port)); + println!("Successfully started server at port {}", args.port); + let pool = ThreadPool::new(8); + for stream in listener.incoming() { + pool.execute(|| { + handle_connection(stream.unwrap()); + }); + } +} + +fn handle_connection(stream: TcpStream) { + let contents = match parse_connection_to_header(&stream) { + Ok(request) => { + println!("Request: {request}"); + match parse_header_to_filepath(request) { + Ok(filepath) => read_file(filepath), + Err(err) => Err(err), + } + } + Err(err) => Err(err), + }; + respond(&stream, contents); +} + +fn parse_connection_to_header(stream: &TcpStream) -> Result { + // read first line of buffer + let buf_reader = BufReader::new(stream); + match buf_reader.lines() { + mut lines => match lines.next() { + Some(Ok(line)) => Ok(line), + _ => Err(HTTP_400_BAD_REQUEST), + }, + } +} + +fn parse_header_to_filepath(request: String) -> Result { + let regex = Regex::new(r"GET /(?[^/].*|) HTTP/1\.1").unwrap(); + match regex.captures(&request) { + Some(captures) => Ok(captures["path"].to_string()), + None => Err(HTTP_400_BAD_REQUEST), + } +} + +fn read_file(filename: String) -> Result { + match fs::read_to_string(if filename == "" { + "index.html" + } else { + &filename + }) { + Ok(file) => Ok(file), + Err(_) => Err(HTTP_404_NOT_FOUND), + } +} + +fn respond(mut stream: &TcpStream, contents: Result) { + let (status, length, contents) = match contents { + Ok(contents) => + ( format!("HTTP/1.1 {HTTP_200_OK}") + , contents.len() + , contents + ), + Err(err) => + ( format!("HTTP/1.1 {err}") + , "404 NOT FOUNDPage not found. Check that you typed the address correctly, or contact the site owner.".len() + , "404 NOT FOUNDPage not found. Check that you typed the address correctly, or contact the site owner.".to_string() + ), + }; + println!("Response: {status}\r\nContent-Length: {length}\r\n\r\n{contents}"); + stream + .write_all(format!("{status}\r\nContent-Length: {length}\r\n\r\n{contents}").as_bytes()) + .expect("Error writing to stream"); +}