Merge pull request #625 from hyperium/no-content
improve detection of Client Response bodies
This commit is contained in:
		| @@ -47,9 +47,9 @@ impl Response { | |||||||
|             version: version, |             version: version, | ||||||
|             headers: headers, |             headers: headers, | ||||||
|             url: url, |             url: url, | ||||||
|             message: message, |  | ||||||
|             status_raw: raw_status, |             status_raw: raw_status, | ||||||
|             is_drained: false, |             is_drained: !message.has_body(), | ||||||
|  |             message: message, | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,37 +1,86 @@ | |||||||
| header! { | use std::fmt; | ||||||
|     #[doc="`Content-Length` header, defined in"] |  | ||||||
|     #[doc="[RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.2)"] |  | ||||||
|     #[doc=""] |  | ||||||
|     #[doc="When a message does not have a `Transfer-Encoding` header field, a"] |  | ||||||
|     #[doc="Content-Length header field can provide the anticipated size, as a"] |  | ||||||
|     #[doc="decimal number of octets, for a potential payload body.  For messages"] |  | ||||||
|     #[doc="that do include a payload body, the Content-Length field-value"] |  | ||||||
|     #[doc="provides the framing information necessary for determining where the"] |  | ||||||
|     #[doc="body (and message) ends.  For messages that do not include a payload"] |  | ||||||
|     #[doc="body, the Content-Length indicates the size of the selected"] |  | ||||||
|     #[doc="representation."] |  | ||||||
|     #[doc=""] |  | ||||||
|     #[doc="# ABNF"] |  | ||||||
|     #[doc="```plain"] |  | ||||||
|     #[doc="Content-Length = 1*DIGIT"] |  | ||||||
|     #[doc="```"] |  | ||||||
|     #[doc=""] |  | ||||||
|     #[doc="# Example values"] |  | ||||||
|     #[doc="* `3495`"] |  | ||||||
|     #[doc=""] |  | ||||||
|     #[doc="# Example"] |  | ||||||
|     #[doc="```"] |  | ||||||
|     #[doc="use hyper::header::{Headers, ContentLength};"] |  | ||||||
|     #[doc=""] |  | ||||||
|     #[doc="let mut headers = Headers::new();"] |  | ||||||
|     #[doc="headers.set(ContentLength(1024u64));"] |  | ||||||
|     #[doc="```"] |  | ||||||
|     (ContentLength, "Content-Length") => [u64] |  | ||||||
|  |  | ||||||
|     test_content_length { | use header::{HeaderFormat, Header, parsing}; | ||||||
|         // Testcase from RFC |  | ||||||
|         test_header!(test1, vec![b"3495"], Some(HeaderField(3495))); | #[doc="`Content-Length` header, defined in"] | ||||||
|  | #[doc="[RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.2)"] | ||||||
|  | #[doc=""] | ||||||
|  | #[doc="When a message does not have a `Transfer-Encoding` header field, a"] | ||||||
|  | #[doc="Content-Length header field can provide the anticipated size, as a"] | ||||||
|  | #[doc="decimal number of octets, for a potential payload body.  For messages"] | ||||||
|  | #[doc="that do include a payload body, the Content-Length field-value"] | ||||||
|  | #[doc="provides the framing information necessary for determining where the"] | ||||||
|  | #[doc="body (and message) ends.  For messages that do not include a payload"] | ||||||
|  | #[doc="body, the Content-Length indicates the size of the selected"] | ||||||
|  | #[doc="representation."] | ||||||
|  | #[doc=""] | ||||||
|  | #[doc="# ABNF"] | ||||||
|  | #[doc="```plain"] | ||||||
|  | #[doc="Content-Length = 1*DIGIT"] | ||||||
|  | #[doc="```"] | ||||||
|  | #[doc=""] | ||||||
|  | #[doc="# Example values"] | ||||||
|  | #[doc="* `3495`"] | ||||||
|  | #[doc=""] | ||||||
|  | #[doc="# Example"] | ||||||
|  | #[doc="```"] | ||||||
|  | #[doc="use hyper::header::{Headers, ContentLength};"] | ||||||
|  | #[doc=""] | ||||||
|  | #[doc="let mut headers = Headers::new();"] | ||||||
|  | #[doc="headers.set(ContentLength(1024u64));"] | ||||||
|  | #[doc="```"] | ||||||
|  | #[derive(Clone, Copy, Debug, PartialEq)] | ||||||
|  | pub struct ContentLength(pub u64); | ||||||
|  |  | ||||||
|  | impl Header for ContentLength { | ||||||
|  |     #[inline] | ||||||
|  |     fn header_name() -> &'static str { | ||||||
|  |         "Content-Length" | ||||||
|  |     } | ||||||
|  |     fn parse_header(raw: &[Vec<u8>]) -> ::Result<ContentLength> { | ||||||
|  |         // If multiple Content-Length headers were sent, everything can still | ||||||
|  |         // be alright if they all contain the same value, and all parse | ||||||
|  |         // correctly. If not, then it's an error. | ||||||
|  |         raw.iter() | ||||||
|  |             .map(::std::ops::Deref::deref) | ||||||
|  |             .map(parsing::from_raw_str) | ||||||
|  |             .fold(None, |prev, x| { | ||||||
|  |                 match (prev, x) { | ||||||
|  |                     (None, x) => Some(x), | ||||||
|  |                     (e@Some(Err(_)), _ ) => e, | ||||||
|  |                     (Some(Ok(prev)), Ok(x)) if prev == x => Some(Ok(prev)), | ||||||
|  |                     _ => Some(Err(::Error::Header)) | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  |             .unwrap_or(Err(::Error::Header)) | ||||||
|  |             .map(ContentLength) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | impl HeaderFormat for ContentLength { | ||||||
|  |     #[inline] | ||||||
|  |     fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|  |         fmt::Display::fmt(&self.0, f) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl fmt::Display for ContentLength { | ||||||
|  |     #[inline] | ||||||
|  |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|  |         fmt::Display::fmt(&self.0, f) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | __hyper__deref!(ContentLength => u64); | ||||||
|  | __hyper_generate_header_serialization!(ContentLength); | ||||||
|  |  | ||||||
|  | __hyper__tm!(ContentLength, tests { | ||||||
|  |     // Testcase from RFC | ||||||
|  |     test_header!(test1, vec![b"3495"], Some(HeaderField(3495))); | ||||||
|  |  | ||||||
|  |     test_header!(test_invalid, vec![b"34v95"], None); | ||||||
|  |     test_header!(test_duplicates, vec![b"5", b"5"], Some(HeaderField(5))); | ||||||
|  |     test_header!(test_duplicates_vary, vec![b"5", b"6", b"5"], None); | ||||||
|  | }); | ||||||
|  |  | ||||||
| bench_header!(bench, ContentLength, { vec![b"42349984".to_vec()] }); | bench_header!(bench, ContentLength, { vec![b"42349984".to_vec()] }); | ||||||
|   | |||||||
| @@ -148,12 +148,13 @@ macro_rules! test_header { | |||||||
|         fn $id() { |         fn $id() { | ||||||
|             let a: Vec<Vec<u8>> = $raw.iter().map(|x| x.to_vec()).collect(); |             let a: Vec<Vec<u8>> = $raw.iter().map(|x| x.to_vec()).collect(); | ||||||
|             let val = HeaderField::parse_header(&a[..]); |             let val = HeaderField::parse_header(&a[..]); | ||||||
|  |             let typed: Option<HeaderField> = $typed; | ||||||
|             // Test parsing |             // Test parsing | ||||||
|             assert_eq!(val.ok(), $typed); |             assert_eq!(val.ok(), typed); | ||||||
|             // Test formatting |             // Test formatting | ||||||
|             if $typed != None { |             if typed.is_some() { | ||||||
|                 let res: &str = str::from_utf8($raw[0]).unwrap(); |                 let res: &str = str::from_utf8($raw[0]).unwrap(); | ||||||
|                 assert_eq!(format!("{}", $typed.unwrap()), res); |                 assert_eq!(format!("{}", typed.unwrap()), res); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -3,16 +3,17 @@ | |||||||
| use std::str; | use std::str; | ||||||
| use std::fmt::{self, Display}; | use std::fmt::{self, Display}; | ||||||
|  |  | ||||||
| /// Reads a single raw string when parsing a header | /// Reads a single raw string when parsing a header. | ||||||
| pub fn from_one_raw_str<T: str::FromStr>(raw: &[Vec<u8>]) -> ::Result<T> { | pub fn from_one_raw_str<T: str::FromStr>(raw: &[Vec<u8>]) -> ::Result<T> { | ||||||
|     if raw.len() != 1 || unsafe { raw.get_unchecked(0) } == b"" { return Err(::Error::Header) } |     if raw.len() != 1 || unsafe { raw.get_unchecked(0) } == b"" { return Err(::Error::Header) } | ||||||
|     // we JUST checked that raw.len() == 1, so raw[0] WILL exist. |     // we JUST checked that raw.len() == 1, so raw[0] WILL exist. | ||||||
|     let s: &str = try!(str::from_utf8(& unsafe { raw.get_unchecked(0) }[..])); |     from_raw_str(& unsafe { raw.get_unchecked(0) }) | ||||||
|     if let Ok(x) = str::FromStr::from_str(s) { | } | ||||||
|         Ok(x) |  | ||||||
|     } else { | /// Reads a raw string into a value. | ||||||
|         Err(::Error::Header) | pub fn from_raw_str<T: str::FromStr>(raw: &[u8]) -> ::Result<T> { | ||||||
|     } |     let s = try!(str::from_utf8(raw)); | ||||||
|  |     T::from_str(s).or(Err(::Error::Header)) | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Reads a comma-delimited raw header into a Vec. | /// Reads a comma-delimited raw header into a Vec. | ||||||
|   | |||||||
| @@ -36,6 +36,7 @@ use version; | |||||||
| /// An implementation of the `HttpMessage` trait for HTTP/1.1. | /// An implementation of the `HttpMessage` trait for HTTP/1.1. | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| pub struct Http11Message { | pub struct Http11Message { | ||||||
|  |     method: Option<Method>, | ||||||
|     stream: Option<Box<NetworkStream + Send>>, |     stream: Option<Box<NetworkStream + Send>>, | ||||||
|     writer: Option<HttpWriter<BufWriter<Box<NetworkStream + Send>>>>, |     writer: Option<HttpWriter<BufWriter<Box<NetworkStream + Send>>>>, | ||||||
|     reader: Option<HttpReader<BufReader<Box<NetworkStream + Send>>>>, |     reader: Option<HttpReader<BufReader<Box<NetworkStream + Send>>>>, | ||||||
| @@ -91,8 +92,8 @@ 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 = match &head.method { | ||||||
|             Method::Get | Method::Head => { |             &Method::Get | &Method::Head => { | ||||||
|                 debug!("headers={:?}", head.headers); |                 debug!("headers={:?}", head.headers); | ||||||
|                 try!(write!(&mut stream, "{}{}", head.headers, LINE_ENDING)); |                 try!(write!(&mut stream, "{}{}", head.headers, LINE_ENDING)); | ||||||
|                 EmptyWriter(stream) |                 EmptyWriter(stream) | ||||||
| @@ -137,6 +138,7 @@ impl HttpMessage for Http11Message { | |||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|  |         self.method = Some(head.method.clone()); | ||||||
|         self.writer = Some(stream); |         self.writer = Some(stream); | ||||||
|  |  | ||||||
|         Ok(head) |         Ok(head) | ||||||
| @@ -159,30 +161,37 @@ impl HttpMessage for Http11Message { | |||||||
|         let raw_status = head.subject; |         let raw_status = head.subject; | ||||||
|         let headers = head.headers; |         let headers = head.headers; | ||||||
|  |  | ||||||
|         let body = if headers.has::<TransferEncoding>() { |         let method = self.method.take().unwrap_or(Method::Get); | ||||||
|             match headers.get::<TransferEncoding>() { |         // According to https://tools.ietf.org/html/rfc7230#section-3.3.3 | ||||||
|                 Some(&TransferEncoding(ref codings)) => { |         // 1. HEAD reponses, and Status 1xx, 204, and 304 cannot have a body. | ||||||
|                     if codings.len() > 1 { |         // 2. Status 2xx to a CONNECT cannot have a body. | ||||||
|                         trace!("TODO: #2 handle other codings: {:?}", codings); |         // 3. Transfer-Encoding: chunked has a chunked body. | ||||||
|                     }; |         // 4. If multiple differing Content-Length headers or invalid, close connection. | ||||||
|  |         // 5. Content-Length header has a sized body. | ||||||
|                     if codings.contains(&Chunked) { |         // 6. Not Client. | ||||||
|  |         // 7. Read till EOF. | ||||||
|  |         let body = match (method, raw_status.0) { | ||||||
|  |             (Method::Head, _) => EmptyReader(stream), | ||||||
|  |             (_, 100...199) | (_, 204) | (_, 304) => EmptyReader(stream), | ||||||
|  |             (Method::Connect, 200...299) => EmptyReader(stream), | ||||||
|  |             _ => { | ||||||
|  |                  if let Some(&TransferEncoding(ref codings)) = headers.get() { | ||||||
|  |                     if codings.last() == Some(&Chunked) { | ||||||
|                         ChunkedReader(stream, None) |                         ChunkedReader(stream, None) | ||||||
|                     } else { |                     } else { | ||||||
|                         trace!("not chuncked. read till eof"); |                         trace!("not chuncked. read till eof"); | ||||||
|                         EofReader(stream) |                         EofReader(stream) | ||||||
|                     } |                     } | ||||||
|  |                 } else if let Some(&ContentLength(len)) =  headers.get() { | ||||||
|  |                     SizedReader(stream, len) | ||||||
|  |                 } else if headers.has::<ContentLength>() { | ||||||
|  |                     trace!("illegal Content-Length: {:?}", headers.get_raw("Content-Length")); | ||||||
|  |                     return Err(Error::Header); | ||||||
|  |                 } else { | ||||||
|  |                     trace!("neither Transfer-Encoding nor Content-Length"); | ||||||
|  |                     EofReader(stream) | ||||||
|                 } |                 } | ||||||
|                 None => unreachable!() |  | ||||||
|             } |             } | ||||||
|         } else if headers.has::<ContentLength>() { |  | ||||||
|             match headers.get::<ContentLength>() { |  | ||||||
|                 Some(&ContentLength(len)) => SizedReader(stream, len), |  | ||||||
|                 None => unreachable!() |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             trace!("neither Transfer-Encoding nor Content-Length"); |  | ||||||
|             EofReader(stream) |  | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         self.reader = Some(body); |         self.reader = Some(body); | ||||||
| @@ -194,6 +203,13 @@ impl HttpMessage for Http11Message { | |||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fn has_body(&self) -> bool { | ||||||
|  |         match self.reader { | ||||||
|  |             Some(EmptyReader(..)) => false, | ||||||
|  |             _ => true | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     #[cfg(feature = "timeouts")] |     #[cfg(feature = "timeouts")] | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> { |     fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> { | ||||||
| @@ -259,6 +275,7 @@ impl Http11Message { | |||||||
|     /// the peer. |     /// the peer. | ||||||
|     pub fn with_stream(stream: Box<NetworkStream + Send>) -> Http11Message { |     pub fn with_stream(stream: Box<NetworkStream + Send>) -> Http11Message { | ||||||
|         Http11Message { |         Http11Message { | ||||||
|  |             method: None, | ||||||
|             stream: Some(stream), |             stream: Some(stream), | ||||||
|             writer: None, |             writer: None, | ||||||
|             reader: None, |             reader: None, | ||||||
|   | |||||||
| @@ -400,6 +400,10 @@ impl<S> HttpMessage for Http2Message<S> where S: CloneableStream { | |||||||
|         Ok(head) |         Ok(head) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fn has_body(&self) -> bool { | ||||||
|  |         true | ||||||
|  |     } | ||||||
|  |  | ||||||
|     #[cfg(feature = "timeouts")] |     #[cfg(feature = "timeouts")] | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn set_read_timeout(&self, _dur: Option<Duration>) -> io::Result<()> { |     fn set_read_timeout(&self, _dur: Option<Duration>) -> io::Result<()> { | ||||||
|   | |||||||
| @@ -72,6 +72,8 @@ pub trait HttpMessage: Write + Read + Send + Any + Typeable + Debug { | |||||||
|     fn set_write_timeout(&self, dur: Option<Duration>) -> io::Result<()>; |     fn set_write_timeout(&self, dur: Option<Duration>) -> io::Result<()>; | ||||||
|     /// Closes the underlying HTTP connection. |     /// Closes the underlying HTTP connection. | ||||||
|     fn close_connection(&mut self) -> ::Result<()>; |     fn close_connection(&mut self) -> ::Result<()>; | ||||||
|  |     /// Returns whether the incoming message has a body. | ||||||
|  |     fn has_body(&self) -> bool; | ||||||
| } | } | ||||||
|  |  | ||||||
| impl HttpMessage { | impl HttpMessage { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user