1use blake3_lite::Hasher;
4use percent_encoding_lite::{is_encoded, encode, Bitmask};
5
6use crate::reqres::{HttpRequest, HttpHeader, HttpBody, StatusCode};
7use crate::reqres::sse::HttpSse;
8
9#[derive(Debug)]
11#[non_exhaustive]
12pub struct HttpResponse {
13 pub code: StatusCode,
14 pub headers: Vec<HttpHeader>,
15 pub body: HttpBody,
16 pub content_type: String,
17}
18
19impl HttpResponse {
20 pub fn new() -> HttpResponse {
22 HttpResponse::with_type("", vec![])
23 }
24
25 pub fn add_header(&mut self, name: impl Into<String>, value: impl Into<String>) -> &mut HttpResponse {
27 self.headers.push(HttpHeader { name: name.into(), value: value.into() });
28 self
29 }
30
31 pub fn with_type(content_type: impl Into<String>, body: impl Into<HttpBody>) -> HttpResponse {
33 HttpResponse {
34 code: StatusCode::OK,
35 headers: vec![],
36 body: body.into(),
37 content_type: content_type.into(),
38 }
39 }
40}
41
42impl Default for HttpResponse {
43 fn default() -> HttpResponse {
44 HttpResponse::new()
45 }
46}
47
48pub fn bytes(bytes: Vec<u8>) -> HttpResponse {
50 HttpResponse::with_type("application/octet-stream", bytes)
51}
52
53pub fn text(text: impl Into<String>) -> HttpResponse {
55 HttpResponse::with_type("text/plain; charset=utf-8", text.into())
56}
57
58pub fn html(req: &HttpRequest, html: impl Into<String>) -> HttpResponse {
62 let html = html.into();
63
64 let mut hasher = Hasher::new();
65 hasher.update(html.as_bytes());
66 let mut hash = [0; 8];
68 hasher.finalize(&mut hash);
69 let hex = format!("\"{}\"", crate::util::hex(&hash));
70
71 let mut res = HttpResponse::with_type("text/html; charset=utf-8", html);
72 if req.cmp_header("If-None-Match", &hex) {
73 res.code = StatusCode::NOT_MODIFIED;
74 res.body = HttpBody::Empty;
75 }
76 res.add_header("ETag", hex);
77 res
78}
79
80pub fn json(json: impl Into<String>) -> HttpResponse {
82 HttpResponse::with_type("application/json", json.into())
83}
84
85pub fn redirect(dest: impl Into<String>) -> HttpResponse {
87 let mut dest = dest.into();
88 if !is_encoded(&dest, Bitmask::URI) {
90 dest = encode(dest, Bitmask::URI);
91 }
92 HttpResponse {
93 code: StatusCode::MOVED_PERMANENTLY,
94 headers: vec![HttpHeader { name: "Location".to_string(), value: dest.clone() }],
95 body: format!("<a href=\"{dest}\">Click here if you weren't redirected</a>\n").into(),
96 content_type: "text/html; charset=utf-8".to_string(),
97 }
98}
99
100pub fn sse(handler: impl HttpSse) -> HttpResponse {
101 HttpResponse::with_type("text/event-stream", HttpBody::Upgrade(Box::new(handler)))
102}
103
104pub use super::file::file;