czh_http_server/
response.rs

1use std::{
2    cell::RefCell,
3    collections::HashMap,
4    fmt::format,
5    io::{BufReader, BufWriter, Read, Write},
6    net::TcpStream,
7    ops::{Deref, DerefMut},
8    rc::Rc,
9};
10
11struct StatusLine {
12    version: String,
13    status_code: u16,
14    reason: String,
15}
16impl StatusLine {
17    fn to_string(&self) -> String {
18        format!("{} {} {}\r\n", self.version, self.status_code, self.reason)
19    }
20}
21pub struct HttpResponse {
22    stream: Option<Rc<RefCell<TcpStream>>>,
23    headers: HashMap<String, String>,
24    status_line: StatusLine,
25}
26impl HttpResponse {
27    pub fn init(stream: Rc<RefCell<TcpStream>>, version: &str) -> Self {
28        let headers = init_headers();
29        HttpResponse {
30            stream: Some(stream),
31            headers,
32            status_line: StatusLine {
33                version: String::from(version),
34                status_code: 200,
35                reason: "OK".to_string(),
36            },
37        }
38    }
39    fn set_content_length(&mut self, len: u64) {
40        self.headers
41            .insert("Content-Length".to_string(), len.to_string());
42    }
43    fn set_content_type(&mut self, content_type: ContentType) {
44        self.headers
45            .insert("Content-Type".to_string(), content_type.into());
46    }
47
48    pub fn json<T>(mut self, data: T)
49    where
50        T: serde::Serialize,
51    {
52        self.headers
53            .insert(String::from("Content-Type"), "application/json".to_string());
54        let json = serde_json::to_string(&data).unwrap();
55        let data = json.as_bytes();
56        self.set_content_length(data.len() as u64);
57        let stream = self.stream.take().unwrap();
58        let mut stream = stream.borrow_mut();
59        let mut writer: BufWriter<&mut TcpStream> = BufWriter::new(stream.deref_mut());
60        self.write_status_headers(&mut writer);
61        writer.write_all(data).unwrap();
62    }
63    fn write_status_headers(&mut self, writer: &mut BufWriter<&mut TcpStream>) {
64        let status = self.status_line.to_string();
65        writer.write_all(status.as_bytes()).unwrap();
66        for (key, value) in self.headers.iter() {
67            let header = format!("{}:{}\r\n", key, value);
68            writer.write_all(header.as_bytes()).unwrap();
69        }
70        writer.write_all(b"\r\n").unwrap();
71    }
72    pub(crate) fn file(mut self, file: std::fs::File, content_type: ContentType) {
73        self.set_content_length(file.metadata().unwrap().len());
74        self.set_content_type(content_type);
75
76        let stream = self.stream.take().unwrap();
77        let mut stream = stream.borrow_mut();
78        let mut writer: BufWriter<&mut TcpStream> = BufWriter::new(stream.deref_mut());
79        self.write_status_headers(&mut writer);
80        let mut reader = BufReader::new(file);
81        let mut buffer = [0; 1024];
82        loop {
83            let size = reader.read(&mut buffer).unwrap();
84            if size == 0 {
85                break;
86            }
87            writer.write_all(&buffer[0..size]).unwrap();
88        }
89    }
90    /// Set-Cookie: <cookie-name>=<cookie-value>; Path=<path>; Expires=<date>; HttpOnly; Secure; SameSite=<strict|lax|none>
91    /// 
92    /// 
93    /// 1.	<cookie-name> 和 <cookie-value>:
94	/// •	cookie-name: Cookie 的键名。
95	/// •	cookie-value: Cookie 的值,支持 Base64 编码以存储复杂数据。
96	/// 2.	Path:
97	/// •	指定 Cookie 的作用范围。例如,Path=/ 使 Cookie 在整个网站有效。
98	/// 3.	Expires 或 Max-Age:
99	/// •	Expires: 设置具体过期时间(UTC 格式)。
100	/// •	Max-Age: 设置相对过期时间(秒数)。
101	/// 4.	HttpOnly:
102	/// •	限制 Cookie 只能通过 HTTP 请求访问,JavaScript 无法读取(防止 XSS 攻击)。
103	/// 5.	Secure:
104	/// •	仅在 HTTPS 请求中发送(提升安全性)。
105	/// 6.	SameSite:
106	/// •	Strict: 禁止跨站点发送 Cookie(最安全)。
107	/// •	Lax: 允许部分跨站点请求(如导航链接)。
108	/// •	None: 允许所有跨站点发送 Cookie,需配合 Secure。
109    pub fn set_cookie(&mut self, name: &str, value: &str) {
110        self.headers.insert("Set-Cookie".to_string(), format!("{}={};", name, value));
111    }
112}
113
114fn init_headers() -> HashMap<String, String> {
115    let mut headers = HashMap::new();
116    // TODO: init headers
117    headers
118}
119pub enum ContentType {
120    JSON,
121    HTML,
122    CSS,
123    JS,
124    PNG,
125    JPG,
126    SVG,
127    TXT,
128    OTHER,
129}
130impl From<&str> for ContentType {
131    fn from(suffix: &str) -> Self {
132        match suffix {
133            "json" => ContentType::JSON,
134            "html" => ContentType::HTML,
135            "css" => ContentType::CSS,
136            "js" => ContentType::JS,
137            "png" => ContentType::PNG,
138            "jpg" => ContentType::JPG,
139            "jpeg" => ContentType::JPG,
140            "svg" => ContentType::SVG,
141            "txt" => ContentType::TXT,
142            _ => ContentType::OTHER,
143        }
144    }
145}
146impl Into<String> for ContentType {
147    fn into(self) -> String {
148        match self {
149            ContentType::JSON => "application/json".to_string(),
150            ContentType::HTML => "text/html".to_string(),
151            ContentType::CSS => "text/css".to_string(),
152            ContentType::JS => "text/javascript".to_string(),
153            ContentType::PNG => "image/png".to_string(),
154            ContentType::JPG => "image/jpeg".to_string(),
155            ContentType::SVG => "image/svg+xml".to_string(),
156            ContentType::TXT => "text/plain".to_string(),
157            ContentType::OTHER => "application/octet-stream".to_string(),
158        }
159    }
160}