fix(client): improve HttpReader selection for client Responses
Closes #436
This commit is contained in:
		| @@ -1,37 +1,86 @@ | ||||
| header! { | ||||
|     #[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] | ||||
| use std::fmt; | ||||
|  | ||||
|     test_content_length { | ||||
|         // Testcase from RFC | ||||
|         test_header!(test1, vec![b"3495"], Some(HeaderField(3495))); | ||||
| use header::{HeaderFormat, Header, parsing}; | ||||
|  | ||||
| #[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()] }); | ||||
|   | ||||
| @@ -148,12 +148,13 @@ macro_rules! test_header { | ||||
|         fn $id() { | ||||
|             let a: Vec<Vec<u8>> = $raw.iter().map(|x| x.to_vec()).collect(); | ||||
|             let val = HeaderField::parse_header(&a[..]); | ||||
|             let typed: Option<HeaderField> = $typed; | ||||
|             // Test parsing | ||||
|             assert_eq!(val.ok(), $typed); | ||||
|             assert_eq!(val.ok(), typed); | ||||
|             // Test formatting | ||||
|             if $typed != None { | ||||
|             if typed.is_some() { | ||||
|                 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::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> { | ||||
|     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. | ||||
|     let s: &str = try!(str::from_utf8(& unsafe { raw.get_unchecked(0) }[..])); | ||||
|     if let Ok(x) = str::FromStr::from_str(s) { | ||||
|         Ok(x) | ||||
|     } else { | ||||
|         Err(::Error::Header) | ||||
|     } | ||||
|     from_raw_str(& unsafe { raw.get_unchecked(0) }) | ||||
| } | ||||
|  | ||||
| /// Reads a raw string into a value. | ||||
| 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. | ||||
|   | ||||
| @@ -36,6 +36,7 @@ use version; | ||||
| /// An implementation of the `HttpMessage` trait for HTTP/1.1. | ||||
| #[derive(Debug)] | ||||
| pub struct Http11Message { | ||||
|     method: Option<Method>, | ||||
|     stream: Option<Box<NetworkStream + Send>>, | ||||
|     writer: Option<HttpWriter<BufWriter<Box<NetworkStream + Send>>>>, | ||||
|     reader: Option<HttpReader<BufReader<Box<NetworkStream + Send>>>>, | ||||
| @@ -91,8 +92,8 @@ impl HttpMessage for Http11Message { | ||||
|         try!(write!(&mut stream, "{} {} {}{}", | ||||
|                     head.method, uri, version, LINE_ENDING)); | ||||
|  | ||||
|         let stream = match head.method { | ||||
|             Method::Get | Method::Head => { | ||||
|         let stream = match &head.method { | ||||
|             &Method::Get | &Method::Head => { | ||||
|                 debug!("headers={:?}", head.headers); | ||||
|                 try!(write!(&mut stream, "{}{}", head.headers, LINE_ENDING)); | ||||
|                 EmptyWriter(stream) | ||||
| @@ -137,6 +138,7 @@ impl HttpMessage for Http11Message { | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         self.method = Some(head.method.clone()); | ||||
|         self.writer = Some(stream); | ||||
|  | ||||
|         Ok(head) | ||||
| @@ -159,30 +161,37 @@ impl HttpMessage for Http11Message { | ||||
|         let raw_status = head.subject; | ||||
|         let headers = head.headers; | ||||
|  | ||||
|         let body = if headers.has::<TransferEncoding>() { | ||||
|             match headers.get::<TransferEncoding>() { | ||||
|                 Some(&TransferEncoding(ref codings)) => { | ||||
|                     if codings.len() > 1 { | ||||
|                         trace!("TODO: #2 handle other codings: {:?}", codings); | ||||
|                     }; | ||||
|  | ||||
|                     if codings.contains(&Chunked) { | ||||
|         let method = self.method.take().unwrap_or(Method::Get); | ||||
|         // According to https://tools.ietf.org/html/rfc7230#section-3.3.3 | ||||
|         // 1. HEAD reponses, and Status 1xx, 204, and 304 cannot have a body. | ||||
|         // 2. Status 2xx to a CONNECT cannot have a body. | ||||
|         // 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. | ||||
|         // 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) | ||||
|                     } else { | ||||
|                         trace!("not chuncked. read till eof"); | ||||
|                         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); | ||||
| @@ -259,6 +268,7 @@ impl Http11Message { | ||||
|     /// the peer. | ||||
|     pub fn with_stream(stream: Box<NetworkStream + Send>) -> Http11Message { | ||||
|         Http11Message { | ||||
|             method: None, | ||||
|             stream: Some(stream), | ||||
|             writer: None, | ||||
|             reader: None, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user