fix(server): improve detection of when a Response can have a body
By knowing if the incoming Request was a HEAD, or checking for 204 or 304 status codes, the server will do a better job of either adding or removing `Content-Length` and `Transfer-Encoding` headers. Closes #1257
This commit is contained in:
		| @@ -7,10 +7,10 @@ use futures::task::Task; | |||||||
| use tokio_io::{AsyncRead, AsyncWrite}; | use tokio_io::{AsyncRead, AsyncWrite}; | ||||||
| use tokio_proto::streaming::pipeline::{Frame, Transport}; | use tokio_proto::streaming::pipeline::{Frame, Transport}; | ||||||
|  |  | ||||||
| use header::{ContentLength, TransferEncoding}; |  | ||||||
| use http::{self, Http1Transaction, DebugTruncate}; | use http::{self, Http1Transaction, DebugTruncate}; | ||||||
| use http::io::{Cursor, Buffered}; | use http::io::{Cursor, Buffered}; | ||||||
| use http::h1::{Encoder, Decoder}; | use http::h1::{Encoder, Decoder}; | ||||||
|  | use method::Method; | ||||||
| use version::HttpVersion; | use version::HttpVersion; | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -37,10 +37,11 @@ where I: AsyncRead + AsyncWrite, | |||||||
|         Conn { |         Conn { | ||||||
|             io: Buffered::new(io), |             io: Buffered::new(io), | ||||||
|             state: State { |             state: State { | ||||||
|  |                 keep_alive: keep_alive, | ||||||
|  |                 method: None, | ||||||
|  |                 read_task: None, | ||||||
|                 reading: Reading::Init, |                 reading: Reading::Init, | ||||||
|                 writing: Writing::Init, |                 writing: Writing::Init, | ||||||
|                 read_task: None, |  | ||||||
|                 keep_alive: keep_alive, |  | ||||||
|             }, |             }, | ||||||
|             _marker: PhantomData, |             _marker: PhantomData, | ||||||
|         } |         } | ||||||
| @@ -103,7 +104,7 @@ where I: AsyncRead + AsyncWrite, | |||||||
|  |  | ||||||
|         match version { |         match version { | ||||||
|             HttpVersion::Http10 | HttpVersion::Http11 => { |             HttpVersion::Http10 | HttpVersion::Http11 => { | ||||||
|                 let decoder = match T::decoder(&head) { |                 let decoder = match T::decoder(&head, &mut self.state.method) { | ||||||
|                     Ok(d) => d, |                     Ok(d) => d, | ||||||
|                     Err(e) => { |                     Err(e) => { | ||||||
|                         debug!("decoder error = {:?}", e); |                         debug!("decoder error = {:?}", e); | ||||||
| @@ -234,17 +235,8 @@ where I: AsyncRead + AsyncWrite, | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn write_head(&mut self, mut head: http::MessageHead<T::Outgoing>, body: bool) { |     fn write_head(&mut self, head: http::MessageHead<T::Outgoing>, body: bool) { | ||||||
|         debug_assert!(self.can_write_head()); |         debug_assert!(self.can_write_head()); | ||||||
|         if !body { |  | ||||||
|             head.headers.remove::<TransferEncoding>(); |  | ||||||
|             //TODO: check that this isn't a response to a HEAD |  | ||||||
|             //request, which could include the content-length |  | ||||||
|             //even if no body is to be written |  | ||||||
|             if T::should_set_length(&head) { |  | ||||||
|                 head.headers.set(ContentLength(0)); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         let wants_keep_alive = head.should_keep_alive(); |         let wants_keep_alive = head.should_keep_alive(); | ||||||
|         self.state.keep_alive &= wants_keep_alive; |         self.state.keep_alive &= wants_keep_alive; | ||||||
| @@ -256,8 +248,8 @@ where I: AsyncRead + AsyncWrite, | |||||||
|                 buf.extend_from_slice(pending.buf()); |                 buf.extend_from_slice(pending.buf()); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         let encoder = T::encode(head, buf); |         let encoder = T::encode(head, body, &mut self.state.method, buf); | ||||||
|         self.state.writing = if body { |         self.state.writing = if !encoder.is_eof() { | ||||||
|             Writing::Body(encoder, None) |             Writing::Body(encoder, None) | ||||||
|         } else { |         } else { | ||||||
|             Writing::KeepAlive |             Writing::KeepAlive | ||||||
| @@ -493,10 +485,11 @@ impl<I, B: AsRef<[u8]>, T, K: fmt::Debug> fmt::Debug for Conn<I, B, T, K> { | |||||||
| } | } | ||||||
|  |  | ||||||
| struct State<B, K> { | struct State<B, K> { | ||||||
|  |     keep_alive: K, | ||||||
|  |     method: Option<Method>, | ||||||
|  |     read_task: Option<Task>, | ||||||
|     reading: Reading, |     reading: Reading, | ||||||
|     writing: Writing<B>, |     writing: Writing<B>, | ||||||
|     read_task: Option<Task>, |  | ||||||
|     keep_alive: K, |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| @@ -522,6 +515,7 @@ impl<B: AsRef<[u8]>, K: fmt::Debug> fmt::Debug for State<B, K> { | |||||||
|             .field("reading", &self.reading) |             .field("reading", &self.reading) | ||||||
|             .field("writing", &self.writing) |             .field("writing", &self.writing) | ||||||
|             .field("keep_alive", &self.keep_alive) |             .field("keep_alive", &self.keep_alive) | ||||||
|  |             .field("method", &self.method) | ||||||
|             .field("read_task", &self.read_task) |             .field("read_task", &self.read_task) | ||||||
|             .finish() |             .finish() | ||||||
|     } |     } | ||||||
| @@ -641,6 +635,7 @@ impl<B, K: KeepAlive> State<B, K> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn idle(&mut self) { |     fn idle(&mut self) { | ||||||
|  |         self.method = None; | ||||||
|         self.reading = Reading::Init; |         self.reading = Reading::Init; | ||||||
|         self.writing = Writing::Init; |         self.writing = Writing::Init; | ||||||
|         self.keep_alive.idle(); |         self.keep_alive.idle(); | ||||||
|   | |||||||
| @@ -5,7 +5,8 @@ use httparse; | |||||||
| use bytes::{BytesMut, Bytes}; | use bytes::{BytesMut, Bytes}; | ||||||
|  |  | ||||||
| use header::{self, Headers, ContentLength, TransferEncoding}; | use header::{self, Headers, ContentLength, TransferEncoding}; | ||||||
| use http::{MessageHead, RawStatus, Http1Transaction, ParseResult, ServerTransaction, ClientTransaction, RequestLine}; | use http::{MessageHead, RawStatus, Http1Transaction, ParseResult, | ||||||
|  |            ServerTransaction, ClientTransaction, RequestLine, RequestHead}; | ||||||
| use http::h1::{Encoder, Decoder, date}; | use http::h1::{Encoder, Decoder, date}; | ||||||
| use method::Method; | use method::Method; | ||||||
| use status::StatusCode; | use status::StatusCode; | ||||||
| @@ -72,8 +73,11 @@ impl Http1Transaction for ServerTransaction { | |||||||
|         }, len))) |         }, len))) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn decoder(head: &MessageHead<Self::Incoming>) -> ::Result<Decoder> { |     fn decoder(head: &MessageHead<Self::Incoming>, method: &mut Option<Method>) -> ::Result<Decoder> { | ||||||
|         use ::header; |         use ::header; | ||||||
|  |  | ||||||
|  |         *method = Some(head.subject.0.clone()); | ||||||
|  |  | ||||||
|         // According to https://tools.ietf.org/html/rfc7230#section-3.3.3 |         // According to https://tools.ietf.org/html/rfc7230#section-3.3.3 | ||||||
|         // 1. (irrelevant to Request) |         // 1. (irrelevant to Request) | ||||||
|         // 2. (irrelevant to Request) |         // 2. (irrelevant to Request) | ||||||
| @@ -105,30 +109,11 @@ impl Http1Transaction for ServerTransaction { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     fn encode(mut head: MessageHead<Self::Outgoing>, dst: &mut Vec<u8>) -> Encoder { |     fn encode(mut head: MessageHead<Self::Outgoing>, has_body: bool, method: &mut Option<Method>, dst: &mut Vec<u8>) -> Encoder { | ||||||
|         use ::header; |         trace!("ServerTransaction::encode head={:?}, has_body={}, method={:?}", | ||||||
|         trace!("writing head: {:?}", head); |                head, has_body, method); | ||||||
|  |  | ||||||
|         let len = head.headers.get::<header::ContentLength>().map(|n| **n); |         let body = ServerTransaction::set_length(&mut head, has_body, method.as_ref()); | ||||||
|  |  | ||||||
|         let body = if let Some(len) = len { |  | ||||||
|             Encoder::length(len) |  | ||||||
|         } else { |  | ||||||
|             let encodings = match head.headers.get_mut::<header::TransferEncoding>() { |  | ||||||
|                 Some(&mut header::TransferEncoding(ref mut encodings)) => { |  | ||||||
|                     if encodings.last() != Some(&header::Encoding::Chunked) { |  | ||||||
|                         encodings.push(header::Encoding::Chunked); |  | ||||||
|                     } |  | ||||||
|                     false |  | ||||||
|                 }, |  | ||||||
|                 None => true |  | ||||||
|             }; |  | ||||||
|  |  | ||||||
|             if encodings { |  | ||||||
|                 head.headers.set(header::TransferEncoding(vec![header::Encoding::Chunked])); |  | ||||||
|             } |  | ||||||
|             Encoder::chunked() |  | ||||||
|         }; |  | ||||||
|         debug!("encode headers = {:?}", head.headers); |         debug!("encode headers = {:?}", head.headers); | ||||||
|  |  | ||||||
|         let init_cap = 30 + head.headers.len() * AVERAGE_HEADER_SIZE; |         let init_cap = 30 + head.headers.len() * AVERAGE_HEADER_SIZE; | ||||||
| @@ -150,16 +135,39 @@ impl Http1Transaction for ServerTransaction { | |||||||
|         extend(dst, b"\r\n"); |         extend(dst, b"\r\n"); | ||||||
|         body |         body | ||||||
|     } |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|     fn should_set_length(head: &MessageHead<Self::Outgoing>) -> bool { | impl ServerTransaction { | ||||||
|         //TODO: pass method, check if method == HEAD |     fn set_length(head: &mut MessageHead<StatusCode>, has_body: bool, method: Option<&Method>) -> Encoder { | ||||||
|  |         // these are here thanks to borrowck | ||||||
|  |         // `if method == Some(&Method::Get)` says the RHS doesnt live long enough | ||||||
|  |         const HEAD: Option<&'static Method> = Some(&Method::Head); | ||||||
|  |         const CONNECT: Option<&'static Method> = Some(&Method::Connect); | ||||||
|  |  | ||||||
|         match head.subject { |         let can_have_body = { | ||||||
|             // TODO: support for 1xx codes needs improvement everywhere |             if method == HEAD { | ||||||
|             // would be 100...199 => false |                 false | ||||||
|             StatusCode::NoContent | |             } else if method == CONNECT && head.subject.is_success() { | ||||||
|             StatusCode::NotModified => false, |                 false | ||||||
|             _ => true, |             } else { | ||||||
|  |                 match head.subject { | ||||||
|  |                     // TODO: support for 1xx codes needs improvement everywhere | ||||||
|  |                     // would be 100...199 => false | ||||||
|  |                     StatusCode::NoContent | | ||||||
|  |                     StatusCode::NotModified => false, | ||||||
|  |                     _ => true, | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         if has_body && can_have_body { | ||||||
|  |             set_length(&mut head.headers) | ||||||
|  |         } else { | ||||||
|  |             head.headers.remove::<TransferEncoding>(); | ||||||
|  |             if can_have_body { | ||||||
|  |                 head.headers.set(ContentLength(0)); | ||||||
|  |             } | ||||||
|  |             Encoder::length(0) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -213,8 +221,7 @@ impl Http1Transaction for ClientTransaction { | |||||||
|         }, len))) |         }, len))) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn decoder(inc: &MessageHead<Self::Incoming>) -> ::Result<Decoder> { |     fn decoder(inc: &MessageHead<Self::Incoming>, method: &mut Option<Method>) -> ::Result<Decoder> { | ||||||
|         use ::header; |  | ||||||
|         // According to https://tools.ietf.org/html/rfc7230#section-3.3.3 |         // According to https://tools.ietf.org/html/rfc7230#section-3.3.3 | ||||||
|         // 1. HEAD responses, and Status 1xx, 204, and 304 cannot have a body. |         // 1. HEAD responses, and Status 1xx, 204, and 304 cannot have a body. | ||||||
|         // 2. Status 2xx to a CONNECT cannot have a body. |         // 2. Status 2xx to a CONNECT cannot have a body. | ||||||
| @@ -224,7 +231,21 @@ impl Http1Transaction for ClientTransaction { | |||||||
|         // 6. (irrelevant to Response) |         // 6. (irrelevant to Response) | ||||||
|         // 7. Read till EOF. |         // 7. Read till EOF. | ||||||
|  |  | ||||||
|         //TODO: need a way to pass the Method that caused this Response |         match *method { | ||||||
|  |             Some(Method::Head) => { | ||||||
|  |                 return Ok(Decoder::length(0)); | ||||||
|  |             } | ||||||
|  |             Some(Method::Connect) => match inc.subject.0 { | ||||||
|  |                 200...299 => { | ||||||
|  |                     return Ok(Decoder::length(0)); | ||||||
|  |                 }, | ||||||
|  |                 _ => {}, | ||||||
|  |             }, | ||||||
|  |             Some(_) => {}, | ||||||
|  |             None => { | ||||||
|  |                 trace!("ClientTransaction::decoder is missing the Method"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         match inc.subject.0 { |         match inc.subject.0 { | ||||||
|             100...199 | |             100...199 | | ||||||
| @@ -251,39 +272,14 @@ impl Http1Transaction for ClientTransaction { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn encode(mut head: MessageHead<Self::Outgoing>, dst: &mut Vec<u8>) -> Encoder { |     fn encode(mut head: MessageHead<Self::Outgoing>, has_body: bool, method: &mut Option<Method>, dst: &mut Vec<u8>) -> Encoder { | ||||||
|         trace!("writing head: {:?}", head); |         trace!("ClientTransaction::encode head={:?}, has_body={}, method={:?}", | ||||||
|  |                head, has_body, method); | ||||||
|  |  | ||||||
|  |  | ||||||
|         let mut body = Encoder::length(0); |         *method = Some(head.subject.0.clone()); | ||||||
|         let expects_no_body = match head.subject.0 { |  | ||||||
|             Method::Head | Method::Get | Method::Connect => true, |  | ||||||
|             _ => false |  | ||||||
|         }; |  | ||||||
|         let mut chunked = false; |  | ||||||
|  |  | ||||||
|         if let Some(con_len) = head.headers.get::<ContentLength>() { |         let body = ClientTransaction::set_length(&mut head, has_body); | ||||||
|             body = Encoder::length(**con_len); |  | ||||||
|         } else { |  | ||||||
|             chunked = !expects_no_body; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if chunked { |  | ||||||
|             body = Encoder::chunked(); |  | ||||||
|             let encodings = match head.headers.get_mut::<TransferEncoding>() { |  | ||||||
|                 Some(encodings) => { |  | ||||||
|                     if !encodings.contains(&header::Encoding::Chunked) { |  | ||||||
|                         encodings.push(header::Encoding::Chunked); |  | ||||||
|                     } |  | ||||||
|                     true |  | ||||||
|                 }, |  | ||||||
|                 None => false |  | ||||||
|             }; |  | ||||||
|  |  | ||||||
|             if !encodings { |  | ||||||
|                 head.headers.set(TransferEncoding(vec![header::Encoding::Chunked])); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         debug!("encode headers = {:?}", head.headers); |         debug!("encode headers = {:?}", head.headers); | ||||||
|  |  | ||||||
|         let init_cap = 30 + head.headers.len() * AVERAGE_HEADER_SIZE; |         let init_cap = 30 + head.headers.len() * AVERAGE_HEADER_SIZE; | ||||||
| @@ -292,16 +288,43 @@ impl Http1Transaction for ClientTransaction { | |||||||
|  |  | ||||||
|         body |         body | ||||||
|     } |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl ClientTransaction { | ||||||
|     fn should_set_length(head: &MessageHead<Self::Outgoing>) -> bool { |     fn set_length(head: &mut RequestHead, has_body: bool) -> Encoder { | ||||||
|         match &head.subject.0 { |         if has_body { | ||||||
|             &Method::Get | &Method::Head => false, |             set_length(&mut head.headers) | ||||||
|             _ => true |         } else { | ||||||
|  |             head.headers.remove::<ContentLength>(); | ||||||
|  |             head.headers.remove::<TransferEncoding>(); | ||||||
|  |             Encoder::length(0) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | fn set_length(headers: &mut Headers) -> Encoder { | ||||||
|  |     let len = headers.get::<header::ContentLength>().map(|n| **n); | ||||||
|  |  | ||||||
|  |     if let Some(len) = len { | ||||||
|  |         Encoder::length(len) | ||||||
|  |     } else { | ||||||
|  |         let encodings = match headers.get_mut::<header::TransferEncoding>() { | ||||||
|  |             Some(&mut header::TransferEncoding(ref mut encodings)) => { | ||||||
|  |                 if encodings.last() != Some(&header::Encoding::Chunked) { | ||||||
|  |                     encodings.push(header::Encoding::Chunked); | ||||||
|  |                 } | ||||||
|  |                 false | ||||||
|  |             }, | ||||||
|  |             None => true | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         if encodings { | ||||||
|  |             headers.set(header::TransferEncoding(vec![header::Encoding::Chunked])); | ||||||
|  |         } | ||||||
|  |         Encoder::chunked() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| #[derive(Clone, Copy)] | #[derive(Clone, Copy)] | ||||||
| struct HeaderIndices { | struct HeaderIndices { | ||||||
|     name: (usize, usize), |     name: (usize, usize), | ||||||
| @@ -421,63 +444,83 @@ mod tests { | |||||||
|     fn test_decoder_request() { |     fn test_decoder_request() { | ||||||
|         use super::Decoder; |         use super::Decoder; | ||||||
|  |  | ||||||
|  |         let method = &mut None; | ||||||
|         let mut head = MessageHead::<::http::RequestLine>::default(); |         let mut head = MessageHead::<::http::RequestLine>::default(); | ||||||
|  |  | ||||||
|         head.subject.0 = ::Method::Get; |         head.subject.0 = ::Method::Get; | ||||||
|         assert_eq!(Decoder::length(0), ServerTransaction::decoder(&head).unwrap()); |         assert_eq!(Decoder::length(0), ServerTransaction::decoder(&head, method).unwrap()); | ||||||
|  |         assert_eq!(*method, Some(::Method::Get)); | ||||||
|  |  | ||||||
|         head.subject.0 = ::Method::Post; |         head.subject.0 = ::Method::Post; | ||||||
|         assert_eq!(Decoder::length(0), ServerTransaction::decoder(&head).unwrap()); |         assert_eq!(Decoder::length(0), ServerTransaction::decoder(&head, method).unwrap()); | ||||||
|  |         assert_eq!(*method, Some(::Method::Post)); | ||||||
|  |  | ||||||
|         head.headers.set(TransferEncoding::chunked()); |         head.headers.set(TransferEncoding::chunked()); | ||||||
|         assert_eq!(Decoder::chunked(), ServerTransaction::decoder(&head).unwrap()); |         assert_eq!(Decoder::chunked(), ServerTransaction::decoder(&head, method).unwrap()); | ||||||
|         // transfer-encoding and content-length = chunked |         // transfer-encoding and content-length = chunked | ||||||
|         head.headers.set(ContentLength(10)); |         head.headers.set(ContentLength(10)); | ||||||
|         assert_eq!(Decoder::chunked(), ServerTransaction::decoder(&head).unwrap()); |         assert_eq!(Decoder::chunked(), ServerTransaction::decoder(&head, method).unwrap()); | ||||||
|  |  | ||||||
|         head.headers.remove::<TransferEncoding>(); |         head.headers.remove::<TransferEncoding>(); | ||||||
|         assert_eq!(Decoder::length(10), ServerTransaction::decoder(&head).unwrap()); |         assert_eq!(Decoder::length(10), ServerTransaction::decoder(&head, method).unwrap()); | ||||||
|  |  | ||||||
|         head.headers.set_raw("Content-Length", vec![b"5".to_vec(), b"5".to_vec()]); |         head.headers.set_raw("Content-Length", vec![b"5".to_vec(), b"5".to_vec()]); | ||||||
|         assert_eq!(Decoder::length(5), ServerTransaction::decoder(&head).unwrap()); |         assert_eq!(Decoder::length(5), ServerTransaction::decoder(&head, method).unwrap()); | ||||||
|  |  | ||||||
|         head.headers.set_raw("Content-Length", vec![b"10".to_vec(), b"11".to_vec()]); |         head.headers.set_raw("Content-Length", vec![b"10".to_vec(), b"11".to_vec()]); | ||||||
|         ServerTransaction::decoder(&head).unwrap_err(); |         ServerTransaction::decoder(&head, method).unwrap_err(); | ||||||
|  |  | ||||||
|         head.headers.remove::<ContentLength>(); |         head.headers.remove::<ContentLength>(); | ||||||
|  |  | ||||||
|         head.headers.set_raw("Transfer-Encoding", "gzip"); |         head.headers.set_raw("Transfer-Encoding", "gzip"); | ||||||
|         ServerTransaction::decoder(&head).unwrap_err(); |         ServerTransaction::decoder(&head, method).unwrap_err(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_decoder_response() { |     fn test_decoder_response() { | ||||||
|         use super::Decoder; |         use super::Decoder; | ||||||
|  |  | ||||||
|  |         let method = &mut Some(::Method::Get); | ||||||
|         let mut head = MessageHead::<::http::RawStatus>::default(); |         let mut head = MessageHead::<::http::RawStatus>::default(); | ||||||
|  |  | ||||||
|         head.subject.0 = 204; |         head.subject.0 = 204; | ||||||
|         assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head).unwrap()); |         assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head, method).unwrap()); | ||||||
|         head.subject.0 = 304; |         head.subject.0 = 304; | ||||||
|         assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head).unwrap()); |         assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head, method).unwrap()); | ||||||
|  |  | ||||||
|         head.subject.0 = 200; |         head.subject.0 = 200; | ||||||
|         assert_eq!(Decoder::eof(), ClientTransaction::decoder(&head).unwrap()); |         assert_eq!(Decoder::eof(), ClientTransaction::decoder(&head, method).unwrap()); | ||||||
|  |  | ||||||
|  |         *method = Some(::Method::Head); | ||||||
|  |         assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head, method).unwrap()); | ||||||
|  |  | ||||||
|  |         *method = Some(::Method::Connect); | ||||||
|  |         assert_eq!(Decoder::length(0), ClientTransaction::decoder(&head, method).unwrap()); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         // CONNECT receiving non 200 can have a body | ||||||
|  |         head.subject.0 = 404; | ||||||
|  |         head.headers.set(ContentLength(10)); | ||||||
|  |         assert_eq!(Decoder::length(10), ClientTransaction::decoder(&head, method).unwrap()); | ||||||
|  |         head.headers.remove::<ContentLength>(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         *method = Some(::Method::Get); | ||||||
|         head.headers.set(TransferEncoding::chunked()); |         head.headers.set(TransferEncoding::chunked()); | ||||||
|         assert_eq!(Decoder::chunked(), ClientTransaction::decoder(&head).unwrap()); |         assert_eq!(Decoder::chunked(), ClientTransaction::decoder(&head, method).unwrap()); | ||||||
|  |  | ||||||
|         // transfer-encoding and content-length = chunked |         // transfer-encoding and content-length = chunked | ||||||
|         head.headers.set(ContentLength(10)); |         head.headers.set(ContentLength(10)); | ||||||
|         assert_eq!(Decoder::chunked(), ClientTransaction::decoder(&head).unwrap()); |         assert_eq!(Decoder::chunked(), ClientTransaction::decoder(&head, method).unwrap()); | ||||||
|  |  | ||||||
|         head.headers.remove::<TransferEncoding>(); |         head.headers.remove::<TransferEncoding>(); | ||||||
|         assert_eq!(Decoder::length(10), ClientTransaction::decoder(&head).unwrap()); |         assert_eq!(Decoder::length(10), ClientTransaction::decoder(&head, method).unwrap()); | ||||||
|  |  | ||||||
|         head.headers.set_raw("Content-Length", vec![b"5".to_vec(), b"5".to_vec()]); |         head.headers.set_raw("Content-Length", vec![b"5".to_vec(), b"5".to_vec()]); | ||||||
|         assert_eq!(Decoder::length(5), ClientTransaction::decoder(&head).unwrap()); |         assert_eq!(Decoder::length(5), ClientTransaction::decoder(&head, method).unwrap()); | ||||||
|  |  | ||||||
|         head.headers.set_raw("Content-Length", vec![b"10".to_vec(), b"11".to_vec()]); |         head.headers.set_raw("Content-Length", vec![b"10".to_vec(), b"11".to_vec()]); | ||||||
|         ClientTransaction::decoder(&head).unwrap_err(); |         ClientTransaction::decoder(&head, method).unwrap_err(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[cfg(feature = "nightly")] |     #[cfg(feature = "nightly")] | ||||||
| @@ -541,7 +584,7 @@ mod tests { | |||||||
|  |  | ||||||
|         b.iter(|| { |         b.iter(|| { | ||||||
|             let mut vec = Vec::new(); |             let mut vec = Vec::new(); | ||||||
|             ServerTransaction::encode(head.clone(), &mut vec); |             ServerTransaction::encode(head.clone(), true, &mut None, &mut vec); | ||||||
|             assert_eq!(vec.len(), len); |             assert_eq!(vec.len(), len); | ||||||
|             ::test::black_box(vec); |             ::test::black_box(vec); | ||||||
|         }) |         }) | ||||||
|   | |||||||
| @@ -144,9 +144,8 @@ pub trait Http1Transaction { | |||||||
|     type Incoming; |     type Incoming; | ||||||
|     type Outgoing: Default; |     type Outgoing: Default; | ||||||
|     fn parse(bytes: &mut BytesMut) -> ParseResult<Self::Incoming>; |     fn parse(bytes: &mut BytesMut) -> ParseResult<Self::Incoming>; | ||||||
|     fn decoder(head: &MessageHead<Self::Incoming>) -> ::Result<h1::Decoder>; |     fn decoder(head: &MessageHead<Self::Incoming>, method: &mut Option<::Method>) -> ::Result<h1::Decoder>; | ||||||
|     fn encode(head: MessageHead<Self::Outgoing>, dst: &mut Vec<u8>) -> h1::Encoder; |     fn encode(head: MessageHead<Self::Outgoing>, has_body: bool, method: &mut Option<Method>, dst: &mut Vec<u8>) -> h1::Encoder; | ||||||
|     fn should_set_length(head: &MessageHead<Self::Outgoing>) -> bool; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| pub type ParseResult<T> = ::Result<Option<(MessageHead<T>, usize)>>; | pub type ParseResult<T> = ::Result<Option<(MessageHead<T>, usize)>>; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| #![doc(html_root_url = "https://docs.rs/hyper/0.11.1")] | #![doc(html_root_url = "https://docs.rs/hyper/0.11.1")] | ||||||
| #![deny(missing_docs)] | #![deny(missing_docs)] | ||||||
| #![deny(warnings)] | //#![deny(warnings)] | ||||||
| #![deny(missing_debug_implementations)] | #![deny(missing_debug_implementations)] | ||||||
| #![cfg_attr(all(test, feature = "nightly"), feature(test))] | #![cfg_attr(all(test, feature = "nightly"), feature(test))] | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ use std::time::Duration; | |||||||
| use hyper::client::{Client, Request, HttpConnector}; | use hyper::client::{Client, Request, HttpConnector}; | ||||||
| use hyper::{Method, StatusCode}; | use hyper::{Method, StatusCode}; | ||||||
|  |  | ||||||
| use futures::Future; | use futures::{Future, Stream}; | ||||||
| use futures::sync::oneshot; | use futures::sync::oneshot; | ||||||
|  |  | ||||||
| use tokio_core::reactor::{Core, Handle}; | use tokio_core::reactor::{Core, Handle}; | ||||||
| @@ -93,6 +93,12 @@ macro_rules! test { | |||||||
|             $( |             $( | ||||||
|                 assert_eq!(res.headers().get(), Some(&$response_headers)); |                 assert_eq!(res.headers().get(), Some(&$response_headers)); | ||||||
|             )* |             )* | ||||||
|  |  | ||||||
|  |             let body = core.run(res.body().concat2()).unwrap(); | ||||||
|  |  | ||||||
|  |             let expected_res_body = Option::<&[u8]>::from($response_body) | ||||||
|  |                 .unwrap_or_default(); | ||||||
|  |             assert_eq!(body.as_ref(), expected_res_body); | ||||||
|         } |         } | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
| @@ -225,6 +231,36 @@ test! { | |||||||
|             body: None, |             body: None, | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | test! { | ||||||
|  |     name: client_head_ignores_body, | ||||||
|  |  | ||||||
|  |     server: | ||||||
|  |         expected: "\ | ||||||
|  |             HEAD /head HTTP/1.1\r\n\ | ||||||
|  |             Host: {addr}\r\n\ | ||||||
|  |             \r\n\ | ||||||
|  |             ", | ||||||
|  |         reply: "\ | ||||||
|  |             HTTP/1.1 200 OK\r\n\ | ||||||
|  |             Content-Length: 11\r\n\ | ||||||
|  |             \r\n\ | ||||||
|  |             Hello World\ | ||||||
|  |             ", | ||||||
|  |  | ||||||
|  |     client: | ||||||
|  |         request: | ||||||
|  |             method: Head, | ||||||
|  |             url: "http://{addr}/head", | ||||||
|  |             headers: [], | ||||||
|  |             body: None, | ||||||
|  |             proxy: false, | ||||||
|  |         response: | ||||||
|  |             status: Ok, | ||||||
|  |             headers: [], | ||||||
|  |             body: None, | ||||||
|  | } | ||||||
|  |  | ||||||
| #[test] | #[test] | ||||||
| fn client_keep_alive() { | fn client_keep_alive() { | ||||||
|     let server = TcpListener::bind("127.0.0.1:0").unwrap(); |     let server = TcpListener::bind("127.0.0.1:0").unwrap(); | ||||||
|   | |||||||
							
								
								
									
										787
									
								
								tests/server.rs
									
									
									
									
									
								
							
							
						
						
									
										787
									
								
								tests/server.rs
									
									
									
									
									
								
							| @@ -16,6 +16,431 @@ use std::time::Duration; | |||||||
|  |  | ||||||
| use hyper::server::{Http, Request, Response, Service, NewService}; | use hyper::server::{Http, Request, Response, Service, NewService}; | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn get_should_ignore_body() { | ||||||
|  |     let server = serve(); | ||||||
|  |  | ||||||
|  |     let mut req = connect(server.addr()); | ||||||
|  |     // Connection: close = don't try to parse the body as a new request | ||||||
|  |     req.write_all(b"\ | ||||||
|  |         GET / HTTP/1.1\r\n\ | ||||||
|  |         Host: example.domain\r\n\ | ||||||
|  |         Connection: close\r\n\ | ||||||
|  |         \r\n\ | ||||||
|  |         I shouldn't be read.\r\n\ | ||||||
|  |     ").unwrap(); | ||||||
|  |     req.read(&mut [0; 256]).unwrap(); | ||||||
|  |  | ||||||
|  |     assert_eq!(server.body(), b""); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn get_with_body() { | ||||||
|  |     let server = serve(); | ||||||
|  |     let mut req = connect(server.addr()); | ||||||
|  |     req.write_all(b"\ | ||||||
|  |         GET / HTTP/1.1\r\n\ | ||||||
|  |         Host: example.domain\r\n\ | ||||||
|  |         Content-Length: 19\r\n\ | ||||||
|  |         \r\n\ | ||||||
|  |         I'm a good request.\r\n\ | ||||||
|  |     ").unwrap(); | ||||||
|  |     req.read(&mut [0; 256]).unwrap(); | ||||||
|  |  | ||||||
|  |     // note: doesn't include trailing \r\n, cause Content-Length wasn't 21 | ||||||
|  |     assert_eq!(server.body(), b"I'm a good request."); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn get_fixed_response() { | ||||||
|  |     let foo_bar = b"foo bar baz"; | ||||||
|  |     let server = serve(); | ||||||
|  |     server.reply() | ||||||
|  |         .status(hyper::Ok) | ||||||
|  |         .header(hyper::header::ContentLength(foo_bar.len() as u64)) | ||||||
|  |         .body(foo_bar); | ||||||
|  |     let mut req = connect(server.addr()); | ||||||
|  |     req.write_all(b"\ | ||||||
|  |         GET / HTTP/1.1\r\n\ | ||||||
|  |         Host: example.domain\r\n\ | ||||||
|  |         Connection: close\r\n\ | ||||||
|  |         \r\n\ | ||||||
|  |     ").unwrap(); | ||||||
|  |     let mut body = String::new(); | ||||||
|  |     req.read_to_string(&mut body).unwrap(); | ||||||
|  |     let n = body.find("\r\n\r\n").unwrap() + 4; | ||||||
|  |  | ||||||
|  |     assert_eq!(&body[n..], "foo bar baz"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn get_chunked_response() { | ||||||
|  |     let foo_bar = b"foo bar baz"; | ||||||
|  |     let server = serve(); | ||||||
|  |     server.reply() | ||||||
|  |         .status(hyper::Ok) | ||||||
|  |         .header(hyper::header::TransferEncoding::chunked()) | ||||||
|  |         .body(foo_bar); | ||||||
|  |     let mut req = connect(server.addr()); | ||||||
|  |     req.write_all(b"\ | ||||||
|  |         GET / HTTP/1.1\r\n\ | ||||||
|  |         Host: example.domain\r\n\ | ||||||
|  |         Connection: close\r\n\ | ||||||
|  |         \r\n\ | ||||||
|  |     ").unwrap(); | ||||||
|  |     let mut body = String::new(); | ||||||
|  |     req.read_to_string(&mut body).unwrap(); | ||||||
|  |     let n = body.find("\r\n\r\n").unwrap() + 4; | ||||||
|  |  | ||||||
|  |     assert_eq!(&body[n..], "B\r\nfoo bar baz\r\n0\r\n\r\n"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn get_chunked_response_with_ka() { | ||||||
|  |     let foo_bar = b"foo bar baz"; | ||||||
|  |     let foo_bar_chunk = b"\r\nfoo bar baz\r\n0\r\n\r\n"; | ||||||
|  |     let server = serve(); | ||||||
|  |     server.reply() | ||||||
|  |         .status(hyper::Ok) | ||||||
|  |         .header(hyper::header::TransferEncoding::chunked()) | ||||||
|  |         .body(foo_bar); | ||||||
|  |     let mut req = connect(server.addr()); | ||||||
|  |     req.write_all(b"\ | ||||||
|  |         GET / HTTP/1.1\r\n\ | ||||||
|  |         Host: example.domain\r\n\ | ||||||
|  |         Connection: keep-alive\r\n\ | ||||||
|  |         \r\n\ | ||||||
|  |     ").expect("writing 1"); | ||||||
|  |  | ||||||
|  |     let mut buf = [0; 1024 * 4]; | ||||||
|  |     let mut ntotal = 0; | ||||||
|  |     loop { | ||||||
|  |         let n = req.read(&mut buf[ntotal..]).expect("reading 1"); | ||||||
|  |         ntotal = ntotal + n; | ||||||
|  |         assert!(ntotal < buf.len()); | ||||||
|  |         if &buf[ntotal - foo_bar_chunk.len()..ntotal] == foo_bar_chunk { | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     // try again! | ||||||
|  |  | ||||||
|  |     let quux = b"zar quux"; | ||||||
|  |     server.reply() | ||||||
|  |         .status(hyper::Ok) | ||||||
|  |         .header(hyper::header::ContentLength(quux.len() as u64)) | ||||||
|  |         .body(quux); | ||||||
|  |     req.write_all(b"\ | ||||||
|  |         GET /quux HTTP/1.1\r\n\ | ||||||
|  |         Host: example.domain\r\n\ | ||||||
|  |         Connection: close\r\n\ | ||||||
|  |         \r\n\ | ||||||
|  |     ").expect("writing 2"); | ||||||
|  |  | ||||||
|  |     let mut buf = [0; 1024 * 8]; | ||||||
|  |     loop { | ||||||
|  |         let n = req.read(&mut buf[..]).expect("reading 2"); | ||||||
|  |         assert!(n > 0, "n = {}", n); | ||||||
|  |         if n < buf.len() && n > 0  { | ||||||
|  |             if &buf[n - quux.len()..n] == quux { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn post_with_chunked_body() { | ||||||
|  |     let server = serve(); | ||||||
|  |     let mut req = connect(server.addr()); | ||||||
|  |     req.write_all(b"\ | ||||||
|  |         POST / HTTP/1.1\r\n\ | ||||||
|  |         Host: example.domain\r\n\ | ||||||
|  |         Transfer-Encoding: chunked\r\n\ | ||||||
|  |         \r\n\ | ||||||
|  |         1\r\n\ | ||||||
|  |         q\r\n\ | ||||||
|  |         2\r\n\ | ||||||
|  |         we\r\n\ | ||||||
|  |         2\r\n\ | ||||||
|  |         rt\r\n\ | ||||||
|  |         0\r\n\ | ||||||
|  |         \r\n\ | ||||||
|  |     ").unwrap(); | ||||||
|  |     req.read(&mut [0; 256]).unwrap(); | ||||||
|  |  | ||||||
|  |     assert_eq!(server.body(), b"qwert"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn empty_response_chunked() { | ||||||
|  |     let server = serve(); | ||||||
|  |  | ||||||
|  |     server.reply() | ||||||
|  |         .status(hyper::Ok) | ||||||
|  |         .body(""); | ||||||
|  |  | ||||||
|  |     let mut req = connect(server.addr()); | ||||||
|  |     req.write_all(b"\ | ||||||
|  |         GET / HTTP/1.1\r\n\ | ||||||
|  |         Host: example.domain\r\n\ | ||||||
|  |         Content-Length: 0\r\n\ | ||||||
|  |         Connection: close\r\n\ | ||||||
|  |         \r\n\ | ||||||
|  |     ").unwrap(); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     let mut response = String::new(); | ||||||
|  |     req.read_to_string(&mut response).unwrap(); | ||||||
|  |  | ||||||
|  |     assert!(response.contains("Transfer-Encoding: chunked\r\n")); | ||||||
|  |  | ||||||
|  |     let mut lines = response.lines(); | ||||||
|  |     assert_eq!(lines.next(), Some("HTTP/1.1 200 OK")); | ||||||
|  |  | ||||||
|  |     let mut lines = lines.skip_while(|line| !line.is_empty()); | ||||||
|  |     assert_eq!(lines.next(), Some("")); | ||||||
|  |     // 0\r\n\r\n | ||||||
|  |     assert_eq!(lines.next(), Some("0")); | ||||||
|  |     assert_eq!(lines.next(), Some("")); | ||||||
|  |     assert_eq!(lines.next(), None); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn empty_response_chunked_without_body_should_set_content_length() { | ||||||
|  |     extern crate pretty_env_logger; | ||||||
|  |     let _ = pretty_env_logger::init(); | ||||||
|  |     let server = serve(); | ||||||
|  |     server.reply() | ||||||
|  |         .status(hyper::Ok) | ||||||
|  |         .header(hyper::header::TransferEncoding::chunked()); | ||||||
|  |     let mut req = connect(server.addr()); | ||||||
|  |     req.write_all(b"\ | ||||||
|  |         GET / HTTP/1.1\r\n\ | ||||||
|  |         Host: example.domain\r\n\ | ||||||
|  |         Connection: close\r\n\ | ||||||
|  |         \r\n\ | ||||||
|  |     ").unwrap(); | ||||||
|  |  | ||||||
|  |     let mut response = String::new(); | ||||||
|  |     req.read_to_string(&mut response).unwrap(); | ||||||
|  |  | ||||||
|  |     assert!(!response.contains("Transfer-Encoding: chunked\r\n")); | ||||||
|  |     assert!(response.contains("Content-Length: 0\r\n")); | ||||||
|  |  | ||||||
|  |     let mut lines = response.lines(); | ||||||
|  |     assert_eq!(lines.next(), Some("HTTP/1.1 200 OK")); | ||||||
|  |  | ||||||
|  |     let mut lines = lines.skip_while(|line| !line.is_empty()); | ||||||
|  |     assert_eq!(lines.next(), Some("")); | ||||||
|  |     assert_eq!(lines.next(), None); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn head_response_can_send_content_length() { | ||||||
|  |     extern crate pretty_env_logger; | ||||||
|  |     let _ = pretty_env_logger::init(); | ||||||
|  |     let server = serve(); | ||||||
|  |     server.reply() | ||||||
|  |         .status(hyper::Ok) | ||||||
|  |         .header(hyper::header::ContentLength(1024)); | ||||||
|  |     let mut req = connect(server.addr()); | ||||||
|  |     req.write_all(b"\ | ||||||
|  |         HEAD / HTTP/1.1\r\n\ | ||||||
|  |         Host: example.domain\r\n\ | ||||||
|  |         Connection: close\r\n\ | ||||||
|  |         \r\n\ | ||||||
|  |     ").unwrap(); | ||||||
|  |  | ||||||
|  |     let mut response = String::new(); | ||||||
|  |     req.read_to_string(&mut response).unwrap(); | ||||||
|  |  | ||||||
|  |     assert!(response.contains("Content-Length: 1024\r\n")); | ||||||
|  |  | ||||||
|  |     let mut lines = response.lines(); | ||||||
|  |     assert_eq!(lines.next(), Some("HTTP/1.1 200 OK")); | ||||||
|  |  | ||||||
|  |     let mut lines = lines.skip_while(|line| !line.is_empty()); | ||||||
|  |     assert_eq!(lines.next(), Some("")); | ||||||
|  |     assert_eq!(lines.next(), None); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn response_does_not_set_chunked_if_body_not_allowed() { | ||||||
|  |     extern crate pretty_env_logger; | ||||||
|  |     let _ = pretty_env_logger::init(); | ||||||
|  |     let server = serve(); | ||||||
|  |     server.reply() | ||||||
|  |         .status(hyper::StatusCode::NotModified) | ||||||
|  |         .header(hyper::header::TransferEncoding::chunked()); | ||||||
|  |     let mut req = connect(server.addr()); | ||||||
|  |     req.write_all(b"\ | ||||||
|  |         GET / HTTP/1.1\r\n\ | ||||||
|  |         Host: example.domain\r\n\ | ||||||
|  |         Connection: close\r\n\ | ||||||
|  |         \r\n\ | ||||||
|  |     ").unwrap(); | ||||||
|  |  | ||||||
|  |     let mut response = String::new(); | ||||||
|  |     req.read_to_string(&mut response).unwrap(); | ||||||
|  |  | ||||||
|  |     assert!(!response.contains("Transfer-Encoding")); | ||||||
|  |  | ||||||
|  |     let mut lines = response.lines(); | ||||||
|  |     assert_eq!(lines.next(), Some("HTTP/1.1 304 Not Modified")); | ||||||
|  |  | ||||||
|  |     // no body or 0\r\n\r\n | ||||||
|  |     let mut lines = lines.skip_while(|line| !line.is_empty()); | ||||||
|  |     assert_eq!(lines.next(), Some("")); | ||||||
|  |     assert_eq!(lines.next(), None); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn keep_alive() { | ||||||
|  |     let foo_bar = b"foo bar baz"; | ||||||
|  |     let server = serve(); | ||||||
|  |     server.reply() | ||||||
|  |         .status(hyper::Ok) | ||||||
|  |         .header(hyper::header::ContentLength(foo_bar.len() as u64)) | ||||||
|  |         .body(foo_bar); | ||||||
|  |     let mut req = connect(server.addr()); | ||||||
|  |     req.write_all(b"\ | ||||||
|  |         GET / HTTP/1.1\r\n\ | ||||||
|  |         Host: example.domain\r\n\ | ||||||
|  |         Connection: keep-alive\r\n\ | ||||||
|  |         \r\n\ | ||||||
|  |     ").expect("writing 1"); | ||||||
|  |  | ||||||
|  |     let mut buf = [0; 1024 * 8]; | ||||||
|  |     loop { | ||||||
|  |         let n = req.read(&mut buf[..]).expect("reading 1"); | ||||||
|  |         if n < buf.len() { | ||||||
|  |             if &buf[n - foo_bar.len()..n] == foo_bar { | ||||||
|  |                 break; | ||||||
|  |             } else { | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // try again! | ||||||
|  |  | ||||||
|  |     let quux = b"zar quux"; | ||||||
|  |     server.reply() | ||||||
|  |         .status(hyper::Ok) | ||||||
|  |         .header(hyper::header::ContentLength(quux.len() as u64)) | ||||||
|  |         .body(quux); | ||||||
|  |     req.write_all(b"\ | ||||||
|  |         GET /quux HTTP/1.1\r\n\ | ||||||
|  |         Host: example.domain\r\n\ | ||||||
|  |         Connection: close\r\n\ | ||||||
|  |         \r\n\ | ||||||
|  |     ").expect("writing 2"); | ||||||
|  |  | ||||||
|  |     let mut buf = [0; 1024 * 8]; | ||||||
|  |     loop { | ||||||
|  |         let n = req.read(&mut buf[..]).expect("reading 2"); | ||||||
|  |         assert!(n > 0, "n = {}", n); | ||||||
|  |         if n < buf.len() { | ||||||
|  |             if &buf[n - quux.len()..n] == quux { | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn disable_keep_alive() { | ||||||
|  |     let foo_bar = b"foo bar baz"; | ||||||
|  |     let server = serve_with_options(ServeOptions { | ||||||
|  |         keep_alive_disabled: true, | ||||||
|  |         .. Default::default() | ||||||
|  |     }); | ||||||
|  |     server.reply() | ||||||
|  |         .status(hyper::Ok) | ||||||
|  |         .header(hyper::header::ContentLength(foo_bar.len() as u64)) | ||||||
|  |         .body(foo_bar); | ||||||
|  |     let mut req = connect(server.addr()); | ||||||
|  |     req.write_all(b"\ | ||||||
|  |         GET / HTTP/1.1\r\n\ | ||||||
|  |         Host: example.domain\r\n\ | ||||||
|  |         Connection: keep-alive\r\n\ | ||||||
|  |         \r\n\ | ||||||
|  |     ").expect("writing 1"); | ||||||
|  |  | ||||||
|  |     let mut buf = [0; 1024 * 8]; | ||||||
|  |     loop { | ||||||
|  |         let n = req.read(&mut buf[..]).expect("reading 1"); | ||||||
|  |         if n < buf.len() { | ||||||
|  |             if &buf[n - foo_bar.len()..n] == foo_bar { | ||||||
|  |                 break; | ||||||
|  |             } else { | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // try again! | ||||||
|  |  | ||||||
|  |     let quux = b"zar quux"; | ||||||
|  |     server.reply() | ||||||
|  |         .status(hyper::Ok) | ||||||
|  |         .header(hyper::header::ContentLength(quux.len() as u64)) | ||||||
|  |         .body(quux); | ||||||
|  |  | ||||||
|  |     let _ = req.write_all(b"\ | ||||||
|  |         GET /quux HTTP/1.1\r\n\ | ||||||
|  |         Host: example.domain\r\n\ | ||||||
|  |         Connection: close\r\n\ | ||||||
|  |         \r\n\ | ||||||
|  |     "); | ||||||
|  |  | ||||||
|  |     // the write can possibly succeed, since it fills the kernel buffer on the first write | ||||||
|  |     let mut buf = [0; 1024 * 8]; | ||||||
|  |     match req.read(&mut buf[..]) { | ||||||
|  |         // Ok(0) means EOF, so a proper shutdown | ||||||
|  |         // Err(_) could mean ConnReset or something, also fine | ||||||
|  |         Ok(0) | | ||||||
|  |         Err(_) => {} | ||||||
|  |         Ok(n) => { | ||||||
|  |             panic!("read {} bytes on a disabled keep-alive socket", n); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[test] | ||||||
|  | fn expect_continue() { | ||||||
|  |     let server = serve(); | ||||||
|  |     let mut req = connect(server.addr()); | ||||||
|  |     server.reply().status(hyper::Ok); | ||||||
|  |  | ||||||
|  |     req.write_all(b"\ | ||||||
|  |         POST /foo HTTP/1.1\r\n\ | ||||||
|  |         Host: example.domain\r\n\ | ||||||
|  |         Expect: 100-continue\r\n\ | ||||||
|  |         Content-Length: 5\r\n\ | ||||||
|  |         Connection: Close\r\n\ | ||||||
|  |         \r\n\ | ||||||
|  |     ").expect("write 1"); | ||||||
|  |  | ||||||
|  |     let msg = b"HTTP/1.1 100 Continue\r\n\r\n"; | ||||||
|  |     let mut buf = vec![0; msg.len()]; | ||||||
|  |     req.read_exact(&mut buf).expect("read 1"); | ||||||
|  |     assert_eq!(buf, msg); | ||||||
|  |  | ||||||
|  |     let msg = b"hello"; | ||||||
|  |     req.write_all(msg).expect("write 2"); | ||||||
|  |  | ||||||
|  |     let mut body = String::new(); | ||||||
|  |     req.read_to_string(&mut body).expect("read 2"); | ||||||
|  |  | ||||||
|  |     let body = server.body(); | ||||||
|  |     assert_eq!(body, msg); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ------------------------------------------------- | ||||||
|  | // the Server that is used to run all the tests with | ||||||
|  | // ------------------------------------------------- | ||||||
|  |  | ||||||
| struct Serve { | struct Serve { | ||||||
|     addr: SocketAddr, |     addr: SocketAddr, | ||||||
|     msg_rx: mpsc::Receiver<Msg>, |     msg_rx: mpsc::Receiver<Msg>, | ||||||
| @@ -190,366 +615,4 @@ fn serve_with_options(options: ServeOptions) -> Serve { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn server_get_should_ignore_body() { |  | ||||||
|     let server = serve(); |  | ||||||
|  |  | ||||||
|     let mut req = connect(server.addr()); |  | ||||||
|     // Connection: close = don't try to parse the body as a new request |  | ||||||
|     req.write_all(b"\ |  | ||||||
|         GET / HTTP/1.1\r\n\ |  | ||||||
|         Host: example.domain\r\n\ |  | ||||||
|         Connection: close\r\n\ |  | ||||||
|         \r\n\ |  | ||||||
|         I shouldn't be read.\r\n\ |  | ||||||
|     ").unwrap(); |  | ||||||
|     req.read(&mut [0; 256]).unwrap(); |  | ||||||
|  |  | ||||||
|     assert_eq!(server.body(), b""); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn server_get_with_body() { |  | ||||||
|     let server = serve(); |  | ||||||
|     let mut req = connect(server.addr()); |  | ||||||
|     req.write_all(b"\ |  | ||||||
|         GET / HTTP/1.1\r\n\ |  | ||||||
|         Host: example.domain\r\n\ |  | ||||||
|         Content-Length: 19\r\n\ |  | ||||||
|         \r\n\ |  | ||||||
|         I'm a good request.\r\n\ |  | ||||||
|     ").unwrap(); |  | ||||||
|     req.read(&mut [0; 256]).unwrap(); |  | ||||||
|  |  | ||||||
|     // note: doesn't include trailing \r\n, cause Content-Length wasn't 21 |  | ||||||
|     assert_eq!(server.body(), b"I'm a good request."); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn server_get_fixed_response() { |  | ||||||
|     let foo_bar = b"foo bar baz"; |  | ||||||
|     let server = serve(); |  | ||||||
|     server.reply() |  | ||||||
|         .status(hyper::Ok) |  | ||||||
|         .header(hyper::header::ContentLength(foo_bar.len() as u64)) |  | ||||||
|         .body(foo_bar); |  | ||||||
|     let mut req = connect(server.addr()); |  | ||||||
|     req.write_all(b"\ |  | ||||||
|         GET / HTTP/1.1\r\n\ |  | ||||||
|         Host: example.domain\r\n\ |  | ||||||
|         Connection: close\r\n\ |  | ||||||
|         \r\n\ |  | ||||||
|     ").unwrap(); |  | ||||||
|     let mut body = String::new(); |  | ||||||
|     req.read_to_string(&mut body).unwrap(); |  | ||||||
|     let n = body.find("\r\n\r\n").unwrap() + 4; |  | ||||||
|  |  | ||||||
|     assert_eq!(&body[n..], "foo bar baz"); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn server_get_chunked_response() { |  | ||||||
|     let foo_bar = b"foo bar baz"; |  | ||||||
|     let server = serve(); |  | ||||||
|     server.reply() |  | ||||||
|         .status(hyper::Ok) |  | ||||||
|         .header(hyper::header::TransferEncoding::chunked()) |  | ||||||
|         .body(foo_bar); |  | ||||||
|     let mut req = connect(server.addr()); |  | ||||||
|     req.write_all(b"\ |  | ||||||
|         GET / HTTP/1.1\r\n\ |  | ||||||
|         Host: example.domain\r\n\ |  | ||||||
|         Connection: close\r\n\ |  | ||||||
|         \r\n\ |  | ||||||
|     ").unwrap(); |  | ||||||
|     let mut body = String::new(); |  | ||||||
|     req.read_to_string(&mut body).unwrap(); |  | ||||||
|     let n = body.find("\r\n\r\n").unwrap() + 4; |  | ||||||
|  |  | ||||||
|     assert_eq!(&body[n..], "B\r\nfoo bar baz\r\n0\r\n\r\n"); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn server_get_chunked_response_with_ka() { |  | ||||||
|     let foo_bar = b"foo bar baz"; |  | ||||||
|     let foo_bar_chunk = b"\r\nfoo bar baz\r\n0\r\n\r\n"; |  | ||||||
|     let server = serve(); |  | ||||||
|     server.reply() |  | ||||||
|         .status(hyper::Ok) |  | ||||||
|         .header(hyper::header::TransferEncoding::chunked()) |  | ||||||
|         .body(foo_bar); |  | ||||||
|     let mut req = connect(server.addr()); |  | ||||||
|     req.write_all(b"\ |  | ||||||
|         GET / HTTP/1.1\r\n\ |  | ||||||
|         Host: example.domain\r\n\ |  | ||||||
|         Connection: keep-alive\r\n\ |  | ||||||
|         \r\n\ |  | ||||||
|     ").expect("writing 1"); |  | ||||||
|  |  | ||||||
|     let mut buf = [0; 1024 * 4]; |  | ||||||
|     let mut ntotal = 0; |  | ||||||
|     loop { |  | ||||||
|         let n = req.read(&mut buf[ntotal..]).expect("reading 1"); |  | ||||||
|         ntotal = ntotal + n; |  | ||||||
|         assert!(ntotal < buf.len()); |  | ||||||
|         if &buf[ntotal - foo_bar_chunk.len()..ntotal] == foo_bar_chunk { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     // try again! |  | ||||||
|  |  | ||||||
|     let quux = b"zar quux"; |  | ||||||
|     server.reply() |  | ||||||
|         .status(hyper::Ok) |  | ||||||
|         .header(hyper::header::ContentLength(quux.len() as u64)) |  | ||||||
|         .body(quux); |  | ||||||
|     req.write_all(b"\ |  | ||||||
|         GET /quux HTTP/1.1\r\n\ |  | ||||||
|         Host: example.domain\r\n\ |  | ||||||
|         Connection: close\r\n\ |  | ||||||
|         \r\n\ |  | ||||||
|     ").expect("writing 2"); |  | ||||||
|  |  | ||||||
|     let mut buf = [0; 1024 * 8]; |  | ||||||
|     loop { |  | ||||||
|         let n = req.read(&mut buf[..]).expect("reading 2"); |  | ||||||
|         assert!(n > 0, "n = {}", n); |  | ||||||
|         if n < buf.len() && n > 0  { |  | ||||||
|             if &buf[n - quux.len()..n] == quux { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn server_post_with_chunked_body() { |  | ||||||
|     let server = serve(); |  | ||||||
|     let mut req = connect(server.addr()); |  | ||||||
|     req.write_all(b"\ |  | ||||||
|         POST / HTTP/1.1\r\n\ |  | ||||||
|         Host: example.domain\r\n\ |  | ||||||
|         Transfer-Encoding: chunked\r\n\ |  | ||||||
|         \r\n\ |  | ||||||
|         1\r\n\ |  | ||||||
|         q\r\n\ |  | ||||||
|         2\r\n\ |  | ||||||
|         we\r\n\ |  | ||||||
|         2\r\n\ |  | ||||||
|         rt\r\n\ |  | ||||||
|         0\r\n\ |  | ||||||
|         \r\n\ |  | ||||||
|     ").unwrap(); |  | ||||||
|     req.read(&mut [0; 256]).unwrap(); |  | ||||||
|  |  | ||||||
|     assert_eq!(server.body(), b"qwert"); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn server_empty_response_chunked() { |  | ||||||
|     let server = serve(); |  | ||||||
|  |  | ||||||
|     server.reply() |  | ||||||
|         .status(hyper::Ok) |  | ||||||
|         .body(""); |  | ||||||
|  |  | ||||||
|     let mut req = connect(server.addr()); |  | ||||||
|     req.write_all(b"\ |  | ||||||
|         GET / HTTP/1.1\r\n\ |  | ||||||
|         Host: example.domain\r\n\ |  | ||||||
|         Content-Length: 0\r\n\ |  | ||||||
|         Connection: close\r\n\ |  | ||||||
|         \r\n\ |  | ||||||
|     ").unwrap(); |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     let mut response = String::new(); |  | ||||||
|     req.read_to_string(&mut response).unwrap(); |  | ||||||
|  |  | ||||||
|     assert!(response.contains("Transfer-Encoding: chunked\r\n")); |  | ||||||
|  |  | ||||||
|     let mut lines = response.lines(); |  | ||||||
|     assert_eq!(lines.next(), Some("HTTP/1.1 200 OK")); |  | ||||||
|  |  | ||||||
|     let mut lines = lines.skip_while(|line| !line.is_empty()); |  | ||||||
|     assert_eq!(lines.next(), Some("")); |  | ||||||
|     // 0\r\n\r\n |  | ||||||
|     assert_eq!(lines.next(), Some("0")); |  | ||||||
|     assert_eq!(lines.next(), Some("")); |  | ||||||
|     assert_eq!(lines.next(), None); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn server_empty_response_chunked_without_body_should_set_content_length() { |  | ||||||
|     extern crate pretty_env_logger; |  | ||||||
|     let _ = pretty_env_logger::init(); |  | ||||||
|     let server = serve(); |  | ||||||
|     server.reply() |  | ||||||
|         .status(hyper::Ok) |  | ||||||
|         .header(hyper::header::TransferEncoding::chunked()); |  | ||||||
|     let mut req = connect(server.addr()); |  | ||||||
|     req.write_all(b"\ |  | ||||||
|         GET / HTTP/1.1\r\n\ |  | ||||||
|         Host: example.domain\r\n\ |  | ||||||
|         Connection: close\r\n\ |  | ||||||
|         \r\n\ |  | ||||||
|     ").unwrap(); |  | ||||||
|  |  | ||||||
|     let mut response = String::new(); |  | ||||||
|     req.read_to_string(&mut response).unwrap(); |  | ||||||
|  |  | ||||||
|     assert!(!response.contains("Transfer-Encoding: chunked\r\n")); |  | ||||||
|     assert!(response.contains("Content-Length: 0\r\n")); |  | ||||||
|  |  | ||||||
|     let mut lines = response.lines(); |  | ||||||
|     assert_eq!(lines.next(), Some("HTTP/1.1 200 OK")); |  | ||||||
|  |  | ||||||
|     let mut lines = lines.skip_while(|line| !line.is_empty()); |  | ||||||
|     assert_eq!(lines.next(), Some("")); |  | ||||||
|     assert_eq!(lines.next(), None); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn server_keep_alive() { |  | ||||||
|     let foo_bar = b"foo bar baz"; |  | ||||||
|     let server = serve(); |  | ||||||
|     server.reply() |  | ||||||
|         .status(hyper::Ok) |  | ||||||
|         .header(hyper::header::ContentLength(foo_bar.len() as u64)) |  | ||||||
|         .body(foo_bar); |  | ||||||
|     let mut req = connect(server.addr()); |  | ||||||
|     req.write_all(b"\ |  | ||||||
|         GET / HTTP/1.1\r\n\ |  | ||||||
|         Host: example.domain\r\n\ |  | ||||||
|         Connection: keep-alive\r\n\ |  | ||||||
|         \r\n\ |  | ||||||
|     ").expect("writing 1"); |  | ||||||
|  |  | ||||||
|     let mut buf = [0; 1024 * 8]; |  | ||||||
|     loop { |  | ||||||
|         let n = req.read(&mut buf[..]).expect("reading 1"); |  | ||||||
|         if n < buf.len() { |  | ||||||
|             if &buf[n - foo_bar.len()..n] == foo_bar { |  | ||||||
|                 break; |  | ||||||
|             } else { |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // try again! |  | ||||||
|  |  | ||||||
|     let quux = b"zar quux"; |  | ||||||
|     server.reply() |  | ||||||
|         .status(hyper::Ok) |  | ||||||
|         .header(hyper::header::ContentLength(quux.len() as u64)) |  | ||||||
|         .body(quux); |  | ||||||
|     req.write_all(b"\ |  | ||||||
|         GET /quux HTTP/1.1\r\n\ |  | ||||||
|         Host: example.domain\r\n\ |  | ||||||
|         Connection: close\r\n\ |  | ||||||
|         \r\n\ |  | ||||||
|     ").expect("writing 2"); |  | ||||||
|  |  | ||||||
|     let mut buf = [0; 1024 * 8]; |  | ||||||
|     loop { |  | ||||||
|         let n = req.read(&mut buf[..]).expect("reading 2"); |  | ||||||
|         assert!(n > 0, "n = {}", n); |  | ||||||
|         if n < buf.len() { |  | ||||||
|             if &buf[n - quux.len()..n] == quux { |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn test_server_disable_keep_alive() { |  | ||||||
|   let foo_bar = b"foo bar baz"; |  | ||||||
|     let server = serve_with_options(ServeOptions { |  | ||||||
|         keep_alive_disabled: true, |  | ||||||
|         .. Default::default() |  | ||||||
|     }); |  | ||||||
|     server.reply() |  | ||||||
|         .status(hyper::Ok) |  | ||||||
|         .header(hyper::header::ContentLength(foo_bar.len() as u64)) |  | ||||||
|         .body(foo_bar); |  | ||||||
|     let mut req = connect(server.addr()); |  | ||||||
|     req.write_all(b"\ |  | ||||||
|         GET / HTTP/1.1\r\n\ |  | ||||||
|         Host: example.domain\r\n\ |  | ||||||
|         Connection: keep-alive\r\n\ |  | ||||||
|         \r\n\ |  | ||||||
|     ").expect("writing 1"); |  | ||||||
|  |  | ||||||
|     let mut buf = [0; 1024 * 8]; |  | ||||||
|     loop { |  | ||||||
|         let n = req.read(&mut buf[..]).expect("reading 1"); |  | ||||||
|         if n < buf.len() { |  | ||||||
|             if &buf[n - foo_bar.len()..n] == foo_bar { |  | ||||||
|                 break; |  | ||||||
|             } else { |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // try again! |  | ||||||
|  |  | ||||||
|     let quux = b"zar quux"; |  | ||||||
|     server.reply() |  | ||||||
|         .status(hyper::Ok) |  | ||||||
|         .header(hyper::header::ContentLength(quux.len() as u64)) |  | ||||||
|         .body(quux); |  | ||||||
|  |  | ||||||
|     let _ = req.write_all(b"\ |  | ||||||
|         GET /quux HTTP/1.1\r\n\ |  | ||||||
|         Host: example.domain\r\n\ |  | ||||||
|         Connection: close\r\n\ |  | ||||||
|         \r\n\ |  | ||||||
|     "); |  | ||||||
|  |  | ||||||
|     // the write can possibly succeed, since it fills the kernel buffer on the first write |  | ||||||
|     let mut buf = [0; 1024 * 8]; |  | ||||||
|     match req.read(&mut buf[..]) { |  | ||||||
|         // Ok(0) means EOF, so a proper shutdown |  | ||||||
|         // Err(_) could mean ConnReset or something, also fine |  | ||||||
|         Ok(0) | |  | ||||||
|         Err(_) => {} |  | ||||||
|         Ok(n) => { |  | ||||||
|             panic!("read {} bytes on a disabled keep-alive socket", n); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn expect_continue() { |  | ||||||
|     let server = serve(); |  | ||||||
|     let mut req = connect(server.addr()); |  | ||||||
|     server.reply().status(hyper::Ok); |  | ||||||
|  |  | ||||||
|     req.write_all(b"\ |  | ||||||
|         POST /foo HTTP/1.1\r\n\ |  | ||||||
|         Host: example.domain\r\n\ |  | ||||||
|         Expect: 100-continue\r\n\ |  | ||||||
|         Content-Length: 5\r\n\ |  | ||||||
|         Connection: Close\r\n\ |  | ||||||
|         \r\n\ |  | ||||||
|     ").expect("write 1"); |  | ||||||
|  |  | ||||||
|     let msg = b"HTTP/1.1 100 Continue\r\n\r\n"; |  | ||||||
|     let mut buf = vec![0; msg.len()]; |  | ||||||
|     req.read_exact(&mut buf).expect("read 1"); |  | ||||||
|     assert_eq!(buf, msg); |  | ||||||
|  |  | ||||||
|     let msg = b"hello"; |  | ||||||
|     req.write_all(msg).expect("write 2"); |  | ||||||
|  |  | ||||||
|     let mut body = String::new(); |  | ||||||
|     req.read_to_string(&mut body).expect("read 2"); |  | ||||||
|  |  | ||||||
|     let body = server.body(); |  | ||||||
|     assert_eq!(body, msg); |  | ||||||
| } |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user