noxp/http/
response.rs

1use std::fs;
2use std::collections::BTreeMap;
3use std::collections::btree_map::Entry;
4use std::fmt::Display;
5use std::io::Write;
6use std::net::TcpStream;
7
8use super::status_code::StatusCode;
9
10/// The response type for handling responses
11#[derive(Clone)]
12pub struct Response {
13    status: StatusCode,
14    headers: BTreeMap<String, String>,
15    body: String,
16}
17
18impl Response {
19    pub fn builder() -> ResponseWriter {
20        ResponseWriter::default()
21    }
22
23    /// Write the response to the user with a `TcpStream`
24    pub fn write(&self, mut stream: TcpStream) {
25        let mut response = format!("HTTP/1.1 {}\r\n", self.status.get_status());
26
27        for (key, value) in &self.headers {
28            let kv = format!("{}: {}\r\n", key, value);
29            response.push_str(&kv);
30        }
31
32        let body = format!("\r\n{}", self.body);
33        response.push_str(&body);
34
35        stream.write_all(response.as_bytes()).unwrap();
36        stream.flush().unwrap();
37    }
38}
39
40#[derive(Default)]
41pub struct ResponseWriter {
42    status: StatusCode,
43    headers: BTreeMap<String, String>,
44    body: String,
45}
46
47impl ResponseWriter {
48    /// Set the response status
49    pub fn set_status(&mut self, status: StatusCode) -> &mut Self {
50        self.status = status;
51        self
52    }
53
54    /// Add another header to the response
55    pub fn header(&mut self, name: &str, value: &str) -> &mut Self {
56        self.headers.insert(name.into(), value.into());
57        self
58    }
59
60    /// Remove a header from the response
61    pub fn remove_header(&mut self, name: &str) -> &mut Self {
62        if let Entry::Occupied(header) = self.headers.entry(name.to_string()) {
63            header.remove_entry();
64        }
65        self
66    }
67
68    /// Set a text/plain response with a text
69    pub fn set_text(&mut self, body: &str) -> &mut Self {
70        self.body = body.into();
71        self.headers.insert("Content-Length".into(), body.len().to_string());
72        self.headers.insert("Content-Type".into(), "text/plain".into());
73        self
74    }
75
76    /// Set a text/html body with the file path
77    /// Files should be located at `views/`
78    pub fn set_html(&mut self, path: &str) -> &mut Self {
79        let path = format!("views/{}", path);
80        let body = fs::read_to_string(path).unwrap();
81
82        self.body = body.clone().into();
83        self.headers.insert("Content-Length".into(), body.len().to_string());
84        self.headers.insert("Content-Type".into(), "text/html".into());
85        self
86    }
87
88    /// Set a application/json body with the struct
89    /// The struct should implement the `Display` trait
90    pub fn set_json(&mut self, body: impl Display) -> &mut Self {
91        let body = format!("{}", body);
92
93        self.body = body.clone().into();
94        self.headers.insert("Content-Length".into(), body.len().to_string());
95        self.headers.insert("Content-Type".into(), "application/json".into());
96        self
97    }
98
99    /// Builds the Response from the ResponseWriter
100    pub fn build(self) -> Response {
101        Response {
102            status: self.status,
103            headers: self.headers,
104            body: self.body,
105        }
106    }
107}