http/response/
mod.rs

1use core::fmt;
2use std::{
3    collections::HashMap,
4    io::{self, BufRead, BufReader, Read, Write},
5};
6
7use builders::Builder;
8use parse::parse_response;
9
10use crate::{
11    HttpStream, Result,
12    stream::{self, IntoHttpStream},
13};
14
15mod parse;
16
17/// An Http response
18#[derive(Builder)]
19pub struct HttpResponse {
20    #[builder(map = "header")]
21    headers: HashMap<String, String>,
22    #[builder(disabled = true)]
23    #[builder(def = { BufReader::new(stream::dummy())} )]
24    stream: BufReader<Box<dyn HttpStream>>,
25    #[builder(def = 200u16)]
26    status: u16,
27    #[builder(optional = true)]
28    body: Option<Box<[u8]>>,
29    #[builder(def = 1.0)]
30    version: f32,
31}
32
33impl fmt::Debug for HttpResponse {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        f.debug_struct("HttpResponse")
36            .field("headers", &self.headers)
37            .field("status", &self.status)
38            .field("body", &self.body)
39            .field("version", &self.version)
40            .finish()
41    }
42}
43
44impl HttpResponse {
45    pub fn parse<S: IntoHttpStream>(stream: S) -> crate::Result<Self> {
46        let stream: Box<dyn HttpStream> = Box::new(stream.into_http_stream());
47        parse_response(BufReader::new(stream))
48    }
49    #[inline]
50    #[must_use]
51    pub fn status(&self) -> u16 {
52        self.status
53    }
54
55    #[inline]
56    #[must_use]
57    pub fn content_length(&self) -> usize {
58        match self.headers.get("Content-Length") {
59            Some(l) => l.parse().unwrap_or(0),
60            None => 0,
61        }
62    }
63    /// Get the value of the given header key, if present
64    #[inline]
65    #[must_use]
66    pub fn header(&self, key: &str) -> Option<&str> {
67        self.headers.get(key).map(String::as_str)
68    }
69
70    #[inline]
71    #[must_use]
72    pub fn version(&self) -> f32 {
73        self.version
74    }
75
76    #[inline]
77    #[must_use]
78    pub fn headers(&self) -> &HashMap<String, String> {
79        &self.headers
80    }
81
82    /// Reads the body from the stream into the buffer.
83    ///
84    /// This method is primarly used by [`body`](Self::body), and
85    /// for unit tests, where we need to force-load the body into
86    /// the [stream]'s buffer.
87    ///
88    /// [stream]: HttpStream
89    pub(crate) fn read_body_into_buffer(&mut self) -> std::io::Result<()> {
90        let len = self.content_length();
91        let mut buf = Vec::with_capacity(len);
92        self.stream.read_to_end(&mut buf)?;
93        self.body = Some(buf.into_boxed_slice());
94        Ok(())
95    }
96
97    /// Reads the body from the [`stream`] into the response's buffer.
98    ///
99    /// # NOTE
100    /// This loads the whole body of the response into memory,
101    /// and it'll stick with the response for it's lifetime.
102    /// It's not very efficient memory-wise for responses with big bodies.
103    ///
104    /// For a nicer way to process a response's body, see the
105    /// [read_body](Self::read_body) function.
106    ///
107    /// # Errors
108    /// If some IO error happens when reading the body from the [`stream`]
109    ///
110    /// # Returns
111    /// And option of &[u8]. A None variant means the response doesn't have a body.
112    ///
113    /// [`stream`]: HttpStream
114    pub fn body(&mut self) -> Result<Option<&[u8]>> {
115        if self.body.is_none() {
116            self.read_body_into_buffer()?;
117        }
118        Ok(self.body.as_deref())
119    }
120
121    /// Returns true if the [`stream`](HttpStream) has a body,
122    /// and false if it's empty.
123    ///
124    /// This method is preferred to check the presence of a body,
125    /// over calling [body](Self::body) and checking the returned Option,
126    /// since this function doesn't allocate memory, nor mutates the response.
127    ///
128    /// # Errors
129    /// If some IO error happens in the process of checking the
130    /// [`stream`]'s availability
131    ///
132    /// [`stream`]: HttpStream
133    pub fn has_body(&mut self) -> Result<bool> {
134        Ok(self.body.is_some() || !self.stream.fill_buf()?.is_empty())
135    }
136
137    /// Reads the response's body into [writer](Write)
138    ///
139    /// # Errors
140    /// If, while reading or writing, some io Error is found
141    pub fn read_body(&mut self, writer: &mut dyn Write) -> Result<()> {
142        const CHUNK_SIZE: usize = 1024;
143        let mut buf: [u8; CHUNK_SIZE] = [0; CHUNK_SIZE];
144        let len = self.content_length();
145        let n = len / CHUNK_SIZE;
146        let remainder = len % CHUNK_SIZE;
147
148        for _ in 0..n {
149            self.stream.read_exact(&mut buf)?;
150            writer.write_all(&buf)?;
151        }
152
153        if remainder > 0 {
154            self.stream.read_exact(&mut buf[0..remainder])?;
155            writer.write_all(&buf[0..remainder])?;
156        }
157
158        Ok(())
159    }
160
161    pub fn write_to(&mut self, out: &mut dyn io::Write) -> io::Result<usize> {
162        let mut total = 0;
163        loop {
164            let slice = self.stream.fill_buf()?;
165            if slice.is_empty() {
166                break;
167            }
168            out.write_all(slice)?;
169
170            let len = slice.len();
171            self.stream.consume(len);
172            total += len;
173        }
174        out.flush()?;
175        Ok(total)
176    }
177}
178
179impl PartialEq for HttpResponse {
180    fn eq(&self, other: &Self) -> bool {
181        self.headers == other.headers
182            && self.status == other.status
183            && self.body == other.body
184            && self.version == other.version
185    }
186}
187
188#[cfg(test)]
189mod test;