tinyhttp_internal/
response.rs1use std::{
2 collections::HashMap,
3 error::Error,
4 io::{Read, Write},
5};
6
7#[cfg(feature = "async")]
8use tokio::io::{AsyncReadExt, AsyncWriteExt};
9
10#[derive(Clone, Debug)]
11pub struct Response {
12 pub headers: HashMap<String, String>,
13 pub status_line: String,
14 pub body: Option<Vec<u8>>,
15 pub mime: Option<String>,
16 pub http2: bool,
17 pub(crate) manual_override: bool,
18}
19
20impl Default for Response {
21 fn default() -> Self {
22 Self::new()
23 }
24}
25
26impl<'a> From<&'a str> for Response {
27 fn from(value: &'a str) -> Self {
28 Response::new()
29 .body(value.into())
30 .mime("text/plain")
31 .status_line("HTTP/1.1 200 OK")
32 }
33}
34
35impl From<String> for Response {
36 fn from(value: String) -> Self {
37 Response::new()
38 .body(value.into_bytes())
39 .mime("text/plain")
40 .status_line("HTTP/1.1 200 OK")
41 }
42}
43
44impl From<Vec<u8>> for Response {
45 fn from(value: Vec<u8>) -> Self {
46 Response::new()
47 .body(value)
48 .mime("application/octet-stream")
49 .status_line("HTTP/1.1 200 OK")
50 }
51}
52
53impl From<()> for Response {
54 fn from(_value: ()) -> Self {
55 Response::new()
56 .body(vec![])
57 .mime("text/plain")
58 .status_line("HTTP/1.1 403 Forbidden")
59 }
60}
61
62impl<T: Into<Response>, E: Error + Into<Response>> From<Result<T, E>> for Response {
63 fn from(value: Result<T, E>) -> Self {
64 match value {
65 Ok(body) => body.into(),
66 Err(e) => e.into(),
67 }
68 }
69}
70
71impl From<Box<dyn Error>> for Response {
72 fn from(value: Box<dyn Error>) -> Self {
73 Response::new()
74 .body(value.to_string().into_bytes())
75 .mime("text/plain")
76 .status_line("HTTP/1.1 403 Forbidden")
77 }
78}
79
80impl Response {
81 pub fn new() -> Response {
82 Response {
83 headers: HashMap::new(),
84 status_line: String::new(),
85 body: None,
86 mime: Some(String::from("HTTP/1.1 200 OK")),
87 http2: false,
88 manual_override: false,
89 }
90 }
91
92 pub fn empty() -> Response {
93 Response {
94 headers: HashMap::new(),
95 status_line: String::new(),
96 body: None,
97 mime: None,
98 manual_override: true,
99 http2: false,
100 }
101 }
102
103 pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
104 self.headers = headers;
105 self
106 }
107
108 pub fn status_line<P: Into<String>>(mut self, line: P) -> Self {
109 let line_str = line.into();
110 self.status_line = line_str.trim().to_string() + "\r\n";
111 self
112 }
113
114 pub fn body(mut self, body: Vec<u8>) -> Self {
115 self.body = Some(body);
116 self
117 }
118
119 pub fn mime<P>(mut self, mime: P) -> Self
120 where
121 P: Into<String>,
122 {
123 self.mime = Some(mime.into());
124 self
125 }
126
127 #[cfg(not(feature = "async"))]
128 pub fn send<P: Read + Write>(self, sock: &mut P) {
129 let line_bytes = self.status_line.as_bytes();
130 #[cfg(feature = "log")]
131 log::trace!("res status line: {:#?}", self.status_line);
132
133 let mut header_bytes: Vec<u8> = self
134 .headers
135 .into_iter()
136 .flat_map(|(i, j)| [(i + ": ").as_bytes(), (j + "\r\n").as_bytes()].concat())
137 .collect();
138
139 header_bytes.extend(b"\r\n");
140
141 #[cfg(all(feature = "log", debug_assertions))]
142 {
143 log::trace!(
144 "HEADER AS STR: {}",
145 String::from_utf8(header_bytes.clone()).unwrap()
146 );
147 log::trace!(
148 "STATUS LINE AS STR: {}",
149 std::str::from_utf8(line_bytes).unwrap()
150 );
151 };
152
153 let full_req: &[u8] = &[
154 line_bytes,
155 header_bytes.as_slice(),
156 self.body.as_ref().unwrap(),
157 ]
158 .concat();
159
160 #[cfg(feature = "log")]
161 log::trace!("size of response: {}", full_req.len());
162
163 sock.write_all(full_req).unwrap();
164 }
165
166 #[cfg(feature = "async")]
167 pub(crate) async fn send<P: AsyncReadExt + AsyncWriteExt + Unpin>(&self, sock: &mut P) {
168 let line_bytes = self.status_line.as_bytes();
169 #[cfg(feature = "log")]
170 log::trace!("res status line: {:#?}", self.status_line);
171
172 let mut header_bytes: Vec<u8> = self
173 .headers
174 .iter()
175 .flat_map(|s| [s.0.as_bytes(), s.1.as_bytes()].concat())
176 .collect();
177
178 header_bytes.extend(b"\r\n");
179
180 #[cfg(all(feature = "log", debug_assertions))]
181 {
182 log::trace!(
183 "HEADER AS STR: {}",
184 String::from_utf8(header_bytes.clone()).unwrap()
185 );
186 log::trace!(
187 "STATUS LINE AS STR: {}",
188 std::str::from_utf8(line_bytes).unwrap()
189 );
190 };
191
192 let full_req: &[u8] = &[
193 line_bytes,
194 header_bytes.as_slice(),
195 self.body.as_ref().unwrap(),
196 ]
197 .concat();
198
199 sock.write_all(full_req).await.unwrap();
200 }
201}