cogo_http/client/
response.rs1use 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#[derive(Debug)]
15pub struct Response {
16 pub status: status::StatusCode,
18 pub headers: header::Headers,
20 pub version: version::HttpVersion,
22 pub url: Url,
24 status_raw: RawStatus,
25 message: Box<dyn HttpMessage>,
26}
27
28impl Response {
29 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 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 #[inline]
61 pub fn status_raw(&self) -> &RawStatus {
62 &self.status_raw
63 }
64
65 #[inline]
67 pub fn get_ref(&self) -> &dyn HttpMessage {
68 &*self.message
69 }
70}
71
72impl 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 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 assert_eq!(res.status, status::StatusCode::Ok);
157 assert_eq!(res.version, version::HttpVersion::Http11);
158 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 assert_eq!(read_to_string(res).unwrap(), "qwert".to_owned());
168 }
169
170 #[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 #[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 #[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}