1use std::{io::Write, str};
2
3use bytes::Bytes;
4
5use crate::{utils::find_slice, Error, Headers, Method, Status, StatusCode, Version};
6
7#[derive(Debug, PartialEq, Eq, Clone)]
8pub struct Response {
9 pub status: Status,
10 pub headers: Headers,
11 pub method: Method,
12 pub body: Bytes,
13}
14
15impl Response {
16 pub fn from_header(header: &[u8]) -> Result<Response, Error> {
17 let mut header = str::from_utf8(header)?.splitn(2, '\n');
18
19 let status = header.next().ok_or(Error::EmptyStatus)?.parse()?;
20 let headers = header.next().ok_or(Error::HeadersErr)?.parse()?;
21 let body = Bytes::new();
22
23 Ok(Response {
24 status,
25 headers,
26 method: Method::Get,
27 body,
28 })
29 }
30
31 pub fn try_from<T: Write>(res: &[u8], writer: &mut T) -> Result<Response, Error> {
32 if res.is_empty() {
33 Err(Error::EmptyResponse)
34 } else {
35 let mut pos = res.len();
36 if let Some(v) = find_slice(res, &[13, 10, 13, 10]) {
37 pos = v;
38 }
39
40 let response = Self::from_header(&res[..pos])?;
41 writer.write_all(&res[pos..])?;
42
43 Ok(response)
44 }
45 }
46
47 pub fn status_code(&self) -> StatusCode {
48 self.status.status_code()
49 }
50
51 pub fn version(&self) -> Version {
52 self.status.version()
53 }
54
55 pub fn reason(&self) -> &str {
56 self.status.reason()
57 }
58
59 pub fn headers(&self) -> &Headers {
60 &self.headers
61 }
62
63 pub fn header(&self, value: &str) -> Option<String> {
64 self.headers.get(value)
65 }
66
67 pub fn content_len(&self) -> Option<usize> {
68 self.headers().content_length()
69 }
70
71 pub fn body(&self) -> Bytes {
72 self.body.clone()
73 }
74
75 pub fn text(&self) -> Result<String, Error> {
76 Ok(String::from_utf8_lossy(&self.body).to_string())
77 }
78
79 pub fn has_body(&self) -> bool {
80 let has_no_body = self.method == Method::Head || self.status_code().is_nobody();
81 !has_no_body
82 }
83
84 pub fn has_chuncked_body(&self) -> bool {
85 let is_http10 = self.status.version() == Version::Http10;
86 let is_chunked = self
87 .headers
88 .get_array("transfer-encoding")
89 .contains(&"chunked".to_string());
90 !is_http10 && self.has_body() && is_chunked
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use httpmock::{Method::GET, MockServer};
97
98 use super::*;
99 use crate::{get, status::StatusCode, Client};
100
101 const RESPONSE: &[u8; 129] = b"HTTP/1.1 200 OK\r\n\
102 Date: Sat, 11 Jan 2003 02:44:04 GMT\r\n\
103 Content-Type: text/html\r\n\
104 Content-Length: 100\r\n\r\n\
105 <html>hello</html>\r\n\r\nhello";
106 const RESPONSE_H: &[u8; 102] = b"HTTP/1.1 200 OK\r\n\
107 Date: Sat, 11 Jan 2003 02:44:04 GMT\r\n\
108 Content-Type: text/html\r\n\
109 Content-Length: 100\r\n\r\n";
110 const BODY: &[u8; 27] = b"<html>hello</html>\r\n\r\nhello";
111
112 #[test]
113 fn res_from_head() {
114 Response::from_header(RESPONSE_H).unwrap();
115 }
116
117 #[test]
118 fn res_try_from() {
119 let mut writer = Vec::new();
120
121 Response::try_from(RESPONSE, &mut writer).unwrap();
122 Response::try_from(RESPONSE_H, &mut writer).unwrap();
123 }
124
125 #[test]
126 #[should_panic]
127 fn res_from_empty() {
128 let mut writer = Vec::new();
129 Response::try_from(&[], &mut writer).unwrap();
130 }
131
132 #[test]
133 fn res_status_code() {
134 let code: StatusCode = StatusCode::from_u16(200).unwrap();
135 let mut writer = Vec::new();
136 let res = Response::try_from(RESPONSE, &mut writer).unwrap();
137
138 assert_eq!(res.status_code(), code);
139 }
140
141 #[test]
142 fn res_version() {
143 let mut writer = Vec::new();
144 let res = Response::try_from(RESPONSE, &mut writer).unwrap();
145
146 assert_eq!(&res.version().to_string(), "HTTP/1.1");
147 }
148
149 #[test]
150 fn res_reason() {
151 let mut writer = Vec::new();
152 let res = Response::try_from(RESPONSE, &mut writer).unwrap();
153
154 assert_eq!(res.reason(), "OK");
155 }
156
157 #[test]
158 fn res_headers() {
159 let mut writer = Vec::new();
160 let res = Response::try_from(RESPONSE, &mut writer).unwrap();
161
162 let mut headers = Headers::with_capacity(2);
163 headers.insert("Date", "Sat, 11 Jan 2003 02:44:04 GMT");
164 headers.insert("Content-Type", "text/html");
165 headers.insert("Content-Length", "100");
166
167 assert_eq!(res.headers(), &headers);
168 }
169
170 #[test]
171 fn res_content_len() {
172 let mut writer = Vec::with_capacity(101);
173 let res = Response::try_from(RESPONSE, &mut writer).unwrap();
174
175 assert_eq!(res.content_len(), Some(100));
176 }
177
178 #[test]
179 fn res_body() {
180 let mut writer = Vec::new();
181 Response::try_from(RESPONSE, &mut writer).unwrap();
182
183 assert_eq!(writer, BODY);
184 }
185
186 #[tokio::test]
187 async fn res_status_code_200() {
188 let path = "/foo";
189 let server = MockServer::start_async().await;
190 let mock = server
191 .mock_async(|when, then| {
192 when.method(GET).path(path);
193 then.status(200)
194 .header("content-type", "text/html; charset=UTF-8")
195 .body("GET");
196 })
197 .await;
198 let url = server.url(path);
199 let response = get(&url).await.unwrap();
200 assert_eq!(response.status_code().as_u16(), 200);
201 mock.assert_async().await;
202 }
203
204 #[tokio::test]
205 async fn res_status_code_302() {
206 let redirect_path = "/redirectPath";
207 let final_path = "/finalPath";
208
209 let redirect_server = MockServer::start_async().await;
210 let final_server = MockServer::start_async().await;
211
212 let redirect_url = redirect_server.url(redirect_path);
213 let final_url = final_server.url(final_path);
214
215 let redirect_mock = redirect_server
216 .mock_async(|when, then| {
217 when.method(GET).path(redirect_path);
218 then.status(302).header("Location", final_url);
219 })
220 .await;
221
222 let final_mock = final_server
223 .mock_async(|when, then| {
224 when.method(GET).path(final_path);
225 then.status(200)
226 .header("content-type", "text/html; charset=UTF-8")
227 .body("GET");
228 })
229 .await;
230
231 let mut client = Client::builder().get(&redirect_url).build().await.unwrap();
232 let response = client.send().await.unwrap();
233 assert_eq!(response.status_code().as_u16(), 200);
234 let body = response.text().unwrap();
235 assert_eq!(&body, "GET");
236 assert!(client.redirects() == 1);
237
238 redirect_mock.assert_async().await;
239 final_mock.assert_async().await;
240 }
241}