From 5d676fb989917c1ebf913f8a38c1ff4648fb3f07 Mon Sep 17 00:00:00 2001 From: Atdunbg Date: Wed, 11 Feb 2026 23:51:58 +0800 Subject: [PATCH] first commit --- .gitignore | 1 + Cargo.lock | 7 +++ Cargo.toml | 6 +++ src/lib.rs | 97 +++++++++++++++++++++++++++++++++ src/main.rs | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 263 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..bdcb978 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "mini_web_server" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..72b688f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "mini_web_server" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..83aae29 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,97 @@ +use std::sync::mpsc; +use std::sync::Arc; +use std::sync::Mutex; +use std::thread; + + +enum Message{ + NewJob(Job), + Terminate, +} + +pub struct ThreadPool{ + workers: Vec, + sender: mpsc::Sender, +} + +type Job = Box; + +impl ThreadPool { + /// Create a new ThreadPool. + /// + /// this size is the number of threads in the pool. + /// + /// # Panics + /// + /// the `new` function will panic if the size is not greater than 0. + 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} + } + + pub fn execute(&self, f: F) + where F: FnOnce() + Send + 'static, + { + let job = Box::new(f); + self.sender.send(Message::NewJob(job)).unwrap(); + } +} + +impl Drop for ThreadPool { + fn drop(&mut self) { + + for _ in &mut self.workers { + self.sender.send(Message::Terminate).unwrap(); + } + println!("shutting down all workers"); + + for workder in &mut self.workers { + println!("shutting down worker {}", workder.id); + + if let Some(thread) = workder.thread.take(){ + thread.join().unwrap(); + } + } + } +} + + +struct Worker{ + id: usize, + thread: Option>, +} + +impl Worker { + fn new(id: usize, receiver: Arc>>) -> Worker { + let thread = thread::spawn(move || loop { + let message = receiver.lock().unwrap().recv().unwrap(); + match message { + Message::NewJob(job) => { + println!("Worker {} got a job. executing.", id); + job(); + } + Message::Terminate => { + println!("Worker {} was told to terminate.", id); + break; + } + + } + }); + + Worker { + id, + thread: Some(thread) + } + } +} + diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..1470cf0 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,152 @@ +use std::fs; +use std::path::Path; +use std::io::{BufRead, BufReader, Write}; +use std::net::{TcpListener, TcpStream}; +use mini_web_server::ThreadPool; + + +fn main() { + let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); + + let pool = ThreadPool::new(10); + + for stream in listener.incoming() { + let stream = stream.unwrap(); + pool.execute(||{ + handle_connection(stream); + }); + } + + println!("shutdown"); +} + +fn handle_connection(mut stream: TcpStream) { + // 1. 用 BufReader 包装 stream,提供按行读取的能力 + let mut reader = BufReader::new(&mut stream); + + // 2. 读取请求行(第一行) + let mut request_line = String::new(); + + if reader.read_line(&mut request_line).is_err() { + // 读取出错,返回 400 + stream.write(b"HTTP/1.1 400 Bad Request\r\n\r\n").unwrap(); + stream.flush().unwrap(); + return; + } + let request_line = request_line.trim(); // 去掉末尾的 \r\n + + // 3. 解析请求行:方法 路径 版本 + let parts: Vec<&str> = request_line.split_whitespace().collect(); + if parts.len() < 3 { + stream.write(b"HTTP/1.1 400 Bad Request\r\n\r\n").unwrap(); + stream.flush().unwrap(); + return; + } + let method = parts[0]; + let path = parts[1]; + + // 4. 只允许 GET + if method != "GET" { + stream.write(b"HTTP/1.1 405 Method Not Allowed\r\n\r\n").unwrap(); + stream.flush().unwrap(); + return; + } + + // 5. 继续读取请求头,直到空行 + loop { + let mut header_line = String::new(); + if reader.read_line(&mut header_line).is_err() { + break; + } + // 空行表示头部结束 + if header_line == "\r\n" || header_line == "\n" { + break; + } + // 这里可以解析头部(目前我们不需要) + } + + // 6. 安全地构建文件路径 + let base_dir = "./public"; + let requested_path = path.trim_start_matches('/'); + if requested_path.contains("..") { + stream.write(b"HTTP/1.1 403 Forbidden\r\n\r\n").unwrap(); + stream.flush().unwrap(); + return; + } + + // v1 + // let file_path = if requested_path.is_empty() { + // format!("{}/index.html", base_dir) + // } else { + // format!("{}/{}", base_dir, requested_path) + // }; + + // v2 + // let file_path = format!("{}/{}", base_dir, requested_path); + + // v3 + // let file_path = if requested_path.is_empty() || path.ends_with('/') { + // format!("{}/index.html", requested_path.trim_end_matches('/')) + // } else { + // requested_path + // }; + + // v4 + let base_path = Path::new(base_dir); + let requested_path = path.trim_start_matches('/'); // 去掉开头的 '/' + + // 禁止路径遍历 + if requested_path.contains("..") { + stream.write(b"HTTP/1.1 403 Forbidden\r\n\r\n").unwrap(); + stream.flush().unwrap(); + return; + } + + let mut full_path = base_path.join(requested_path); + + if full_path.is_dir() { + full_path = full_path.join("index.html"); + } + + // temp debug print + println!("file: {}", full_path.display()); + + let path = Path::new(&full_path); + + // 7. 尝试读取文件并响应 + if path.exists() && path.is_file() { + match fs::read(path) { + Ok(contents) => { + let content_type = match path.extension().and_then(|ext| ext.to_str()) { + Some("html") => "text/html; charset=utf-8", + Some("css") => "text/css; charset=utf-8", + Some("js") => "application/javascript", + Some("png") => "image/png", + Some("jpg" | "jpeg") => "image/jpeg", + Some("gif") => "image/gif", + Some("svg") => "image/svg+xml", + Some("ico") => "image/x-icon", + Some("json") => "application/json", + Some("txt") => "text/plain; charset=utf-8", + _ => "application/octet-stream", + }; + + let status_line = "HTTP/1.1 200 OK\r\n"; + let length_header = format!("Content-Length: {}\r\n", contents.len()); + let type_header = format!("Content-Type: {}\r\n", content_type); + let response_head = format!("{}{}{}\r\n", status_line, type_header, length_header); + + stream.write(response_head.as_bytes()).unwrap(); + stream.write(&contents).unwrap(); + stream.flush().unwrap(); + } + Err(_) => { + stream.write(b"http/1.1 500 internal server error\r\n\r\n").unwrap(); + stream.flush().unwrap(); + } + } + } else { + stream.write(b"HTTP/1.1 404 Not Found\r\n\r\n404 Not Found").unwrap(); + stream.flush().unwrap(); + } +} \ No newline at end of file