1 //! Client Responses 2 use std::io::{self, Read}; 3 4 use url::Url; 5 6 use header; 7 use net::NetworkStream; 8 use http::{self, RawStatus, ResponseHead, HttpMessage}; 9 use http::h1::Http11Message; 10 use status; 11 use version; 12 13 /// A response for a client request to a remote server. 14 #[derive(Debug)] 15 pub 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<HttpMessage>, 26 } 27 28 impl Response { 29 /// Creates a new response from a server. new(url: Url, stream: Box<NetworkStream + Send>) -> ::Result<Response>30 pub fn new(url: Url, stream: Box<NetworkStream + Send>) -> ::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`. with_message(url: Url, mut message: Box<HttpMessage>) -> ::Result<Response>36 pub fn with_message(url: Url, mut message: Box<HttpMessage>) -> ::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] status_raw(&self) -> &RawStatus61 pub fn status_raw(&self) -> &RawStatus { 62 &self.status_raw 63 } 64 65 /// Gets a borrowed reference to the underlying `HttpMessage`. 66 #[inline] get_ref(&self) -> &HttpMessage67 pub fn get_ref(&self) -> &HttpMessage { 68 &*self.message 69 } 70 } 71 72 /// Read the response body. 73 impl Read for Response { 74 #[inline] read(&mut self, buf: &mut [u8]) -> io::Result<usize>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 86 impl Drop for Response { drop(&mut self)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)] 105 mod tests { 106 use std::io::{self, Read}; 107 108 use url::Url; 109 110 use header::TransferEncoding; 111 use header::Encoding; 112 use http::HttpMessage; 113 use mock::MockStream; 114 use status; 115 use version; 116 use http::h1::Http11Message; 117 118 use super::Response; 119 read_to_string(mut r: Response) -> io::Result<String>120 fn read_to_string(mut r: Response) -> io::Result<String> { 121 let mut s = String::new(); 122 try!(r.read_to_string(&mut s)); 123 Ok(s) 124 } 125 126 127 #[test] test_into_inner()128 fn test_into_inner() { 129 let message: Box<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] test_parse_chunked_response()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] test_invalid_chunk_size_not_hex_digit()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] test_invalid_chunk_size_extension()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] test_chunk_size_with_extension()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] test_parse_error_closes()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 } 240