netc/
response.rs

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}