From 7e93987cd754b10c144947e450af45de7acb577d Mon Sep 17 00:00:00 2001 From: liuziheng Date: Sat, 7 Oct 2023 19:46:58 +0800 Subject: [PATCH] fix chunk Signed-off-by: liuziheng --- ylong_http/src/body/chunk.rs | 222 ++++++++++++++++-- ylong_http/src/body/mod.rs | 78 +++++- ylong_http_client/src/async_impl/client.rs | 17 +- .../src/async_impl/conn/http2.rs | 10 +- ylong_http_client/src/async_impl/http_body.rs | 193 ++++++++------- ylong_http_client/src/sync_impl/http_body.rs | 84 +------ 6 files changed, 394 insertions(+), 210 deletions(-) diff --git a/ylong_http/src/body/chunk.rs b/ylong_http/src/body/chunk.rs index 8b3ec35..77674e1 100644 --- a/ylong_http/src/body/chunk.rs +++ b/ylong_http/src/body/chunk.rs @@ -937,8 +937,10 @@ pub struct ChunkBodyDecoder { chunk_num: usize, total_size: usize, rest_size: usize, - size_vec: Vec, + hex_count: i64, + trailer: Vec, cr_meet: bool, + chunk_flag: bool, is_last_chunk: bool, is_chunk_trailer: bool, is_trailer: bool, @@ -960,8 +962,10 @@ impl ChunkBodyDecoder { chunk_num: 0, total_size: 0, rest_size: 0, - size_vec: vec![], + hex_count: 0, + trailer: vec![], cr_meet: false, + chunk_flag: false, is_last_chunk: false, is_chunk_trailer: false, is_trailer: false, @@ -976,6 +980,16 @@ impl ChunkBodyDecoder { self } + fn merge_trailer(&mut self, chunk: &Chunk) { + if chunk.state() == &ChunkState::Finish || chunk.state() == &ChunkState::DataCrlf { + self.trailer.extend_from_slice(chunk.trailer().unwrap()); + if !self.trailer.is_empty() { + self.trailer.extend_from_slice(b"\r\n"); + } + } else { + self.trailer.extend_from_slice(chunk.trailer().unwrap()); + } + } /// Decode interface of the chunk decoder. /// It transfers a u8 slice pointing to the chunk data and returns the data /// of a chunk and the remaining data. When the data in the u8 slice is @@ -1030,10 +1044,17 @@ impl ChunkBodyDecoder { }?; chunk.set_id(self.chunk_num); + + if chunk.trailer.is_some() { + self.merge_trailer(&chunk); + } + remains = rest; match (chunk.is_complete(), self.is_last_chunk) { (false, _) => { - if self.is_chunk_trailer && chunk.state == ChunkState::Data { + if self.is_chunk_trailer + && (chunk.state == ChunkState::Data || chunk.state == ChunkState::DataCrlf) + { results.push(chunk); self.chunk_num += 1; if remains.is_empty() { @@ -1062,6 +1083,59 @@ impl ChunkBodyDecoder { Ok((results, remains)) } + /// Get trailer headers. + pub fn get_trailer(&self) -> Result, HttpError> { + if self.trailer.is_empty() { + return Ok(None); + } + + let mut colon = 0; + let mut lf = 0; + let mut trailer_header_name = HeaderName::from_bytes(b"")?; + let mut trailer_headers = Headers::new(); + for (i, b) in self.trailer.iter().enumerate() { + if *b == b' ' { + continue; + } + + if *b == b':' { + colon = i; + if lf == 0 { + let trailer_name = &self.trailer[..colon]; + trailer_header_name = HeaderName::from_bytes(trailer_name)?; + } else { + let trailer_name = &self.trailer[lf + 1..colon]; + trailer_header_name = HeaderName::from_bytes(trailer_name)?; + } + continue; + } + + if *b == b'\n' { + if &self.trailer[i - 2..i - 1] == "\n".as_bytes() { + break; + } + lf = i; + let trailer_value = &self.trailer[colon + 1..lf - 1]; + let trailer_header_value = HeaderValue::from_bytes(trailer_value)?; + let _ = trailer_headers.insert::( + trailer_header_name.clone(), + trailer_header_value.clone(), + )?; + } + } + + Ok(Some(trailer_headers)) + } + + fn hex_to_decimal(mut count: i64, num: i64) -> Result { + count = count + .checked_mul(16) + .ok_or_else(|| HttpError::from(ErrorKind::InvalidInput))?; + count + .checked_add(num) + .ok_or_else(|| HttpError::from(ErrorKind::InvalidInput)) + } + fn decode_size<'a>(&mut self, buf: &'a [u8]) -> Result<(Chunk<'a>, &'a [u8]), HttpError> { self.stage = Stage::Size; if buf.is_empty() { @@ -1077,36 +1151,50 @@ impl ChunkBodyDecoder { buf, )); } + self.chunk_flag = false; for (i, &b) in buf.iter().enumerate() { match b { b'0' => { if buf.len() <= i + 1 { + self.hex_count = Self::hex_to_decimal(self.hex_count, 0_i64)?; continue; } if buf[i + 1] != b';' && buf[i + 1] != b' ' && buf[i + 1] != b'\r' { + self.hex_count = Self::hex_to_decimal(self.hex_count, 0_i64)?; continue; } - if self.is_trailer { + if self.is_trailer && !self.chunk_flag { self.is_chunk_trailer = true; return self.skip_extension(&buf[i..]); } else { + self.hex_count = Self::hex_to_decimal(self.hex_count, 0_i64)?; continue; } } - b'1'..=b'9' | b'A'..=b'F' | b'a'..=b'f' => {} + b'1'..=b'9' => { + self.hex_count = Self::hex_to_decimal(self.hex_count, b as i64 - '0' as i64)?; + self.chunk_flag = true; + continue; + } + + b'a'..=b'f' => { + self.hex_count = + Self::hex_to_decimal(self.hex_count, b as i64 - 'a' as i64 + 10i64)?; + self.chunk_flag = true; + continue; + } + b'A'..=b'F' => { + self.hex_count = + Self::hex_to_decimal(self.hex_count, b as i64 - 'A' as i64 + 10i64)?; + self.chunk_flag = true; + continue; + } b' ' | b'\t' | b';' | b'\r' | b'\n' => { if self.is_chunk_trailer { return self.skip_trailer_crlf(&buf[i..]); } else { - self.size_vec.extend_from_slice(&buf[..i]); - self.total_size = usize::from_str_radix( - unsafe { from_utf8_unchecked(self.size_vec.as_slice()) }, - 16, - ) - .map_err(|_| { - >::into(ErrorKind::InvalidInput) - })?; - self.size_vec.clear(); + self.total_size = self.hex_count as usize; + self.hex_count = 0; // Decode to the last chunk return if self.total_size == 0 { self.is_last_chunk = true; @@ -1120,7 +1208,6 @@ impl ChunkBodyDecoder { _ => return Err(ErrorKind::InvalidInput.into()), } } - self.size_vec.extend_from_slice(buf); Ok(( Chunk { id: 0, @@ -1252,6 +1339,7 @@ impl ChunkBodyDecoder { return Err(ErrorKind::InvalidInput.into()); } self.cr_meet = false; + self.is_trailer_crlf = true; return self.decode_trailer_data(&buf[i + 1..]); } _ => return Err(ErrorKind::InvalidInput.into()), @@ -1289,7 +1377,7 @@ impl ChunkBodyDecoder { )); } - if buf[0] == b'\r' { + if buf[0] == b'\r' && self.is_trailer_crlf { self.is_last_chunk = true; } @@ -1312,7 +1400,7 @@ impl ChunkBodyDecoder { _ => {} } } - + self.is_trailer_crlf = false; Ok(( Chunk { id: 0, @@ -1341,6 +1429,7 @@ impl ChunkBodyDecoder { &buf[buf.len()..], )); } + let rest = self.rest_size; if buf.len() >= rest { self.rest_size = 0; @@ -1396,6 +1485,7 @@ impl ChunkBodyDecoder { )) } else { self.cr_meet = false; + self.is_trailer_crlf = true; self.stage = Stage::TrailerData; let complete_chunk = Chunk { id: 0, @@ -1513,6 +1603,13 @@ mod ut_chunk { res.extend_from_slice(b"\r\n"); res } + + /// UT test cases for `ChunkBody::set_trailer`. + /// + /// # Brief + /// 1. Creates a `ChunkBody` by calling `ChunkBody::set_trailer`. + /// 2. Encodes chunk body by calling `ChunkBody::data` + /// 3. Checks if the test result is correct. #[test] fn ut_chunk_body_encode_trailer_0() { let mut headers = Headers::new(); @@ -1577,9 +1674,15 @@ mod ut_chunk { /// 1. Creates a `ChunkBody` by calling `ChunkBody::from_bytes`. /// 2. Encodes chunk body by calling `async_impl::Body::data` /// 3. Checks if the test result is correct. - #[cfg(feature = "tokio_base")] - #[tokio::test] - async fn ut_asnyc_chunk_body_encode_0() { + #[test] + fn ut_asnyc_chunk_body_encode_0() { + let handle = ylong_runtime::spawn(async move { + asnyc_chunk_body_encode_0().await; + }); + ylong_runtime::block_on(handle).unwrap(); + } + + async fn asnyc_chunk_body_encode_0() { let content = data_message(); let mut task = ChunkBody::from_bytes(content.as_slice()); let mut user_slice = [0_u8; 20]; @@ -1601,9 +1704,15 @@ mod ut_chunk { /// 1. Creates a `ChunkBody` by calling `ChunkBody::from_async_reader`. /// 2. Encodes chunk body by calling `async_impl::Body::data` /// 3. Checks if the test result is correct. - #[cfg(feature = "tokio_base")] - #[tokio::test] - async fn ut_asnyc_chunk_body_encode_1() { + #[test] + fn ut_asnyc_chunk_body_encode_1() { + let handle = ylong_runtime::spawn(async move { + asnyc_chunk_body_encode_1().await; + }); + ylong_runtime::block_on(handle).unwrap(); + } + + async fn asnyc_chunk_body_encode_1() { let content = data_message(); let mut task = ChunkBody::from_async_reader(content.as_slice()); let mut user_slice = [0_u8; 1024]; @@ -1849,7 +1958,7 @@ mod ut_chunk { let res = decoder.decode(&chunk_body_bytes[87..119]); let mut chunks = Chunks::new(); chunks.push(Chunk { - id: 3, + id: 4, state: ChunkState::DataCrlf, size: 0, extension: ChunkExt::new(), @@ -1862,7 +1971,7 @@ mod ut_chunk { let res = decoder.decode(&chunk_body_bytes[119..121]); let mut chunks = Chunks::new(); chunks.push(Chunk { - id: 3, + id: 5, state: ChunkState::Finish, size: 0, extension: ChunkExt::new(), @@ -2086,4 +2195,67 @@ mod ut_chunk { ) ); } + + /// UT test cases for `ChunkBodyDecoder::decode`. + /// + /// # Brief + /// 1. Creates a `ChunkBodyDecoder` by calling `ChunkBodyDecoder::new`. + /// 2. Decodes chunk body by calling `ChunkBodyDecoder::decode` + /// 3. Checks if the test result is correct. + #[test] + fn ut_chunk_body_decode_7() { + let mut decoder = ChunkBodyDecoder::new().contains_trailer(true); + let buf = b"010\r\nAAAAAAAAAAAAAAAA\r\n0\r\ntrailer:value\r\n\r\n"; + let res = decoder.decode(&buf[0..23]); // 010\r\nAAAAAAAAAAAAAAAA\r\n + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 0, + state: ChunkState::Finish, + size: 16, + extension: ChunkExt::new(), + data: "AAAAAAAAAAAAAAAA".as_bytes(), + trailer: None, + }); + assert_eq!(res, Ok((chunks, &[] as &[u8]))); + + let res = decoder.decode(&buf[23..39]); // 0\r\ntrailer:value + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 1, + state: ChunkState::Data, + size: 0, + extension: ChunkExt::new(), + data: &[] as &[u8], + trailer: Some("trailer:value".as_bytes()), + }); + assert_eq!(res, Ok((chunks, &[] as &[u8]))); + + let res = decoder.decode(&buf[39..41]); //\r\n + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 2, + state: ChunkState::DataCrlf, + size: 0, + extension: ChunkExt::new(), + data: &[] as &[u8], + trailer: Some(&[] as &[u8]), + }); + assert_eq!(res, Ok((chunks, &[] as &[u8]))); + + let res = decoder.decode(&buf[41..]); //\r\n + let mut chunks = Chunks::new(); + chunks.push(Chunk { + id: 3, + state: ChunkState::Finish, + size: 0, + extension: ChunkExt::new(), + data: &[] as &[u8], + trailer: Some(&[] as &[u8]), + }); + assert_eq!(res, Ok((chunks, &[] as &[u8]))); + + let trailer_headers = decoder.get_trailer().unwrap().unwrap(); + let value = trailer_headers.get("trailer"); + assert_eq!(value.unwrap().to_str().unwrap(), "value"); + } } diff --git a/ylong_http/src/body/mod.rs b/ylong_http/src/body/mod.rs index 60b3baa..a0c31b2 100644 --- a/ylong_http/src/body/mod.rs +++ b/ylong_http/src/body/mod.rs @@ -295,8 +295,82 @@ pub mod async_impl { } /// Gets trailer headers. - fn trailer(&mut self) -> Result, Self::Error> { - Ok(None) + fn poll_trailer( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll, Self::Error>> { + Poll::Ready(Ok(None)) + } + + /// Returns a future that reads part of the trailer data, returning the + /// headers which were read. Trialer data will be written into + /// buf as headers. + /// + /// # Return Value + /// + /// - `Ok(Some(headers))`: + /// If the trailer has been completely read, headers will be returned. + /// + /// - `Ok(None)`: + /// If return none, means trailer is empty. + /// + /// - `Err(e)`: + /// An error occurred while reading trailer data. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::async_impl::Body; + /// use ylong_http::body::ChunkBody; + /// # async fn read_trailer_data() { + /// let box_stream = Box::new("".as_bytes()); + /// // Chunk body contain trailer data + /// let chunk_body_bytes = "\ + /// 5\r\n\ + /// hello\r\n\ + /// C ; type = text ;end = !\r\n\ + /// hello world!\r\n\ + /// 000; message = last\r\n\ + /// accept:text/html\r\n\r\n\ + /// "; + /// + /// // Gets `ChunkBody` + /// let mut chunk = ChunkBody::from_bytes(chunk_body_bytes.as_bytes()); + /// // read chunk body and return headers + /// let res = chunk.trailer().await.unwrap().unwrap(); + /// assert_eq!( + /// res.get("accept").unwrap().to_str().unwrap(), + /// "text/html".to_string() + /// ); + /// # } + /// ``` + fn trailer<'a>(&'a mut self) -> TrailerFuture<'a, Self> + where + Self: 'a, + { + TrailerFuture { body: self } + } + } + + /// A future that reads data from trailer, returning whole headers + /// were read. + /// + /// This future is the return value of `async_impl::Body::trailer`. + pub struct TrailerFuture<'a, T> + where + T: Body + 'a, + { + body: &'a mut T, + } + + impl<'a, T> Future for TrailerFuture<'a, T> + where + T: Body + 'a, + { + type Output = Result, T::Error>; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let fut = self.get_mut(); + Pin::new(&mut *fut.body).poll_trailer(cx) } } diff --git a/ylong_http_client/src/async_impl/client.rs b/ylong_http_client/src/async_impl/client.rs index 636424c..3585268 100644 --- a/ylong_http_client/src/async_impl/client.rs +++ b/ylong_http_client/src/async_impl/client.rs @@ -677,13 +677,7 @@ impl Default for ClientBuilder { #[cfg(test)] mod ut_async_impl_client { - use ylong_http::h1::ResponseDecoder; - use ylong_http::request::uri::Uri; - use ylong_http::request::Request; - use ylong_http::response::Response; - - use crate::async_impl::{Client, HttpBody}; - use crate::util::normalizer::BodyLength; + use crate::async_impl::Client; use crate::Proxy; /// UT test cases for `Client::builder`. @@ -738,8 +732,15 @@ mod ut_async_impl_client { ylong_runtime::block_on(handle).unwrap(); } + #[cfg(all(feature = "__tls", feature = "ylong_base"))] async fn client_request_redirect() { - use crate::async_impl::ClientBuilder; + use ylong_http::h1::ResponseDecoder; + use ylong_http::request::uri::Uri; + use ylong_http::request::Request; + use ylong_http::response::Response; + + use crate::async_impl::{ClientBuilder, HttpBody}; + use crate::util::normalizer::BodyLength; use crate::util::{Redirect, Timeout}; let response_str = "HTTP/1.1 304 \r\nAge: \t 270646 \t \t\r\nLocation: \t http://example3.com:80/foo?a=1 \t \t\r\nDate: \t Mon, 19 Dec 2022 01:46:59 GMT \t \t\r\nEtag:\t \"3147526947+gzip\" \t \t\r\n\r\n".as_bytes(); diff --git a/ylong_http_client/src/async_impl/conn/http2.rs b/ylong_http_client/src/async_impl/conn/http2.rs index d00f546..68dc833 100644 --- a/ylong_http_client/src/async_impl/conn/http2.rs +++ b/ylong_http_client/src/async_impl/conn/http2.rs @@ -40,9 +40,9 @@ pub(crate) async fn request( request: &mut Request, retryable: &mut Retryable, ) -> Result, HttpClientError> - where - T: Body, - S: AsyncRead + AsyncWrite + Sync + Send + Unpin + 'static, +where + T: Body, + S: AsyncRead + AsyncWrite + Sync + Send + Unpin + 'static, { let part = request.part().clone(); let body = request.body_mut(); @@ -83,8 +83,8 @@ fn frame_2_response( headers_frame: Frame, retryable: &mut Retryable, ) -> Result, HttpClientError> - where - S: AsyncRead + AsyncWrite + Sync + Send + Unpin + 'static, +where + S: AsyncRead + AsyncWrite + Sync + Send + Unpin + 'static, { let part = match headers_frame.payload() { Payload::Headers(headers) => { diff --git a/ylong_http_client/src/async_impl/http_body.rs b/ylong_http_client/src/async_impl/http_body.rs index 60b1f00..a96f180 100644 --- a/ylong_http_client/src/async_impl/http_body.rs +++ b/ylong_http_client/src/async_impl/http_body.rs @@ -20,14 +20,14 @@ use ylong_http::body::TextBodyDecoder; #[cfg(feature = "http1_1")] use ylong_http::body::{ChunkBodyDecoder, ChunkState}; use ylong_http::headers::Headers; -#[cfg(feature = "http1_1")] -use ylong_http::headers::{HeaderName, HeaderValue}; use super::{Body, StreamData}; use crate::error::{ErrorKind, HttpClientError}; use crate::util::normalizer::BodyLength; use crate::{AsyncRead, ReadBuf, Sleep}; +const TRAILER_SIZE: usize = 1024; + /// `HttpBody` is the body part of the `Response` returned by `Client::request`. /// `HttpBody` implements `Body` trait, so users can call related methods to get /// body data. @@ -140,11 +140,42 @@ impl Body for HttpBody { } } - fn trailer(&mut self) -> Result, Self::Error> { + fn poll_trailer( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll, Self::Error>> { + // Get trailer data from io + if let Some(delay) = self.sleep.as_mut() { + if let Poll::Ready(()) = Pin::new(delay).poll(cx) { + return Poll::Ready(Err(HttpClientError::new_with_message( + ErrorKind::Timeout, + "Request timeout", + ))); + } + } + + let mut read_buf = [0_u8; TRAILER_SIZE]; + match self.kind { #[cfg(feature = "http1_1")] - Kind::Chunk(ref mut chunk) => chunk.get_trailer(), - _ => Ok(None), + Kind::Chunk(ref mut chunk) => { + match chunk.data(cx, &mut read_buf) { + Poll::Ready(Ok(_)) => {} + Poll::Pending => { + return Poll::Pending; + } + Poll::Ready(Err(e)) => { + return Poll::Ready(Err(HttpClientError::new_with_cause( + ErrorKind::BodyTransfer, + Some(e), + ))); + } + } + Poll::Ready(Ok(chunk.decoder.get_trailer().map_err(|_| { + HttpClientError::new_with_message(ErrorKind::BodyDecode, "Get trailer failed") + })?)) + } + _ => Poll::Ready(Ok(None)), } } } @@ -360,17 +391,15 @@ struct Chunk { decoder: ChunkBodyDecoder, pre: Option>>, io: Option, - trailer: Vec, } #[cfg(feature = "http1_1")] impl Chunk { pub(crate) fn new(pre: &[u8], io: BoxStreamData) -> Self { Self { - decoder: ChunkBodyDecoder::new().contains_trailer(false), + decoder: ChunkBodyDecoder::new().contains_trailer(true), pre: (!pre.is_empty()).then_some(Cursor::new(pre.to_vec())), io: Some(io), - trailer: vec![], } } } @@ -474,18 +503,11 @@ impl Chunk { let mut finished = false; let mut ptrs = Vec::new(); + for chunk in chunks.into_iter() { if chunk.trailer().is_some() { if chunk.state() == &ChunkState::Finish { finished = true; - self.trailer.extend_from_slice(chunk.trailer().unwrap()); - self.trailer.extend_from_slice(b"\r\n"); - break; - } else if chunk.state() == &ChunkState::DataCrlf { - self.trailer.extend_from_slice(chunk.trailer().unwrap()); - self.trailer.extend_from_slice(b"\r\n"); - } else { - self.trailer.extend_from_slice(chunk.trailer().unwrap()); } } else { if chunk.size() == 0 && chunk.state() != &ChunkState::MetaSize { @@ -515,86 +537,79 @@ impl Chunk { } Ok((idx, finished)) } - - fn get_trailer(&self) -> Result, HttpClientError> { - if self.trailer.is_empty() { - return Err(HttpClientError::new_with_message( - ErrorKind::BodyDecode, - "No trailer received", - )); - } - - let mut colon = 0; - let mut lf = 0; - let mut trailer_header_name = HeaderName::from_bytes(b"") - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; - let mut trailer_headers = Headers::new(); - for (i, b) in self.trailer.iter().enumerate() { - if *b == b' ' { - continue; - } - if *b == b':' { - colon = i; - if lf == 0 { - let trailer_name = &self.trailer[..colon]; - trailer_header_name = HeaderName::from_bytes(trailer_name).map_err(|e| { - HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)) - })?; - } else { - let trailer_name = &self.trailer[lf + 1..colon]; - trailer_header_name = HeaderName::from_bytes(trailer_name).map_err(|e| { - HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)) - })?; - } - continue; - } - - if *b == b'\n' { - lf = i; - let trailer_value = &self.trailer[colon + 1..lf - 1]; - let trailer_header_value = HeaderValue::from_bytes(trailer_value) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; - let _ = trailer_headers - .insert::( - trailer_header_name.clone(), - trailer_header_value.clone(), - ) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; - } - } - Ok(Some(trailer_headers)) - } } #[cfg(test)] mod ut_async_http_body { - use crate::async_impl::http_body::Chunk; + use ylong_http::body::async_impl; use crate::async_impl::HttpBody; use crate::util::normalizer::BodyLength; use crate::ErrorKind; - use ylong_http::body::{async_impl, ChunkBodyDecoder}; - /// UT test cases for `Chunk::get_trailers`. + /// UT test cases for `HttpBody::trailer`. /// /// # Brief - /// 1. Creates a `Chunk` and set `Trailer`. - /// 2. Calls `get_trailer` method. - /// 3. Checks if the result is correct. + /// 1. Creates a `HttpBody` by calling `HttpBody::new`. + /// 2. Calls `trailer` to get headers. + /// 3. Checks if the test result is correct. #[test] - fn ut_http_body_chunk() { - let mut chunk = Chunk { - decoder: ChunkBodyDecoder::new().contains_trailer(true), - pre: None, - io: None, - trailer: vec![], - }; - let trailer_info = "Trailer1:value1\r\nTrailer2:value2\r\n"; - chunk.trailer.extend_from_slice(trailer_info.as_bytes()); - let data = chunk.get_trailer().unwrap().unwrap(); - let value1 = data.get("Trailer1"); - assert_eq!(value1.unwrap().to_str().unwrap(), "value1"); - let value2 = data.get("Trailer2"); - assert_eq!(value2.unwrap().to_str().unwrap(), "value2"); + fn ut_asnyc_chunk_trailer_1() { + let handle = ylong_runtime::spawn(async move { + asnyc_chunk_trailer_1().await; + }); + ylong_runtime::block_on(handle).unwrap(); + } + + async fn asnyc_chunk_trailer_1() { + let box_stream = Box::new("".as_bytes()); + let chunk_body_bytes = "\ + 5\r\n\ + hello\r\n\ + C ; type = text ;end = !\r\n\ + hello world!\r\n\ + 000; message = last\r\n\ + accept:text/html\r\n\r\n\ + "; + let mut chunk = + HttpBody::new(BodyLength::Chunk, box_stream, chunk_body_bytes.as_bytes()).unwrap(); + let res = async_impl::Body::trailer(&mut chunk) + .await + .unwrap() + .unwrap(); + assert_eq!( + res.get("accept").unwrap().to_str().unwrap(), + "text/html".to_string() + ); + let box_stream = Box::new("".as_bytes()); + let chunk_body_no_trailer_bytes = "\ + 5\r\n\ + hello\r\n\ + C ; type = text ;end = !\r\n\ + hello world!\r\n\ + 0\r\n\r\n\ + "; + + let mut chunk = HttpBody::new( + BodyLength::Chunk, + box_stream, + chunk_body_no_trailer_bytes.as_bytes(), + ) + .unwrap(); + + let mut buf = [0u8; 32]; + // Read body part + let read = async_impl::Body::data(&mut chunk, &mut buf).await.unwrap(); + assert_eq!(read, 5); + assert_eq!(&buf[..read], b"hello"); + let read = async_impl::Body::data(&mut chunk, &mut buf).await.unwrap(); + assert_eq!(read, 12); + assert_eq!(&buf[..read], b"hello world!"); + let read = async_impl::Body::data(&mut chunk, &mut buf).await.unwrap(); + assert_eq!(read, 0); + assert_eq!(&buf[..read], b""); + // try read trailer part + let res = async_impl::Body::trailer(&mut chunk).await.unwrap(); + assert!(res.is_none()); } /// UT test cases for `Body::data`. @@ -660,10 +675,8 @@ mod ut_async_http_body { let read = async_impl::Body::data(&mut chunk, &mut buf).await.unwrap(); assert_eq!(read, 0); assert_eq!(&buf[..read], b""); - match async_impl::Body::trailer(&mut chunk) { - Ok(_) => (), - Err(e) => assert_eq!(e.error_kind(), ErrorKind::BodyDecode), - } + let res = async_impl::Body::trailer(&mut chunk).await.unwrap(); + assert!(res.is_none()); } /// UT test cases for `Body::data`. @@ -698,6 +711,7 @@ mod ut_async_http_body { ylong_runtime::block_on(handle).unwrap(); } + #[cfg(feature = "ylong_base")] async fn http_body_text() { let box_stream = Box::new("hello world".as_bytes()); let content_bytes = ""; @@ -745,6 +759,7 @@ mod ut_async_http_body { ylong_runtime::block_on(handle).unwrap(); } + #[cfg(feature = "ylong_base")] async fn http_body_until_close() { let box_stream = Box::new("hello world".as_bytes()); let content_bytes = ""; diff --git a/ylong_http_client/src/sync_impl/http_body.rs b/ylong_http_client/src/sync_impl/http_body.rs index c5cad03..ae3d6ad 100644 --- a/ylong_http_client/src/sync_impl/http_body.rs +++ b/ylong_http_client/src/sync_impl/http_body.rs @@ -110,7 +110,9 @@ impl Body for HttpBody { fn trailer(&mut self) -> Result, Self::Error> { match self.kind { - Kind::Chunk(ref mut chunk) => chunk.get_trailer(), + Kind::Chunk(ref mut chunk) => chunk.decoder.get_trailer().map_err(|_| { + HttpClientError::new_with_message(ErrorKind::BodyDecode, "Get trailer failed") + }), _ => Ok(None), } } @@ -301,14 +303,6 @@ impl Chunk { if chunk.trailer().is_some() { if chunk.state() == &ChunkState::Finish { finished = true; - self.trailer.extend_from_slice(chunk.trailer().unwrap()); - self.trailer.extend_from_slice(b"\r\n"); - break; - } else if chunk.state() == &ChunkState::DataCrlf { - self.trailer.extend_from_slice(chunk.trailer().unwrap()); - self.trailer.extend_from_slice(b"\r\n"); - } else { - self.trailer.extend_from_slice(chunk.trailer().unwrap()); } } else { if chunk.size() == 0 && chunk.state() != &ChunkState::MetaSize { @@ -338,55 +332,6 @@ impl Chunk { } Ok((idx, finished)) } - - fn get_trailer(&self) -> Result, HttpClientError> { - if self.trailer.is_empty() { - return Err(HttpClientError::new_with_message( - ErrorKind::BodyDecode, - "No trailer received", - )); - } - - let mut colon = 0; - let mut lf = 0; - let mut trailer_header_name = HeaderName::from_bytes(b"") - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; - let mut trailer_headers = Headers::new(); - for (i, b) in self.trailer.iter().enumerate() { - if *b == b' ' { - continue; - } - if *b == b':' { - colon = i; - if lf == 0 { - let trailer_name = &self.trailer[..colon]; - trailer_header_name = HeaderName::from_bytes(trailer_name).map_err(|e| { - HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)) - })?; - } else { - let trailer_name = &self.trailer[lf + 1..colon]; - trailer_header_name = HeaderName::from_bytes(trailer_name).map_err(|e| { - HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)) - })?; - } - continue; - } - - if *b == b'\n' { - lf = i; - let trailer_value = &self.trailer[colon + 1..lf - 1]; - let trailer_header_value = HeaderValue::from_bytes(trailer_value) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; - let _ = trailer_headers - .insert::( - trailer_header_name.clone(), - trailer_header_value.clone(), - ) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; - } - } - Ok(Some(trailer_headers)) - } } #[cfg(test)] @@ -410,27 +355,4 @@ mod ut_syn_http_body { assert!(data.is_ok()); assert_eq!(data.unwrap(), 0); } - - /// UT test cases for `Chunk::get_trailers`. - /// - /// # Brief - /// 1. Creates a `Chunk` and set `Trailer`. - /// 2. Calls `get_trailer` method. - /// 3. Checks if the result is correct. - #[test] - fn ut_http_body_chunk() { - let mut chunk = Chunk { - decoder: ChunkBodyDecoder::new().contains_trailer(true), - pre: None, - io: None, - trailer: vec![], - }; - let trailer_info = "Trailer1:value1\r\nTrailer2:value2\r\n"; - chunk.trailer.extend_from_slice(trailer_info.as_bytes()); - let data = chunk.get_trailer().unwrap().unwrap(); - let value1 = data.get("Trailer1"); - assert_eq!(value1.unwrap().to_str().unwrap(), "value1"); - let value2 = data.get("Trailer2"); - assert_eq!(value2.unwrap().to_str().unwrap(), "value2"); - } } -- Gitee