实现一个监听请求的web服务器
能够直接用浏览器访问, 然后真确的响应数据
- 学习一些 TCP 与 HTTP 知识
- 在套接字(socket)上监听 TCP 请求
- 解析少量的 HTTP 请求
- 创建一个合适的 HTTP 响应
- 通过线程池改善 server 的吞吐量
创建项目
sh
cargo new rust-web-server
cargo install cargo-watch # 方便单元测试查看效果
目录结构
txt
.
├── Cargo.lock
├── Cargo.toml
├── public
│ ├── 404.html # NotFound 页面
│ ├── index.html # 首页
│ ├── sleep.html # 测试多线程页面
│ └── test.html # 测试根据请求地址自动读取对应文件页面
└── src
├── config.rs # 命令行参数配置模块
├── http_server.rs # HTTP服务器模块
├── lib.rs # 模块
├── main.rs # 程序入口
└── thread_pool.rs # 线程池模块
具体实现
我和书上写的稍微有点不一样, 但是主要的逻辑是一样的
静态页面文件
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rust Web Server</title>
</head>
<body>
<h2>page not found, back <a href="/">home</a> page</h2>
</body>
</html>
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rust Web Server</title>
</head>
<body>
<h2>hello rust web server</h2>
</body>
</html>
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rust Web Server</title>
</head>
<body>
<h2>request with long process time</h2>
</body>
</html>
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Rust Web Server</title>
</head>
<body>
<h2>Automatically respond to file content based on request path</h2>
</body>
</html>
src/main.rs
rust
use rust_http_server::*;
use std::process;
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let config = Config::new(&args).unwrap_or_else(|err|{
eprintln!("Failed to parse arguments: {}", err);
process::exit(1);
});
http_server::listen(config).unwrap_or_else(|e| {
eprintln!("{}", e);
process::exit(0);
});
}
src/lib.rs
rust
pub mod config;
pub mod http_server;
pub mod thread_pool;
pub use config::Config;
src/config.rs
处理命令行参数
rust
use std::num::ParseIntError;
pub struct Config {
pub port: u16,
pub path: String,
}
impl Config {
pub fn new(args: &[String]) -> Result<Config, ParseIntError> {
let mut config = Config {
port: 8080,
path: String::from("./public"),
};
if args.len() >= 2 {
for item in args.iter() {
// --port
if item == "--port" {
config.port = item.clone().parse()?;
}
// --dir or --path or --public-dir or --public-path
if item == "--dir" || item == "--path" || item == "--public-dir" || item == "--public-path" {
config.path = item.to_string();
}
}
}
Ok(config)
}
}
src/thread_pool
线程池模块: 为web服务器提供多线程支持
rust
use std::thread;
use std::sync::{mpsc, Arc, Mutex};
// Job 是一个闭包, 但闭包是动态大小的类型 DST -> ?Sized
// 所以需要一个 Box 智能指针来包裹, 之后直接通过函数指针来调用
type Job = Box<dyn FnOnce() + Send + 'static>;
// 用枚举来区分, 发送的消息是一个发送新任务还是要终止线程
#[allow(unused)]
pub enum TaskMessage {
NewTask(Job),
Exit
}
// 实际执行任务的对象,用 id 来标记 thread, 就可以很方便的区分不同的线程
#[allow(unused)]
pub struct Worker {
id: usize,
thread: Option<thread::JoinHandle<()>>,
}
impl Worker {
pub fn new(id:usize, receiver: Arc<Mutex<mpsc::Receiver<TaskMessage>>>) -> Worker {
// 实例化 worker 需要传入 id 和 多线程引用计数指针
// 指向一个 被互斥锁保护的 信道接收器对象
// 信道对象接收 Job 类型的数据
let thread = thread::spawn(move || {
loop {
// 由于 recv 只会接收一次, 所以要用循环一直不停的接收(阻塞线程)
// 看是否有任务被发送过来, 如果有任务发送过来就需要处理
let message = receiver.lock().unwrap().recv().unwrap();
// 接收到任务消息过来之后还需要判断
// 是新任务就执行,是终止消息就终止线程
match message {
TaskMessage::NewTask(job) => {
println!("worker-{}-execute-task.", id);
job();
},
TaskMessage::Exit => {
// 终止线程: 停止接收信道对象发送的任务消息
// 退出 loop 循环, 那么这个线程自然就执行完了
println!("worker-{}-exit.", id);
break;
}
}
}
});
let thread = Some(thread);
Worker { id, thread }
}
}
// 线程池
// workers: 实际执行任务的线程(id+thread)
// sender: 信道对象的发送者
#[allow(unused)]
pub struct ThreadPool {
workers: Vec<Worker>,
sender: mpsc::Sender<TaskMessage>,
}
impl ThreadPool {
pub fn new(thread_count: usize) -> ThreadPool {
assert!(thread_count > 0);
let mut workers = Vec::with_capacity(thread_count);
// 初始化信道
let (sender, receiver) = mpsc::channel();
// 由于是多线程, 为了避免数据竞争的问题, 所以需要互斥器锁来保护数据
// 由于是多线程, 所以需要多所有权(否则只有第一个线程能获得所有权)
let receiver = Arc::new(Mutex::new(receiver));
// 初始化 worker 并保存, 注意传入需要引用计数智能指针
// 让 worker 中的线程 闭包 获得所有权
for id in 0..thread_count {
workers.push(Worker::new(id, Arc::clone(&receiver)));
}
ThreadPool { workers, sender }
}
pub fn execute<F>(&self, f: F)
// 为什么这样定义泛型? 因为 f 是一个跨线程传输的闭包
// 或者说闭包类型的数据就是应该这样定义泛型约束:
// FnOnce: 必须传入闭包
// Send: 可以跨线程传输的类型
// 'static: 让传入的闭包在程序运行期间都存活
where F: FnOnce() + Send + 'static
{
let job = Box::new(f);
self.sender.send(TaskMessage::NewTask(job)).unwrap();
}
}
// 优雅的停止服务器(停止主线程之前先关闭所有子线程)
// 如果直接 Ctrl-C 直接强行终止主线程, 那么子线程就算没有执行完也会退出
// 为 ThreadPool 实现 Drop 特性, 在主线程结束时, 会自动调用 drop 方法
// 所以可以在 drop 方法中先停止所有的子线程
impl Drop for ThreadPool {
fn drop(&mut self) {
for worker in &mut self.workers {
println!("Shutting down worker {}", worker.id);
if let Some(thread) = worker.thread.take() {
thread.join().unwrap();
}
}
}
}
src/http_server.rs
监听TCP链接, 解析请求, 处理响应数据
rust
use std::str::FromStr;
use std::time::Duration;
use std::{ fs, thread };
use std::io::{ Read, Write, Result as IOResult };
use std::net::{ Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener, TcpStream };
use std::sync::Arc;
use crate::thread_pool::ThreadPool;
use crate::config::Config;
pub fn listen(config: Config) -> IOResult<()> {
let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), config.port));
let pool = ThreadPool::new(5);
// config 只读的, 没有数据竞争问题, 所以不用互斥锁保护数据
let config = Arc::new(config);
print!("\x1b[1m\x1b[31m"); // 输出红色的调试信息
println!("server started on http://{}", addr);
print!("\x1b[22m\x1b[39m"); // 输出红色的调试信息
// TcpListener::bind 会阻塞主线程
let listener = TcpListener::bind(addr)?;
for stream in listener.incoming() {
let stream = stream.unwrap();
let config_arc = Arc::clone(&config);
// 使用线程池处理请求
pool.execute(move || {
handle_request(stream, config_arc);
});
}
Ok(())
}
fn handle_request(mut stream: TcpStream, config: Arc<Config>) {
// 注意 buffer 是一个 u8 数组 [u8; 1024], 不是一个 Vec<u8>
let mut buffer = [0; 1024];
Read::read(&mut stream, &mut buffer).unwrap();
let http_req_str = String::from_utf8(buffer.to_vec()).unwrap();
// 请求字符串
// println!("{:?}", http_req_str);
print!("\x1b[1m\x1b[31m"); // 输出红色的调试信息
let (status_line, file_path) = get_response(&http_req_str, config);
let contents = fs::read_to_string(file_path).unwrap();
let response = format!(
"{}\r\nContent-Length: {}\r\n\r\n{}",
status_line,
contents.len(),
contents
);
// 响应字符串
// println!("{:?}", response);
print!("\x1b[22m\x1b[39m"); // 输出红色的调试信息
Write::write_all(&mut stream, response.as_bytes()).unwrap();
stream.flush().unwrap();
}
fn get_response(http_req_str: &str, config: Arc<Config>) -> (String, String) {
let public_path = config.path.to_string();
// 解析请求的方法,路径,http协议版本等信息
let (method, req_path, protocol_version) = parse_http_info(http_req_str);
println!("request-info:method={method} req_path={req_path} http_ver={protocol_version}");
// 处理一些特殊的请求路径
let route_path = if req_path == "/" { // 映射首页文件
"/index.html"
} else if req_path == "/sleep.html" { // 测试多线程
thread::sleep(Duration::from_secs(5));
req_path
} else { // 其他情况
req_path
};
// 客户端请求的文件路径, 需要加上 public_path
let mut req_file_path = public_path.clone();
req_file_path.push_str(route_path);
// 响应 header 信息: HTTP/1.1 200 OK\r\nxxx
let mut status_line = String::from_str(protocol_version).unwrap();
let mut res_file_path = req_file_path;
// 由于这是个简单的静态文件服务器,所以:
// 不是 GET 请求或者文件不存在都应该返回 404
if method != "GET" || fs::metadata(res_file_path.as_str()).is_err(){
let mut _404_file_path = public_path.clone();
_404_file_path.push_str("/404.html");
// HTTP/1.1 404 NOT FOUND
status_line.push_str(" 404 NOT FOUND");
res_file_path = _404_file_path;
} else {
status_line.push_str(" 200 OK");
}
(status_line, res_file_path)
}
fn parse_http_info(http_req_str: &str) -> (&str, &str, &str) {
// print!("\x1b[1m \x1b[31m");
let http_info = http_req_str.lines().next().unwrap();
let methods = ["GET", "HEAD", "POST", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"];
// parse request method
let mut http_method = "GET";
for item in methods.iter() {
if let Some(_index) = http_info.find(item) {
http_method = item;
}
}
// parse protocol version
let mut http_version = "HTTP/1.1";
if let Some(start_index) = http_info.rfind("HTTP/") {
http_version = &http_info[start_index..http_info.len()];
}
// parse request path
let req_path_start = http_method.len() + 1;
let req_path_end = http_info.len() - http_version.len() - 1;
let req_path = &http_info[req_path_start..req_path_end];
// print!("\x1b[22m \x1b[39m");
(http_method, req_path, http_version)
}
#[cfg(test)]
mod unit_tests {
use super::*;
#[test]
fn should_be_parse_http_method() {
let http_req_str = String::from("GET / HTTP/1.1\r\n");
let (http_method, _, _) = parse_http_info(&http_req_str);
assert_eq!(http_method, "GET");
let http_req_str = String::from("POST / HTTP/1.1\r\n");
let (http_method, _, _) = parse_http_info(&http_req_str);
assert_eq!(http_method, "POST");
let http_req_str = String::from("DELETE / HTTP/1.1\r\n");
let (http_method, _, _) = parse_http_info(&http_req_str);
assert_eq!(http_method, "DELETE");
}
#[test]
fn should_be_parse_http_version() {
let http_req_str = String::from("GET / HTTP/1.1\r\n");
let (_, _, http_version) = parse_http_info(&http_req_str);
assert_eq!(http_version, "HTTP/1.1");
let http_req_str = String::from("POST / HTTP/2\r\n");
let (_, _, http_version) = parse_http_info(&http_req_str);
assert_eq!(http_version, "HTTP/2");
let http_req_str = String::from("POST / HTTP/2.0\r\n");
let (_, _, http_version) = parse_http_info(&http_req_str);
assert_eq!(http_version, "HTTP/2.0");
}
#[test]
fn should_be_parse_request_path() {
let http_req_str = String::from("GET / HTTP/1.1\r\n");
let (_, request_path, _) = parse_http_info(&http_req_str);
assert_eq!(request_path, "/");
let http_req_str = String::from("GET /test.html HTTP/1.1\r\n");
let (_, request_path, _) = parse_http_info(&http_req_str);
assert_eq!(request_path, "/test.html");
}
}
优化版(进阶练手项目)
上面的处理方式太简单, 且耦合性太高, 需要优化
分析
创建项目
sh
cargo new mp-web-server
目录结构
sh
.
├── Cargo.lock
├── Cargo.toml
├── public
│ ├── 404.html # 404 页面
│ ├── index.html # 首页
│ ├── index.js # JS脚本文件
│ ├── other.html # 其他html
│ ├── sleep.html # 测试多线程处理
│ └── style.css # CSS样式文件
└── src
├── handler.rs # 处理器:处理具体的请求
├── lib.rs # 模块定义文件
├── main.rs # 程序主入口
├── request.rs # 解析请求
├── response.rs # 解析响应
├── router.rs # 路由器:分发请求给处理器
├── server.rs # 服务器:监听请求
└── thread_pool.rs # 线程池
静态页面
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h2>首页</h2>
<script src="index.js"></script>
</body>
</html>
js
(function () {
console.log("index.js execute");
})();
js
h2 {
color: #f00;
}
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>NotFound</h2>
<a href="/">回到首页</a>
</body>
</html>
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>测试多线程</h2>
</body>
</html>
main.rs 和 lib.rs
rust
use mp_web_server::server::Server;
fn main() {
let addr = "127.0.0.1:3000";
let server = Server::new(addr);
server.run();
}
rust
pub mod handler;
pub mod request;
pub mod response;
pub mod router;
pub mod server;
pub mod thread_pool;
src/server.rs
rust
use crate::request::Request;
use crate::router::Router;
use crate::thread_pool::ThreadPool;
use colored::Colorize; // 打印出彩色的字符到标准输出
use std::io::{Read, Write};
use std::net::TcpListener;
pub struct Server<'a> {
socket_addr: &'a str,
}
impl<'a> Server<'a> {
pub fn new(socket_addr: &'a str) -> Self {
Server { socket_addr }
}
pub fn run(&self) {
// 监听请求
let listener = TcpListener::bind(self.socket_addr).unwrap();
let pool = ThreadPool::new(4);
println!(
"Server running on: {}{}",
"http://".red(),
self.socket_addr.red()
);
for stream in listener.incoming() {
let mut stream = stream.unwrap();
// 使用线程池处理请求
pool.execute(move || {
handle_http_request(&mut stream);
});
}
}
}
// 处理请求:
// 1. 解析请求字符串为 Request struct
// 2. 使用 Router 分发给 Handler 去处理
fn handle_http_request<T: Read + Write>(stream: &mut T) {
// read stream to String
let mut buffer: [u8; 1024] = [0; 1024];
Read::read(stream, &mut buffer).unwrap();
// parse String to Request
let request: Request = String::from_utf8(buffer.to_vec()).unwrap().into();
// dispatch to handlers
Router::route(request, stream);
}
src/request.rs
rust
use std::collections::HashMap;
#[derive(PartialEq, Debug)]
pub enum Method {
// 请求方式
Get,
Post,
Put,
Patch,
Delete,
Uninitialized,
}
impl From<&str> for Method {
fn from(s: &str) -> Self {
match s {
"GET" => Method::Get,
"POST" => Method::Post,
"PUT" => Method::Put,
"PATCH" => Method::Patch,
"DELETE" => Method::Delete,
_ => Method::Uninitialized,
}
}
}
#[derive(Debug, PartialEq)]
pub enum ProtocolVersion {
// HTTP 协议版本
HTTP1_1,
HTTP2,
Uninitialized,
}
impl From<&str> for ProtocolVersion {
fn from(s: &str) -> Self {
match s {
"HTTP/1.1" => ProtocolVersion::HTTP1_1,
"HTTP/2" => ProtocolVersion::HTTP2,
_ => ProtocolVersion::Uninitialized,
}
}
}
#[derive(Debug, PartialEq)]
pub enum Resource {
// HTTP 请求资源(路径字符串)
Path(String),
}
#[derive(Debug)]
pub struct Request {
pub method: Method,
pub protocol_version: ProtocolVersion,
pub resource: Resource,
pub headers: HashMap<String, String>,
pub body: String,
}
impl From<String> for Request {
fn from(req_str: String) -> Self {
let mut method = Method::Uninitialized;
let mut protocol_version = ProtocolVersion::Uninitialized;
let mut resource = Resource::Path(String::new());
let mut headers = HashMap::new();
let mut request_body = "";
let iter = req_str.lines();
for line in iter {
if line.contains("HTTP") {
let (m, r, v) = process_req_line(line);
method = m;
resource = r;
protocol_version = v;
} else if line.contains(":") {
let (key, val) = process_header(line);
headers.insert(key, val);
} else if line.is_empty() {
// empty line
} else {
request_body = line;
}
}
Request {
method,
protocol_version,
headers,
resource,
body: request_body.into(),
}
}
}
// 解析请求行
fn process_req_line(line: &str) -> (Method, Resource, ProtocolVersion) {
let mut words = line.split_whitespace(); // split by space charetacter
let m = words.next().unwrap();
let r = words.next().unwrap();
let v = words.next().unwrap();
(m.into(), Resource::Path(String::from(r)), v.into())
}
// 解析请求头
fn process_header(line: &str) -> (String, String) {
let mut key = String::new();
let mut val = String::new();
let mut iter = line.split(":");
if let Some(k) = iter.next() {
key = k.trim().to_string();
}
if let Some(v) = iter.next() {
val = v.trim().to_string();
}
(key, val)
}
// 单元测试模块
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_return_method_get_variant() {
assert_eq!(Method::Get, Method::from("GET"));
// 实现 From 特性后,就可以调用 into 方法
assert_eq!(Method::Get, "GET".into());
}
#[test]
fn should_return_protocol_version_variant() {
assert_eq!(ProtocolVersion::HTTP1_1, "HTTP/1.1".into());
}
#[test]
fn should_parse_req_line_string_to_enums() {
let (m, r, v) = process_req_line("GET /test.html HTTP/1.1");
assert_eq!(m, Method::Get);
assert_eq!(r, Resource::Path("/test.html".to_string()));
assert_eq!(v, ProtocolVersion::HTTP1_1);
}
#[test]
fn should_parse_header_line_string_to_tuple() {
let (k, v) = process_header("Accept:text/html");
assert_eq!(k, String::from("Accept"));
assert_eq!(v, String::from("text/html"));
// trim spaces
let (k, v) = process_header(" Host : localhost ");
assert_eq!(k, String::from("Host"));
assert_eq!(v, String::from("localhost"));
}
#[test]
fn should_parse_string_to_request_struct() {
// parse str to Request struct
let req_str =
String::from("GET /search HTTP/1.1\r\nAccept:text/html\r\nHost:localhost\r\n\r\nhello");
let request = Request::from(req_str);
// expected request instance
let expected = Request {
method: Method::Get,
protocol_version: ProtocolVersion::HTTP1_1,
resource: Resource::Path(String::from("/search")),
headers: {
let mut headers = HashMap::new();
headers.insert("Accept".to_string(), "text/html".to_string());
headers.insert("Host".to_string(), "localhost".to_string());
headers
},
body: String::from("hello"),
};
assert_eq!(expected.method, request.method);
assert_eq!(expected.protocol_version, request.protocol_version);
assert_eq!(expected.resource, request.resource);
assert_eq!(expected.headers, request.headers);
}
}
src/router.rs
rust
use crate::handler::{ApiHandler, Handler, NotFoundHandler, StaticFileHandler};
use crate::{request, response::Response};
use std::io::{Read, Write};
pub struct Router;
impl Router {
#[allow(unused)]
pub fn route<T: Read + Write>(req: request::Request, stream: &mut T) {
// 此处只处理 GET 请求凡事
if let request::Method::Get = req.method {
// 结构方式获取请求路径
let request::Resource::Path(s) = &req.resource;
let route: Vec<&str> = s.split("/").collect();
// 根据 handler 获取响应
let response: Response = if route[1] == "api" {
ApiHandler::handle(&req)
} else {
StaticFileHandler::handle(&req)
};
// 发送响应
response.send(stream).unwrap();
return;
}
// 如果不是 GET 请求就直接使用 NotFoundHandler
NotFoundHandler::handle(&req).send(stream).unwrap();
}
}
src/handler.rs
rust
use crate::request;
use crate::{request::Request, response::Response};
use colored::Colorize;
use std::collections::HashMap;
use std::time::Duration;
use std::{env, fs, thread};
// 定义处理器特性(接口)
pub trait Handler {
fn handle(req: &Request) -> Response;
fn load_file(file_name: &str) -> Option<String> {
// use the CARGO_MANIFEST_DIR constant to get the root path of the current package
let default_path = format!("{}/public", env!("CARGO_MANIFEST_DIR"));
let public_path = env::var("PUBLIC_PATH").unwrap_or(default_path);
let file_path = format!("{}/{}", public_path, file_name);
println!("{}", format!("file_path:{}", file_path).purple());
let contents = fs::read_to_string(file_path);
// convert Result to Option
contents.ok()
}
}
pub struct ApiHandler;
pub struct StaticFileHandler;
pub struct NotFoundHandler;
// Not Found
impl Handler for NotFoundHandler {
fn handle(_: &Request) -> Response {
Response::new("404", None, Self::load_file("404.html"))
}
}
// static file
impl Handler for StaticFileHandler {
fn handle(req: &Request) -> Response {
let request::Resource::Path(s) = &req.resource;
let route: Vec<&str> = s.split("/").collect();
println!("{}:{:?}", "route".cyan(), route);
match route[1] {
"" | "index.html" => Response::new("200", None, Self::load_file("index.html")),
"health" => Response::new("200", None, Self::load_file("health.html")),
"sleep.html" => {
// for test multiple threads
thread::sleep(Duration::from_secs(5));
Response::new("200", None, Self::load_file("sleep.html"))
}
path => {
if let Some(contents) = Self::load_file(path) {
let mut headers: HashMap<&str, &str> = HashMap::new();
if path.ends_with(".css") {
headers.insert("Content-Type", "text/css");
} else if path.ends_with(".js") {
headers.insert("Content-Type", "text/javascript");
} else if path.ends_with(".html") {
headers.insert("Content-Type", "text/html");
} else {
headers.insert("Content-Type", "text/plain");
}
return Response::new("200", Some(headers), Some(contents));
}
return NotFoundHandler::handle(req);
}
}
}
}
// api service handler
// TODO: 应该根据请求方法和路径再次分发到
// 不同的 cntroller, 实现类似 Rest 的 api 风格
// 比如 get /api/user -> UserApi::index
// 比如 post /api/user -> UserApi::cerate
// 这里就不实现了, 直接返回一个固定的 json 字符串
impl Handler for ApiHandler {
fn handle(_req: &Request) -> Response {
let mut headers: HashMap<&str, &str> = HashMap::new();
headers.insert("Content-Type", "application/json");
let json_str = String::from("{\"errno\":0,\"msg\":\"success\", \"data\": null}");
Response::new("200", Some(headers), Some(json_str))
}
}
src/response.rs
rust
use std::collections::HashMap;
use std::io::{Result as IOResult, Write};
#[derive(Debug, PartialEq, Clone)]
pub struct Response<'a> {
protocol_version: &'a str,
status_code: &'a str,
status_text: &'a str,
headers: Option<HashMap<&'a str, &'a str>>,
body: Option<String>,
}
impl<'a> Default for Response<'a> {
fn default() -> Self {
Self {
protocol_version: "HTTP/1.1",
status_code: "200",
status_text: "OK",
headers: None,
body: None,
}
}
}
// 将 Response struct 解析为响应字符串
impl<'a> From<Response<'a>> for String {
fn from(r: Response) -> Self {
let res = r.clone();
// response line
// header lines
// empty line
// response body line
let res_str = format!(
"{} {} {}\r\nContent-Length:{}\r\n\r\n{}",
&res.protocol_version(),
&res.status_code(),
&res.status_text(),
&r.body().len(),
&res.body(),
);
println!("res_str:{:?}\r\n---end---", res_str);
res_str
}
}
impl<'a> Response<'a> {
pub fn new(
status_code: &'a str,
headers: Option<HashMap<&'a str, &'a str>>,
body: Option<String>,
) -> Response<'a> {
let mut response: Response<'a> = Response::default();
if status_code != "200" {
response.status_code = status_code;
}
response.status_text = match response.status_code {
"200" => "OK",
"400" => "Bad Request",
"500" => "Internal Server Error",
_ => "Not Found",
};
response.headers = match &headers {
Some(_h) => headers,
None => {
let mut header_items = HashMap::new();
header_items.insert("Content-Type", "text/plain");
Some(header_items)
}
};
response.body = body;
response
}
pub fn protocol_version(&self) -> &str {
self.protocol_version
}
pub fn status_code(&self) -> &str {
self.status_code
}
pub fn status_text(&self) -> &str {
self.status_text
}
pub fn headers(&self) -> String {
let headers_map: HashMap<&str, &str> = self.headers.clone().unwrap();
let mut headers_str = String::new();
for (k, v) in headers_map.iter() {
headers_str.push_str(k);
headers_str.push(':');
headers_str.push_str(v);
headers_str.push_str("\r\n");
}
headers_str
}
pub fn body(&self) -> &str {
match &self.body {
Some(s) => s.as_str(),
None => "",
}
}
// 发送响应
pub fn send<T: Write>(&self, stream: &mut T) -> IOResult<()> {
let res = self.clone();
let res_string: String = res.into();
Write::write_all(stream, res_string.as_bytes())?;
Ok(())
}
}