fix(client): fix panics when some errors occured inside HttpMessage
BREAKING CHANGE: This changes the signature of HttpWriter.end(), returning a `EndError` that is similar to std::io::IntoInnerError, allowing HttpMessage to retrieve the broken connections and not panic. The breaking change isn't exposed in any usage of the `Client` API, but for anyone using `HttpWriter` directly, since this was technically a public method, that change is breaking.
This commit is contained in:
		| @@ -226,4 +226,14 @@ mod tests { | |||||||
|  |  | ||||||
|         assert_eq!(read_to_string(res).unwrap(), "1".to_owned()); |         assert_eq!(read_to_string(res).unwrap(), "1".to_owned()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_parse_error_closes() { | ||||||
|  |         let url = Url::parse("http://hyper.rs").unwrap(); | ||||||
|  |         let stream = MockStream::with_input(b"\ | ||||||
|  |             definitely not http | ||||||
|  |         "); | ||||||
|  |  | ||||||
|  |         assert!(Response::new(url, Box::new(stream)).is_err()); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										164
									
								
								src/http/h1.rs
									
									
									
									
									
								
							
							
						
						
									
										164
									
								
								src/http/h1.rs
									
									
									
									
									
								
							| @@ -74,12 +74,15 @@ impl Read for Http11Message { | |||||||
|  |  | ||||||
| impl HttpMessage for Http11Message { | impl HttpMessage for Http11Message { | ||||||
|     fn set_outgoing(&mut self, mut head: RequestHead) -> ::Result<RequestHead> { |     fn set_outgoing(&mut self, mut head: RequestHead) -> ::Result<RequestHead> { | ||||||
|         if self.stream.is_none() { |         let stream = match self.stream.take() { | ||||||
|             return Err(From::from(io::Error::new( |             Some(stream) => stream, | ||||||
|                         io::ErrorKind::Other, |             None => { | ||||||
|                         "Message not idle, cannot start new outgoing"))); |                 return Err(From::from(io::Error::new( | ||||||
|         } |                             io::ErrorKind::Other, | ||||||
|         let mut stream = BufWriter::new(self.stream.take().unwrap()); |                             "Message not idle, cannot start new outgoing"))); | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |         let mut stream = BufWriter::new(stream); | ||||||
|  |  | ||||||
|         let mut uri = head.url.serialize_path().unwrap(); |         let mut uri = head.url.serialize_path().unwrap(); | ||||||
|         if let Some(ref q) = head.url.query { |         if let Some(ref q) = head.url.query { | ||||||
| @@ -92,72 +95,89 @@ impl HttpMessage for Http11Message { | |||||||
|         try!(write!(&mut stream, "{} {} {}{}", |         try!(write!(&mut stream, "{} {} {}{}", | ||||||
|                     head.method, uri, version, LINE_ENDING)); |                     head.method, uri, version, LINE_ENDING)); | ||||||
|  |  | ||||||
|         let stream = match &head.method { |         let stream = { | ||||||
|             &Method::Get | &Method::Head => { |             let mut write_headers = |mut stream: BufWriter<Box<NetworkStream + Send>>, head: &RequestHead| { | ||||||
|                 debug!("headers={:?}", head.headers); |                 debug!("headers={:?}", head.headers); | ||||||
|                 try!(write!(&mut stream, "{}{}", head.headers, LINE_ENDING)); |                 match write!(&mut stream, "{}{}", head.headers, LINE_ENDING) { | ||||||
|                 EmptyWriter(stream) |                     Ok(_) => Ok(stream), | ||||||
|             }, |                     Err(e) => { | ||||||
|             _ => { |                         self.stream = Some(stream.into_inner().unwrap()); | ||||||
|                 let mut chunked = true; |                         Err(e) | ||||||
|                 let mut len = 0; |  | ||||||
|  |  | ||||||
|                 match head.headers.get::<header::ContentLength>() { |  | ||||||
|                     Some(cl) => { |  | ||||||
|                         chunked = false; |  | ||||||
|                         len = **cl; |  | ||||||
|                     }, |  | ||||||
|                     None => () |  | ||||||
|                 }; |  | ||||||
|  |  | ||||||
|                 // can't do in match above, thanks borrowck |  | ||||||
|                 if chunked { |  | ||||||
|                     let encodings = match head.headers.get_mut::<header::TransferEncoding>() { |  | ||||||
|                         Some(&mut header::TransferEncoding(ref mut encodings)) => { |  | ||||||
|                             //TODO: check if chunked is already in encodings. use HashSet? |  | ||||||
|                             encodings.push(header::Encoding::Chunked); |  | ||||||
|                             false |  | ||||||
|                         }, |  | ||||||
|                         None => true |  | ||||||
|                     }; |  | ||||||
|  |  | ||||||
|                     if encodings { |  | ||||||
|                         head.headers.set::<header::TransferEncoding>( |  | ||||||
|                             header::TransferEncoding(vec![header::Encoding::Chunked])) |  | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |             }; | ||||||
|  |             match &head.method { | ||||||
|  |                 &Method::Get | &Method::Head => { | ||||||
|  |                     EmptyWriter(try!(write_headers(stream, &head))) | ||||||
|  |                 }, | ||||||
|  |                 _ => { | ||||||
|  |                     let mut chunked = true; | ||||||
|  |                     let mut len = 0; | ||||||
|  |  | ||||||
|                 debug!("headers={:?}", head.headers); |                     match head.headers.get::<header::ContentLength>() { | ||||||
|                 try!(write!(&mut stream, "{}{}", head.headers, LINE_ENDING)); |                         Some(cl) => { | ||||||
|  |                             chunked = false; | ||||||
|  |                             len = **cl; | ||||||
|  |                         }, | ||||||
|  |                         None => () | ||||||
|  |                     }; | ||||||
|  |  | ||||||
|                 if chunked { |                     // can't do in match above, thanks borrowck | ||||||
|                     ChunkedWriter(stream) |                     if chunked { | ||||||
|                 } else { |                         let encodings = match head.headers.get_mut::<header::TransferEncoding>() { | ||||||
|                     SizedWriter(stream, len) |                             Some(encodings) => { | ||||||
|  |                                 //TODO: check if chunked is already in encodings. use HashSet? | ||||||
|  |                                 encodings.push(header::Encoding::Chunked); | ||||||
|  |                                 false | ||||||
|  |                             }, | ||||||
|  |                             None => true | ||||||
|  |                         }; | ||||||
|  |  | ||||||
|  |                         if encodings { | ||||||
|  |                             head.headers.set( | ||||||
|  |                                 header::TransferEncoding(vec![header::Encoding::Chunked])) | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     let stream = try!(write_headers(stream, &head)); | ||||||
|  |  | ||||||
|  |                     if chunked { | ||||||
|  |                         ChunkedWriter(stream) | ||||||
|  |                     } else { | ||||||
|  |                         SizedWriter(stream, len) | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         self.method = Some(head.method.clone()); |  | ||||||
|         self.writer = Some(stream); |         self.writer = Some(stream); | ||||||
|  |         self.method = Some(head.method.clone()); | ||||||
|  |  | ||||||
|         Ok(head) |         Ok(head) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn get_incoming(&mut self) -> ::Result<ResponseHead> { |     fn get_incoming(&mut self) -> ::Result<ResponseHead> { | ||||||
|         try!(self.flush_outgoing()); |         try!(self.flush_outgoing()); | ||||||
|         if self.stream.is_none() { |         let stream = match self.stream.take() { | ||||||
|             // The message was already in the reading state... |             Some(stream) => stream, | ||||||
|             // TODO Decide what happens in case we try to get a new incoming at that point |             None => { | ||||||
|             return Err(From::from( |                 // The message was already in the reading state... | ||||||
|                     io::Error::new(io::ErrorKind::Other, |                 // TODO Decide what happens in case we try to get a new incoming at that point | ||||||
|                     "Read already in progress"))); |                 return Err(From::from( | ||||||
|         } |                         io::Error::new(io::ErrorKind::Other, | ||||||
|  |                         "Read already in progress"))); | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |  | ||||||
|         let stream = self.stream.take().unwrap(); |  | ||||||
|         let mut stream = BufReader::new(stream); |         let mut stream = BufReader::new(stream); | ||||||
|  |  | ||||||
|         let head = try!(parse_response(&mut stream)); |         let head = match parse_response(&mut stream) { | ||||||
|  |             Ok(head) => head, | ||||||
|  |             Err(e) => { | ||||||
|  |                 self.stream = Some(stream.into_inner()); | ||||||
|  |                 return Err(e); | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|         let raw_status = head.subject; |         let raw_status = head.subject; | ||||||
|         let headers = head.headers; |         let headers = head.headers; | ||||||
|  |  | ||||||
| @@ -170,7 +190,7 @@ impl HttpMessage for Http11Message { | |||||||
|         // 5. Content-Length header has a sized body. |         // 5. Content-Length header has a sized body. | ||||||
|         // 6. Not Client. |         // 6. Not Client. | ||||||
|         // 7. Read till EOF. |         // 7. Read till EOF. | ||||||
|         let body = match (method, raw_status.0) { |         self.reader = Some(match (method, raw_status.0) { | ||||||
|             (Method::Head, _) => EmptyReader(stream), |             (Method::Head, _) => EmptyReader(stream), | ||||||
|             (_, 100...199) | (_, 204) | (_, 304) => EmptyReader(stream), |             (_, 100...199) | (_, 204) | (_, 304) => EmptyReader(stream), | ||||||
|             (Method::Connect, 200...299) => EmptyReader(stream), |             (Method::Connect, 200...299) => EmptyReader(stream), | ||||||
| @@ -192,9 +212,8 @@ impl HttpMessage for Http11Message { | |||||||
|                     EofReader(stream) |                     EofReader(stream) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }; |         }); | ||||||
|  |  | ||||||
|         self.reader = Some(body); |  | ||||||
|  |  | ||||||
|         Ok(ResponseHead { |         Ok(ResponseHead { | ||||||
|             headers: headers, |             headers: headers, | ||||||
| @@ -292,9 +311,15 @@ impl Http11Message { | |||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         let writer = self.writer.take().unwrap(); |         let writer = self.writer.take().unwrap(); | ||||||
|         let raw = try!(writer.end()).into_inner().unwrap(); // end() already flushes |          // end() already flushes | ||||||
|  |         let raw = match writer.end() { | ||||||
|  |             Ok(buf) => buf.into_inner().unwrap(), | ||||||
|  |             Err(e) => { | ||||||
|  |                 self.writer = Some(e.1); | ||||||
|  |                 return Err(From::from(e.0)); | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|         self.stream = Some(raw); |         self.stream = Some(raw); | ||||||
|  |  | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -617,10 +642,25 @@ impl<W: Write> HttpWriter<W> { | |||||||
|     /// A final `write_all()` is called with an empty message, and then flushed. |     /// A final `write_all()` is called with an empty message, and then flushed. | ||||||
|     /// The ChunkedWriter variant will use this to write the 0-sized last-chunk. |     /// The ChunkedWriter variant will use this to write the 0-sized last-chunk. | ||||||
|     #[inline] |     #[inline] | ||||||
|     pub fn end(mut self) -> io::Result<W> { |     pub fn end(mut self) -> Result<W, EndError<W>> { | ||||||
|         try!(self.write(&[])); |         fn inner<W: Write>(w: &mut W) -> io::Result<()> { | ||||||
|         try!(self.flush()); |             try!(w.write(&[])); | ||||||
|         Ok(self.into_inner()) |             w.flush() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         match inner(&mut self) { | ||||||
|  |             Ok(..) => Ok(self.into_inner()), | ||||||
|  |             Err(e) => Err(EndError(e, self)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Debug)] | ||||||
|  | pub struct EndError<W: Write>(io::Error, HttpWriter<W>); | ||||||
|  |  | ||||||
|  | impl<W: Write> From<EndError<W>> for io::Error { | ||||||
|  |     fn from(e: EndError<W>) -> io::Error { | ||||||
|  |         e.0 | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user