diff --git a/CHANGES.md b/CHANGES.md index 5265d9d79..ee109fe07 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ ### Added +* Add `HttpMessage::readlines()` for reading line by line. + * Add `ClientRequestBuilder::form()` for sending `application/x-www-form-urlencoded` requests. * Add method to configure custom error handler to Form extractor. diff --git a/src/error.rs b/src/error.rs index 4cbfe39eb..c272a2dca 100644 --- a/src/error.rs +++ b/src/error.rs @@ -592,9 +592,13 @@ impl From for JsonPayloadError { /// Error type returned when reading body as lines. pub enum ReadlinesError { + /// Error when decoding a line. EncodingError, + /// Payload error. PayloadError(PayloadError), + /// Line limit exceeded. LimitOverflow, + /// ContentType error. ContentTypeError(ContentTypeError), } @@ -604,12 +608,6 @@ impl From for ReadlinesError { } } -impl From for ReadlinesError { - fn from(_: Error) -> Self { - ReadlinesError::EncodingError - } -} - impl From for ReadlinesError { fn from(err: ContentTypeError) -> Self { ReadlinesError::ContentTypeError(err) diff --git a/src/httpmessage.rs b/src/httpmessage.rs index a380f3b98..82c50d775 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -13,7 +13,7 @@ use std::str; use error::{ ContentTypeError, HttpRangeError, ParseError, PayloadError, UrlencodedError, - Error, ErrorBadRequest, ReadlinesError + ReadlinesError }; use header::Header; use json::JsonBody; @@ -279,6 +279,7 @@ where req: T, buff: BytesMut, limit: usize, + checked_buff: bool, } impl Readlines @@ -291,6 +292,7 @@ where req, buff: BytesMut::with_capacity(262_144), limit: 262_144, + checked_buff: true, } } @@ -311,29 +313,32 @@ where fn poll(&mut self) -> Poll, Self::Error> { let encoding = self.req.encoding()?; // check if there is a newline in the buffer - let mut found: Option = None; - for (ind, b) in self.buff.iter().enumerate() { - if *b == '\n' as u8 { - found = Some(ind); - break; + if !self.checked_buff { + let mut found: Option = None; + for (ind, b) in self.buff.iter().enumerate() { + if *b == '\n' as u8 { + found = Some(ind); + break; + } } - } - if let Some(ind) = found { - // check if line is longer than limit - if ind+1 > self.limit { - return Err(ReadlinesError::LimitOverflow); + if let Some(ind) = found { + // check if line is longer than limit + if ind+1 > self.limit { + return Err(ReadlinesError::LimitOverflow); + } + let enc: *const Encoding = encoding as *const Encoding; + let line = if enc == UTF_8 { + str::from_utf8(&self.buff.split_to(ind+1)) + .map_err(|_| ReadlinesError::EncodingError)? + .to_owned() + } else { + encoding + .decode(&self.buff.split_to(ind+1), DecoderTrap::Strict) + .map_err(|_| ReadlinesError::EncodingError)? + }; + return Ok(Async::Ready(Some(line))); } - let enc: *const Encoding = encoding as *const Encoding; - let line = if enc == UTF_8 { - str::from_utf8(&self.buff.split_to(ind+1)) - .map_err(|_| ErrorBadRequest("Can not decode body"))? - .to_owned() - } else { - encoding - .decode(&self.buff.split_to(ind+1), DecoderTrap::Strict) - .map_err(|_| ErrorBadRequest("Can not decode body"))? - }; - return Ok(Async::Ready(Some(line))); + self.checked_buff = true; } // poll req for more bytes match self.req.poll() { @@ -354,15 +359,16 @@ where let enc: *const Encoding = encoding as *const Encoding; let line = if enc == UTF_8 { str::from_utf8(&bytes.split_to(ind+1)) - .map_err(|_| ErrorBadRequest("Can not decode body"))? + .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { encoding .decode(&bytes.split_to(ind+1), DecoderTrap::Strict) - .map_err(|_| ErrorBadRequest("Can not decode body"))? + .map_err(|_| ReadlinesError::EncodingError)? }; // extend buffer with rest of the bytes; self.buff.extend_from_slice(&bytes); + self.checked_buff = false; return Ok(Async::Ready(Some(line))); } self.buff.extend_from_slice(&bytes); @@ -379,12 +385,12 @@ where let enc: *const Encoding = encoding as *const Encoding; let line = if enc == UTF_8 { str::from_utf8(&self.buff) - .map_err(|_| ErrorBadRequest("Can not decode body"))? + .map_err(|_| ReadlinesError::EncodingError)? .to_owned() } else { encoding .decode(&self.buff, DecoderTrap::Strict) - .map_err(|_| ErrorBadRequest("Can not decode body"))? + .map_err(|_| ReadlinesError::EncodingError)? }; self.buff.clear(); return Ok(Async::Ready(Some(line))) @@ -799,4 +805,30 @@ mod tests { _ => unreachable!("error"), } } + + #[test] + fn test_readlines() { + let mut req = HttpRequest::default(); + req.payload_mut().unread_data(Bytes::from_static( + b"Lorem Ipsum is simply dummy text of the printing and typesetting\n\ + industry. Lorem Ipsum has been the industry's standard dummy\n\ + Contrary to popular belief, Lorem Ipsum is not simply random text." + )); + let mut r = Readlines::new(req); + match r.poll().ok().unwrap() { + Async::Ready(Some(s)) => assert_eq!(s, + "Lorem Ipsum is simply dummy text of the printing and typesetting\n"), + _ => unreachable!("error"), + } + match r.poll().ok().unwrap() { + Async::Ready(Some(s)) => assert_eq!(s, + "industry. Lorem Ipsum has been the industry's standard dummy\n"), + _ => unreachable!("error"), + } + match r.poll().ok().unwrap() { + Async::Ready(Some(s)) => assert_eq!(s, + "Contrary to popular belief, Lorem Ipsum is not simply random text."), + _ => unreachable!("error"), + } + } }