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#[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 #[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 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 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 pub fn has_body(&mut self) -> Result<bool> {
134 Ok(self.body.is_some() || !self.stream.fill_buf()?.is_empty())
135 }
136
137 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;