1use http_body_util::{BodyExt, Empty, Full};
2use hyper::{
3 HeaderMap, StatusCode,
4 body::{Body, Bytes},
5 header::{
6 ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_LENGTH, HeaderName,
7 HeaderValue, ORIGIN, VARY,
8 },
9 http::response::Builder,
10};
11
12use std::{collections::HashMap, str::FromStr};
13
14use super::{
15 constant::DEFAULT_RESPONSE_HEADERS, response::error_response::internal_server_error_response,
16};
17use crate::types::BoxBody;
18
19#[derive(Clone, Default)]
20pub enum BodyKind {
21 #[default]
22 Empty,
23 Text(String),
24 Binary(Vec<u8>),
25}
26
27#[derive(Default)]
28pub struct ResponseHandler {
29 response_builder: Builder,
30 status: Option<StatusCode>,
31 headers: HashMap<String, Option<String>>,
32 body_kind: BodyKind,
33}
34
35impl ResponseHandler {
36 pub fn into_response(
38 self,
39 request_headers: &HeaderMap,
40 ) -> Result<hyper::Response<BoxBody>, hyper::http::Error> {
41 let response = match self.body_kind {
43 BodyKind::Text(s) => self
44 .response_builder
45 .body(Full::new(Bytes::from(s.to_owned())).boxed()),
46 BodyKind::Binary(b) => self
47 .response_builder
48 .body(Full::new(Bytes::from(b)).boxed()),
49 BodyKind::Empty => self.response_builder.body(Empty::new().boxed()),
50 };
51
52 let mut response = match response {
53 Ok(x) => x,
54 Err(err) => {
55 return internal_server_error_response(
56 &format!("failed to create response: {}", err),
57 request_headers,
58 );
59 }
60 };
61
62 *response.status_mut() = if let Some(status) = self.status {
64 status
65 } else {
66 StatusCode::OK
67 };
68
69 let content_length = response.body().size_hint().exact().unwrap_or_default();
71
72 let headers = response.headers_mut();
73
74 headers.insert(CONTENT_LENGTH, HeaderValue::from(content_length));
75
76 for (header_key, header_value) in default_response_headers(request_headers).iter() {
78 headers.insert(header_key, header_value.to_owned());
79 }
80
81 for (header_key, header_value) in self.headers {
83 match HeaderName::from_str(header_key.as_str()) {
84 Ok(header_key) => {
85 match HeaderValue::from_str(header_value.unwrap_or_default().as_str()) {
86 Ok(header_value) => {
87 headers.insert(header_key, header_value);
88 }
89 Err(err) => {
90 log::warn!(
91 "failed to create header with the header value (header key = {}) ({})",
92 header_key,
93 err
94 );
95 headers.insert(header_key, HeaderValue::from_static(""));
96 }
97 }
98 }
99 Err(err) => log::warn!(
100 "failed to create header with the header key: {} ({})",
101 header_key,
102 err
103 ),
104 };
105 }
106
107 Ok(response)
108 }
109
110 pub fn with_status(mut self, status: &StatusCode) -> Self {
112 self.status = Some(status.to_owned());
113 self
114 }
115
116 pub fn with_header(mut self, key: impl Into<String>, value: Option<impl Into<String>>) -> Self {
118 self.headers.insert(key.into(), value.map(|x| x.into()));
119 self
120 }
121
122 pub fn with_headers<K, V, I>(mut self, headers: I) -> Self
124 where
125 K: Into<String>,
126 V: Into<String>,
127 I: IntoIterator<Item = (K, Option<V>)>,
128 {
129 for (key, value) in headers {
130 self.headers.insert(key.into(), value.map(|x| x.into()));
131 }
132 self
133 }
134
135 pub fn with_custom_headers(
156 self,
157 custom_headers: Option<&HashMap<String, Option<String>>>,
158 ) -> Self {
159 match custom_headers {
160 Some(custom_headers) => self.with_headers(custom_headers.to_owned()),
161 None => self,
162 }
163 }
164
165 pub fn with_text(mut self, text: impl Into<String>, content_type: Option<&str>) -> Self {
167 let content_type = if let Some(content_type) = content_type {
168 content_type.into()
169 } else {
170 "text/plain; charset=utf-8".to_owned()
171 };
172 self.headers
173 .insert("content-type".into(), Some(content_type));
174
175 self.body_kind = BodyKind::Text(text.into());
176 self
177 }
178
179 pub fn with_json_body(mut self, body: impl Into<String>) -> Self {
181 self.headers
182 .insert("content-type".into(), Some("application/json".into()));
183 self.body_kind = BodyKind::Text(body.into());
184 self
185 }
186
187 pub fn with_binary_body(
189 mut self,
190 body: Vec<u8>,
191 content_type: Option<impl Into<String>>,
192 ) -> Self {
193 let content_type = if let Some(content_type) = content_type {
194 content_type.into()
195 } else {
196 "application/octet-stream".to_owned()
197 };
198 self.headers
199 .insert("content-type".into(), Some(content_type));
200
201 self.body_kind = BodyKind::Binary(body);
202
203 self
204 }
205}
206
207pub fn default_response_headers(request_headers: &HeaderMap) -> HeaderMap {
209 let mut header_map_src = Vec::with_capacity(DEFAULT_RESPONSE_HEADERS.len() + 1);
210
211 header_map_src.extend(
214 DEFAULT_RESPONSE_HEADERS
215 .iter()
216 .map(|(k, v)| (k.to_string(), v.to_string())),
217 );
218
219 let origin = if is_likely_authenticated_request(request_headers) {
221 request_headers.get(ORIGIN).map(|x| x.to_owned())
222 } else {
223 None
224 };
225 let (origin, vary) = if let Some(origin) = origin {
226 header_map_src.push((
227 ACCESS_CONTROL_ALLOW_CREDENTIALS.to_string(),
228 "true".to_owned(),
229 ));
230
231 (origin, HeaderValue::from_static("Origin"))
232 } else {
233 (HeaderValue::from_static("*"), HeaderValue::from_static("*"))
234 };
235 header_map_src.push((
236 ACCESS_CONTROL_ALLOW_ORIGIN.to_string(),
237 origin.to_str().unwrap_or_default().to_owned(),
238 ));
239 header_map_src.push((
240 VARY.to_string(),
241 vary.to_str().unwrap_or_default().to_owned(),
242 ));
243
244 header_map_src
246 .iter()
247 .fold(HeaderMap::new(), |mut ret, (header_key, header_value)| {
248 match HeaderName::from_str(header_key) {
249 Ok(header_key) => match HeaderValue::from_str(header_value.as_str()) {
250 Ok(header_value) => {
251 ret.insert(header_key, header_value);
252 ret
253 }
254 Err(err) => {
255 log::warn!(
256 "only header key set because failed to get header value: {} [key = {}] ({})",
257 header_value.as_str(),
258 header_key,
259 err
260 );
261 ret.insert(header_key, HeaderValue::from_static(""));
262 ret
263 }
264 },
265 Err(err) => {
266 log::warn!("failed to set header key: {} ({})", header_key, err);
267 ret
268 }
269 }
270 })
271}
272
273fn is_likely_authenticated_request(request_headers: &HeaderMap) -> bool {
275 request_headers.contains_key("cookie") || request_headers.contains_key("authorization")
276}