# Simple Web Server 修改版 Rust 权威指南中最后实现了一个简单的 Http-Server ,我尝试修改了下。使其能够简单可用,能解析路径和图片。也就是说,在一个 Hugo 生成的 public 下运行能够正常访问网站。 ## CODE 首先执行 cargo new p20-web-server 根目录下创建一个 index.html ```html Hello!

Hello!

Hi from Rust

``` lib.rs 和权威指南中是一样的,只增加了一个 return_404() 作为默认的 404 主页。 src\lib.rs ```rust 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 { /// 创建线程池。传入线程池中线程的数量。 /// /// # Panics /// /// `new` 函数在 size 为 0 时会 panic。 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) { println!("Sending terminate message to all workers."); for _ in &mut self.workers { self.sender.send(Message::Terminate).unwrap(); } println!("Shutting down all workers."); 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>, } 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), } } } pub fn return_404() -> &'static str { " 404

404

" } ``` src/main.rs ```rust use p20_web_server::ThreadPool; use std::fs; use std::io::prelude::*; use std::net::TcpListener; use std::net::TcpStream; fn main() { let address = "127.0.0.1:7878"; println!("Server is Listening http://{address}"); let listener = TcpListener::bind(address).unwrap(); let pool = ThreadPool::new(4); for stream in listener.incoming() { let stream = stream.unwrap(); pool.execute(|| { handle_connection(stream); }); } println!("Shutting down."); } fn handle_connection(mut stream: TcpStream) { let mut buffer = [0; 1024]; stream.read(&mut buffer).unwrap(); let requires = String::from_utf8_lossy(&buffer[..]); //读取 let first_line = requires.lines().next(); let path = read_the_routes(first_line); println!("GET {:?}", path); let (mut status_line, filename) = if path.eq("") { ("HTTP/1.1 200 OK\r\n\r\n", "index.html") } else { ("HTTP/1.1 200 OK\r\n\r\n", path) }; // 读取文件 权威指南中读取为String,要求字符UTF-8。这样不能去读图片等非法UTF-8文件,因此修改了这部分。直接全转为 `Vec` ,合并然后再 as_bytes let mut contents = fs::read(filename).unwrap_or_else(|_| { status_line = "HTTP/1.1 404 NOT FOUND\r\n\r\n"; Vec::from(p20_web_server::return_404()) }); let mut response = Vec::from(status_line.as_bytes()); let _ = &response.append(&mut contents); stream.write(&response).unwrap(); stream.flush().unwrap(); } /// 用于解析路径,传入请求的第一行,形如`GET /some/main.js HTTP/1.1`。掐头去尾,取第二个'/some/main.js',然后解析删除第一个字符'/'。其余作为文件路径参数返回 /// explme path in Windows is example/index.html fn read_the_routes(str: Option<&str>) -> &str { let str = str.unwrap_or("GET /error.html HTTP/1.1"); let str = str.trim().split(' ').collect::>(); let path = str.get(1).unwrap_or(&"/error.html"); let path = &path[1..]; path } ``` ## BUG 注意: read_the_routes函数中,返回的路径中的空格,中文等字符并没有处理,因此可能出现找不的问题。 比如寻找 `1 1.txt` ,但是浏览器会转换为`%201.txt`。这样直接传入就是错误的。