Simple Web Server 修改版
目录
警告
本文最后更新于 2024-02-24,文中内容可能已过时。
Rust 权威指南中最后实现了一个简单的 Http-Server ,我尝试修改了下。使其能够简单可用,能解析路径和图片。也就是说,在一个 Hugo 生成的 public 下运行能够正常访问网站。
CODE
首先执行 cargo new p20-web-server
根目录下创建一个 index.html
<!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>
lib.rs 和权威指南中是一样的,只增加了一个 return_404() 作为默认的 404 主页。
src\lib.rs
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<Worker>,
sender: mpsc::Sender<Message>,
}
type Job = Box<dyn FnOnce() + Send + 'static>;
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<F>(&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<thread::JoinHandle<()>>,
}
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Message>>>) -> 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 {
"<!DOCTYPE html>
<html lang=\"en\">
<head>
<meta charset=\"utf-8\">
<title>404</title>
</head>
<body>
<h1>404</h1>
</body>
</html>
"
}
src/main.rs
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<u8>` ,合并然后再 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::<Vec<&str>>();
let path = str.get(1).unwrap_or(&"/error.html");
let path = &path[1..];
path
}
BUG
注意: read_the_routes函数中,返回的路径中的空格,中文等字符并没有处理,因此可能出现找不的问题。
比如寻找 1 1.txt
,但是浏览器会转换为%201.txt
。这样直接传入就是错误的。
欢迎赞赏~
赞赏