cogo_http/client/
response.rs

1//! Client Responses
2use std::io::{self, Read};
3
4use url::Url;
5
6use crate::header;
7use crate::net::NetworkStream;
8use crate::http::{self, RawStatus, ResponseHead, HttpMessage};
9use crate::http::h1::Http11Message;
10use crate::status;
11use crate::version;
12
13/// A response for a client request to a remote server.
14#[derive(Debug)]
15pub struct Response {
16    /// The status from the server.
17    pub status: status::StatusCode,
18    /// The headers from the server.
19    pub headers: header::Headers,
20    /// The HTTP version of this response from the server.
21    pub version: version::HttpVersion,
22    /// The final URL of this response.
23    pub url: Url,
24    status_raw: RawStatus,
25    message: Box<dyn HttpMessage>,
26}
27
28impl Response {
29    /// Creates a new response from a server.
30    pub fn new(url: Url, stream: Box<dyn NetworkStream + Send>) -> crate::Result<Response> {
31        trace!("Response::new");
32        Response::with_message(url, Box::new(Http11Message::with_stream(stream)))
33    }
34
35    /// Creates a new response received from the server on the given `HttpMessage`.
36    pub fn with_message(url: Url, mut message: Box<dyn HttpMessage>) -> crate::Result<Response> {
37        trace!("Response::with_message");
38        let ResponseHead { headers, raw_status, version } = match message.get_incoming() {
39            Ok(head) => head,
40            Err(e) => {
41                let _ = message.close_connection();
42                return Err(From::from(e));
43            }
44        };
45        let status = status::StatusCode::from_u16(raw_status.0);
46        debug!("version={:?}, status={:?}", version, status);
47        debug!("headers={:?}", headers);
48
49        Ok(Response {
50            status: status,
51            version: version,
52            headers: headers,
53            url: url,
54            status_raw: raw_status,
55            message: message,
56        })
57    }
58
59    /// Get the raw status code and reason.
60    #[inline]
61    pub fn status_raw(&self) -> &RawStatus {
62        &self.status_raw
63    }
64
65    /// Gets a borrowed reference to the underlying `HttpMessage`.
66    #[inline]
67    pub fn get_ref(&self) -> &dyn HttpMessage {
68        &*self.message
69    }
70}
71
72/// Read the response body.
73impl Read for Response {
74    #[inline]
75    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
76        match self.message.read(buf) {
77            Err(e) => {
78                let _ = self.message.close_connection();
79                Err(e)
80            }
81            r => r
82        }
83    }
84}
85
86impl Drop for Response {
87    fn drop(&mut self) {
88        // if not drained, theres old bits in the Reader. we can't reuse this,
89        // since those old bits would end up in new Responses
90        //
91        // otherwise, the response has been drained. we should check that the
92        // server has agreed to keep the connection open
93        let is_drained = !self.message.has_body();
94        trace!("Response.drop is_drained={}", is_drained);
95        if !(is_drained && http::should_keep_alive(self.version, &self.headers)) {
96            trace!("Response.drop closing connection");
97            if let Err(e) = self.message.close_connection() {
98                info!("Response.drop error closing connection: {}", e);
99            }
100        }
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use std::io::{self, Read};
107
108    use url::Url;
109
110    use crate::header::TransferEncoding;
111    use crate::header::Encoding;
112    use crate::http::HttpMessage;
113    use crate::mock::MockStream;
114    use crate::status;
115    use crate::version;
116    use crate::http::h1::Http11Message;
117
118    use super::Response;
119
120    fn read_to_string(mut r: Response) -> io::Result<String> {
121        let mut s = String::new();
122        r#try!(r.read_to_string(&mut s));
123        Ok(s)
124    }
125
126
127    #[test]
128    fn test_into_inner() {
129        let message: Box<dyn HttpMessage> = Box::new(
130            Http11Message::with_stream(Box::new(MockStream::new())));
131        let message = message.downcast::<Http11Message>().ok().unwrap();
132        let b = message.into_inner().downcast::<MockStream>().ok().unwrap();
133        assert_eq!(b, Box::new(MockStream::new()));
134    }
135
136    #[test]
137    fn test_parse_chunked_response() {
138        let stream = MockStream::with_input(b"\
139            HTTP/1.1 200 OK\r\n\
140            Transfer-Encoding: chunked\r\n\
141            \r\n\
142            1\r\n\
143            q\r\n\
144            2\r\n\
145            we\r\n\
146            2\r\n\
147            rt\r\n\
148            0\r\n\
149            \r\n"
150        );
151
152        let url = Url::parse("http://hyper.rs").unwrap();
153        let res = Response::new(url, Box::new(stream)).unwrap();
154
155        // The status line is correct?
156        assert_eq!(res.status, status::StatusCode::Ok);
157        assert_eq!(res.version, version::HttpVersion::Http11);
158        // The header is correct?
159        match res.headers.get::<TransferEncoding>() {
160            Some(encodings) => {
161                assert_eq!(1, encodings.len());
162                assert_eq!(Encoding::Chunked, encodings[0]);
163            },
164            None => panic!("Transfer-Encoding: chunked expected!"),
165        };
166        // The body is correct?
167        assert_eq!(read_to_string(res).unwrap(), "qwert".to_owned());
168    }
169
170    /// Tests that when a chunk size is not a valid radix-16 number, an error
171    /// is returned.
172    #[test]
173    fn test_invalid_chunk_size_not_hex_digit() {
174        let stream = MockStream::with_input(b"\
175            HTTP/1.1 200 OK\r\n\
176            Transfer-Encoding: chunked\r\n\
177            \r\n\
178            X\r\n\
179            1\r\n\
180            0\r\n\
181            \r\n"
182        );
183
184        let url = Url::parse("http://hyper.rs").unwrap();
185        let res = Response::new(url, Box::new(stream)).unwrap();
186
187        assert!(read_to_string(res).is_err());
188    }
189
190    /// Tests that when a chunk size contains an invalid extension, an error is
191    /// returned.
192    #[test]
193    fn test_invalid_chunk_size_extension() {
194        let stream = MockStream::with_input(b"\
195            HTTP/1.1 200 OK\r\n\
196            Transfer-Encoding: chunked\r\n\
197            \r\n\
198            1 this is an invalid extension\r\n\
199            1\r\n\
200            0\r\n\
201            \r\n"
202        );
203
204        let url = Url::parse("http://hyper.rs").unwrap();
205        let res = Response::new(url, Box::new(stream)).unwrap();
206
207        assert!(read_to_string(res).is_err());
208    }
209
210    /// Tests that when a valid extension that contains a digit is appended to
211    /// the chunk size, the chunk is correctly read.
212    #[test]
213    fn test_chunk_size_with_extension() {
214        let stream = MockStream::with_input(b"\
215            HTTP/1.1 200 OK\r\n\
216            Transfer-Encoding: chunked\r\n\
217            \r\n\
218            1;this is an extension with a digit 1\r\n\
219            1\r\n\
220            0\r\n\
221            \r\n"
222        );
223
224        let url = Url::parse("http://hyper.rs").unwrap();
225        let res = Response::new(url, Box::new(stream)).unwrap();
226
227        assert_eq!(read_to_string(res).unwrap(), "1".to_owned());
228    }
229
230    #[test]
231    fn test_parse_error_closes() {
232        let url = Url::parse("http://hyper.rs").unwrap();
233        let stream = MockStream::with_input(b"\
234            definitely not http
235        ");
236
237        assert!(Response::new(url, Box::new(stream)).is_err());
238    }
239}