1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
use std::{
    fs::File,
    io::{self, BufReader},
};

use crate::ReadWrite;

#[derive(Debug)]
pub enum Body {
    Text(String),
    Json(serde_json::Value),
    FileStream(File, String),
    StaticFile(&'static [u8], String),
}

#[derive(Debug)]
pub struct HttpResponse {
    pub content_type: String,
    pub body: Option<Body>,
    pub status_code: u16,
    pub headers: Vec<(String, String)>,
}

impl HttpResponse {
    pub fn new(body: Option<Body>, content_type: Option<String>, status_code: u16) -> Self {
        HttpResponse {
            content_type: content_type.unwrap_or_else(|| "application/json".to_string()),
            body,
            status_code,
            headers: Vec::new(),
        }
    }
    pub fn write_response(
        self,
        mut stream: &mut Box<dyn ReadWrite>,
    ) -> Result<(), Box<dyn std::error::Error>> {
        let mut base_headers = format!(
            "HTTP/1.1 {}\r\n\
            Content-Type: {}\r\n\
            Connection: keep-alive\r\n\
            Server: RustHttpServer/1.0\r\n\
            ",
            self.status_code, self.content_type
        );

        self.headers.iter().for_each(|(key, value)| {
            base_headers.push_str(&format!("{}: {}\r\n", key, value));
        });

        if let Some(body) = self.body {
            let body = match body {
                Body::Text(text) => text.clone(),
                Body::Json(json) => json.to_string(),
                Body::StaticFile(file, _) => {
                    base_headers.push_str(&format!("Content-Length: {}\r\n", file.len()));
                    base_headers.push_str("\r\n");
                    stream.write_all(base_headers.as_bytes())?;
                    stream.write_all(file)?;
                    return Ok(());
                }
                Body::FileStream(file, name) => {
                    base_headers.push_str(&format!(
                        "Content-Disposition: attachment; filename=\"{}\"\r\n\
                    \rn",
                        name
                    ));
                    stream.write_all(base_headers.as_bytes())?;
                    let mut reader = BufReader::new(file);
                    io::copy(&mut reader, &mut stream)?;
                    return Ok(());
                }
            };
            base_headers.push_str(&format!(
                "Content-Length: {}\r\n\
            \r\n\
            {}",
                body.len(),
                body
            ));
        }

        stream.write_all(base_headers.as_bytes())?;

        Ok(())
    }
    pub fn add_response_header(mut self, key: &str, value: &str) -> Self {
        self.headers.push((key.to_string(), value.to_string()));
        self
    }
}