From 6ad98682c697c336e7f9d1dea0f654f7b320bdac Mon Sep 17 00:00:00 2001 From: hu-kai45 Date: Tue, 12 Sep 2023 11:49:42 +0800 Subject: [PATCH] =?UTF-8?q?ylong=5Fhttp=5Fclient=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=EF=BC=8C=E5=87=8F=E5=B0=91=E4=BA=8C=E8=BF=9B=E5=88=B6=E5=A4=A7?= =?UTF-8?q?=E5=B0=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: hu-kai45 --- ylong_http/src/body/chunk.rs | 84 +- ylong_http/src/body/mime/common/part.rs | 2 +- ylong_http/src/body/mime/mimetype.rs | 2 +- ylong_http/src/body/mime/mod.rs | 740 +---------------- ylong_http/src/body/mime/simple.rs | 758 ++++++++++++++++++ ylong_http/src/body/mod.rs | 61 +- ylong_http/src/body/text.rs | 30 - ylong_http/src/h1/request/encoder.rs | 4 +- ylong_http/src/h1/response/decoder.rs | 12 +- ylong_http/src/h2/decoder.rs | 2 +- ylong_http/src/h2/hpack/decoder.rs | 2 +- .../src/h2/hpack/representation/encoder.rs | 2 +- ylong_http/src/h3/qpack/decoder.rs | 2 +- ylong_http/src/h3/qpack/format/encoder.rs | 2 +- ylong_http/src/headers.rs | 59 +- ylong_http/src/request/mod.rs | 61 +- ylong_http/src/request/uri.rs | 23 + ylong_http/src/response/mod.rs | 7 +- ylong_http/src/response/status.rs | 8 +- .../examples/async_certs_adapter.rs | 10 +- ylong_http_client/examples/async_http.rs | 12 +- .../examples/async_http_multi.rs | 10 +- .../examples/async_https_outside.rs | 16 +- .../examples/async_proxy_http.rs | 15 +- .../examples/async_redirect_http.rs | 15 +- ylong_http_client/examples/sync_http.rs | 2 +- .../examples/sync_https_outside.rs | 5 +- ylong_http_client/examples/sync_proxy_http.rs | 5 +- .../examples/sync_redirect_http.rs | 5 +- ylong_http_client/src/async_impl/adapter.rs | 185 ----- ylong_http_client/src/async_impl/client.rs | 428 +++++----- .../src/async_impl/conn/http1.rs | 132 +-- .../src/async_impl/conn/http2.rs | 35 +- ylong_http_client/src/async_impl/conn/mod.rs | 30 +- .../src/async_impl/connector/mod.rs | 54 +- .../src/async_impl/connector/stream.rs | 2 +- .../src/async_impl/downloader/builder.rs | 3 +- .../src/async_impl/downloader/mod.rs | 30 +- .../src/async_impl/downloader/operator.rs | 7 +- ylong_http_client/src/async_impl/http_body.rs | 98 +-- ylong_http_client/src/async_impl/mod.rs | 25 +- ylong_http_client/src/async_impl/pool.rs | 50 +- ylong_http_client/src/async_impl/request.rs | 392 +++++++++ ylong_http_client/src/async_impl/response.rs | 66 ++ .../src/async_impl/ssl_stream/c_ssl_stream.rs | 2 +- .../src/async_impl/ssl_stream/mix.rs | 2 +- .../src/async_impl/ssl_stream/wrapper.rs | 2 +- ylong_http_client/src/async_impl/timeout.rs | 34 +- .../src/async_impl/uploader/builder.rs | 12 +- .../src/async_impl/uploader/mod.rs | 66 +- .../src/async_impl/uploader/operator.rs | 2 +- ylong_http_client/src/error.rs | 178 ++-- ylong_http_client/src/lib.rs | 71 +- ylong_http_client/src/sync_impl/client.rs | 65 +- ylong_http_client/src/sync_impl/conn/http1.rs | 28 +- ylong_http_client/src/sync_impl/conn/mod.rs | 5 +- ylong_http_client/src/sync_impl/connector.rs | 44 +- ylong_http_client/src/sync_impl/http_body.rs | 34 +- ylong_http_client/src/sync_impl/mod.rs | 5 +- ylong_http_client/src/sync_impl/pool.rs | 6 +- ylong_http_client/src/sync_impl/reader.rs | 16 +- .../src/util/c_openssl/adapter.rs | 70 +- .../src/util/c_openssl/ffi/callback.rs | 1 - .../src/util/c_openssl/ffi/ssl.rs | 2 +- .../src/util/c_openssl/ssl/ctx.rs | 6 - .../src/util/c_openssl/ssl/error.rs | 2 + .../src/util/c_openssl/ssl/mod.rs | 2 +- .../src/util/c_openssl/ssl/ssl_base.rs | 13 +- ylong_http_client/src/util/c_openssl/stack.rs | 7 - ylong_http_client/src/util/c_openssl/x509.rs | 2 +- ylong_http_client/src/util/config/http.rs | 104 +-- ylong_http_client/src/util/config/mod.rs | 2 +- ylong_http_client/src/util/config/settings.rs | 191 +---- .../src/util/config/tls/alpn/mod.rs | 4 +- .../src/util/config/tls/verifier/openssl.rs | 19 +- ylong_http_client/src/util/dispatcher.rs | 32 +- ylong_http_client/src/util/mod.rs | 29 +- ylong_http_client/src/util/normalizer.rs | 63 +- ylong_http_client/src/util/pool.rs | 4 +- ylong_http_client/src/util/proxy.rs | 4 +- ylong_http_client/src/util/redirect.rs | 476 +++-------- ylong_http_client/tests/common/async_utils.rs | 26 +- ylong_http_client/tests/common/mod.rs | 25 + ylong_http_client/tests/common/sync_utils.rs | 2 +- ylong_http_client/tests/sdv_async_http.rs | 2 - .../tests/sdv_async_http_on_tcp.rs | 17 - .../tests/sdv_async_http_proxy.rs | 2 +- .../tests/sdv_async_https_c_ssl.rs | 2 - ylong_http_client/tests/sdv_sync_http.rs | 3 - .../tests/sdv_sync_https_c_ssl.rs | 3 - .../tests/tcp_server/async_utils.rs | 5 +- ylong_http_client/tests/tcp_server/mod.rs | 27 +- .../tests/tcp_server/sync_utils.rs | 2 +- 93 files changed, 2401 insertions(+), 2813 deletions(-) create mode 100644 ylong_http/src/body/mime/simple.rs delete mode 100644 ylong_http_client/src/async_impl/adapter.rs create mode 100644 ylong_http_client/src/async_impl/request.rs create mode 100644 ylong_http_client/src/async_impl/response.rs diff --git a/ylong_http/src/body/chunk.rs b/ylong_http/src/body/chunk.rs index f5f312c..a7c8179 100644 --- a/ylong_http/src/body/chunk.rs +++ b/ylong_http/src/body/chunk.rs @@ -192,32 +192,6 @@ impl ChunkBody> { } } -impl ChunkBody> { - /// Creates a new `ChunkBody` by `async body`. - /// - /// # Examples - /// - /// ``` - /// use ylong_http::body::ChunkBody; - /// - /// let task = ChunkBody::from_async_body("".as_bytes()); - /// ``` - pub fn from_async_body(body: T) -> Self { - ChunkBody { - from: FromAsyncBody::new(body), - trailer_value: vec![], - chunk_data: ChunkData::new(vec![0; CHUNK_SIZE]), - data_status: DataState::Partial, - encode_status: EncodeStatus::new(), - trailer: EncodeTrailer::new(), - } - } - - fn chunk_encode(&mut self, dst: &mut [u8]) -> usize { - self.chunk_encode_reader(dst) - } -} - impl<'a> sync_impl::Body for ChunkBody> { type Error = Infallible; @@ -343,60 +317,6 @@ impl async_impl::Body for ChunkBody async_impl::Body for ChunkBody> { - type Error = T::Error; - - fn poll_data( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - let chunk_body = self.get_mut(); - let mut count = 0; - while count != buf.len() { - let encode_size = match chunk_body.data_status { - DataState::Partial => { - if !chunk_body.encode_status.get_flag() { - match Pin::new(&mut *chunk_body.from) - .poll_data(_cx, &mut chunk_body.chunk_data.chunk_buf) - { - Poll::Ready(Ok(size)) => { - chunk_body.encode_status.set_flag(true); - // chunk idx reset zero - chunk_body.encode_status.set_chunk_idx(0); - chunk_body.chunk_data.chunk_last = size; - let data_size = chunk_body.chunk_encode(&mut buf[count..]); - Poll::Ready(data_size) - } - Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), - Poll::Pending => Poll::Pending, - } - } else { - Poll::Ready(chunk_body.chunk_encode(&mut buf[count..])) - } - } - DataState::Complete => Poll::Ready(chunk_body.trailer_encode(&mut buf[count..])), - DataState::Finish => { - return Poll::Ready(Ok(count)); - } - }; - - match encode_size { - Poll::Ready(size) => { - count += size; - } - Poll::Pending => { - if count != 0 { - return Poll::Ready(Ok(count)); - } - return Poll::Pending; - } - } - } - Poll::Ready(Ok(buf.len())) - } -} - impl<'a> ChunkBody> { fn bytes_encode(&mut self, dst: &mut [u8]) -> usize { if !self.encode_status.get_flag() { @@ -435,7 +355,7 @@ impl ChunkBody { trailer_vec.extend_from_slice(name.as_bytes()); trailer_vec.extend_from_slice(b":"); // to_string will not return err, so can use unwrap directly - trailer_vec.extend_from_slice(value.to_str().unwrap().as_bytes()); + trailer_vec.extend_from_slice(value.to_string().unwrap().as_bytes()); trailer_vec.extend_from_slice(b"\r\n"); } self.trailer_value = trailer_vec; @@ -2260,6 +2180,6 @@ mod ut_chunk { let trailer_headers = decoder.get_trailer().unwrap().unwrap(); let value = trailer_headers.get("trailer"); - assert_eq!(value.unwrap().to_str().unwrap(), "value"); + assert_eq!(value.unwrap().to_string().unwrap(), "value"); } } diff --git a/ylong_http/src/body/mime/common/part.rs b/ylong_http/src/body/mime/common/part.rs index 6c5e4df..915badb 100644 --- a/ylong_http/src/body/mime/common/part.rs +++ b/ylong_http/src/body/mime/common/part.rs @@ -488,7 +488,7 @@ mod ut_mime_part { .append_header("accept", "text/plain"); let binding = part.inner.unwrap(); let value = binding.headers.get("accept"); - assert_eq!(value.unwrap().to_str().unwrap(), "text/html, text/plain"); + assert_eq!(value.unwrap().to_string().unwrap(), "text/html, text/plain"); } /// UT test cases for `MimePart::builder`. diff --git a/ylong_http/src/body/mime/mimetype.rs b/ylong_http/src/body/mime/mimetype.rs index 8c995eb..0dfc983 100644 --- a/ylong_http/src/body/mime/mimetype.rs +++ b/ylong_http/src/body/mime/mimetype.rs @@ -27,7 +27,7 @@ use crate::error::{ErrorKind, HttpError}; /// between: /// /// ```type/subtype``` -/// +/// /// It is case-insensitive but are traditionally written in lowercase, such as: /// ```application/octet-stream```. /// diff --git a/ylong_http/src/body/mime/mod.rs b/ylong_http/src/body/mime/mod.rs index 1d72404..5be0fb6 100644 --- a/ylong_http/src/body/mime/mod.rs +++ b/ylong_http/src/body/mime/mod.rs @@ -20,11 +20,7 @@ mod common; mod decode; mod encode; mod mimetype; - -use std::io::Cursor; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::vec::IntoIter; +mod simple; pub(crate) use common::{ DecodeHeaders, EncodeHeaders, HeaderStatus, MixFrom, PartStatus, CR, CRLF, HTAB, LF, SP, @@ -34,736 +30,4 @@ pub use decode::MimeMultiDecoder; pub(crate) use decode::MimePartDecoder; pub use encode::{MimeMultiEncoder, MimePartEncoder}; pub use mimetype::MimeType; - -// TODO: reuse mime later. - -// TODO: Adapter, remove this later. -use crate::body::async_impl::{poll_read, Body}; -use crate::{AsyncRead, ReadBuf}; - -/// A structure that helps you build a `multipart/form-data` message. -/// -/// # Examples -/// -/// ``` -/// # use ylong_http::body::{MultiPart, Part}; -/// -/// let multipart = MultiPart::new() -/// .part(Part::new().name("name").body("xiaoming")) -/// .part(Part::new().name("password").body("123456789")); -/// ``` -pub struct MultiPart { - parts: Vec, - boundary: String, - status: ReadStatus, -} - -impl MultiPart { - /// Creates an empty `Multipart` with boundary created automatically. - /// - /// # Examples - /// - /// ``` - /// # use ylong_http::body::MultiPart; - /// - /// let multipart = MultiPart::new(); - /// ``` - pub fn new() -> Self { - Self { - parts: Vec::new(), - boundary: gen_boundary(), - status: ReadStatus::Never, - } - } - - /// Sets a part to the `Multipart`. - /// - /// # Examples - /// - /// ``` - /// # use ylong_http::body::{MultiPart, Part}; - /// - /// let multipart = MultiPart::new().part(Part::new().name("name").body("xiaoming")); - /// ``` - pub fn part(mut self, part: Part) -> Self { - self.parts.push(part); - self - } - - /// Gets the boundary of this `Multipart`. - /// - /// # Examples - /// - /// ``` - /// # use ylong_http::body::MultiPart; - /// - /// let multipart = MultiPart::new(); - /// let boundary = multipart.boundary(); - /// ``` - pub fn boundary(&self) -> &str { - self.boundary.as_str() - } - - /// Get the total bytes of the `multpart/form-data` message, including - /// length of every parts, such as boundaries, headers, bodies, etc. - /// - /// # Examples - /// - /// ``` - /// # use ylong_http::body::{MultiPart, Part}; - /// - /// let multipart = MultiPart::new().part(Part::new().name("name").body("xiaoming")); - /// - /// let bytes = multipart.total_bytes(); - /// ``` - pub fn total_bytes(&self) -> Option { - let mut size = 0u64; - for part in self.parts.iter() { - size += part.length?; - - // start boundary + \r\n - size += 2 + self.boundary.len() as u64 + 2; - - // Content-Disposition: form-data - size += 30; - - // ; name="xxx" - if let Some(name) = part.name.as_ref() { - size += 9 + name.len() as u64; - } - - // ; filename="xxx" - if let Some(name) = part.file_name.as_ref() { - size += 13 + name.len() as u64; - } - - // \r\n - size += 2; - - // Content-Type: xxx - if let Some(mime) = part.mime.as_ref() { - size += 16 + mime.len() as u64; - } - - // \r\n\r\n - size += 2 + 2; - } - // last boundary - size += 2 + self.boundary.len() as u64 + 4; - Some(size) - } - - pub(crate) fn build_status(&mut self) { - let mut states = Vec::new(); - for part in self.parts.iter_mut() { - states.push(MultiPartState::bytes( - format!("--{}\r\n", self.boundary).into_bytes(), - )); - states.push(MultiPartState::bytes( - b"Content-Disposition: form-data".to_vec(), - )); - - if let Some(ref name) = part.name { - states.push(MultiPartState::bytes( - format!("; name=\"{name}\"").into_bytes(), - )); - } - - if let Some(ref file_name) = part.file_name { - states.push(MultiPartState::bytes( - format!("; filename=\"{file_name}\"").into_bytes(), - )); - } - - states.push(MultiPartState::bytes(b"\r\n".to_vec())); - - if let Some(ref mime) = part.mime { - states.push(MultiPartState::bytes( - format!("Content-Type: {mime}\r\n").into_bytes(), - )); - } - - states.push(MultiPartState::bytes(b"\r\n".to_vec())); - - if let Some(body) = part.body.take() { - states.push(body); - } - - states.push(MultiPartState::bytes(b"\r\n".to_vec())); - } - states.push(MultiPartState::bytes( - format!("--{}--\r\n", self.boundary).into_bytes(), - )); - self.status = ReadStatus::Reading(MultiPartStates { - states: states.into_iter(), - curr: None, - }) - } -} - -impl Default for MultiPart { - fn default() -> Self { - Self::new() - } -} - -impl AsyncRead for MultiPart { - fn poll_read( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut ReadBuf<'_>, - ) -> Poll> { - match self.status { - ReadStatus::Never => self.build_status(), - ReadStatus::Reading(_) => {} - ReadStatus::Finish => return Poll::Ready(Ok(())), - } - - if let ReadStatus::Reading(ref mut status) = self.status { - if buf.initialize_unfilled().is_empty() { - return Poll::Ready(Ok(())); - } - let filled = buf.filled().len(); - return match Pin::new(status).poll_read(cx, buf) { - Poll::Ready(Ok(())) => { - let new_filled = buf.filled().len(); - if filled == new_filled { - self.status = ReadStatus::Finish; - } - Poll::Ready(Ok(())) - } - Poll::Pending => { - let new_filled = buf.filled().len(); - return if new_filled != filled { - Poll::Ready(Ok(())) - } else { - Poll::Pending - }; - } - x => x, - }; - } - Poll::Ready(Ok(())) - } -} - -// TODO: Adapter, remove this later. -impl Body for MultiPart { - type Error = std::io::Error; - - fn poll_data( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - poll_read(self, cx, buf) - } -} - -/// A structure that represents a part of `multipart/form-data` message. -/// -/// # Examples -/// -/// ``` -/// # use ylong_http::body::Part; -/// -/// let part = Part::new().name("name").body("xiaoming"); -/// ``` -pub struct Part { - name: Option, - file_name: Option, - mime: Option, - length: Option, - body: Option, -} - -impl Part { - /// Creates an empty `Part`. - /// - /// # Examples - /// - /// ``` - /// use ylong_http::body::Part; - /// - /// let part = Part::new(); - /// ``` - pub fn new() -> Self { - Self { - name: None, - file_name: None, - mime: None, - length: None, - body: None, - } - } - - /// Sets the name of this `Part`. - /// - /// The name message will be set to `Content-Disposition` header. - /// - /// # Examples - /// - /// ``` - /// use ylong_http::body::Part; - /// - /// let part = Part::new().name("name"); - /// ``` - pub fn name(mut self, name: &str) -> Self { - self.name = Some(String::from(name)); - self - } - - /// Sets the file name of this `Part`. - /// - /// The file name message will be set to `Content-Disposition` header. - /// - /// # Examples - /// - /// ``` - /// use ylong_http::body::Part; - /// - /// let part = Part::new().file_name("example.txt"); - /// ``` - pub fn file_name(mut self, file_name: &str) -> Self { - self.file_name = Some(String::from(file_name)); - self - } - - /// Sets the mime type of this `Part`. - /// - /// The mime type message will be set to `Content-Type` header. - /// - /// # Examples - /// - /// ``` - /// use ylong_http::body::Part; - /// - /// let part = Part::new().mime("application/octet-stream"); - /// ``` - pub fn mime(mut self, mime: &str) -> Self { - self.mime = Some(String::from(mime)); - self - } - - /// Sets the length of body of this `Part`. - /// - /// The length message will be set to `Content-Length` header. - /// - /// # Examples - /// - /// ``` - /// use ylong_http::body::Part; - /// - /// let part = Part::new().length(Some(8)).body("xiaoming"); - /// ``` - pub fn length(mut self, length: Option) -> Self { - self.length = length; - self - } - - /// Sets a slice body of this `Part`. - /// - /// The body message will be set to the body part. - /// - /// # Examples - /// - /// ``` - /// use ylong_http::body::Part; - /// - /// let part = Part::new().mime("application/octet-stream"); - /// ``` - pub fn body>(mut self, body: T) -> Self { - let body = body.as_ref().to_vec(); - self.length = Some(body.len() as u64); - self.body = Some(MultiPartState::bytes(body)); - self - } - - /// Sets a stream body of this `Part`. - /// - /// The body message will be set to the body part. - pub fn stream(mut self, body: T) -> Self { - self.body = Some(MultiPartState::stream(Box::pin(body))); - self - } -} - -impl Default for Part { - fn default() -> Self { - Self::new() - } -} - -impl AsRef for MultiPart { - fn as_ref(&self) -> &MultiPart { - self - } -} - -enum ReadStatus { - Never, - Reading(MultiPartStates), - Finish, -} - -struct MultiPartStates { - states: IntoIter, - curr: Option, -} - -impl MultiPartStates { - fn poll_read_curr( - &mut self, - cx: &mut Context<'_>, - buf: &mut ReadBuf<'_>, - ) -> Poll> { - if let Some(mut state) = self.curr.take() { - return match state { - MultiPartState::Bytes(ref mut bytes) => { - let filled_len = buf.filled().len(); - let unfilled = buf.initialize_unfilled(); - let unfilled_len = unfilled.len(); - let new = std::io::Read::read(bytes, unfilled).unwrap(); - buf.set_filled(filled_len + new); - - if new >= unfilled_len { - self.curr = Some(state); - } - Poll::Ready(Ok(())) - } - MultiPartState::Stream(ref mut stream) => { - let old_len = buf.filled().len(); - match stream.as_mut().poll_read(cx, buf) { - Poll::Ready(Ok(())) => { - if old_len != buf.filled().len() { - self.curr = Some(state); - } - Poll::Ready(Ok(())) - } - Poll::Pending => { - self.curr = Some(state); - Poll::Pending - } - x => x, - } - } - }; - } - Poll::Ready(Ok(())) - } -} - -impl AsyncRead for MultiPartStates { - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut ReadBuf<'_>, - ) -> Poll> { - let this = self.get_mut(); - while !buf.initialize_unfilled().is_empty() { - if this.curr.is_none() { - this.curr = match this.states.next() { - None => break, - x => x, - } - } - - match this.poll_read_curr(cx, buf) { - Poll::Ready(Ok(())) => {} - x => return x, - } - } - Poll::Ready(Ok(())) - } -} - -enum MultiPartState { - Bytes(Cursor>), - Stream(Pin>), -} - -impl MultiPartState { - fn bytes(bytes: Vec) -> Self { - Self::Bytes(Cursor::new(bytes)) - } - - fn stream(reader: Pin>) -> Self { - Self::Stream(reader) - } -} - -#[cfg(test)] -impl PartialEq for MultiPartState { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Bytes(l0), Self::Bytes(r0)) => l0 == r0, - // Cant not compare Stream, Should not do this. - (Self::Stream(l0), Self::Stream(r0)) => core::ptr::eq(l0, r0), - _ => false, - } - } -} - -#[cfg(test)] -impl core::fmt::Debug for MultiPartState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Bytes(arg0) => f.debug_tuple("Bytes").field(arg0).finish(), - Self::Stream(arg0) => f.debug_tuple("Stream").field(&(arg0 as *const _)).finish(), - } - } -} - -fn gen_boundary() -> String { - format!( - "{:016x}-{:016x}-{:016x}-{:016x}", - xor_shift(), - xor_shift(), - xor_shift(), - xor_shift() - ) -} - -// XORShift* fast-random realization. -fn xor_shift() -> u64 { - use std::cell::Cell; - use std::collections::hash_map::RandomState; - use std::hash::{BuildHasher, Hasher}; - use std::num::Wrapping; - - thread_local! { - static RNG: Cell> = Cell::new(Wrapping(seed())); - } - - // The returned value of `seed()` must be nonzero. - fn seed() -> u64 { - let seed = RandomState::new(); - - let mut out; - let mut cnt = 1; - let mut hasher = seed.build_hasher(); - - loop { - hasher.write_usize(cnt); - out = hasher.finish(); - if out != 0 { - break; - } - cnt += 1; - hasher = seed.build_hasher(); - } - out - } - - RNG.with(|rng| { - let mut n = rng.get(); - n ^= n >> 12; - n ^= n << 25; - n ^= n >> 27; - rng.set(n); - n.0.wrapping_mul(0x2545_f491_4f6c_dd1d) - }) -} - -#[cfg(test)] -mod ut_mime { - use crate::body::mime::{gen_boundary, MultiPartState, ReadStatus}; - use crate::body::{MultiPart, Part}; - - /// UT test cases for `gen_boundar`. - /// - /// # Brief - /// 1. Creates two boundarys and compares. - /// 3. Checks whether the result is correct. - #[test] - fn ut_gen_boundary() { - let s1 = gen_boundary(); - let s2 = gen_boundary(); - assert_ne!(s1, s2); - } - - /// UT test cases for `Part::new`. - /// - /// # Brief - /// 1. Creates a `Part` by `Part::new`. - /// 2. Checks members of `Part`. - /// 3. Checks whether the result is correct. - #[test] - fn ut_part_new() { - let part = Part::new(); - assert!(part.name.is_none()); - assert!(part.file_name.is_none()); - assert!(part.mime.is_none()); - assert!(part.length.is_none()); - assert!(part.body.is_none()); - } - - /// UT test cases for `Part::default`. - /// - /// # Brief - /// 1. Creates a `Part` by `Part::default`. - /// 2. Checks members of `Part`. - /// 3. Checks whether the result is correct. - #[test] - fn ut_part_default() { - let part = Part::default(); - assert!(part.name.is_none()); - assert!(part.file_name.is_none()); - assert!(part.mime.is_none()); - assert!(part.length.is_none()); - assert!(part.body.is_none()); - } - - /// UT test cases for `Part::name`, `Part::name`, `Part::file_name` and - /// `Part::body`. - /// - /// # Brief - /// 1. Creates a `Part` and sets values. - /// 2. Checks members of `Part`. - /// 3. Checks whether the result is correct. - #[test] - fn ut_part_set() { - let part = Part::new() - .name("name") - .file_name("example.txt") - .mime("application/octet-stream") - .body("1234"); - assert_eq!(part.name, Some("name".to_string())); - assert_eq!(part.file_name, Some("example.txt".to_string())); - assert_eq!(part.mime, Some("application/octet-stream".to_string())); - assert_eq!(part.body, Some(MultiPartState::bytes("1234".into()))); - assert_eq!(part.length, Some(4)); - - let part = part.stream("11223344".as_bytes()).length(Some(8)); - assert_eq!(part.length, Some(8)); - } - - /// UT test cases for `MultiPart::new`. - /// - /// # Brief - /// 1. Creates a `MultiPart` by `MultiPart::new`. - /// 2. Checks members of `MultiPart`. - /// 3. Checks whether the result is correct. - #[test] - fn ut_multipart_new() { - let mp = MultiPart::new(); - assert!(mp.parts.is_empty()); - assert!(!mp.boundary().is_empty()); - } - - /// UT test cases for `MultiPart::part` and `MultiPart::total_bytes`. - /// - /// # Brief - /// 1. Creates a `MultiPart` and sets values. - /// 2. Checks total bytes of `MultiPart`. - /// 3. Checks whether the result is correct. - #[test] - fn ut_multipart_set() { - let mp = MultiPart::default(); - // --boundary--/r/n - assert_eq!(mp.total_bytes(), Some(2 + mp.boundary().len() as u64 + 4)); - - let mp = mp.part( - Part::new() - .name("name") - .file_name("example.txt") - .mime("application/octet-stream") - .body("1234"), - ); - assert_eq!( - mp.total_bytes(), - Some( - (2 + mp.boundary().len() as u64 + 2) - + (30 + 9 + 4 + 13 + 11 + 2) // name, filename, \r\n - + (16 + 24 + 2 + 2) // mime, \r\n - + 4 // body - + (2 + mp.boundary().len() as u64 + 4) - ) - ); - } - - /// UT test cases for `MultiPart::poll_data`. - /// - /// # Brief - /// 1. Creates a `MultiPart` and sets values. - /// 2. Encodes `MultiPart` by `async_impl::Body::data`. - /// 3. Checks whether the result is correct. - #[cfg(feature = "ylong_base")] - #[test] - fn ut_multipart_poll_data() { - let handle = ylong_runtime::spawn(async move { - multipart_poll_data().await; - }); - ylong_runtime::block_on(handle).unwrap(); - } - - #[cfg(feature = "ylong_base")] - async fn multipart_poll_data() { - let mut mp = MultiPart::new().part( - Part::new() - .name("name") - .file_name("example.txt") - .mime("application/octet-stream") - .body("1234"), - ); - - let mut buf = vec![0u8; 50]; - let mut v_size = vec![]; - let mut v_str = vec![]; - - loop { - let len = crate::body::async_impl::Body::data(&mut mp, &mut buf) - .await - .unwrap(); - if len == 0 { - break; - } - v_size.push(len); - v_str.extend_from_slice(&buf[..len]); - } - assert_eq!(v_size, vec![50, 50, 50, 50, 50, 11]); - } - - /// UT test cases for `MultiPart::poll_data`. - /// - /// # Brief - /// 1. Creates a `MultiPart` and sets values. - /// 2. Encodes `MultiPart` by `async_impl::Body::data`. - /// 3. Checks whether the result is correct. - #[cfg(feature = "ylong_base")] - #[test] - fn ut_multipart_poll_data_stream() { - let handle = ylong_runtime::spawn(async move { - multipart_poll_data_stream().await; - }); - ylong_runtime::block_on(handle).unwrap(); - } - - #[cfg(feature = "ylong_base")] - async fn multipart_poll_data_stream() { - let mut mp = MultiPart::new().part( - Part::new() - .name("name") - .file_name("example.txt") - .mime("application/octet-stream") - .stream("1234".as_bytes()) - .length(Some(4)), - ); - - let mut buf = vec![0u8; 50]; - let mut v_size = vec![]; - let mut v_str = vec![]; - - loop { - let len = crate::body::async_impl::Body::data(&mut mp, &mut buf) - .await - .unwrap(); - if len == 0 { - break; - } - v_size.push(len); - v_str.extend_from_slice(&buf[..len]); - } - assert_eq!(v_size, vec![50, 50, 50, 50, 50, 11]); - } -} +pub use simple::{MultiPart, MultiPartBase, Part}; diff --git a/ylong_http/src/body/mime/simple.rs b/ylong_http/src/body/mime/simple.rs new file mode 100644 index 0000000..832dda2 --- /dev/null +++ b/ylong_http/src/body/mime/simple.rs @@ -0,0 +1,758 @@ +// Copyright (c) 2023 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// TODO: reuse mime later. + +use std::io::Cursor; +use std::pin::Pin; +use std::task::{Context, Poll}; +use std::vec::IntoIter; + +use crate::body::async_impl::Body; +use crate::{AsyncRead, ReadBuf}; + +/// A structure that helps you build a `multipart/form-data` message. +/// +/// # Examples +/// +/// ``` +/// # use ylong_http::body::{MultiPart, Part}; +/// +/// let multipart = MultiPart::new() +/// .part(Part::new().name("name").body("xiaoming")) +/// .part(Part::new().name("password").body("123456789")); +/// ``` +pub struct MultiPart { + parts: Vec, + boundary: String, + status: ReadStatus, +} + +impl MultiPart { + /// Creates an empty `Multipart` with boundary created automatically. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http::body::MultiPart; + /// + /// let multipart = MultiPart::new(); + /// ``` + pub fn new() -> Self { + Self { + parts: Vec::new(), + boundary: gen_boundary(), + status: ReadStatus::Never, + } + } + + /// Sets a part to the `Multipart`. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http::body::{MultiPart, Part}; + /// + /// let multipart = MultiPart::new().part(Part::new().name("name").body("xiaoming")); + /// ``` + pub fn part(mut self, part: Part) -> Self { + self.parts.push(part); + self + } + + /// Gets the boundary of this `Multipart`. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http::body::MultiPart; + /// + /// let multipart = MultiPart::new(); + /// let boundary = multipart.boundary(); + /// ``` + pub fn boundary(&self) -> &str { + self.boundary.as_str() + } + + /// Get the total bytes of the `multpart/form-data` message, including + /// length of every parts, such as boundaries, headers, bodies, etc. + /// + /// # Examples + /// + /// ``` + /// # use ylong_http::body::{MultiPart, Part}; + /// + /// let multipart = MultiPart::new().part(Part::new().name("name").body("xiaoming")); + /// + /// let bytes = multipart.total_bytes(); + /// ``` + pub fn total_bytes(&self) -> Option { + let mut size = 0u64; + for part in self.parts.iter() { + size += part.length?; + + // start boundary + \r\n + size += 2 + self.boundary.len() as u64 + 2; + + // Content-Disposition: form-data + size += 30; + + // ; name="xxx" + if let Some(name) = part.name.as_ref() { + size += 9 + name.len() as u64; + } + + // ; filename="xxx" + if let Some(name) = part.file_name.as_ref() { + size += 13 + name.len() as u64; + } + + // \r\n + size += 2; + + // Content-Type: xxx + if let Some(mime) = part.mime.as_ref() { + size += 16 + mime.len() as u64; + } + + // \r\n\r\n + size += 2 + 2; + } + // last boundary + size += 2 + self.boundary.len() as u64 + 4; + Some(size) + } + + pub(crate) fn build_status(&mut self) { + let mut states = Vec::new(); + for part in self.parts.iter_mut() { + states.push(MultiPartState::bytes( + format!("--{}\r\n", self.boundary).into_bytes(), + )); + states.push(MultiPartState::bytes( + b"Content-Disposition: form-data".to_vec(), + )); + + if let Some(ref name) = part.name { + states.push(MultiPartState::bytes( + format!("; name=\"{name}\"").into_bytes(), + )); + } + + if let Some(ref file_name) = part.file_name { + states.push(MultiPartState::bytes( + format!("; filename=\"{file_name}\"").into_bytes(), + )); + } + + states.push(MultiPartState::bytes(b"\r\n".to_vec())); + + if let Some(ref mime) = part.mime { + states.push(MultiPartState::bytes( + format!("Content-Type: {mime}\r\n").into_bytes(), + )); + } + + states.push(MultiPartState::bytes(b"\r\n".to_vec())); + + if let Some(body) = part.body.take() { + states.push(body); + } + + states.push(MultiPartState::bytes(b"\r\n".to_vec())); + } + states.push(MultiPartState::bytes( + format!("--{}--\r\n", self.boundary).into_bytes(), + )); + self.status = ReadStatus::Reading(MultiPartStates { + states: states.into_iter(), + curr: None, + }) + } +} + +impl Default for MultiPart { + fn default() -> Self { + Self::new() + } +} + +impl AsyncRead for MultiPart { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + match self.status { + ReadStatus::Never => self.build_status(), + ReadStatus::Reading(_) => {} + ReadStatus::Finish => return Poll::Ready(Ok(())), + } + + if let ReadStatus::Reading(ref mut status) = self.status { + if buf.initialize_unfilled().is_empty() { + return Poll::Ready(Ok(())); + } + let filled = buf.filled().len(); + return match Pin::new(status).poll_read(cx, buf) { + Poll::Ready(Ok(())) => { + let new_filled = buf.filled().len(); + if filled == new_filled { + self.status = ReadStatus::Finish; + } + Poll::Ready(Ok(())) + } + Poll::Pending => { + let new_filled = buf.filled().len(); + return if new_filled != filled { + Poll::Ready(Ok(())) + } else { + Poll::Pending + }; + } + x => x, + }; + } + Poll::Ready(Ok(())) + } +} + +/// A structure that represents a part of `multipart/form-data` message. +/// +/// # Examples +/// +/// ``` +/// # use ylong_http::body::Part; +/// +/// let part = Part::new().name("name").body("xiaoming"); +/// ``` +pub struct Part { + name: Option, + file_name: Option, + mime: Option, + length: Option, + body: Option, +} + +impl Part { + /// Creates an empty `Part`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::Part; + /// + /// let part = Part::new(); + /// ``` + pub fn new() -> Self { + Self { + name: None, + file_name: None, + mime: None, + length: None, + body: None, + } + } + + /// Sets the name of this `Part`. + /// + /// The name message will be set to `Content-Disposition` header. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::Part; + /// + /// let part = Part::new().name("name"); + /// ``` + pub fn name(mut self, name: &str) -> Self { + self.name = Some(String::from(name)); + self + } + + /// Sets the file name of this `Part`. + /// + /// The file name message will be set to `Content-Disposition` header. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::Part; + /// + /// let part = Part::new().file_name("example.txt"); + /// ``` + pub fn file_name(mut self, file_name: &str) -> Self { + self.file_name = Some(String::from(file_name)); + self + } + + /// Sets the mime type of this `Part`. + /// + /// The mime type message will be set to `Content-Type` header. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::Part; + /// + /// let part = Part::new().mime("application/octet-stream"); + /// ``` + pub fn mime(mut self, mime: &str) -> Self { + self.mime = Some(String::from(mime)); + self + } + + /// Sets the length of body of this `Part`. + /// + /// The length message will be set to `Content-Length` header. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::Part; + /// + /// let part = Part::new().length(Some(8)).body("xiaoming"); + /// ``` + pub fn length(mut self, length: Option) -> Self { + self.length = length; + self + } + + /// Sets a slice body of this `Part`. + /// + /// The body message will be set to the body part. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::body::Part; + /// + /// let part = Part::new().mime("application/octet-stream"); + /// ``` + pub fn body>(mut self, body: T) -> Self { + let body = body.as_ref().to_vec(); + self.length = Some(body.len() as u64); + self.body = Some(MultiPartState::bytes(body)); + self + } + + /// Sets a stream body of this `Part`. + /// + /// The body message will be set to the body part. + pub fn stream(mut self, body: T) -> Self { + self.body = Some(MultiPartState::stream(Box::pin(body))); + self + } +} + +impl Default for Part { + fn default() -> Self { + Self::new() + } +} + +/// A basic trait for MultiPart. +pub trait MultiPartBase: AsyncRead { + /// Get reference of MultiPart. + fn multipart(&self) -> &MultiPart; +} + +impl MultiPartBase for MultiPart { + fn multipart(&self) -> &MultiPart { + self + } +} + +enum ReadStatus { + Never, + Reading(MultiPartStates), + Finish, +} + +struct MultiPartStates { + states: IntoIter, + curr: Option, +} + +impl MultiPartStates { + fn poll_read_curr( + &mut self, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + if let Some(mut state) = self.curr.take() { + return match state { + MultiPartState::Bytes(ref mut bytes) => { + let filled_len = buf.filled().len(); + let unfilled = buf.initialize_unfilled(); + let unfilled_len = unfilled.len(); + let new = std::io::Read::read(bytes, unfilled).unwrap(); + buf.set_filled(filled_len + new); + + if new >= unfilled_len { + self.curr = Some(state); + } + Poll::Ready(Ok(())) + } + MultiPartState::Stream(ref mut stream) => { + let old_len = buf.filled().len(); + match stream.as_mut().poll_read(cx, buf) { + Poll::Ready(Ok(())) => { + if old_len != buf.filled().len() { + self.curr = Some(state); + } + Poll::Ready(Ok(())) + } + Poll::Pending => { + self.curr = Some(state); + Poll::Pending + } + x => x, + } + } + }; + } + Poll::Ready(Ok(())) + } +} + +impl AsyncRead for MultiPartStates { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let this = self.get_mut(); + while !buf.initialize_unfilled().is_empty() { + if this.curr.is_none() { + this.curr = match this.states.next() { + None => break, + x => x, + } + } + + match this.poll_read_curr(cx, buf) { + Poll::Ready(Ok(())) => {} + x => return x, + } + } + Poll::Ready(Ok(())) + } +} + +enum MultiPartState { + Bytes(Cursor>), + Stream(Pin>), +} + +impl MultiPartState { + fn bytes(bytes: Vec) -> Self { + Self::Bytes(Cursor::new(bytes)) + } + + fn stream(reader: Pin>) -> Self { + Self::Stream(reader) + } +} + +#[cfg(test)] +impl PartialEq for MultiPartState { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Bytes(l0), Self::Bytes(r0)) => l0 == r0, + // Cant not compare Stream, Should not do this. + (Self::Stream(l0), Self::Stream(r0)) => core::ptr::eq(l0, r0), + _ => false, + } + } +} + +#[cfg(test)] +impl core::fmt::Debug for MultiPartState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Bytes(arg0) => f.debug_tuple("Bytes").field(arg0).finish(), + Self::Stream(arg0) => f.debug_tuple("Stream").field(&(arg0 as *const _)).finish(), + } + } +} + +fn gen_boundary() -> String { + format!( + "{:016x}-{:016x}-{:016x}-{:016x}", + xor_shift(), + xor_shift(), + xor_shift(), + xor_shift() + ) +} + +// XORShift* fast-random realization. +fn xor_shift() -> u64 { + use std::cell::Cell; + use std::collections::hash_map::RandomState; + use std::hash::{BuildHasher, Hasher}; + use std::num::Wrapping; + + thread_local! { + static RNG: Cell> = Cell::new(Wrapping(seed())); + } + + // The returned value of `seed()` must be nonzero. + fn seed() -> u64 { + let seed = RandomState::new(); + + let mut out; + let mut cnt = 1; + let mut hasher = seed.build_hasher(); + + loop { + hasher.write_usize(cnt); + out = hasher.finish(); + if out != 0 { + break; + } + cnt += 1; + hasher = seed.build_hasher(); + } + out + } + + RNG.with(|rng| { + let mut n = rng.get(); + n ^= n >> 12; + n ^= n << 25; + n ^= n >> 27; + rng.set(n); + n.0.wrapping_mul(0x2545_f491_4f6c_dd1d) + }) +} + +#[cfg(test)] +mod ut_mime { + use crate::body::mime::simple::{gen_boundary, MultiPartState, ReadStatus}; + use crate::body::{MultiPart, Part}; + + /// UT test cases for `gen_boundar`. + /// + /// # Brief + /// 1. Creates two boundarys and compares. + /// 3. Checks whether the result is correct. + #[test] + fn ut_gen_boundary() { + let s1 = gen_boundary(); + let s2 = gen_boundary(); + assert_ne!(s1, s2); + } + + /// UT test cases for `Part::new`. + /// + /// # Brief + /// 1. Creates a `Part` by `Part::new`. + /// 2. Checks members of `Part`. + /// 3. Checks whether the result is correct. + #[test] + fn ut_part_new() { + let part = Part::new(); + assert!(part.name.is_none()); + assert!(part.file_name.is_none()); + assert!(part.mime.is_none()); + assert!(part.length.is_none()); + assert!(part.body.is_none()); + } + + /// UT test cases for `Part::default`. + /// + /// # Brief + /// 1. Creates a `Part` by `Part::default`. + /// 2. Checks members of `Part`. + /// 3. Checks whether the result is correct. + #[test] + fn ut_part_default() { + let part = Part::default(); + assert!(part.name.is_none()); + assert!(part.file_name.is_none()); + assert!(part.mime.is_none()); + assert!(part.length.is_none()); + assert!(part.body.is_none()); + } + + /// UT test cases for `Part::name`, `Part::name`, `Part::file_name` and + /// `Part::body`. + /// + /// # Brief + /// 1. Creates a `Part` and sets values. + /// 2. Checks members of `Part`. + /// 3. Checks whether the result is correct. + #[test] + fn ut_part_set() { + let part = Part::new() + .name("name") + .file_name("example.txt") + .mime("application/octet-stream") + .body("1234"); + assert_eq!(part.name, Some("name".to_string())); + assert_eq!(part.file_name, Some("example.txt".to_string())); + assert_eq!(part.mime, Some("application/octet-stream".to_string())); + assert_eq!(part.body, Some(MultiPartState::bytes("1234".into()))); + assert_eq!(part.length, Some(4)); + + let part = part.stream("11223344".as_bytes()).length(Some(8)); + assert_eq!(part.length, Some(8)); + } + + /// UT test cases for `MultiPart::new`. + /// + /// # Brief + /// 1. Creates a `MultiPart` by `MultiPart::new`. + /// 2. Checks members of `MultiPart`. + /// 3. Checks whether the result is correct. + #[test] + fn ut_multipart_new() { + let mp = MultiPart::new(); + assert!(mp.parts.is_empty()); + assert!(!mp.boundary().is_empty()); + } + + /// UT test cases for `MultiPart::part` and `MultiPart::total_bytes`. + /// + /// # Brief + /// 1. Creates a `MultiPart` and sets values. + /// 2. Checks total bytes of `MultiPart`. + /// 3. Checks whether the result is correct. + #[test] + fn ut_multipart_set() { + let mp = MultiPart::default(); + // --boundary--/r/n + assert_eq!(mp.total_bytes(), Some(2 + mp.boundary().len() as u64 + 4)); + + let mp = mp.part( + Part::new() + .name("name") + .file_name("example.txt") + .mime("application/octet-stream") + .body("1234"), + ); + assert_eq!( + mp.total_bytes(), + Some( + (2 + mp.boundary().len() as u64 + 2) + + (30 + 9 + 4 + 13 + 11 + 2) // name, filename, \r\n + + (16 + 24 + 2 + 2) // mime, \r\n + + 4 // body + + (2 + mp.boundary().len() as u64 + 4) + ) + ); + } + + /// UT test cases for `MultiPart::poll_data`. + /// + /// # Brief + /// 1. Creates a `MultiPart` and sets values. + /// 2. Encodes `MultiPart` by `async_impl::Body::data`. + /// 3. Checks whether the result is correct. + #[cfg(feature = "ylong_base")] + #[test] + fn ut_multipart_poll_data() { + let handle = ylong_runtime::spawn(async move { + multipart_poll_data().await; + }); + ylong_runtime::block_on(handle).unwrap(); + } + + #[cfg(feature = "ylong_base")] + async fn multipart_poll_data() { + use std::pin::Pin; + + use ylong_runtime::futures::poll_fn; + use ylong_runtime::io::{AsyncRead, ReadBuf}; + + let mut mp = MultiPart::new().part( + Part::new() + .name("name") + .file_name("example.txt") + .mime("application/octet-stream") + .body("1234"), + ); + + let mut buf = vec![0u8; 50]; + let mut v_size = vec![]; + let mut v_str = vec![]; + + loop { + let mut read_buf = ReadBuf::new(&mut buf); + poll_fn(|cx| Pin::new(&mut mp).poll_read(cx, &mut read_buf)) + .await + .unwrap(); + + let len = read_buf.filled_len(); + if len == 0 { + break; + } + v_size.push(len); + v_str.extend_from_slice(&buf[..len]); + } + assert_eq!(v_size, vec![50, 50, 50, 50, 50, 11]); + } + + /// UT test cases for `MultiPart::poll_data`. + /// + /// # Brief + /// 1. Creates a `MultiPart` and sets values. + /// 2. Encodes `MultiPart` by `async_impl::Body::data`. + /// 3. Checks whether the result is correct. + #[cfg(feature = "ylong_base")] + #[test] + fn ut_multipart_poll_data_stream() { + let handle = ylong_runtime::spawn(async move { + multipart_poll_data_stream().await; + }); + ylong_runtime::block_on(handle).unwrap(); + } + + #[cfg(feature = "ylong_base")] + async fn multipart_poll_data_stream() { + use std::pin::Pin; + + use ylong_runtime::futures::poll_fn; + use ylong_runtime::io::{AsyncRead, ReadBuf}; + + let mut mp = MultiPart::new().part( + Part::new() + .name("name") + .file_name("example.txt") + .mime("application/octet-stream") + .stream("1234".as_bytes()) + .length(Some(4)), + ); + + let mut buf = vec![0u8; 50]; + let mut v_size = vec![]; + let mut v_str = vec![]; + + loop { + let mut read_buf = ReadBuf::new(&mut buf); + poll_fn(|cx| Pin::new(&mut mp).poll_read(cx, &mut read_buf)) + .await + .unwrap(); + + let len = read_buf.filled().len(); + if len == 0 { + break; + } + v_size.push(len); + v_str.extend_from_slice(&buf[..len]); + } + assert_eq!(v_size, vec![50, 50, 50, 50, 50, 11]); + } +} diff --git a/ylong_http/src/body/mod.rs b/ylong_http/src/body/mod.rs index 78729a3..b71d862 100644 --- a/ylong_http/src/body/mod.rs +++ b/ylong_http/src/body/mod.rs @@ -50,7 +50,7 @@ pub use chunk::{Chunk, ChunkBody, ChunkBodyDecoder, ChunkExt, ChunkState, Chunks pub use empty::EmptyBody; pub use mime::{ MimeMulti, MimeMultiBuilder, MimeMultiDecoder, MimeMultiEncoder, MimePart, MimePartBuilder, - MimePartEncoder, MimeType, MultiPart, Part, TokenStatus, XPart, + MimePartEncoder, MimeType, MultiPart, MultiPartBase, Part, TokenStatus, XPart, }; pub use text::{Text, TextBody, TextBodyDecoder}; @@ -339,7 +339,7 @@ pub mod async_impl { /// // read chunk body and return headers /// let res = chunk.trailer().await.unwrap().unwrap(); /// assert_eq!( - /// res.get("accept").unwrap().to_str().unwrap(), + /// res.get("accept").unwrap().to_string().unwrap(), /// "text/html".to_string() /// ); /// # } @@ -399,63 +399,6 @@ pub mod async_impl { Pin::new(&mut *fut.body).poll_data(cx, fut.buf) } } - - // TODO: Adapter, remove this later. - pub(crate) fn poll_read( - reader: Pin<&mut T>, - cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - let mut read_buf = ReadBuf::new(buf); - let filled = read_buf.filled().len(); - match reader.poll_read(cx, &mut read_buf) { - Poll::Ready(Ok(())) => { - let new_filled = read_buf.filled().len(); - Poll::Ready(Ok(new_filled - filled)) - } - Poll::Ready(Err(e)) => Poll::Ready(Err(e)), - Poll::Pending => Poll::Pending, - } - } - - // TODO: Adapter, remove this later. - impl Body for &'static [u8] { - type Error = std::io::Error; - - fn poll_data( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - poll_read(self, cx, buf) - } - } - - // TODO: Adapter, remove this later. - impl Body for &'static str { - type Error = std::io::Error; - - fn poll_data( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - poll_read(Pin::new(&mut self.as_bytes()), cx, buf) - } - } - - // TODO: Adapter, remove this later. - impl Body for String { - type Error = std::io::Error; - - fn poll_data( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - poll_read(Pin::new(&mut self.as_bytes()), cx, buf) - } - } } // Type definitions of the origin of the body data. diff --git a/ylong_http/src/body/text.rs b/ylong_http/src/body/text.rs index dc2e046..5d96dbe 100644 --- a/ylong_http/src/body/text.rs +++ b/ylong_http/src/body/text.rs @@ -181,24 +181,6 @@ impl TextBody> { } } -impl TextBody> { - /// Creates a `TextBody` from an asynchronous body. - /// - /// ```no_run - /// use ylong_http::body::TextBody; - /// - /// async fn text_body_from_async_body() { - /// let reader = "Hello World"; - /// let body = TextBody::from_async_body(reader.as_bytes()); - /// } - /// ``` - pub fn from_async_body(body: T) -> Self { - Self { - from: FromAsyncBody::new(body), - } - } -} - impl<'a> sync_impl::Body for TextBody> { type Error = Error; @@ -244,18 +226,6 @@ impl async_impl::Body for TextBody async_impl::Body for TextBody> { - type Error = T::Error; - - fn poll_data( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - Pin::new(&mut *self.from).poll_data(cx, buf) - } -} - /// A decoder for decoding plaintext body. /// /// You need to provide the decoder with a body length and some byte slices diff --git a/ylong_http/src/h1/request/encoder.rs b/ylong_http/src/h1/request/encoder.rs index 9efcf15..313b33f 100644 --- a/ylong_http/src/h1/request/encoder.rs +++ b/ylong_http/src/h1/request/encoder.rs @@ -512,7 +512,7 @@ impl EncodeHeader { inner: header_iter, status: Some(HeaderStatus::Name), name: header_name, - value: header_value.to_str().unwrap().into_bytes(), + value: header_value.to_string().unwrap().into_bytes(), name_idx: 0, colon_idx: 0, value_idx: 0, @@ -581,7 +581,7 @@ impl EncodeHeader { let (header_name, header_value) = iter; self.status = Some(HeaderStatus::Name); self.name = header_name; - self.value = header_value.to_str().unwrap().into_bytes(); + self.value = header_value.to_string().unwrap().into_bytes(); self.name_idx = 0; self.colon_idx = 0; self.value_idx = 0; diff --git a/ylong_http/src/h1/response/decoder.rs b/ylong_http/src/h1/response/decoder.rs index 881d5f5..98f5d82 100644 --- a/ylong_http/src/h1/response/decoder.rs +++ b/ylong_http/src/h1/response/decoder.rs @@ -52,7 +52,7 @@ use crate::version::Version; /// part.headers /// .get("content-length") /// .unwrap() -/// .to_str() +/// .to_string() /// .unwrap(), /// "4" /// ); @@ -651,7 +651,10 @@ mod ut_decoder { assert_eq!($res3, result.0.status.as_u16()); assert_eq!($res4.len(), result.0.headers.len()); for (key, value) in $res4 { - assert_eq!(value, result.0.headers.get(key).unwrap().to_str().unwrap()) + assert_eq!( + value, + result.0.headers.get(key).unwrap().to_string().unwrap() + ) } assert_eq!($res5, result.1); }}; @@ -667,7 +670,10 @@ mod ut_decoder { assert_eq!($res3, result.0.status.as_u16()); assert_eq!($res4.len(), result.0.headers.len()); for (key, value) in $res4 { - assert_eq!(value, result.0.headers.get(key).unwrap().to_str().unwrap()) + assert_eq!( + value, + result.0.headers.get(key).unwrap().to_string().unwrap() + ) } assert_eq!($res5, result.1); }}; diff --git a/ylong_http/src/h2/decoder.rs b/ylong_http/src/h2/decoder.rs index 64f1643..6c35595 100644 --- a/ylong_http/src/h2/decoder.rs +++ b/ylong_http/src/h2/decoder.rs @@ -909,7 +909,7 @@ mod ut_frame_decoder { "assert header length failed" ); for (key, value) in $payload.1.iter() { - assert_eq!(header.get(*key).unwrap().to_str().unwrap(), *value); + assert_eq!(header.get(*key).unwrap().to_string().unwrap(), *value); } for (key, value) in $payload.0.iter() { match *key { diff --git a/ylong_http/src/h2/hpack/decoder.rs b/ylong_http/src/h2/hpack/decoder.rs index 15d7f6e..700bac6 100644 --- a/ylong_http/src/h2/hpack/decoder.rs +++ b/ylong_http/src/h2/hpack/decoder.rs @@ -240,7 +240,7 @@ mod ut_hpack_decoder { macro_rules! check_map { ($map: expr, { $($(,)? $k: literal => $v: literal)* } $(,)?) => { $( - assert_eq!($map.get($k).unwrap().to_str().unwrap(), $v); + assert_eq!($map.get($k).unwrap().to_string().unwrap(), $v); )* } } diff --git a/ylong_http/src/h2/hpack/representation/encoder.rs b/ylong_http/src/h2/hpack/representation/encoder.rs index 66234ea..cdd9fd2 100644 --- a/ylong_http/src/h2/hpack/representation/encoder.rs +++ b/ylong_http/src/h2/hpack/representation/encoder.rs @@ -200,7 +200,7 @@ impl PartsIter { return self .map .next() - .map(|(h, v)| (Header::Other(h.to_string()), v.to_str().unwrap())) + .map(|(h, v)| (Header::Other(h.to_string()), v.to_string().unwrap())) } } } diff --git a/ylong_http/src/h3/qpack/decoder.rs b/ylong_http/src/h3/qpack/decoder.rs index c2ec906..aef9f48 100644 --- a/ylong_http/src/h3/qpack/decoder.rs +++ b/ylong_http/src/h3/qpack/decoder.rs @@ -655,7 +655,7 @@ mod ut_qpack_decoder { macro_rules! check_map { ($map: expr, { $($(,)? $k: literal => $v: literal)* } $(,)?) => { $( - assert_eq!($map.get($k).unwrap().to_str().unwrap(), $v); + assert_eq!($map.get($k).unwrap().to_string().unwrap(), $v); )* } } diff --git a/ylong_http/src/h3/qpack/format/encoder.rs b/ylong_http/src/h3/qpack/format/encoder.rs index 9965228..4261422 100644 --- a/ylong_http/src/h3/qpack/format/encoder.rs +++ b/ylong_http/src/h3/qpack/format/encoder.rs @@ -955,7 +955,7 @@ impl PartsIter { return self .map .next() - .map(|(h, v)| (Field::Other(h.to_string()), v.to_str().unwrap())); + .map(|(h, v)| (Field::Other(h.to_string()), v.to_string().unwrap())); } } } diff --git a/ylong_http/src/headers.rs b/ylong_http/src/headers.rs index a6fafb2..2cc7911 100644 --- a/ylong_http/src/headers.rs +++ b/ylong_http/src/headers.rs @@ -35,11 +35,11 @@ //! headers.insert("Content-Length", "3495").unwrap(); //! //! assert_eq!( -//! headers.get("accept").unwrap().to_str().unwrap(), +//! headers.get("accept").unwrap().to_string().unwrap(), //! "text/html" //! ); //! assert_eq!( -//! headers.get("content-length").unwrap().to_str().unwrap(), +//! headers.get("content-length").unwrap().to_string().unwrap(), //! "3495" //! ); //! ``` @@ -72,7 +72,7 @@ use crate::error::{ErrorKind, HttpError}; /// /// // All characters of this header string can be displayed, so the `to_string` /// // interface can be used to output. -/// assert_eq!(header.value().to_str().unwrap(), "Foo"); +/// assert_eq!(header.value().to_string().unwrap(), "Foo"); /// ``` #[derive(Clone, Debug, Eq, PartialEq)] pub struct Header { @@ -93,7 +93,7 @@ impl Header { /// /// let header = Header::from_raw_parts(name, value); /// assert_eq!(header.name().as_bytes(), b"example-field"); - /// assert_eq!(header.value().to_str().unwrap(), "Foo"); + /// assert_eq!(header.value().to_string().unwrap(), "Foo"); /// ``` pub fn from_raw_parts(name: HeaderName, value: HeaderValue) -> Self { Self { name, value } @@ -129,7 +129,7 @@ impl Header { /// let header = Header::try_from(("Example-Field", "Foo")).unwrap(); /// /// let value = header.value(); - /// assert_eq!(value.to_str().unwrap(), "Foo"); + /// assert_eq!(value.to_string().unwrap(), "Foo"); /// ``` pub fn value(&self) -> &HeaderValue { &self.value @@ -149,7 +149,7 @@ impl Header { /// let (name, value) = header.into_parts(); /// /// assert_eq!(name.as_bytes(), b"example-field"); - /// assert_eq!(value.to_str().unwrap(), "Foo"); + /// assert_eq!(value.to_string().unwrap(), "Foo"); /// ``` pub fn into_parts(self) -> (HeaderName, HeaderValue) { (self.name, self.value) @@ -285,7 +285,7 @@ impl TryFrom<&[u8]> for HeaderName { /// let mut value = HeaderValue::from_bytes(b"text/html").unwrap(); /// value.append_bytes(b"application/xml").unwrap(); /// -/// assert_eq!(value.to_str().unwrap(), "text/html, application/xml"); +/// assert_eq!(value.to_string().unwrap(), "text/html, application/xml"); /// assert!(!value.is_sensitive()); /// ``` #[derive(Clone, Debug, Eq, PartialEq)] @@ -306,12 +306,12 @@ impl HeaderValue { /// use ylong_http::headers::HeaderValue; /// /// let value = HeaderValue::from_bytes(b"text/html").unwrap(); - /// assert_eq!(value.to_str().unwrap(), "text/html"); + /// assert_eq!(value.to_string().unwrap(), "text/html"); /// assert!(!value.is_sensitive()); /// /// // `HeaderValue` is case-sensitive. Legal characters will remain unchanged. /// let value = HeaderValue::from_bytes(b"TEXT/HTML").unwrap(); - /// assert_eq!(value.to_str().unwrap(), "TEXT/HTML"); + /// assert_eq!(value.to_string().unwrap(), "TEXT/HTML"); /// ``` pub fn from_bytes(bytes: &[u8]) -> Result { if !bytes.iter().all(|b| Self::is_valid(*b)) { @@ -336,7 +336,7 @@ impl HeaderValue { /// let other = HeaderValue::from_bytes(b"text/plain").unwrap(); /// /// value.append(other); - /// assert_eq!(value.to_str().unwrap(), "text/html, text/plain"); + /// assert_eq!(value.to_string().unwrap(), "text/html, text/plain"); /// ``` pub fn append(&mut self, mut other: Self) { self.inner.append(&mut other.inner) @@ -352,7 +352,7 @@ impl HeaderValue { /// let mut value = HeaderValue::from_bytes(b"text/html").unwrap(); /// value.append_bytes(b"application/xml").unwrap(); /// - /// assert_eq!(value.to_str().unwrap(), "text/html, application/xml"); + /// assert_eq!(value.to_string().unwrap(), "text/html, application/xml"); /// ``` pub fn append_bytes(&mut self, bytes: &[u8]) -> Result<(), HttpError> { if !bytes.iter().all(|b| Self::is_valid(*b)) { @@ -376,10 +376,9 @@ impl HeaderValue { /// let mut value = HeaderValue::from_bytes(b"text/html").unwrap(); /// value.append_bytes(b"application/xml").unwrap(); /// - /// assert_eq!(value.to_str().unwrap(), "text/html, application/xml"); + /// assert_eq!(value.to_string().unwrap(), "text/html, application/xml"); /// ``` - // TODO: change this name to `to_string`? - pub fn to_str(&self) -> Result { + pub fn to_string(&self) -> Result { let mut content = Vec::new(); for (n, i) in self.inner.iter().enumerate() { if n != 0 { @@ -559,11 +558,11 @@ pub type HeaderValueIterMut<'a> = slice::IterMut<'a, Vec>; /// headers.append("Accept", "text/plain").unwrap(); /// /// assert_eq!( -/// headers.get("accept").unwrap().to_str().unwrap(), +/// headers.get("accept").unwrap().to_string().unwrap(), /// "text/html, text/plain" /// ); /// assert_eq!( -/// headers.get("content-length").unwrap().to_str().unwrap(), +/// headers.get("content-length").unwrap().to_string().unwrap(), /// "3495" /// ); /// ``` @@ -579,7 +578,7 @@ impl fmt::Display for Headers { f, "{}: {}", k.to_string(), - v.to_str() + v.to_string() .unwrap_or_else(|_| "".to_string()) )?; } @@ -654,7 +653,7 @@ impl Headers { /// headers.append("accept", "text/html").unwrap(); /// /// let value = headers.get("accept"); - /// assert_eq!(value.unwrap().to_str().unwrap(), "text/html"); + /// assert_eq!(value.unwrap().to_string().unwrap(), "text/html"); /// ``` pub fn get(&self, name: T) -> Option<&HeaderValue> where @@ -681,7 +680,7 @@ impl Headers { /// headers.append("accept", "text/html").unwrap(); /// /// let value = headers.get_mut("accept"); - /// assert_eq!(value.unwrap().to_str().unwrap(), "text/html"); + /// assert_eq!(value.unwrap().to_string().unwrap(), "text/html"); /// ``` pub fn get_mut(&mut self, name: T) -> Option<&mut HeaderValue> where @@ -714,7 +713,7 @@ impl Headers { /// assert_eq!(headers.insert("accept", "text/html"), Ok(None)); /// /// let old_value = headers.insert("accept", "text/plain").unwrap(); - /// assert_eq!(old_value.unwrap().to_str().unwrap(), "text/html"); + /// assert_eq!(old_value.unwrap().to_string().unwrap(), "text/html"); /// ``` pub fn insert(&mut self, name: N, value: V) -> Result, HttpError> where @@ -751,7 +750,7 @@ impl Headers { /// headers.append("accept", "text/plain").unwrap(); /// /// let value = headers.get("accept"); - /// assert_eq!(value.unwrap().to_str().unwrap(), "text/html, text/plain"); + /// assert_eq!(value.unwrap().to_string().unwrap(), "text/html, text/plain"); /// ``` pub fn append(&mut self, name: N, value: V) -> Result<(), HttpError> where @@ -790,7 +789,7 @@ impl Headers { /// headers.append("accept", "text/html").unwrap(); /// /// let value = headers.remove("accept"); - /// assert_eq!(value.unwrap().to_str().unwrap(), "text/html"); + /// assert_eq!(value.unwrap().to_string().unwrap(), "text/html"); /// ``` pub fn remove(&mut self, name: T) -> Option where @@ -1040,9 +1039,9 @@ mod ut_headers { let value = HeaderValue::from_bytes(b"Foo").unwrap(); let header = Header::from_raw_parts(name, value); assert_eq!(header.name().as_bytes(), b"john-doe"); - assert_eq!(header.value().to_str().unwrap(), "Foo"); + assert_eq!(header.value().to_string().unwrap(), "Foo"); assert_ne!(header.name().as_bytes(), b"John-Doe"); - assert_ne!(header.value().to_str().unwrap(), "foo"); + assert_ne!(header.value().to_string().unwrap(), "foo"); // name let name = header.name(); @@ -1052,16 +1051,16 @@ mod ut_headers { // value let value = header.value(); - assert_eq!(value.to_str().unwrap(), "Foo"); - assert_ne!(value.to_str().unwrap(), "foo"); - assert_ne!(value.to_str().unwrap(), "oof"); + assert_eq!(value.to_string().unwrap(), "Foo"); + assert_ne!(value.to_string().unwrap(), "foo"); + assert_ne!(value.to_string().unwrap(), "oof"); // into_parts let (name, value) = header.into_parts(); assert_eq!(name.as_bytes(), b"john-doe"); - assert_eq!(value.to_str().unwrap(), "Foo"); + assert_eq!(value.to_string().unwrap(), "Foo"); assert_ne!(name.as_bytes(), b"John-Doe"); - assert_ne!(value.to_str().unwrap(), "foo"); + assert_ne!(value.to_string().unwrap(), "foo"); } /// UT test cases for `HeaderValue::iter`. @@ -1166,7 +1165,7 @@ mod ut_headers { let result = "text/html, application/xhtml+xml, text/plain, text/css, application/xml".to_string(); - assert_eq!(value.to_str(), Ok(result)); + assert_eq!(value.to_string(), Ok(result)); } /// UT test cases for `HeaderValue::set_sensitive`. diff --git a/ylong_http/src/request/mod.rs b/ylong_http/src/request/mod.rs index b9458fc..0d9e97a 100644 --- a/ylong_http/src/request/mod.rs +++ b/ylong_http/src/request/mod.rs @@ -42,7 +42,12 @@ //! assert_eq!(request.uri().to_string(), "www.example.com"); //! assert_eq!(request.version(), &Version::HTTP1_1); //! assert_eq!( -//! request.headers().get("accept").unwrap().to_str().unwrap(), +//! request +//! .headers() +//! .get("accept") +//! .unwrap() +//! .to_string() +//! .unwrap(), //! "text/html, application/xml" //! ); //! ``` @@ -56,7 +61,7 @@ use method::Method; use uri::Uri; #[cfg(any(feature = "ylong_base", feature = "tokio_base"))] -use crate::body::MultiPart; +use crate::body::{MultiPart, MultiPartBase}; use crate::error::{ErrorKind, HttpError}; use crate::headers::{Header, HeaderName, HeaderValue, Headers}; use crate::version::Version; @@ -476,7 +481,12 @@ impl Clone for Request { /// assert_eq!(request.uri().to_string(), "www.example.com"); /// assert_eq!(request.version(), &Version::HTTP1_1); /// assert_eq!( -/// request.headers().get("accept").unwrap().to_str().unwrap(), +/// request +/// .headers() +/// .get("accept") +/// .unwrap() +/// .to_string() +/// .unwrap(), /// "text/html, application/xml" /// ); /// ``` @@ -543,7 +553,7 @@ impl RequestBuilder { self } - /// Sets the `Version` of the `Request`. Uses `Version::HTTP11` by default. + /// Sets the `Version` of the `Request`. Uses `Version::HTTP1_1` by default. /// /// # Examples /// @@ -629,49 +639,6 @@ impl RequestBuilder { body, }) } - - /// Creates a `Request` that uses this `RequestBuilder` configuration and - /// the provided `Multipart`. You can also provide a `Uploader` - /// as the body. - /// - /// # Error - /// - /// This method fails if some configurations are wrong. - /// - /// # Examples - /// - /// ``` - /// # use ylong_http::body::{MultiPart, Part}; - /// # use ylong_http::request::RequestBuilder; - /// - /// # fn create_request_with_multipart(multipart: MultiPart) { - /// let request = RequestBuilder::new().multipart(multipart).unwrap(); - /// # } - /// ``` - #[cfg(any(feature = "ylong_base", feature = "tokio_base"))] - pub fn multipart(self, body: T) -> Result, HttpError> - where - T: AsRef, - { - let value = format!("multipart/form-data; boundary={}", body.as_ref().boundary()); - - let mut part = self.part?; - let _ = part.headers.insert( - "Content-Type", - HeaderValue::try_from(value.as_str()) - .map_err(|_| HttpError::from(ErrorKind::InvalidInput))?, - ); - - if let Some(size) = body.as_ref().total_bytes() { - let _ = part.headers.insert( - "Content-Length", - HeaderValue::try_from(format!("{size}").as_str()) - .map_err(|_| HttpError::from(ErrorKind::InvalidInput))?, - ); - } - - Ok(Request { part, body }) - } } impl Default for RequestBuilder { diff --git a/ylong_http/src/request/uri.rs b/ylong_http/src/request/uri.rs index 0170619..b89e9a3 100644 --- a/ylong_http/src/request/uri.rs +++ b/ylong_http/src/request/uri.rs @@ -290,6 +290,29 @@ impl Uri { ) { (self.scheme, self.authority, self.path, self.query) } + + /// Creates an `Uri` from `Scheme`, `Authority`, `Path`, `Query`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http::request::uri::Uri; + /// + /// let uri = Uri::from_raw_parts(None, None, None, None); + /// ``` + pub fn from_raw_parts( + scheme: Option, + authority: Option, + path: Option, + query: Option, + ) -> Self { + Self { + scheme, + authority, + path, + query, + } + } } impl ToString for Uri { diff --git a/ylong_http/src/response/mod.rs b/ylong_http/src/response/mod.rs index 2d0d129..61a8f08 100644 --- a/ylong_http/src/response/mod.rs +++ b/ylong_http/src/response/mod.rs @@ -45,10 +45,9 @@ impl Response { &self.part.version } - /// Gets an immutable reference to the `StatusCode`. - // TODO: change this to `status_code`? - pub fn status(&self) -> &StatusCode { - &self.part.status + /// Gets the `StatusCode`. + pub fn status(&self) -> StatusCode { + self.part.status } /// Gets an immutable reference to the `Headers`. diff --git a/ylong_http/src/response/status.rs b/ylong_http/src/response/status.rs index d296d26..ea25434 100644 --- a/ylong_http/src/response/status.rs +++ b/ylong_http/src/response/status.rs @@ -36,7 +36,7 @@ use crate::error::{ErrorKind, HttpError}; /// /// let status = StatusCode::OK; /// ``` -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct StatusCode(u16); impl StatusCode { @@ -112,12 +112,6 @@ impl StatusCode { ] } - /// StatusCode as str. - // TODO: Adapter, remove it later. - pub fn as_str(&self) -> String { - format!("{}", self.0) - } - /// Determines whether the `StatusCode` is [`1xx (Informational)`]. /// /// The 1xx (Informational) class of status code indicates an interim diff --git a/ylong_http_client/examples/async_certs_adapter.rs b/ylong_http_client/examples/async_certs_adapter.rs index 67bf699..cc15708 100644 --- a/ylong_http_client/examples/async_certs_adapter.rs +++ b/ylong_http_client/examples/async_certs_adapter.rs @@ -16,8 +16,8 @@ use std::io::Read; use std::sync::Arc; -use ylong_http_client::async_impl::{Client, ClientBuilder, Downloader}; -use ylong_http_client::{CertVerifier, HttpClientError, Request, ServerCerts}; +use ylong_http_client::async_impl::{Body, Client, ClientBuilder, Downloader, Request}; +use ylong_http_client::{CertVerifier, HttpClientError, ServerCerts}; struct Verifier; @@ -60,9 +60,9 @@ fn main() -> Result<(), HttpClientError> { } async fn request(client: Arc) -> Result<(), HttpClientError> { - let request = Request::get("https://www.example.com") - .body("".as_bytes()) - .map_err(|e| HttpClientError::other(Some(e))) + let request = Request::builder() + .url("https://www.example.com") + .body(Body::empty()) .unwrap(); // Sends request and receives a `Response`. let response = client.request(request).await?; diff --git a/ylong_http_client/examples/async_http.rs b/ylong_http_client/examples/async_http.rs index d713535..c8b17e1 100644 --- a/ylong_http_client/examples/async_http.rs +++ b/ylong_http_client/examples/async_http.rs @@ -15,12 +15,12 @@ //! ylong_http_client crate. It demonstrates creating a client, making a //! request, and reading the response asynchronously. -use ylong_http_client::async_impl::{Client, Downloader}; -use ylong_http_client::{HttpClientError, Request}; +use ylong_http_client::async_impl::{Body, Client, Downloader, Request}; +use ylong_http_client::HttpClientError; fn main() -> Result<(), HttpClientError> { let handle = ylong_runtime::spawn(async move { - let _ = client_send().await.unwrap(); + client_send().await.unwrap(); }); let _ = ylong_runtime::block_on(handle); @@ -32,9 +32,9 @@ async fn client_send() -> Result<(), HttpClientError> { let client = Client::new(); // Creates a `Request`. - let request = Request::get("127.0.0.1:3000") - .body("".as_bytes()) - .map_err(|e| HttpClientError::other(Some(e)))?; + let request = Request::builder() + .url("127.0.0.1:3000") + .body(Body::empty())?; // Sends request and receives a `Response`. let response = client.request(request).await?; diff --git a/ylong_http_client/examples/async_http_multi.rs b/ylong_http_client/examples/async_http_multi.rs index 2df265e..1b7f23a 100644 --- a/ylong_http_client/examples/async_http_multi.rs +++ b/ylong_http_client/examples/async_http_multi.rs @@ -15,17 +15,17 @@ //! ylong_http_client crate. It demonstrates creating a client, making a //! request, and reading the response asynchronously. -use ylong_http_client::async_impl::{Client, Downloader}; -use ylong_http_client::{HttpClientError, Request}; +use ylong_http_client::async_impl::{Body, Client, Downloader, Request}; +use ylong_http_client::HttpClientError; fn main() -> Result<(), HttpClientError> { let mut handles = Vec::new(); for _ in 0..4 { handles.push(ylong_runtime::spawn(async move { let client = Client::new(); - let request = Request::get("127.0.0.1:3000") - .body("".as_bytes()) - .map_err(|e| HttpClientError::other(Some(e))) + let request = Request::builder() + .url("127.0.0.1:3000") + .body(Body::empty()) .unwrap(); // Sends request and receives a `Response`. let response = client.request(request).await.unwrap(); diff --git a/ylong_http_client/examples/async_https_outside.rs b/ylong_http_client/examples/async_https_outside.rs index 16a6f16..1b953c9 100644 --- a/ylong_http_client/examples/async_https_outside.rs +++ b/ylong_http_client/examples/async_https_outside.rs @@ -13,13 +13,9 @@ //! This is a simple asynchronous HTTPS client example. -#[cfg(feature = "tokio_base")] -use ylong_http_client::async_impl::{Client, Downloader}; -#[cfg(feature = "tokio_base")] -use ylong_http_client::util::Redirect; -#[cfg(feature = "tokio_base")] -use ylong_http_client::{Certificate, HttpClientError, Request, TlsVersion}; -#[cfg(feature = "tokio_base")] +use ylong_http_client::async_impl::{Body, Client, Downloader, Request}; +use ylong_http_client::{Certificate, HttpClientError, Redirect, TlsVersion}; + fn main() { let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() @@ -53,9 +49,9 @@ async fn req() -> Result<(), HttpClientError> { .build()?; // Creates a `Request`. - let request = Request::get("https://www.baidu.com") - .body("".as_bytes()) - .map_err(|e| HttpClientError::other(Some(e)))?; + let request = Request::builder() + .url("https://www.baidu.com") + .body(Body::empty())?; // Sends request and receives a `Response`. let response = client.request(request).await?; diff --git a/ylong_http_client/examples/async_proxy_http.rs b/ylong_http_client/examples/async_proxy_http.rs index 8ae11d1..e592c18 100644 --- a/ylong_http_client/examples/async_proxy_http.rs +++ b/ylong_http_client/examples/async_proxy_http.rs @@ -14,12 +14,10 @@ //! This is a simple asynchronous HTTP client example using the //! ylong_http_client crate. It demonstrates creating a client, making a //! request, and reading the response asynchronously. -#[cfg(feature = "tokio_base")] -use ylong_http_client::async_impl::{ClientBuilder, Downloader}; -#[cfg(feature = "tokio_base")] -use ylong_http_client::{EmptyBody, HttpClientError, Proxy, Request}; -#[cfg(feature = "tokio_base")] +use ylong_http_client::async_impl::{Body, ClientBuilder, Downloader, Request}; +use ylong_http_client::{HttpClientError, Proxy}; + #[tokio::main] async fn main() -> Result<(), HttpClientError> { // Creates a `async_impl::Client` @@ -27,9 +25,10 @@ async fn main() -> Result<(), HttpClientError> { .proxy(Proxy::all("http://proxy.example.com").build()?) .build()?; // Creates a `Request`. - let request = Request::get("http://127.0.0.1:3000") - .body(EmptyBody) - .map_err(|e| HttpClientError::other(Some(e)))?; + let request = Request::builder() + .url("http://127.0.0.1:3000") + .body(Body::empty())?; + // Sends request and receives a `Response`. let response = client.request(request).await?; // Reads the body of `Response` by using `BodyReader`. diff --git a/ylong_http_client/examples/async_redirect_http.rs b/ylong_http_client/examples/async_redirect_http.rs index 9d6ba91..5bb1aa8 100644 --- a/ylong_http_client/examples/async_redirect_http.rs +++ b/ylong_http_client/examples/async_redirect_http.rs @@ -14,19 +14,18 @@ //! This is a simple asynchronous HTTP client redirect example using the //! ylong_http_client crate. It demonstrates creating a client, making a //! request, and reading the response asynchronously. -#[cfg(feature = "tokio_base")] -use ylong_http_client::async_impl::{ClientBuilder, Downloader}; -#[cfg(feature = "tokio_base")] -use ylong_http_client::{HttpClientError, Redirect, Request}; -#[cfg(feature = "tokio_base")] + +use ylong_http_client::async_impl::{Body, ClientBuilder, Downloader, Request}; +use ylong_http_client::{HttpClientError, Redirect}; + #[tokio::main] async fn main() -> Result<(), HttpClientError> { // Creates a `async_impl::Client` let client = ClientBuilder::new().redirect(Redirect::default()).build()?; // Creates a `Request`. - let request = Request::get("127.0.0.1:3000") - .body("".as_bytes()) - .map_err(|e| HttpClientError::other(Some(e)))?; + let request = Request::builder() + .url("127.0.0.1:3000") + .body(Body::empty())?; // Sends request and receives a `Response`. let response = client.request(request).await?; diff --git a/ylong_http_client/examples/sync_http.rs b/ylong_http_client/examples/sync_http.rs index 2a5289a..17649e9 100644 --- a/ylong_http_client/examples/sync_http.rs +++ b/ylong_http_client/examples/sync_http.rs @@ -25,7 +25,7 @@ fn main() -> Result<(), HttpClientError> { // Creates a `Request`. let request = Request::get("127.0.0.1:3000") .body("".as_bytes()) - .map_err(|e| HttpClientError::other(Some(e)))?; + .map_err(HttpClientError::other)?; // Sends request and receives a `Response`. let mut response = client.request(request)?; diff --git a/ylong_http_client/examples/sync_https_outside.rs b/ylong_http_client/examples/sync_https_outside.rs index 5378135..eba4d9f 100644 --- a/ylong_http_client/examples/sync_https_outside.rs +++ b/ylong_http_client/examples/sync_https_outside.rs @@ -14,8 +14,7 @@ //! This is a simple synchronous HTTPS client example. use ylong_http_client::sync_impl::Client; -use ylong_http_client::util::Redirect; -use ylong_http_client::{Certificate, HttpClientError, Request, TlsVersion}; +use ylong_http_client::{Certificate, HttpClientError, Redirect, Request, TlsVersion}; fn main() { let mut v = vec![]; @@ -46,7 +45,7 @@ fn req() -> Result<(), HttpClientError> { // Creates a `Request`. let request = Request::get("https://www.baidu.com") .body("".as_bytes()) - .map_err(|e| HttpClientError::other(Some(e)))?; + .map_err(HttpClientError::other)?; // Sends request and receives a `Response`. let response = client.request(request)?; diff --git a/ylong_http_client/examples/sync_proxy_http.rs b/ylong_http_client/examples/sync_proxy_http.rs index fecb7ae..ec183e6 100644 --- a/ylong_http_client/examples/sync_proxy_http.rs +++ b/ylong_http_client/examples/sync_proxy_http.rs @@ -15,8 +15,7 @@ //! crate. It demonstrates creating a client, making a request, and reading the //! response. use ylong_http_client::sync_impl::{BodyReader, ClientBuilder}; -use ylong_http_client::util::Proxy; -use ylong_http_client::{EmptyBody, HttpClientError, Request}; +use ylong_http_client::{EmptyBody, HttpClientError, Proxy, Request}; fn main() -> Result<(), HttpClientError> { // Creates a `sync_impl::Client` @@ -27,7 +26,7 @@ fn main() -> Result<(), HttpClientError> { let request = Request::get("http://127.0.0.1:3000") .body(EmptyBody) - .map_err(|e| HttpClientError::other(Some(e)))?; + .map_err(HttpClientError::other)?; // Sends request and receives a `Response`. let mut response = client.request(request)?; diff --git a/ylong_http_client/examples/sync_redirect_http.rs b/ylong_http_client/examples/sync_redirect_http.rs index 6c7c431..c3069a5 100644 --- a/ylong_http_client/examples/sync_redirect_http.rs +++ b/ylong_http_client/examples/sync_redirect_http.rs @@ -16,8 +16,7 @@ //! request, and reading the response. use ylong_http_client::sync_impl::{BodyReader, ClientBuilder}; -use ylong_http_client::util::Redirect; -use ylong_http_client::{HttpClientError, Request}; +use ylong_http_client::{HttpClientError, Redirect, Request}; fn main() -> Result<(), HttpClientError> { // Creates a `sync_impl::Client` @@ -26,7 +25,7 @@ fn main() -> Result<(), HttpClientError> { // Creates a `Request`. let request = Request::get("127.0.0.1:3000") .body("".as_bytes()) - .map_err(|e| HttpClientError::other(Some(e)))?; + .map_err(HttpClientError::other)?; // Sends request and receives a `Response`. let mut response = client.request(request)?; diff --git a/ylong_http_client/src/async_impl/adapter.rs b/ylong_http_client/src/async_impl/adapter.rs deleted file mode 100644 index 9df2376..0000000 --- a/ylong_http_client/src/async_impl/adapter.rs +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) 2023 Huawei Device Co., Ltd. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::convert::TryFrom; -use std::ops::{Deref, DerefMut}; - -use ylong_http::body::async_impl::Body; -use ylong_http::body::MultiPart; -use ylong_http::error::HttpError; -use ylong_http::headers::{HeaderName, HeaderValue}; -use ylong_http::request::method::Method; -use ylong_http::request::uri::Uri; -use ylong_http::request::{Request, RequestBuilder as ReqBuilder}; -use ylong_http::response::Response as Resp; -use ylong_http::version::Version; - -use crate::async_impl::HttpBody; -use crate::{ErrorKind, HttpClientError}; - -/// Response Adapter. -pub struct Response { - response: Resp, -} - -impl Response { - pub(crate) fn new(response: Resp) -> Self { - Self { response } - } - - /// `text()` adapter. - pub async fn text(self) -> Result { - let mut buf = [0u8; 1024]; - let mut vec = Vec::new(); - let mut response = self.response; - loop { - let size = response.body_mut().data(&mut buf).await?; - if size == 0 { - break; - } - vec.extend_from_slice(&buf[..size]); - } - String::from_utf8(vec).map_err(|_| { - HttpClientError::new_with_message( - ErrorKind::BodyDecode, - "The body content is not valid utf8.", - ) - }) - } -} - -impl Deref for Response { - type Target = Resp; - - fn deref(&self) -> &Self::Target { - &self.response - } -} - -impl DerefMut for Response { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.response - } -} - -/// RequestBuilder Adapter -pub struct RequestBuilder(ReqBuilder); - -impl RequestBuilder { - /// Creates a new, default `RequestBuilder`. - pub fn new() -> Self { - Self(ReqBuilder::new()) - } - - /// Sets the `Method` of the `Request`. - pub fn method(self, method: T) -> Self - where - Method: TryFrom, - >::Error: Into, - { - Self(self.0.method(method)) - } - - /// Sets the `Uri` of the `Request`. `Uri` does not provide a default value, - /// so it must be set. - pub fn url(self, uri: T) -> Self - where - Uri: TryFrom, - >::Error: Into, - { - Self(self.0.url(uri)) - } - - /// Sets the `Version` of the `Request`. Uses `Version::HTTP11` by default. - pub fn version(mut self, version: T) -> Self - where - Version: TryFrom, - >::Error: Into, - { - self.0 = self.0.version(version); - self - } - - /// Adds a `Header` to `Request`. Overwrites `HeaderValue` if the - /// `HeaderName` already exists. - /// - /// # Examples - /// - /// ``` - /// use ylong_http::headers::Headers; - /// use ylong_http::request::RequestBuilder; - /// - /// let request = RequestBuilder::new().header("ACCEPT", "text/html"); - /// ``` - pub fn header(mut self, name: N, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - HeaderValue: TryFrom, - >::Error: Into, - { - self.0 = self.0.header(name, value); - self - } - - /// Adds a `Header` to `Request`. Appends `HeaderValue` to the end of - /// previous `HeaderValue` if the `HeaderName` already exists. - /// - /// # Examples - /// - /// ``` - /// use ylong_http::headers::Headers; - /// use ylong_http::request::RequestBuilder; - /// - /// let request = RequestBuilder::new().append_header("ACCEPT", "text/html"); - /// ``` - pub fn append_header(mut self, name: N, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - HeaderValue: TryFrom, - >::Error: Into, - { - self.0 = self.0.append_header(name, value); - self - } - - /// Try to create a `Request` based on the incoming `body`. - pub fn body(self, body: T) -> Result, HttpClientError> { - self.0 - .body(body) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Build, Some(e))) - } - - /// Creates a `Request` that uses this `RequestBuilder` configuration and - /// the provided `Multipart`. You can also provide a `Uploader` - /// as the body. - /// - /// # Error - /// - /// This method fails if some configurations are wrong. - pub fn multipart(self, body: T) -> Result, HttpClientError> - where - T: AsRef, - { - self.0 - .multipart(body) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Build, Some(e))) - } -} - -impl Default for RequestBuilder { - fn default() -> Self { - Self::new() - } -} diff --git a/ylong_http_client/src/async_impl/client.rs b/ylong_http_client/src/async_impl/client.rs index 3df99e1..5e363a1 100644 --- a/ylong_http_client/src/async_impl/client.rs +++ b/ylong_http_client/src/async_impl/client.rs @@ -11,57 +11,60 @@ // See the License for the specific language governing permissions and // limitations under the License. -use ylong_http::body::{ChunkBody, TextBody}; -use ylong_http::request::method::Method; -use ylong_http::response::Response; -use ylong_http::version::Version; - -use super::{conn, Body, ConnPool, Connector, HttpBody, HttpConnector}; -use crate::async_impl::timeout::TimeoutFuture; -use crate::util::normalizer::{format_host_value, RequestFormatter, UriFormatter}; +use ylong_http::request::uri::Uri; + +use super::pool::ConnPool; +use super::timeout::TimeoutFuture; +use super::{conn, Body, Connector, HttpConnector, Request, Response}; +use crate::error::HttpClientError; +use crate::runtime::timeout; +use crate::util::config::{ + ClientConfig, ConnectorConfig, HttpConfig, HttpVersion, Proxy, Redirect, Timeout, +}; +use crate::util::dispatcher::Conn; +use crate::util::normalizer::RequestFormatter; use crate::util::proxy::Proxies; -use crate::util::redirect::TriggerKind; -use crate::util::{ClientConfig, ConnectorConfig, HttpConfig, HttpVersion, Redirect}; +use crate::util::redirect::{RedirectInfo, Trigger}; #[cfg(feature = "__tls")] use crate::CertVerifier; -#[cfg(feature = "http2")] -use crate::H2Config; -use crate::{sleep, timeout, ErrorKind, HttpClientError, Proxy, Request, Timeout, Uri}; /// HTTP asynchronous client implementation. Users can use `async_impl::Client` -/// to send `Request` asynchronously. `async_impl::Client` depends on a -/// [`async_impl::Connector`] that can be customized by the user. +/// to send `Request` asynchronously. +/// +/// `async_impl::Client` depends on a [`async_impl::Connector`] that can be +/// customized by the user. /// /// [`async_impl::Connector`]: Connector /// /// # Examples /// -/// ``` -/// use ylong_http_client::async_impl::Client; -/// use ylong_http_client::{EmptyBody, Request}; +/// ```no_run +/// use ylong_http_client::async_impl::{Body, Client, Request}; +/// use ylong_http_client::HttpClientError; /// -/// async fn async_client() { +/// async fn async_client() -> Result<(), HttpClientError> { /// // Creates a new `Client`. /// let client = Client::new(); /// /// // Creates a new `Request`. -/// let request = Request::new(EmptyBody); +/// let request = Request::builder().body(Body::empty())?; /// /// // Sends `Request` and wait for the `Response` to return asynchronously. -/// let response = client.request(request).await.unwrap(); +/// let response = client.request(request).await?; /// /// // Gets the content of `Response`. /// let status = response.status(); +/// +/// Ok(()) /// } /// ``` pub struct Client { inner: ConnPool, - client_config: ClientConfig, - http_config: HttpConfig, + config: ClientConfig, } impl Client { - /// Creates a new, default `AsyncClient`, which uses + /// Creates a new, default `Client`, which uses /// [`async_impl::HttpConnector`]. /// /// [`async_impl::HttpConnector`]: HttpConnector @@ -94,13 +97,19 @@ impl Client { } impl Client { - /// Creates a new, default `AsyncClient` with a given connector. + /// Creates a new, default `Client` with a given connector. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::{Client, HttpConnector}; + /// + /// let client = Client::with_connector(HttpConnector::default()); + /// ``` pub fn with_connector(connector: C) -> Self { - let http_config = HttpConfig::default(); Self { - inner: ConnPool::new(http_config.clone(), connector), - client_config: ClientConfig::default(), - http_config, + inner: ConnPool::new(HttpConfig::default(), connector), + config: ClientConfig::default(), } } @@ -109,179 +118,93 @@ impl Client { /// # Examples /// /// ``` - /// use ylong_http_client::async_impl::Client; - /// use ylong_http_client::{EmptyBody, Request}; + /// use ylong_http_client::async_impl::{Body, Client, Request}; + /// use ylong_http_client::HttpClientError; /// - /// async fn async_client() { + /// async fn async_client() -> Result<(), HttpClientError> { /// let client = Client::new(); - /// let response = client.request(Request::new(EmptyBody)).await; + /// let response = client + /// .request(Request::builder().body(Body::empty())?) + /// .await?; + /// Ok(()) /// } /// ``` - // TODO: change result to `Response` later. - pub async fn request( - &self, - request: Request, - ) -> Result { - let (part, body) = request.into_parts(); - - let content_length = part - .headers - .get("Content-Length") - .and_then(|v| v.to_str().ok()) - .and_then(|v| v.parse::().ok()) - .is_some(); - - let transfer_encoding = part - .headers - .get("Transfer-Encoding") - .and_then(|v| v.to_str().ok()) - .map(|v| v.contains("chunked")) - .unwrap_or(false); - - let response = match (content_length, transfer_encoding) { - (_, true) => { - let request = Request::from_raw_parts(part, ChunkBody::from_async_body(body)); - self.retry_send_request(request).await - } - (true, false) => { - let request = Request::from_raw_parts(part, TextBody::from_async_body(body)); - self.retry_send_request(request).await - } - (false, false) => { - let request = Request::from_raw_parts(part, body); - self.retry_send_request(request).await - } - }; - response.map(super::Response::new) - } - - async fn retry_send_request( - &self, - mut request: Request, - ) -> Result, HttpClientError> { - let mut retries = self.client_config.retry.times().unwrap_or(0); + pub async fn request(&self, request: Request) -> Result { + let mut request = request; + let mut retries = self.config.retry.times().unwrap_or(0); loop { - let response = self.send_request_retryable(&mut request).await; - if response.is_ok() || retries == 0 { + let response = self.send_request(&mut request).await; + // Only bodies which are reusable can be retried. + if response.is_ok() || retries == 0 || !request.body_mut().reuse() { return response; } retries -= 1; } } +} - async fn send_request_retryable( - &self, - request: &mut Request, - ) -> Result, HttpClientError> { - let response = self - .send_request_with_uri(request.uri().clone(), request) - .await?; - self.redirect_request(response, request).await +impl Client { + async fn send_request(&self, request: &mut Request) -> Result { + let response = self.send_unformatted_request(request).await?; + self.redirect(response, request).await } - async fn redirect_request( + async fn send_unformatted_request( &self, - mut response: Response, - request: &mut Request, - ) -> Result, HttpClientError> { - let mut redirected_list = vec![]; - let mut dst_uri = Uri::default(); - loop { - if Redirect::is_redirect(response.status().clone(), request) { - redirected_list.push(request.uri().clone()); - let trigger = Redirect::get_redirect( - &mut dst_uri, - &self.client_config.redirect, - &redirected_list, - &response, - request, - )?; - - UriFormatter::new().format(&mut dst_uri)?; - let _ = request - .headers_mut() - .insert("Host", format_host_value(&dst_uri)?.as_bytes()); - match trigger { - TriggerKind::NextLink => { - response = self.send_request_with_uri(dst_uri.clone(), request).await?; - continue; - } - TriggerKind::Stop => { - return Ok(response); - } - } - } else { - return Ok(response); + request: &mut Request, + ) -> Result { + RequestFormatter::new(&mut *request).format()?; + let conn = self.connect_to(request.uri()).await?; + self.send_request_on_conn(conn, request).await + } + + async fn connect_to(&self, uri: &Uri) -> Result, HttpClientError> { + if let Some(dur) = self.config.connect_timeout.inner() { + match timeout(dur, self.inner.connect_to(uri)).await { + Err(elapsed) => err_from_other!(Timeout, elapsed), + Ok(Ok(conn)) => Ok(conn), + Ok(Err(e)) => Err(e), } + } else { + self.inner.connect_to(uri).await } } - async fn send_request_with_uri( + async fn send_request_on_conn( &self, - mut uri: Uri, - request: &mut Request, - ) -> Result, HttpClientError> { - UriFormatter::new().format(&mut uri)?; - RequestFormatter::new(request).normalize()?; - - match self.http_config.version { - #[cfg(feature = "http2")] - HttpVersion::Http2PriorKnowledge => self.http2_request(uri, request).await, - HttpVersion::Http1 => { - if Version::HTTP1_0 == *request.version() && Method::CONNECT == *request.method() { - return Err(HttpClientError::new_with_message( - ErrorKind::Request, - "Unknown METHOD in HTTP/1.0", - )); - } - let conn = if let Some(dur) = self.client_config.connect_timeout.inner() { - match timeout(dur, self.inner.connect_to(uri)).await { - Err(_elapsed) => { - return Err(HttpClientError::new_with_message( - ErrorKind::Timeout, - "Connect timeout", - )) - } - Ok(Ok(conn)) => conn, - Ok(Err(e)) => return Err(e), - } - } else { - self.inner.connect_to(uri).await? - }; - - let mut retryable = Retryable::default(); - if let Some(timeout) = self.client_config.request_timeout.inner() { - TimeoutFuture { - timeout: Some(Box::pin(sleep(timeout))), - future: Box::pin(conn::request(conn, request, &mut retryable)), - } - .await - } else { - conn::request(conn, request, &mut retryable).await - } - } + conn: Conn, + request: &mut Request, + ) -> Result { + if let Some(timeout) = self.config.request_timeout.inner() { + TimeoutFuture::new(conn::request(conn, request), timeout).await + } else { + conn::request(conn, request).await } } - #[cfg(feature = "http2")] - async fn http2_request( + async fn redirect( &self, - uri: Uri, - request: &mut Request, - ) -> Result, HttpClientError> { - let mut retryable = Retryable::default(); - - const RETRY: usize = 1; - let mut times = 0; + response: Response, + request: &mut Request, + ) -> Result { + let mut response = response; + let mut info = RedirectInfo::new(); loop { - retryable.set_retry(false); - let conn = self.inner.connect_to(uri.clone()).await?; - let response = conn::request(conn, request, &mut retryable).await; - if retryable.retry() && times < RETRY { - times += 1; - continue; + match self + .config + .redirect + .inner() + .redirect(request, &response, &mut info)? + { + Trigger::NextLink => { + // Here the body should be reused. + if !request.body_mut().reuse() { + *request.body_mut() = Body::empty(); + } + response = self.send_unformatted_request(request).await?; + } + Trigger::Stop => return Ok(response), } - return response; } } } @@ -292,23 +215,6 @@ impl Default for Client { } } -#[derive(Default)] -pub(crate) struct Retryable { - #[cfg(feature = "http2")] - retry: bool, -} - -#[cfg(feature = "http2")] -impl Retryable { - pub(crate) fn set_retry(&mut self, retryable: bool) { - self.retry = retryable - } - - pub(crate) fn retry(&self) -> bool { - self.retry - } -} - /// A builder which is used to construct `async_impl::Client`. /// /// # Examples @@ -363,42 +269,12 @@ impl ClientBuilder { /// /// let builder = ClientBuilder::new().http1_only(); /// ``` + #[cfg(feature = "http1_1")] pub fn http1_only(mut self) -> Self { self.http.version = HttpVersion::Http1; self } - /// Only use HTTP/2. - /// - /// # Examples - /// - /// ``` - /// use ylong_http_client::async_impl::ClientBuilder; - /// - /// let builder = ClientBuilder::new().http2_prior_knowledge(); - /// ``` - #[cfg(feature = "http2")] - pub fn http2_prior_knowledge(mut self) -> Self { - self.http.version = HttpVersion::Http2PriorKnowledge; - self - } - - /// HTTP/2 settings. - /// - /// # Examples - /// - /// ``` - /// use ylong_http_client::async_impl::ClientBuilder; - /// use ylong_http_client::H2Config; - /// - /// let builder = ClientBuilder::new().http2_settings(H2Config::default()); - /// ``` - #[cfg(feature = "http2")] - pub fn http2_settings(mut self, config: H2Config) -> Self { - self.http.http2_config = config; - self - } - /// Enables a request timeout. /// /// The timeout is applied from when the request starts connection util the @@ -488,13 +364,71 @@ impl ClientBuilder { let connector = HttpConnector::new(config); Ok(Client { - inner: ConnPool::new(self.http.clone(), connector), - client_config: self.client, - http_config: self.http, + inner: ConnPool::new(self.http, connector), + config: self.client, }) } } +#[cfg(feature = "http2")] +impl ClientBuilder { + /// Only use HTTP/2. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::ClientBuilder; + /// + /// let builder = ClientBuilder::new().http2_prior_knowledge(); + /// ``` + pub fn http2_prior_knowledge(mut self) -> Self { + self.http.version = HttpVersion::Http2PriorKnowledge; + self + } + + /// Sets the `SETTINGS_MAX_FRAME_SIZE`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::ClientBuilder; + /// + /// let config = ClientBuilder::new().set_http2_max_frame_size(2 << 13); + /// ``` + pub fn set_http2_max_frame_size(mut self, size: u32) -> Self { + self.http.http2_config.max_frame_size = size; + self + } + + /// Sets the `SETTINGS_MAX_HEADER_LIST_SIZE`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::ClientBuilder; + /// + /// let config = ClientBuilder::new().set_http2_max_header_list_size(16 << 20); + /// ``` + pub fn set_http2_max_header_list_size(mut self, size: u32) -> Self { + self.http.http2_config.max_header_list_size = size; + self + } + + /// Sets the `SETTINGS_HEADER_TABLE_SIZE`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::ClientBuilder; + /// + /// let config = ClientBuilder::new().set_http2_max_header_list_size(4096); + /// ``` + pub fn set_http2_header_table_size(mut self, size: u32) -> Self { + self.http.http2_config.header_table_size = size; + self + } +} + #[cfg(feature = "__tls")] impl ClientBuilder { /// Sets the maximum allowed TLS version for connections. @@ -740,7 +674,11 @@ impl Default for ClientBuilder { #[cfg(test)] mod ut_async_impl_client { use crate::async_impl::Client; - use crate::{CertVerifier, Proxy, ServerCerts}; + #[cfg(all(feature = "__tls", feature = "ylong_base"))] + use crate::async_impl::{Body, Request, Response}; + use crate::Proxy; + #[cfg(all(feature = "__tls", feature = "ylong_base"))] + use crate::{CertVerifier, ServerCerts}; /// UT test cases for `Client::builder`. /// @@ -749,6 +687,7 @@ mod ut_async_impl_client { /// 2. Calls `http_config`, `client_config`, `build` on the builder /// respectively. /// 3. Checks if the result is as expected. + #[cfg(feature = "http1_1")] #[test] fn ut_client_builder() { let builder = Client::builder().http1_only().build(); @@ -797,9 +736,7 @@ mod ut_async_impl_client { #[cfg(all(feature = "__tls", feature = "ylong_base"))] async fn client_request_redirect() { use ylong_http::h1::ResponseDecoder; - use ylong_http::request::uri::Uri; - use ylong_http::request::Request; - use ylong_http::response::Response; + use ylong_http::response::Response as HttpResponse; use crate::async_impl::{ClientBuilder, HttpBody}; use crate::util::normalizer::BodyLength; @@ -813,17 +750,19 @@ mod ut_async_impl_client { let content_bytes = ""; let until_close = HttpBody::new(BodyLength::UntilClose, box_stream, content_bytes.as_bytes()).unwrap(); - let response = Response::from_raw_parts(result.0, until_close); - let mut request = Request::new("this is a body"); - let request_uri = request.uri_mut(); - *request_uri = Uri::from_bytes(b"http://example1.com:80/foo?a=1").unwrap(); + let response = HttpResponse::from_raw_parts(result.0, until_close); + let response = Response::new(response); + let mut request = Request::builder() + .url("http://example1.com:80/foo?a=1") + .body(Body::slice("this is a body")) + .unwrap(); let client = ClientBuilder::default() .redirect(Redirect::limited(2)) .connect_timeout(Timeout::from_secs(2)) .build() .unwrap(); - let res = client.redirect_request(response, &mut request).await; + let res = client.redirect(response, &mut request).await; assert!(res.is_ok()) } @@ -844,10 +783,11 @@ mod ut_async_impl_client { #[cfg(all(feature = "__tls", feature = "ylong_base"))] async fn client_request_version_1_0() { - use ylong_http::request::Request; - let request = Request::connect("http://example1.com:80/foo?a=1") + let request = Request::builder() + .url("http://example1.com:80/foo?a=1") + .method("CONNECT") .version("HTTP/1.0") - .body("") + .body(Body::empty()) .unwrap(); let client = Client::builder().http1_only().build().unwrap(); @@ -869,8 +809,10 @@ mod ut_async_impl_client { ylong_runtime::block_on(handle).unwrap(); } + #[cfg(all(feature = "__tls", feature = "ylong_base"))] struct Verifier; + #[cfg(all(feature = "__tls", feature = "ylong_base"))] impl CertVerifier for Verifier { fn verify(&self, _certs: &ServerCerts) -> bool { false @@ -879,11 +821,13 @@ mod ut_async_impl_client { #[cfg(all(feature = "__tls", feature = "ylong_base"))] async fn client_request_verify() { - use ylong_http::request::Request; // Creates a `async_impl::Client` let client = Client::builder().cert_verifier(Verifier).build().unwrap(); // Creates a `Request`. - let request = Request::get("https://www.example.com").body("").unwrap(); + let request = Request::builder() + .url("https://www.example.com") + .body(Body::empty()) + .unwrap(); // Sends request and receives a `Response`. let response = client.request(request).await; assert!(response.is_err()) diff --git a/ylong_http_client/src/async_impl/conn/http1.rs b/ylong_http_client/src/async_impl/conn/http1.rs index 2de4fe9..15416e3 100644 --- a/ylong_http_client/src/async_impl/conn/http1.rs +++ b/ylong_http_client/src/async_impl/conn/http1.rs @@ -14,26 +14,27 @@ use std::pin::Pin; use std::task::{Context, Poll}; +use ylong_http::body::async_impl::Body; +use ylong_http::body::{ChunkBody, TextBody}; use ylong_http::h1::{RequestEncoder, ResponseDecoder}; use ylong_http::request::uri::Scheme; -use ylong_http::response::Response; use ylong_http::version::Version; +use super::StreamData; use crate::async_impl::connector::ConnInfo; -use crate::async_impl::{Body, HttpBody, StreamData}; -use crate::error::{ErrorKind, HttpClientError}; +use crate::async_impl::{HttpBody, Request, Response}; +use crate::error::HttpClientError; +use crate::runtime::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf}; use crate::util::dispatcher::http1::Http1Conn; use crate::util::normalizer::BodyLengthParser; -use crate::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf, Request}; const TEMP_BUF_SIZE: usize = 16 * 1024; -pub(crate) async fn request( +pub(crate) async fn request( mut conn: Http1Conn, - request: &mut Request, -) -> Result, HttpClientError> + request: &mut Request, +) -> Result where - T: Body, S: AsyncRead + AsyncWrite + ConnInfo + Sync + Send + Unpin + 'static, { let mut buf = vec![0u8; TEMP_BUF_SIZE]; @@ -50,45 +51,48 @@ where // RequestEncoder writes `buf` as much as possible. if let Err(e) = conn.raw_mut().write_all(&buf[..written]).await { conn.shutdown(); - return Err(HttpClientError::new_with_cause(ErrorKind::Request, Some(e))); + return err_from_io!(Request, e); } } Err(e) => { conn.shutdown(); - return Err(HttpClientError::new_with_cause(ErrorKind::Request, Some(e))); + return err_from_other!(Request, e); } } } - // Encodes Request Body. + let content_length = request + .part() + .headers + .get("Content-Length") + .and_then(|v| v.to_string().ok()) + .and_then(|v| v.parse::().ok()) + .is_some(); + + let transfer_encoding = request + .part() + .headers + .get("Transfer-Encoding") + .and_then(|v| v.to_string().ok()) + .map(|v| v.contains("chunked")) + .unwrap_or(false); + let body = request.body_mut(); - let mut written = 0; - let mut end_body = false; - while !end_body { - if written < buf.len() { - match body.data(&mut buf[written..]).await { - Ok(0) => end_body = true, - Ok(size) => written += size, - Err(e) => { - conn.shutdown(); - return Err(HttpClientError::new_with_cause( - ErrorKind::BodyTransfer, - Some(e), - )); - } - } + + match (content_length, transfer_encoding) { + (_, true) => { + let body = ChunkBody::from_async_reader(body); + encode_body(&mut conn, body, &mut buf).await?; } - if written == buf.len() || end_body { - if let Err(e) = conn.raw_mut().write_all(&buf[..written]).await { - conn.shutdown(); - return Err(HttpClientError::new_with_cause( - ErrorKind::BodyTransfer, - Some(e), - )); - } - written = 0; + (true, false) => { + let body = TextBody::from_async_reader(body); + encode_body(&mut conn, body, &mut buf).await?; } - } + (false, false) => { + let body = TextBody::from_async_reader(body); + encode_body(&mut conn, body, &mut buf).await?; + } + }; // Decodes response part. let (part, pre) = { @@ -97,15 +101,12 @@ where let size = match conn.raw_mut().read(buf.as_mut_slice()).await { Ok(0) => { conn.shutdown(); - return Err(HttpClientError::new_with_message( - ErrorKind::Request, - "Tcp Closed", - )); + return err_from_msg!(Request, "Tcp closed"); } Ok(size) => size, Err(e) => { conn.shutdown(); - return Err(HttpClientError::new_with_cause(ErrorKind::Request, Some(e))); + return err_from_io!(Request, e); } }; @@ -114,7 +115,7 @@ where Ok(Some((part, rem))) => break (part, rem), Err(e) => { conn.shutdown(); - return Err(HttpClientError::new_with_cause(ErrorKind::Request, Some(e))); + return err_from_other!(Request, e); } } } @@ -133,14 +134,19 @@ where Some(value) => { if part.version == Version::HTTP1_0 { if value - .to_str() + .to_string() .ok() .and_then(|v| v.find("keep-alive")) .is_none() { conn.shutdown() } - } else if value.to_str().ok().and_then(|v| v.find("close")).is_none() { + } else if value + .to_string() + .ok() + .and_then(|v| v.find("close")) + .is_none() + { conn.shutdown() } } @@ -155,7 +161,43 @@ where }; let body = HttpBody::new(length, Box::new(conn), pre)?; - Ok(Response::from_raw_parts(part, body)) + Ok(Response::new( + ylong_http::response::Response::from_raw_parts(part, body), + )) +} + +async fn encode_body( + conn: &mut Http1Conn, + mut body: T, + buf: &mut [u8], +) -> Result<(), HttpClientError> +where + T: Body, + S: AsyncRead + AsyncWrite + Sync + Send + Unpin + 'static, +{ + // Encodes Request Body. + let mut written = 0; + let mut end_body = false; + while !end_body { + if written < buf.len() { + match body.data(&mut buf[written..]).await { + Ok(0) => end_body = true, + Ok(size) => written += size, + Err(e) => { + conn.shutdown(); + return err_from_other!(BodyTransfer, e); + } + } + } + if written == buf.len() || end_body { + if let Err(e) = conn.raw_mut().write_all(&buf[..written]).await { + conn.shutdown(); + return err_from_io!(BodyTransfer, e); + } + written = 0; + } + } + Ok(()) } impl AsyncRead for Http1Conn { diff --git a/ylong_http_client/src/async_impl/conn/http2.rs b/ylong_http_client/src/async_impl/conn/http2.rs index 94c755a..148d7c8 100644 --- a/ylong_http_client/src/async_impl/conn/http2.rs +++ b/ylong_http_client/src/async_impl/conn/http2.rs @@ -30,8 +30,8 @@ use crate::async_impl::client::Retryable; use crate::async_impl::conn::HttpBody; use crate::async_impl::StreamData; use crate::error::{ErrorKind, HttpClientError}; +use crate::runtime::{AsyncRead, AsyncWrite, ReadBuf}; use crate::util::dispatcher::http2::Http2Conn; -use crate::{AsyncRead, AsyncWrite, ReadBuf}; const UNUSED_FLAG: u8 = 0x0; @@ -53,28 +53,28 @@ where match build_data_frame(conn.id as usize, body).await? { None => { let headers = build_headers_frame(conn.id, part, true) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::Request, Some(e)))?; conn.send_frame_to_controller(headers).map_err(|e| { retryable.set_retry(true); - HttpClientError::new_with_cause(ErrorKind::Request, Some(e)) + HttpClientError::from_error(ErrorKind::Request, Some(e)) })?; } Some(data) => { let headers = build_headers_frame(conn.id, part, false) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::Request, Some(e)))?; conn.send_frame_to_controller(headers).map_err(|e| { retryable.set_retry(true); - HttpClientError::new_with_cause(ErrorKind::Request, Some(e)) + HttpClientError::from_error(ErrorKind::Request, Some(e)) })?; conn.send_frame_to_controller(data).map_err(|e| { retryable.set_retry(true); - HttpClientError::new_with_cause(ErrorKind::Request, Some(e)) + HttpClientError::from_error(ErrorKind::Request, Some(e)) })?; } } let frame = Pin::new(&mut conn.stream_info) .await - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::Request, Some(e)))?; frame_2_response(conn, frame, retryable) } @@ -91,9 +91,9 @@ where let (pseudo, fields) = headers.parts(); let status_code = match pseudo.status() { Some(status) => StatusCode::from_bytes(status.as_bytes()) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))?, + .map_err(|e| HttpClientError::from_error(ErrorKind::Request, Some(e)))?, None => { - return Err(HttpClientError::new_with_cause( + return Err(HttpClientError::from_error( ErrorKind::Request, Some(HttpError::from(H2Error::StreamError( conn.id, @@ -109,23 +109,20 @@ where } } Payload::RstStream(reset) => { - return Err(HttpClientError::new_with_cause( + return Err(HttpClientError::from_error( ErrorKind::Request, Some(HttpError::from(reset.error(conn.id).map_err(|e| { - HttpClientError::new_with_cause(ErrorKind::Request, Some(e)) + HttpClientError::from_error(ErrorKind::Request, Some(e)) })?)), )); } Payload::Goaway(_) => { // return Err(HttpClientError::from(ErrorKind::Resend)); retryable.set_retry(true); - return Err(HttpClientError::new_with_message( - ErrorKind::Request, - "GoAway", - )); + return Err(HttpClientError::from_str(ErrorKind::Request, "GoAway")); } _ => { - return Err(HttpClientError::new_with_cause( + return Err(HttpClientError::from_error( ErrorKind::Request, Some(HttpError::from(H2Error::StreamError( conn.id, @@ -143,7 +140,7 @@ where let content_length = part .headers .get("Content-Length") - .map(|v| v.to_str().unwrap_or(String::new())) + .map(|v| v.to_string().unwrap_or(String::new())) .and_then(|s| s.parse::().ok()); match content_length { None => HttpBody::empty(), @@ -168,7 +165,7 @@ pub(crate) async fn build_data_frame( let size = body .data(&mut buf) .await - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::Request, Some(e)))?; if size == 0 { break; } @@ -231,7 +228,7 @@ fn check_connection_specific_headers(id: u32, headers: &Headers) -> Result<(), H } } if let Some(te_value) = headers.get("te") { - if te_value.to_str()? != "trailers" { + if te_value.to_string()? != "trailers" { return Err(H2Error::StreamError(id, ErrorCode::ProtocolError).into()); } } diff --git a/ylong_http_client/src/async_impl/conn/mod.rs b/ylong_http_client/src/async_impl/conn/mod.rs index 07a86f9..6345d8f 100644 --- a/ylong_http_client/src/async_impl/conn/mod.rs +++ b/ylong_http_client/src/async_impl/conn/mod.rs @@ -11,34 +11,30 @@ // See the License for the specific language governing permissions and // limitations under the License. -use ylong_http::body::async_impl::Body; -use ylong_http::request::Request; -use ylong_http::response::Response; - -use crate::async_impl::client::Retryable; -use crate::async_impl::connector::ConnInfo; -use crate::async_impl::HttpBody; -use crate::error::HttpClientError; -use crate::util::dispatcher::Conn; -use crate::{AsyncRead, AsyncWrite}; - #[cfg(feature = "http1_1")] mod http1; #[cfg(feature = "http2")] mod http2; +use crate::async_impl::connector::ConnInfo; +use crate::async_impl::{Request, Response}; +use crate::error::HttpClientError; +use crate::runtime::{AsyncRead, AsyncWrite}; +use crate::util::dispatcher::Conn; + pub(crate) trait StreamData: AsyncRead { fn shutdown(&self); } -pub(crate) async fn request( +// TODO: Use structures instead of a function to reuse the io buf. +// TODO: Maybe `AsyncWrapper>` ?. + +pub(crate) async fn request( conn: Conn, - request: &mut Request, - _retryable: &mut Retryable, -) -> Result, HttpClientError> + request: &mut Request, +) -> Result where - T: Body, S: AsyncRead + AsyncWrite + ConnInfo + Sync + Send + Unpin + 'static, { match conn { @@ -46,6 +42,6 @@ where Conn::Http1(http1) => http1::request(http1, request).await, #[cfg(feature = "http2")] - Conn::Http2(http2) => http2::request(http2, request, _retryable).await, + Conn::Http2(http2) => http2::request(http2, request).await, } } diff --git a/ylong_http_client/src/async_impl/connector/mod.rs b/ylong_http_client/src/async_impl/connector/mod.rs index ee534c4..2d27592 100644 --- a/ylong_http_client/src/async_impl/connector/mod.rs +++ b/ylong_http_client/src/async_impl/connector/mod.rs @@ -21,9 +21,10 @@ use std::io; /// Information of an IO. pub use stream::ConnInfo; +use ylong_http::request::uri::Uri; -use crate::util::ConnectorConfig; -use crate::{AsyncRead, AsyncWrite, TcpStream, Uri}; +use crate::runtime::{AsyncRead, AsyncWrite, TcpStream}; +use crate::util::config::ConnectorConfig; /// `Connector` trait used by `async_impl::Client`. `Connector` provides /// asynchronous connection establishment interfaces. @@ -70,9 +71,11 @@ mod no_tls { use core::pin::Pin; use std::io::Error; + use ylong_http::request::uri::Uri; + use super::{tcp_stream, Connector, HttpConnector}; use crate::async_impl::connector::stream::HttpStream; - use crate::{TcpStream, Uri}; + use crate::runtime::TcpStream; impl Connector for HttpConnector { type Stream = HttpStream; @@ -101,13 +104,16 @@ mod no_tls { mod tls { use core::future::Future; use core::pin::Pin; + use std::error; + use std::fmt::{Debug, Display, Formatter}; use std::io::{Error, ErrorKind, Write}; + use ylong_http::request::uri::{Scheme, Uri}; + use super::{tcp_stream, Connector, HttpConnector}; use crate::async_impl::connector::stream::HttpStream; use crate::async_impl::ssl_stream::{AsyncSslStream, MixStream}; - use crate::error::CauseMessage; - use crate::{AsyncReadExt, AsyncWriteExt, Scheme, TcpStream, Uri}; + use crate::runtime::{AsyncReadExt, AsyncWriteExt, TcpStream}; impl Connector for HttpConnector { type Stream = HttpStream>; @@ -130,7 +136,7 @@ mod tls { .proxy_info() .basic_auth .as_ref() - .and_then(|v| v.to_str().ok()); + .and_then(|v| v.to_string().ok()); is_proxy = true; } @@ -199,7 +205,7 @@ mod tls { let n = conn.read(&mut buf[pos..]).await?; if n == 0 { - return Err(other_io_error("error receiving from proxy")); + return Err(other_io_error(CreateTunnelErr::Unsuccessful)); } pos += n; @@ -209,17 +215,41 @@ mod tls { return Ok(conn); } if pos == buf.len() { - return Err(other_io_error("proxy headers too long for tunnel")); + return Err(other_io_error(CreateTunnelErr::ProxyHeadersTooLong)); } } else if resp.starts_with(b"HTTP/1.1 407") { - return Err(other_io_error("proxy authentication required")); + return Err(other_io_error(CreateTunnelErr::ProxyAuthenticationRequired)); } else { - return Err(other_io_error("unsuccessful tunnel")); + return Err(other_io_error(CreateTunnelErr::Unsuccessful)); + } + } + } + + fn other_io_error(err: CreateTunnelErr) -> Error { + Error::new(ErrorKind::Other, err) + } + + enum CreateTunnelErr { + ProxyHeadersTooLong, + ProxyAuthenticationRequired, + Unsuccessful, + } + + impl Debug for CreateTunnelErr { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::ProxyHeadersTooLong => f.write_str("Proxy headers too long for tunnel"), + Self::ProxyAuthenticationRequired => f.write_str("Proxy authentication required"), + Self::Unsuccessful => f.write_str("Unsuccessful tunnel"), } } } - fn other_io_error(msg: &str) -> Error { - Error::new(ErrorKind::Other, CauseMessage::new(msg)) + impl Display for CreateTunnelErr { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + Debug::fmt(self, f) + } } + + impl error::Error for CreateTunnelErr {} } diff --git a/ylong_http_client/src/async_impl/connector/stream.rs b/ylong_http_client/src/async_impl/connector/stream.rs index ec10d92..0aed419 100644 --- a/ylong_http_client/src/async_impl/connector/stream.rs +++ b/ylong_http_client/src/async_impl/connector/stream.rs @@ -16,7 +16,7 @@ use std::pin::Pin; use std::task::{Context, Poll}; -use crate::{AsyncRead, AsyncWrite, ReadBuf}; +use crate::runtime::{AsyncRead, AsyncWrite, ReadBuf}; /// `ConnInfo` trait, which is used to obtain information about the current /// connection. diff --git a/ylong_http_client/src/async_impl/downloader/builder.rs b/ylong_http_client/src/async_impl/downloader/builder.rs index 01c480e..158285b 100644 --- a/ylong_http_client/src/async_impl/downloader/builder.rs +++ b/ylong_http_client/src/async_impl/downloader/builder.rs @@ -12,9 +12,8 @@ // limitations under the License. use super::{Console, DownloadConfig, DownloadOperator, Downloader}; -// TODO: Adapter, use Response later. use crate::async_impl::Response; -use crate::{SpeedLimit, Timeout}; +use crate::util::{SpeedLimit, Timeout}; /// A builder that can create a `Downloader`. /// diff --git a/ylong_http_client/src/async_impl/downloader/mod.rs b/ylong_http_client/src/async_impl/downloader/mod.rs index 0ec1c47..7bb4193 100644 --- a/ylong_http_client/src/async_impl/downloader/mod.rs +++ b/ylong_http_client/src/async_impl/downloader/mod.rs @@ -19,12 +19,11 @@ use std::time::Instant; pub use builder::DownloaderBuilder; use builder::WantsBody; use operator::Console; -pub use operator::{DownloadFuture, DownloadOperator, ProgressFuture}; -use ylong_http::body::async_impl::Body; +pub use operator::DownloadOperator; -// TODO: Adapter, use Response later. use crate::async_impl::Response; -use crate::{ErrorKind, HttpClientError, SpeedLimit, Timeout}; +use crate::error::HttpClientError; +use crate::util::{SpeedLimit, Timeout}; /// A downloader that can help you download the response body. /// @@ -169,7 +168,7 @@ impl Downloader { .body .headers() .get("Content") - .and_then(|v| v.to_str().ok()) + .and_then(|v| v.to_string().ok()) .and_then(|v| v.parse::().ok()); self.info = Some(DownloadInfo::new(content_length)); } @@ -185,18 +184,12 @@ impl Downloader { let mut buf = [0; 16 * 1024]; loop { - let data_size = match self.body.body_mut().data(&mut buf).await { - Ok(0) => { + let data_size = match self.body.data(&mut buf).await? { + 0 => { self.show_progress().await?; return Ok(()); } - Ok(size) => size, - Err(e) => { - return Err(HttpClientError::new_with_cause( - ErrorKind::BodyTransfer, - Some(e), - )) - } + size => size, }; let data = &buf[..data_size]; @@ -214,10 +207,7 @@ impl Downloader { if let Some(timeout) = self.config.timeout.inner() { let now = Instant::now(); if now.duration_since(self.info.as_mut().unwrap().start_time) >= timeout { - return Err(HttpClientError::new_with_message( - ErrorKind::Timeout, - "Download timeout", - )); + return err_from_io!(Timeout, std::io::ErrorKind::TimedOut.into()); } } Ok(()) @@ -266,8 +256,8 @@ mod ut_downloader { use ylong_http::h1::ResponseDecoder; use ylong_http::response::Response; - use crate::async_impl::adapter::Response as adpater_resp; - use crate::async_impl::{Downloader, HttpBody, StreamData}; + use crate::async_impl::conn::StreamData; + use crate::async_impl::{Downloader, HttpBody, Response as adpater_resp}; use crate::util::normalizer::BodyLength; impl StreamData for &[u8] { diff --git a/ylong_http_client/src/async_impl/downloader/operator.rs b/ylong_http_client/src/async_impl/downloader/operator.rs index 5fbba43..6c0aad6 100644 --- a/ylong_http_client/src/async_impl/downloader/operator.rs +++ b/ylong_http_client/src/async_impl/downloader/operator.rs @@ -15,7 +15,7 @@ use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; -use crate::HttpClientError; +use crate::error::HttpClientError; /// The trait defines the functionality required for processing bodies of HTTP /// messages. @@ -139,7 +139,10 @@ impl DownloadOperator for Console { _cx: &mut Context<'_>, data: &[u8], ) -> Poll> { - println!("{data:?}"); + println!( + "{}", + std::str::from_utf8(data).unwrap_or("") + ); Poll::Ready(Ok(data.len())) } diff --git a/ylong_http_client/src/async_impl/http_body.rs b/ylong_http_client/src/async_impl/http_body.rs index 0acbb18..432058c 100644 --- a/ylong_http_client/src/async_impl/http_body.rs +++ b/ylong_http_client/src/async_impl/http_body.rs @@ -16,15 +16,16 @@ use core::task::{Context, Poll}; use std::future::Future; use std::io::{Cursor, Read}; +use ylong_http::body::async_impl::Body; use ylong_http::body::TextBodyDecoder; #[cfg(feature = "http1_1")] use ylong_http::body::{ChunkBodyDecoder, ChunkState}; use ylong_http::headers::Headers; -use super::{Body, StreamData}; +use super::conn::StreamData; use crate::error::{ErrorKind, HttpClientError}; +use crate::runtime::{AsyncRead, ReadBuf, Sleep}; use crate::util::normalizer::BodyLength; -use crate::{AsyncRead, ReadBuf, Sleep}; const TRAILER_SIZE: usize = 1024; @@ -35,25 +36,28 @@ const TRAILER_SIZE: usize = 1024; /// # Examples /// /// ```no_run -/// use ylong_http_client::async_impl::{Body, Client, HttpBody}; -/// use ylong_http_client::{EmptyBody, Request}; +/// use ylong_http_client::async_impl::{Body, Client, HttpBody, Request}; +/// use ylong_http_client::HttpClientError; /// -/// async fn read_body() { +/// async fn read_body() -> Result<(), HttpClientError> { /// let client = Client::new(); /// /// // `HttpBody` is the body part of `response`. -/// let mut response = client.request(Request::new(EmptyBody)).await.unwrap(); +/// let mut response = client +/// .request(Request::builder().body(Body::empty())?) +/// .await?; /// /// // Users can use `Body::data` to get body data. /// let mut buf = [0u8; 1024]; /// loop { -/// let size = response.body_mut().data(&mut buf).await.unwrap(); +/// let size = response.data(&mut buf).await.unwrap(); /// if size == 0 { /// break; /// } /// let _data = &buf[..size]; /// // Deals with the data. /// } +/// Ok(()) /// } /// ``` pub struct HttpBody { @@ -74,10 +78,7 @@ impl HttpBody { if !pre.is_empty() { // TODO: Consider the case where BodyLength is empty but pre is not empty. io.shutdown(); - return Err(HttpClientError::new_with_message( - ErrorKind::Request, - "Body length is 0 but read extra data", - )); + return err_from_msg!(Request, "Body length is 0 but read extra data"); } Kind::Empty } @@ -125,12 +126,10 @@ impl Body for HttpBody { 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", - ))); + return Poll::Ready(err_from_io!(Timeout, std::io::ErrorKind::TimedOut.into())); } } + match self.kind { Kind::Empty => Poll::Ready(Ok(0)), Kind::Text(ref mut text) => text.data(cx, buf), @@ -147,10 +146,7 @@ impl Body for HttpBody { // 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", - ))); + return Poll::Ready(err_from_msg!(Timeout, "Request timeout")); } } @@ -165,14 +161,11 @@ impl Body for HttpBody { return Poll::Pending; } Poll::Ready(Err(e)) => { - return Poll::Ready(Err(HttpClientError::new_with_cause( - ErrorKind::BodyTransfer, - Some(e), - ))); + return Poll::Ready(Err(e)); } } - Poll::Ready(Ok(chunk.decoder.get_trailer().map_err(|_| { - HttpClientError::new_with_message(ErrorKind::BodyDecode, "Get trailer failed") + Poll::Ready(Ok(chunk.decoder.get_trailer().map_err(|e| { + HttpClientError::from_error(ErrorKind::BodyDecode, e) })?)) } _ => Poll::Ready(Ok(None)), @@ -242,7 +235,7 @@ impl UntilClose { if !buf[read..].is_empty() { if let Some(mut io) = self.io.take() { let mut read_buf = ReadBuf::new(&mut buf[read..]); - match Pin::new(&mut io).poll_read(cx, &mut read_buf) { + return match Pin::new(&mut io).poll_read(cx, &mut read_buf) { // Disconnected. Poll::Ready(Ok(())) => { let filled = read_buf.filled().len(); @@ -252,24 +245,21 @@ impl UntilClose { self.io = Some(io); } read += filled; - return Poll::Ready(Ok(read)); + Poll::Ready(Ok(read)) } Poll::Pending => { self.io = Some(io); if read != 0 { return Poll::Ready(Ok(read)); } - return Poll::Pending; + Poll::Pending } Poll::Ready(Err(e)) => { // If IO error occurs, shutdowns `io` before return. io.shutdown(); - return Poll::Ready(Err(HttpClientError::new_with_cause( - ErrorKind::BodyTransfer, - Some(e), - ))); + return Poll::Ready(err_from_io!(BodyTransfer, e)); } - } + }; } } Poll::Ready(Ok(read)) @@ -319,10 +309,7 @@ impl Text { if let Some(io) = self.io.take() { io.shutdown(); }; - return Poll::Ready(Err(HttpClientError::new_with_message( - ErrorKind::BodyDecode, - "Not Eof", - ))); + return Poll::Ready(err_from_msg!(BodyDecode, "Not eof")); } (true, true) => { self.io = None; @@ -343,10 +330,10 @@ impl Text { let filled = read_buf.filled().len(); if filled == 0 { io.shutdown(); - return Poll::Ready(Err(HttpClientError::new_with_message( - ErrorKind::BodyDecode, - "Response Body Incomplete", - ))); + return Poll::Ready(err_from_msg!( + BodyDecode, + "Response body incomplete" + )); } let (text, rem) = self.decoder.decode(read_buf.filled()); read += filled; @@ -354,10 +341,7 @@ impl Text { match (text.is_complete(), rem.is_empty()) { (true, false) => { io.shutdown(); - return Poll::Ready(Err(HttpClientError::new_with_message( - ErrorKind::BodyDecode, - "Not Eof", - ))); + return Poll::Ready(err_from_msg!(BodyDecode, "Not eof")); } (true, true) => return Poll::Ready(Ok(read)), _ => {} @@ -374,10 +358,7 @@ impl Text { Poll::Ready(Err(e)) => { // If IO error occurs, shutdowns `io` before return. io.shutdown(); - return Poll::Ready(Err(HttpClientError::new_with_cause( - ErrorKind::BodyTransfer, - Some(e), - ))); + return Poll::Ready(err_from_io!(BodyDecode, e)); } } } @@ -445,10 +426,7 @@ impl Chunk { let filled = read_buf.filled().len(); if filled == 0 { io.shutdown(); - return Poll::Ready(Err(HttpClientError::new_with_message( - ErrorKind::BodyTransfer, - "Response Body Incomplete", - ))); + return Poll::Ready(err_from_msg!(BodyDecode, "Response body incomplete")); } let (size, flag) = self.merge_chunks(read_buf.filled_mut())?; read += size; @@ -469,10 +447,7 @@ impl Chunk { Poll::Ready(Err(e)) => { // If IO error occurs, shutdowns `io` before return. io.shutdown(); - return Poll::Ready(Err(HttpClientError::new_with_cause( - ErrorKind::BodyTransfer, - Some(e), - ))); + return Poll::Ready(err_from_io!(BodyDecode, e)); } } } @@ -499,7 +474,7 @@ impl Chunk { let (chunks, junk) = self .decoder .decode(buf) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::BodyDecode, e))?; let mut finished = false; let mut ptrs = Vec::new(); @@ -520,10 +495,7 @@ impl Chunk { } if finished && !junk.is_empty() { - return Err(HttpClientError::new_with_message( - ErrorKind::BodyDecode, - "Invalid Chunk Body", - )); + return err_from_msg!(BodyDecode, "Invalid chunk body"); } let start = buf.as_ptr(); @@ -579,7 +551,7 @@ mod ut_async_http_body { .unwrap() .unwrap(); assert_eq!( - res.get("accept").unwrap().to_str().unwrap(), + res.get("accept").unwrap().to_string().unwrap(), "text/html".to_string() ); let box_stream = Box::new("".as_bytes()); diff --git a/ylong_http_client/src/async_impl/mod.rs b/ylong_http_client/src/async_impl/mod.rs index 9d12adb..32fd324 100644 --- a/ylong_http_client/src/async_impl/mod.rs +++ b/ylong_http_client/src/async_impl/mod.rs @@ -24,32 +24,29 @@ //! which implements the trait. mod client; -mod conn; mod connector; mod downloader; mod http_body; -mod pool; +mod request; +mod response; mod timeout; mod uploader; +#[cfg(feature = "__tls")] +mod ssl_stream; + +pub(crate) mod conn; +pub(crate) mod pool; + pub use client::ClientBuilder; -pub(crate) use conn::StreamData; -pub use connector::Connector; -pub(crate) use connector::HttpConnector; +pub use connector::{Connector, HttpConnector}; pub use downloader::{DownloadOperator, Downloader, DownloaderBuilder}; pub use http_body::HttpBody; -pub(crate) use pool::ConnPool; +pub use request::{Body, Request, RequestBuilder}; +pub use response::Response; pub use uploader::{UploadOperator, Uploader, UploaderBuilder}; -pub use ylong_http::body::async_impl::Body; pub use ylong_http::body::{MultiPart, Part}; -#[cfg(feature = "__tls")] -mod ssl_stream; - // TODO: Remove these later. /// Client Adapter. pub type Client = client::Client; - -// TODO: Remove these later. -mod adapter; -pub use adapter::{RequestBuilder, Response}; diff --git a/ylong_http_client/src/async_impl/pool.rs b/ylong_http_client/src/async_impl/pool.rs index 5b68df4..6849c5d 100644 --- a/ylong_http_client/src/async_impl/pool.rs +++ b/ylong_http_client/src/async_impl/pool.rs @@ -16,11 +16,14 @@ use std::future::Future; use std::mem::take; use std::sync::{Arc, Mutex}; +use ylong_http::request::uri::Uri; + use crate::async_impl::Connector; -use crate::error::HttpClientError; +use crate::error::{ErrorKind, HttpClientError}; +use crate::runtime::{AsyncRead, AsyncWrite}; +use crate::util::config::{HttpConfig, HttpVersion}; use crate::util::dispatcher::{Conn, ConnDispatcher, Dispatcher}; use crate::util::pool::{Pool, PoolKey}; -use crate::{AsyncRead, AsyncWrite, ErrorKind, HttpConfig, HttpVersion, Uri}; pub(crate) struct ConnPool { pool: Pool>, @@ -37,7 +40,7 @@ impl ConnPool { } } - pub(crate) async fn connect_to(&self, uri: Uri) -> Result, HttpClientError> { + pub(crate) async fn connect_to(&self, uri: &Uri) -> Result, HttpClientError> { let key = PoolKey::new( uri.scheme().unwrap().clone(), uri.authority().unwrap().clone(), @@ -45,7 +48,7 @@ impl ConnPool { self.pool .get(key, Conns::new) - .conn(self.config.clone(), self.connector.clone().connect(&uri)) + .conn(self.config.clone(), self.connector.clone().connect(uri)) .await } } @@ -104,7 +107,7 @@ impl Conns { let dispatcher = ConnDispatcher::http2( config.http2_config, connect_fut.await.map_err(|e| { - HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)) + HttpClientError::from_error(ErrorKind::Connect, Some(e)) })?, ); Ok(self.dispatch_conn(dispatcher)) @@ -115,14 +118,15 @@ impl Conns { if let Some(conn) = self.get_exist_conn() { return Ok(conn); } - let dispatcher = - ConnDispatcher::http1(connect_fut.await.map_err(|e| { - HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)) - })?); + let dispatcher = ConnDispatcher::http1( + connect_fut + .await + .map_err(|e| HttpClientError::from_error(ErrorKind::Connect, e))?, + ); Ok(self.dispatch_conn(dispatcher)) } #[cfg(not(feature = "http1_1"))] - HttpVersion::Http1 => Err(HttpClientError::new_with_message( + HttpVersion::Http11 => Err(HttpClientError::from_str( ErrorKind::Connect, "Invalid HTTP VERSION", )), @@ -138,21 +142,19 @@ impl Conns { } fn get_exist_conn(&self) -> Option> { - { - let mut list = self.list.lock().unwrap(); - let mut conn = None; - let curr = take(&mut *list); - for dispatcher in curr.into_iter() { - // Discard invalid dispatchers. - if dispatcher.is_shutdown() { - continue; - } - if conn.is_none() { - conn = dispatcher.dispatch(); - } - list.push(dispatcher); + let mut list = self.list.lock().unwrap(); + let mut conn = None; + let curr = take(&mut *list); + for dispatcher in curr.into_iter() { + // Discard invalid dispatchers. + if dispatcher.is_shutdown() { + continue; + } + if conn.is_none() { + conn = dispatcher.dispatch(); } - conn + list.push(dispatcher); } + conn } } diff --git a/ylong_http_client/src/async_impl/request.rs b/ylong_http_client/src/async_impl/request.rs new file mode 100644 index 0000000..1593c21 --- /dev/null +++ b/ylong_http_client/src/async_impl/request.rs @@ -0,0 +1,392 @@ +// Copyright (c) 2023 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! `ylong_http_client` `Request` adapter. + +use core::ops::{Deref, DerefMut}; +use core::pin::Pin; +use core::task::{Context, Poll}; +use std::io::Cursor; + +use ylong_http::body::MultiPartBase; +use ylong_http::request::{Request as Req, RequestBuilder as ReqBuilder}; + +use crate::error::{ErrorKind, HttpClientError}; +use crate::runtime::{AsyncRead, ReadBuf}; + +/// A structure that represents an HTTP `Request`. It contains a request line, +/// some HTTP headers and a HTTP body. +/// +/// An HTTP request is made by a client, to a named host, which is located on a +/// server. The aim of the request is to access a resource on the server. +/// +/// This structure is based on `ylong_http::Request`. +/// +/// # Examples +/// +/// ``` +/// use ylong_http_client::async_impl::{Body, Request}; +/// +/// let request = Request::builder().body(Body::empty()); +/// ``` +pub struct Request { + pub(crate) inner: Req, +} + +impl Request { + /// Creates a new, default `RequestBuilder`. + /// + /// # Default + /// + /// - The method of this `RequestBuilder` is `GET`. + /// - The URL of this `RequestBuilder` is `/`. + /// - The HTTP version of this `RequestBuilder` is `HTTP/1.1`. + /// - No headers are in this `RequestBuilder`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::Request; + /// + /// let builder = Request::builder(); + /// ``` + pub fn builder() -> RequestBuilder { + RequestBuilder::new() + } +} + +impl Deref for Request { + type Target = Req; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for Request { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +/// A structure which used to build an HTTP `Request`. +/// +/// # Examples +/// +/// ``` +/// use ylong_http_client::async_impl::{Body, RequestBuilder}; +/// +/// let request = RequestBuilder::new() +/// .method("GET") +/// .version("HTTP/1.1") +/// .url("http://www.example.com") +/// .header("Content-Type", "application/octet-stream") +/// .body(Body::empty()); +/// ``` +#[derive(Default)] +pub struct RequestBuilder(ReqBuilder); + +impl RequestBuilder { + /// Creates a new, default `RequestBuilder`. + /// + /// # Default + /// + /// - The method of this `RequestBuilder` is `GET`. + /// - The URL of this `RequestBuilder` is `/`. + /// - The HTTP version of this `RequestBuilder` is `HTTP/1.1`. + /// - No headers are in this `RequestBuilder`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::RequestBuilder; + /// + /// let builder = RequestBuilder::new(); + /// ``` + pub fn new() -> Self { + Self(ReqBuilder::new()) + } + + /// Sets the `Method` of the `Request`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::RequestBuilder; + /// + /// let builder = RequestBuilder::new().method("GET"); + /// ``` + pub fn method(self, method: &str) -> Self { + Self(self.0.method(method)) + } + + /// Sets the `Url` of the `Request`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::RequestBuilder; + /// + /// let builder = RequestBuilder::new().url("www.example.com"); + /// ``` + pub fn url(self, url: &str) -> Self { + Self(self.0.url(url)) + } + + /// Sets the `Version` of the `Request`. Uses `Version::HTTP11` by default. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::RequestBuilder; + /// + /// let builder = RequestBuilder::new().version("HTTP/1.1"); + /// ``` + pub fn version(mut self, version: &str) -> Self { + self.0 = self.0.version(version); + self + } + + /// Adds a `Header` to `Request`. Overwrites `HeaderValue` if the + /// `HeaderName` already exists. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::RequestBuilder; + /// + /// let builder = RequestBuilder::new().header("Content-Type", "application/octet-stream"); + /// ``` + pub fn header(mut self, name: &str, value: &str) -> Self { + self.0 = self.0.header(name, value); + self + } + + /// Adds a `Header` to `Request`. Appends `HeaderValue` to the end of + /// previous `HeaderValue` if the `HeaderName` already exists. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::RequestBuilder; + /// + /// let builder = RequestBuilder::new().append_header("Content-Type", "application/octet-stream"); + /// ``` + pub fn append_header(mut self, name: &str, value: &str) -> Self { + self.0 = self.0.append_header(name, value); + self + } + + /// Tries to create a `Request` based on the incoming `body`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::{Body, RequestBuilder}; + /// + /// let request = RequestBuilder::new().body(Body::empty()); + /// ``` + pub fn body(self, body: Body) -> Result { + let mut builder = self; + match body.inner { + BodyKind::Slice(ref slice) => { + builder = builder.header( + "Content-Length", + format!("{}", slice.get_ref().len()).as_str(), + ); + } + BodyKind::Multipart(ref multipart) => { + let value = format!( + "multipart/form-data; boundary={}", + multipart.multipart().boundary() + ); + + builder = builder.header("Content-Type", value.as_str()); + + if let Some(size) = multipart.multipart().total_bytes() { + builder = builder.header("Content-Length", format!("{size}").as_str()); + } + } + _ => {} + } + + builder + .0 + .body(body) + .map(|inner| Request { inner }) + .map_err(|e| HttpClientError::from_error(ErrorKind::Build, e)) + } +} + +/// A structure that represents body of HTTP request. +/// +/// There are many kinds of body supported: +/// +/// - Empty: an empty body. +/// - Slice: a body whose content comes from a memory slice. +/// - Stream: a body whose content comes from a stream. +/// - Multipart: a body whose content can transfer into a `Multipart`. +/// +/// # Examples +/// +/// ``` +/// use ylong_http_client::async_impl::Body; +/// +/// let body = Body::empty(); +/// ``` +pub struct Body { + inner: BodyKind, +} + +pub(crate) enum BodyKind { + Empty, + Slice(Cursor>), + Stream(Box), + Multipart(Box), +} + +impl Body { + /// Creates an empty HTTP body. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::Body; + /// + /// let body = Body::empty(); + /// ``` + pub fn empty() -> Self { + Body::new(BodyKind::Empty) + } + + /// Creates an HTTP body that based on memory slice. + /// + /// This kind of body is **reusable**. + /// + /// # Example + /// + /// ``` + /// use ylong_http_client::async_impl::Body; + /// + /// let body = Body::slice("HelloWorld"); + /// ``` + pub fn slice(slice: T) -> Self + where + T: Into>, + { + Body::new(BodyKind::Slice(Cursor::new(slice.into()))) + } + + /// Creates an HTTP body that based on an asynchronous stream. + /// + /// This kind of body is not **reusable**. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::Body; + /// + /// let body = Body::stream("HelloWorld".as_bytes()); + /// ``` + pub fn stream(stream: T) -> Self + where + T: AsyncRead + Send + Sync + Unpin + 'static, + { + Body::new(BodyKind::Stream( + Box::new(stream) as Box + )) + } + + /// Creates an HTTP body that based on a structure which implements + /// `MultiPartBase`. + /// + /// This kind of body is not **reusable**. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::{Body, MultiPart}; + /// + /// async fn create_multipart_body(multipart: MultiPart) { + /// let body = Body::multipart(multipart); + /// } + /// ``` + pub fn multipart(stream: T) -> Self + where + T: MultiPartBase + Send + Sync + Unpin + 'static, + { + Body::new(BodyKind::Multipart( + Box::new(stream) as Box + )) + } +} + +impl Body { + pub(crate) fn new(inner: BodyKind) -> Self { + Self { inner } + } + + // TODO: Considers reusing unread stream ? + pub(crate) fn reuse(&mut self) -> bool { + match self.inner { + BodyKind::Empty => true, + BodyKind::Slice(ref mut slice) => { + slice.set_position(0); + true + } + _ => false, + } + } +} + +impl AsyncRead for Body { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let this = self.get_mut(); + match this.inner { + BodyKind::Empty => Poll::Ready(Ok(())), + BodyKind::Slice(ref mut slice) => { + #[cfg(feature = "tokio_base")] + return Pin::new(slice).poll_read(cx, buf); + #[cfg(feature = "ylong_base")] + return poll_read_cursor(slice, buf); + } + BodyKind::Stream(ref mut stream) => Pin::new(stream).poll_read(cx, buf), + BodyKind::Multipart(ref mut multipart) => Pin::new(multipart).poll_read(cx, buf), + } + } +} + +#[cfg(feature = "ylong_base")] +fn poll_read_cursor( + cursor: &mut Cursor>, + buf: &mut ylong_runtime::io::ReadBuf<'_>, +) -> Poll> { + let pos = cursor.position(); + let data = (*cursor).get_ref(); + + if pos > data.len() as u64 { + return Poll::Ready(Ok(())); + } + + let start = pos as usize; + let target = std::cmp::min(data.len() - start, buf.remaining()); + let end = start + target; + buf.append(&(data.as_slice())[start..end]); + cursor.set_position(end as u64); + + Poll::Ready(Ok(())) +} diff --git a/ylong_http_client/src/async_impl/response.rs b/ylong_http_client/src/async_impl/response.rs new file mode 100644 index 0000000..a611fd6 --- /dev/null +++ b/ylong_http_client/src/async_impl/response.rs @@ -0,0 +1,66 @@ +// Copyright (c) 2023 Huawei Device Co., Ltd. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use core::ops::{Deref, DerefMut}; + +use ylong_http::body::async_impl::Body; +use ylong_http::response::Response as Resp; + +use crate::async_impl::HttpBody; +use crate::error::HttpClientError; +use crate::ErrorKind; + +/// A structure that represents an HTTP `Response`. +pub struct Response { + pub(crate) inner: Resp, +} + +impl Response { + pub(crate) fn new(response: Resp) -> Self { + Self { inner: response } + } + + /// Reads the data of the `HttpBody`. + pub async fn data(&mut self, buf: &mut [u8]) -> Result { + Body::data(self.inner.body_mut(), buf).await + } + + /// Reads all the message of the `HttpBody` and return it as a `String`. + pub async fn text(mut self) -> Result { + let mut buf = [0u8; 1024]; + let mut vec = Vec::new(); + loop { + let size = self.data(&mut buf).await?; + if size == 0 { + break; + } + vec.extend_from_slice(&buf[..size]); + } + + String::from_utf8(vec).map_err(|e| HttpClientError::from_error(ErrorKind::BodyDecode, e)) + } +} + +impl Deref for Response { + type Target = Resp; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for Response { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} diff --git a/ylong_http_client/src/async_impl/ssl_stream/c_ssl_stream.rs b/ylong_http_client/src/async_impl/ssl_stream/c_ssl_stream.rs index 4b2ebb7..54adbf4 100644 --- a/ylong_http_client/src/async_impl/ssl_stream/c_ssl_stream.rs +++ b/ylong_http_client/src/async_impl/ssl_stream/c_ssl_stream.rs @@ -17,9 +17,9 @@ use core::{future, ptr, slice}; use std::io::{self, Read, Write}; use crate::async_impl::ssl_stream::{check_io_to_poll, Wrapper}; +use crate::runtime::{AsyncRead, AsyncWrite, ReadBuf}; use crate::util::c_openssl::error::ErrorStack; use crate::util::c_openssl::ssl::{self, ShutdownResult, Ssl, SslErrorCode}; -use crate::{AsyncRead, AsyncWrite, ReadBuf}; /// An asynchronous version of [`openssl::ssl::SslStream`]. #[derive(Debug)] diff --git a/ylong_http_client/src/async_impl/ssl_stream/mix.rs b/ylong_http_client/src/async_impl/ssl_stream/mix.rs index 1c03a43..4d87042 100644 --- a/ylong_http_client/src/async_impl/ssl_stream/mix.rs +++ b/ylong_http_client/src/async_impl/ssl_stream/mix.rs @@ -15,7 +15,7 @@ use core::pin::Pin; use core::task::{Context, Poll}; use crate::async_impl::ssl_stream::AsyncSslStream; -use crate::{AsyncRead, AsyncWrite, ReadBuf}; +use crate::runtime::{AsyncRead, AsyncWrite, ReadBuf}; /// A stream which may be wrapped with TLS. pub enum MixStream { diff --git a/ylong_http_client/src/async_impl/ssl_stream/wrapper.rs b/ylong_http_client/src/async_impl/ssl_stream/wrapper.rs index b51e2a6..98f9851 100644 --- a/ylong_http_client/src/async_impl/ssl_stream/wrapper.rs +++ b/ylong_http_client/src/async_impl/ssl_stream/wrapper.rs @@ -16,7 +16,7 @@ use core::pin::Pin; use core::task::{Context, Poll}; use std::io::{self, Read, Write}; -use crate::{AsyncRead, AsyncWrite, ReadBuf}; +use crate::runtime::{AsyncRead, AsyncWrite, ReadBuf}; #[derive(Debug)] pub(crate) struct Wrapper { diff --git a/ylong_http_client/src/async_impl/timeout.rs b/ylong_http_client/src/async_impl/timeout.rs index 34e91bb..30d5451 100644 --- a/ylong_http_client/src/async_impl/timeout.rs +++ b/ylong_http_client/src/async_impl/timeout.rs @@ -14,32 +14,38 @@ use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; +use std::time::Duration; -use ylong_http::response::Response; - -use crate::async_impl::HttpBody; -use crate::{ErrorKind, HttpClientError, Sleep}; +use super::Response; +use crate::error::HttpClientError; +use crate::runtime::{sleep, Sleep}; pub(crate) struct TimeoutFuture { pub(crate) timeout: Option>>, pub(crate) future: T, } +impl TimeoutFuture>> { + pub(crate) fn new(future: T, timeout: Duration) -> Self { + Self { + timeout: Some(Box::pin(sleep(timeout))), + future: Box::pin(future), + } + } +} + impl Future for TimeoutFuture where - T: Future, HttpClientError>> + Unpin, + T: Future> + Unpin, { - type Output = Result, HttpClientError>; + type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); if let Some(delay) = this.timeout.as_mut() { if let Poll::Ready(()) = delay.as_mut().poll(cx) { - return Poll::Ready(Err(HttpClientError::new_with_message( - ErrorKind::Timeout, - "Request timeout", - ))); + return Poll::Ready(err_from_io!(Timeout, std::io::ErrorKind::TimedOut.into())); } } match Pin::new(&mut this.future).poll(cx) { @@ -79,11 +85,15 @@ mod ut_timeout { headers: Default::default(), }; let body = HttpBody::new(BodyLength::Empty, Box::new([].as_slice()), &[]).unwrap(); - Ok(Response::from_raw_parts(part, body)) + Ok(crate::async_impl::Response::new(Response::from_raw_parts( + part, body, + ))) }); let future2 = Box::pin(async { - Result::, HttpClientError>::Err(HttpClientError::user_aborted()) + Result::::Err( + HttpClientError::user_aborted(), + ) }); let time_future1 = TimeoutFuture { diff --git a/ylong_http_client/src/async_impl/uploader/builder.rs b/ylong_http_client/src/async_impl/uploader/builder.rs index 58017c2..13c42a9 100644 --- a/ylong_http_client/src/async_impl/uploader/builder.rs +++ b/ylong_http_client/src/async_impl/uploader/builder.rs @@ -13,7 +13,7 @@ use super::{Console, UploadConfig, UploadOperator, Uploader}; use crate::async_impl::MultiPart; -use crate::AsyncRead; +use crate::runtime::AsyncRead; /// A builder that can create a `Uploader`. /// @@ -111,8 +111,8 @@ impl UploaderBuilder> { /// ``` /// # use std::pin::Pin; /// # use std::task::{Context, Poll}; - /// # use ylong_http_client::async_impl::{UploaderBuilder, Uploader, UploadOperator}; - /// # use ylong_http_client::{HttpClientError, Response}; + /// # use ylong_http_client::async_impl::{UploaderBuilder, Uploader, UploadOperator, Response}; + /// # use ylong_http_client::HttpClientError; /// /// struct MyOperator; /// @@ -149,8 +149,7 @@ impl UploaderBuilder> { /// # Examples /// /// ``` - /// # use ylong_http_client::async_impl::{UploaderBuilder, Uploader}; - /// # use ylong_http_client::Response; + /// # use ylong_http_client::async_impl::{UploaderBuilder, Uploader, Response}; /// /// let builder = UploaderBuilder::new() /// .reader("HelloWorld".as_bytes()) @@ -200,8 +199,7 @@ impl UploaderBuilder> { /// # Examples /// /// ``` - /// # use ylong_http_client::async_impl::{UploaderBuilder, Uploader}; - /// # use ylong_http_client::Response; + /// # use ylong_http_client::async_impl::{UploaderBuilder, Uploader, Response}; /// /// let uploader = UploaderBuilder::new() /// .reader("HelloWorld".as_bytes()) diff --git a/ylong_http_client/src/async_impl/uploader/mod.rs b/ylong_http_client/src/async_impl/uploader/mod.rs index 707247e..18f4fad 100644 --- a/ylong_http_client/src/async_impl/uploader/mod.rs +++ b/ylong_http_client/src/async_impl/uploader/mod.rs @@ -19,9 +19,10 @@ use std::task::{Context, Poll}; pub use builder::{UploaderBuilder, WantsReader}; pub use operator::{Console, UploadOperator}; -use ylong_http::body::async_impl::Body; +use ylong_http::body::{MultiPart, MultiPartBase}; -use crate::{AsyncRead, ErrorKind, HttpClientError, ReadBuf}; +use crate::error::ErrorKind; +use crate::runtime::{AsyncRead, ReadBuf}; /// An uploader that can help you upload the request body. /// @@ -47,7 +48,6 @@ use crate::{AsyncRead, ErrorKind, HttpClientError, ReadBuf}; /// `Console`: /// ```no_run /// # use ylong_http_client::async_impl::Uploader; -/// # use ylong_http_client::Response; /// /// // Creates a default `Uploader` that show progress on console. /// let mut uploader = Uploader::console("HelloWorld".as_bytes()); @@ -57,8 +57,8 @@ use crate::{AsyncRead, ErrorKind, HttpClientError, ReadBuf}; /// ```no_run /// # use std::pin::Pin; /// # use std::task::{Context, Poll}; -/// # use ylong_http_client::async_impl::{Uploader, UploadOperator}; -/// # use ylong_http_client::{Response, SpeedLimit, Timeout}; +/// # use ylong_http_client::async_impl::{Uploader, UploadOperator, Response}; +/// # use ylong_http_client::{SpeedLimit, Timeout}; /// # use ylong_http_client::HttpClientError; /// /// # async fn upload_and_show_progress() { @@ -122,18 +122,16 @@ impl Uploader<(), ()> { } } -impl Body for Uploader +impl AsyncRead for Uploader where R: AsyncRead + Unpin, T: UploadOperator + Unpin, { - type Error = HttpClientError; - - fn poll_data( + fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { + buf: &mut ReadBuf<'_>, + ) -> Poll> { let this = self.get_mut(); if this.info.is_none() { @@ -149,34 +147,24 @@ where ) { Poll::Ready(Ok(())) => {} Poll::Ready(Err(e)) if e.error_kind() == ErrorKind::UserAborted => { - return Poll::Ready(Ok(0)); + return Poll::Ready(Ok(())); } - Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), - Poll::Pending => return Poll::Pending, - } - - let mut read_buf = ReadBuf::new(buf); - let filled = read_buf.filled().len(); - match Pin::new(&mut this.reader).poll_read(cx, &mut read_buf) { - Poll::Ready(Ok(_)) => {} + // TODO: Consider another way to handle error. Poll::Ready(Err(e)) => { - return Poll::Ready(Err(HttpClientError::new_with_cause( - ErrorKind::BodyTransfer, - Some(e), + return Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::Other, + Box::new(e), ))) } Poll::Pending => return Poll::Pending, } - let new_filled = read_buf.filled().len(); - let read_bytes = new_filled - filled; - info.uploaded_bytes += read_bytes as u64; - Poll::Ready(Ok(read_bytes)) + Pin::new(&mut this.reader).poll_read(cx, buf) } } -impl AsRef for Uploader { - fn as_ref(&self) -> &R { +impl MultiPartBase for Uploader { + fn multipart(&self) -> &MultiPart { &self.reader } } @@ -198,12 +186,12 @@ impl UploadInfo { #[cfg(all(test, feature = "ylong_base"))] mod ut_uploader { - use ylong_http::body::async_impl::Body; use ylong_http::body::{MultiPart, Part}; + use ylong_runtime::io::AsyncRead; use crate::async_impl::uploader::{Context, Pin, Poll}; use crate::async_impl::{UploadOperator, Uploader, UploaderBuilder}; - use crate::{ErrorKind, HttpClientError}; + use crate::HttpClientError; /// UT test cases for `UploadOperator::data`. /// @@ -227,7 +215,11 @@ mod ut_uploader { let mut size = user_slice.len(); while size == user_slice.len() { - size = uploader.data(user_slice.as_mut_slice()).await.unwrap(); + let mut buf = ylong_runtime::io::ReadBuf::new(user_slice.as_mut_slice()); + ylong_runtime::futures::poll_fn(|cx| Pin::new(&mut uploader).poll_read(cx, &mut buf)) + .await + .unwrap(); + size = buf.filled_len(); output_vec.extend_from_slice(&user_slice[..size]); } assert_eq!(&output_vec, b"HelloWorld"); @@ -238,10 +230,11 @@ mod ut_uploader { .multipart(multipart) .console() .build(); - let size = multi_uploader - .data(user_slice.as_mut_slice()) + let mut buf = ylong_runtime::io::ReadBuf::new(user_slice.as_mut_slice()); + ylong_runtime::futures::poll_fn(|cx| Pin::new(&mut multi_uploader).poll_read(cx, &mut buf)) .await .unwrap(); + let size = buf.filled_len(); assert_eq!(size, 12); } @@ -269,10 +262,7 @@ mod ut_uploader { total: Option, ) -> Poll> { if uploaded > total.unwrap() { - return Poll::Ready(Err(HttpClientError::new_with_message( - ErrorKind::BodyTransfer, - "UploadOperator failed", - ))); + return Poll::Ready(err_from_msg!(BodyTransfer, "UploadOperator failed")); } Poll::Ready(Ok(())) } diff --git a/ylong_http_client/src/async_impl/uploader/operator.rs b/ylong_http_client/src/async_impl/uploader/operator.rs index 5304276..18ceba1 100644 --- a/ylong_http_client/src/async_impl/uploader/operator.rs +++ b/ylong_http_client/src/async_impl/uploader/operator.rs @@ -15,7 +15,7 @@ use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; -use crate::HttpClientError; +use crate::error::HttpClientError; /// A `UploadOperator` represents structures that can read local data to socket. /// diff --git a/ylong_http_client/src/error.rs b/ylong_http_client/src/error.rs index 2788712..13e440c 100644 --- a/ylong_http_client/src/error.rs +++ b/ylong_http_client/src/error.rs @@ -15,13 +15,21 @@ //! this crate. use core::fmt::{Debug, Display, Formatter}; -use std::error::Error; +use std::{error, io}; /// The structure encapsulates errors that can be encountered when working with /// the HTTP client. +/// +/// # Examples +/// +/// ``` +/// use ylong_http_client::HttpClientError; +/// +/// let error = HttpClientError::user_aborted(); +/// ``` pub struct HttpClientError { kind: ErrorKind, - cause: Option>, + cause: Cause, } impl HttpClientError { @@ -30,14 +38,14 @@ impl HttpClientError { /// # Examples /// /// ``` - /// # use ylong_http_client::HttpClientError; + /// use ylong_http_client::HttpClientError; /// /// let user_aborted = HttpClientError::user_aborted(); /// ``` pub fn user_aborted() -> Self { Self { kind: ErrorKind::UserAborted, - cause: None, + cause: Cause::NoReason, } } @@ -46,16 +54,19 @@ impl HttpClientError { /// # Examples /// /// ``` - /// # use ylong_http_client::HttpClientError; + /// use ylong_http_client::HttpClientError; /// - /// # fn error(error: std::io::Error) { - /// let other = HttpClientError::other(Some(error)); - /// # } + /// fn error(error: std::io::Error) { + /// let other = HttpClientError::other(error); + /// } /// ``` - pub fn other>>(cause: Option) -> Self { + pub fn other(cause: T) -> Self + where + T: Into>, + { Self { kind: ErrorKind::Other, - cause: cause.map(|e| e.into()), + cause: Cause::Other(cause.into()), } } @@ -64,7 +75,7 @@ impl HttpClientError { /// # Examples /// /// ``` - /// # use ylong_http_client::{ErrorKind, HttpClientError}; + /// use ylong_http_client::{ErrorKind, HttpClientError}; /// /// let user_aborted = HttpClientError::user_aborted(); /// assert_eq!(user_aborted.error_kind(), ErrorKind::UserAborted); @@ -73,20 +84,49 @@ impl HttpClientError { self.kind } - pub(crate) fn new_with_cause(kind: ErrorKind, cause: Option) -> Self + /// Gets the `io::Error` if this `HttpClientError` comes from an + /// `io::Error`. + /// + /// Returns `None` if the `HttpClientError` doesn't come from an + /// `io::Error`. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::HttpClientError; + /// + /// let error = HttpClientError::user_aborted().io_error(); + /// ``` + pub fn io_error(&self) -> Option<&io::Error> { + match self.cause { + Cause::Io(ref io) => Some(io), + _ => None, + } + } +} + +impl HttpClientError { + pub(crate) fn from_error(kind: ErrorKind, err: T) -> Self where - T: Into>, + T: Into>, { Self { kind, - cause: cause.map(|e| e.into()), + cause: Cause::Other(err.into()), } } - pub(crate) fn new_with_message(kind: ErrorKind, message: &str) -> Self { + pub(crate) fn from_str(kind: ErrorKind, msg: &'static str) -> Self { Self { kind, - cause: Some(CauseMessage::new(message).into()), + cause: Cause::Msg(msg), + } + } + + pub(crate) fn from_io_error(kind: ErrorKind, err: io::Error) -> Self { + Self { + kind, + cause: Cause::Io(err), } } } @@ -95,9 +135,7 @@ impl Debug for HttpClientError { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { let mut builder = f.debug_struct("HttpClientError"); builder.field("ErrorKind", &self.kind); - if let Some(ref cause) = self.cause { - builder.field("Cause", cause); - } + builder.field("Cause", &self.cause); builder.finish() } } @@ -105,15 +143,12 @@ impl Debug for HttpClientError { impl Display for HttpClientError { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { f.write_str(self.kind.as_str())?; - - if let Some(ref cause) = self.cause { - write!(f, ": {cause}")?; - } + write!(f, ": {}", self.cause)?; Ok(()) } } -impl Error for HttpClientError {} +impl error::Error for HttpClientError {} /// Error kinds which can indicate the type of a `HttpClientError`. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -155,7 +190,7 @@ impl ErrorKind { /// # Examples /// /// ``` - /// use ylong_http_client::ErrorKind; + /// # use ylong_http_client::ErrorKind; /// /// assert_eq!(ErrorKind::UserAborted.as_str(), "User Aborted Error"); /// ``` @@ -175,31 +210,63 @@ impl ErrorKind { } } -/// Messages for summarizing the cause of the error -pub(crate) struct CauseMessage(String); +pub(crate) enum Cause { + NoReason, + Io(io::Error), + Msg(&'static str), + Other(Box), +} -impl CauseMessage { - pub(crate) fn new(message: &str) -> Self { - Self(message.to_string()) +impl Debug for Cause { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::NoReason => write!(f, "No reason"), + Self::Io(err) => Debug::fmt(err, f), + Self::Msg(msg) => write!(f, "{}", msg), + Self::Other(err) => Debug::fmt(err, f), + } } } -impl Debug for CauseMessage { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - f.write_str(self.0.as_str()) +impl Display for Cause { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::NoReason => write!(f, "No reason"), + Self::Io(err) => Display::fmt(err, f), + Self::Msg(msg) => write!(f, "{}", msg), + Self::Other(err) => Display::fmt(err, f), + } } } -impl Display for CauseMessage { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - f.write_str(self.0.as_str()) - } +macro_rules! err_from_other { + ($kind: ident, $err: expr) => {{ + use crate::error::{ErrorKind, HttpClientError}; + + Err(HttpClientError::from_error(ErrorKind::$kind, $err)) + }}; +} + +macro_rules! err_from_io { + ($kind: ident, $err: expr) => {{ + use crate::error::{ErrorKind, HttpClientError}; + + Err(HttpClientError::from_io_error(ErrorKind::$kind, $err)) + }}; } -impl Error for CauseMessage {} +macro_rules! err_from_msg { + ($kind: ident, $msg: literal) => {{ + use crate::error::{ErrorKind, HttpClientError}; + + Err(HttpClientError::from_str(ErrorKind::$kind, $msg)) + }}; +} #[cfg(test)] mod ut_util_error { + use std::io; + use crate::{ErrorKind, HttpClientError}; /// UT test cases for `ErrorKind::as_str`. @@ -233,31 +300,46 @@ mod ut_util_error { fn ut_err_kind() { let user_aborted = HttpClientError::user_aborted(); assert_eq!(user_aborted.error_kind(), ErrorKind::UserAborted); - let other = HttpClientError::other(Some("error")); + let other = HttpClientError::other(user_aborted); assert_eq!(other.error_kind(), ErrorKind::Other); } - /// UT test cases for `HttpClientError::new_with_cause` function. + /// UT test cases for `HttpClientError::from_io_error` function. /// /// # Brief - /// 1. Calls `HttpClientError::new_with_cause`. + /// 1. Calls `HttpClientError::from_io_error`. /// 2. Checks if the results are correct. #[test] - fn ut_err_with_cause() { - let error_build = HttpClientError::new_with_cause(ErrorKind::Build, Some("error")); + fn ut_err_from_io_error() { + let error_build = + HttpClientError::from_io_error(ErrorKind::Build, io::Error::from(io::ErrorKind::Other)); + assert_eq!(error_build.error_kind(), ErrorKind::Build); + } + + /// UT test cases for `HttpClientError::from_error` function. + /// + /// # Brief + /// 1. Calls `HttpClientError::from_error`. + /// 2. Checks if the results are correct. + #[test] + fn ut_err_from_error() { + let error_build = HttpClientError::from_error( + ErrorKind::Build, + HttpClientError::from_str(ErrorKind::Request, "test error"), + ); assert_eq!(error_build.error_kind(), ErrorKind::Build); } - /// UT test cases for `HttpClientError::new_with_message` function. + /// UT test cases for `HttpClientError::from_str` function. /// /// # Brief - /// 1. Calls `HttpClientError::new_with_message`. + /// 1. Calls `HttpClientError::from_str`. /// 2. Checks if the results are correct. #[test] - fn ut_err_with_message() { - let error_request = HttpClientError::new_with_message(ErrorKind::Request, "error"); + fn ut_err_from_str() { + let error_request = HttpClientError::from_str(ErrorKind::Request, "error"); assert_eq!(error_request.error_kind(), ErrorKind::Request); - let error_timeout = HttpClientError::new_with_message(ErrorKind::Timeout, "error"); + let error_timeout = HttpClientError::from_str(ErrorKind::Timeout, "error"); assert_eq!( format!("{:?}", error_timeout), "HttpClientError { ErrorKind: Timeout, Cause: error }" diff --git a/ylong_http_client/src/lib.rs b/ylong_http_client/src/lib.rs index 7a20864..aebced7 100644 --- a/ylong_http_client/src/lib.rs +++ b/ylong_http_client/src/lib.rs @@ -16,7 +16,8 @@ //! response. //! //! # Supported HTTP Version -//! - HTTP1.1 +//! - HTTP/1.1 +//! - HTTP/2 // TODO: Need doc. // ylong_http crate re-export. @@ -33,11 +34,15 @@ pub use ylong_http::response::status::StatusCode; pub use ylong_http::response::ResponsePart; pub use ylong_http::version::Version; -#[cfg(all(feature = "async", any(feature = "http1_1", feature = "http2")))] -pub mod async_impl; +#[macro_use] +#[cfg(all( + any(feature = "async", feature = "sync"), + any(feature = "http1_1", feature = "http2"), +))] +mod error; #[cfg(all(feature = "async", any(feature = "http1_1", feature = "http2")))] -pub use async_impl::{Body, RequestBuilder, Response}; +pub mod async_impl; #[cfg(all(feature = "sync", any(feature = "http1_1", feature = "http2")))] pub mod sync_impl; @@ -46,44 +51,42 @@ pub mod sync_impl; any(feature = "async", feature = "sync"), any(feature = "http1_1", feature = "http2"), ))] -mod error; +pub(crate) mod util; #[cfg(all( any(feature = "async", feature = "sync"), any(feature = "http1_1", feature = "http2"), ))] pub use error::{ErrorKind, HttpClientError}; - -#[cfg(all( - any(feature = "async", feature = "sync"), - any(feature = "http1_1", feature = "http2"), -))] -pub mod util; - -#[cfg(all(feature = "tokio_base", feature = "http2"))] -pub(crate) use tokio::sync::{ - mpsc::{error::TryRecvError, unbounded_channel, UnboundedReceiver, UnboundedSender}, - Mutex as AsyncMutex, MutexGuard, -}; -#[cfg(all(feature = "tokio_base", feature = "async"))] -pub(crate) use tokio::{ - io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf}, - net::TcpStream, - time::{sleep, timeout, Sleep}, -}; #[cfg(all( any(feature = "async", feature = "sync"), any(feature = "http1_1", feature = "http2"), ))] pub use util::*; -#[cfg(all(feature = "ylong_base", feature = "http2"))] -pub(crate) use ylong_runtime::sync::{ - mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, - Mutex as AsyncMutex, MutexGuard, RecvError as TryRecvError, -}; -#[cfg(all(feature = "ylong_base", feature = "async"))] -pub(crate) use ylong_runtime::{ - io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf}, - net::TcpStream, - time::{sleep, timeout, Sleep}, -}; + +// Runtime components import adapter. +#[cfg(any(feature = "tokio_base", feature = "ylong_base"))] +pub(crate) mod runtime { + #[cfg(all(feature = "tokio_base", feature = "http2"))] + pub(crate) use tokio::sync::{ + mpsc::{error::TryRecvError, unbounded_channel, UnboundedReceiver, UnboundedSender}, + Mutex as AsyncMutex, MutexGuard, + }; + #[cfg(all(feature = "tokio_base", feature = "async"))] + pub(crate) use tokio::{ + io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf}, + net::TcpStream, + time::{sleep, timeout, Sleep}, + }; + #[cfg(all(feature = "ylong_base", feature = "http2"))] + pub(crate) use ylong_runtime::sync::{ + mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, + Mutex as AsyncMutex, MutexGuard, RecvError as TryRecvError, + }; + #[cfg(feature = "ylong_base")] + pub(crate) use ylong_runtime::{ + io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf}, + net::TcpStream, + time::{sleep, timeout, Sleep}, + }; +} diff --git a/ylong_http_client/src/sync_impl/client.rs b/ylong_http_client/src/sync_impl/client.rs index 8abd750..f9f65ea 100644 --- a/ylong_http_client/src/sync_impl/client.rs +++ b/ylong_http_client/src/sync_impl/client.rs @@ -12,20 +12,17 @@ // limitations under the License. use ylong_http::request::uri::Uri; -// TODO: Adapter, remove this later. -use ylong_http::response::Response; -use super::{Body, Connector, HttpBody, HttpConnector}; +use super::{Body, Connector, HttpBody, HttpConnector, Request, Response}; use crate::error::HttpClientError; use crate::sync_impl::conn; use crate::sync_impl::pool::ConnPool; -use crate::util::normalizer::RequestFormatter; -use crate::util::proxy::Proxies; -use crate::util::redirect::TriggerKind; -use crate::util::{ +use crate::util::config::{ ClientConfig, ConnectorConfig, HttpConfig, HttpVersion, Proxy, Redirect, Timeout, }; -use crate::Request; +use crate::util::normalizer::RequestFormatter; +use crate::util::proxy::Proxies; +use crate::util::redirect::{RedirectInfo, Trigger}; /// HTTP synchronous client implementation. Users can use `Client` to /// send `Request` synchronously. `Client` depends on a `Connector` that @@ -34,8 +31,7 @@ use crate::Request; /// # Examples /// /// ```no_run -/// use ylong_http_client::sync_impl::Client; -/// use ylong_http_client::{EmptyBody, Request}; +/// use ylong_http_client::sync_impl::{Client, EmptyBody, Request}; /// /// // Creates a new `Client`. /// let client = Client::new(); @@ -51,7 +47,7 @@ use crate::Request; /// ``` pub struct Client { inner: ConnPool, - client_config: ClientConfig, + config: ClientConfig, } impl Client { @@ -92,7 +88,7 @@ impl Client { pub fn with_connector(connector: C) -> Self { Self { inner: ConnPool::new(connector), - client_config: ClientConfig::new(), + config: ClientConfig::new(), } } @@ -102,8 +98,7 @@ impl Client { /// # Examples /// /// ```no_run - /// use ylong_http_client::sync_impl::Client; - /// use ylong_http_client::{EmptyBody, Request}; + /// use ylong_http_client::sync_impl::{Client, EmptyBody, Request}; /// /// let client = Client::new(); /// let response = client.request(Request::new(EmptyBody)); @@ -112,7 +107,7 @@ impl Client { &self, mut request: Request, ) -> Result, HttpClientError> { - RequestFormatter::new(&mut request).normalize()?; + RequestFormatter::new(&mut request).format()?; self.retry_send_request(request) } @@ -120,7 +115,7 @@ impl Client { &self, mut request: Request, ) -> Result, HttpClientError> { - let mut retries = self.client_config.retry.times().unwrap_or(0); + let mut retries = self.config.retry.times().unwrap_or(0); loop { let response = self.send_request_retryable(&mut request); if response.is_ok() || retries == 0 { @@ -140,33 +135,23 @@ impl Client { fn redirect_request( &self, - mut response: Response, + response: Response, request: &mut Request, ) -> Result, HttpClientError> { - let mut redirected_list = vec![]; - let mut dst_uri = Uri::default(); + let mut response = response; + let mut info = RedirectInfo::new(); loop { - if Redirect::is_redirect(response.status().clone(), request) { - redirected_list.push(request.uri().clone()); - let trigger = Redirect::get_redirect( - &mut dst_uri, - &self.client_config.redirect, - &redirected_list, - &response, - request, - )?; - - match trigger { - TriggerKind::NextLink => { - response = conn::request(self.inner.connect_to(dst_uri.clone())?, request)?; - continue; - } - TriggerKind::Stop => { - return Ok(response); - } + match self + .config + .redirect + .inner() + .redirect(request, &mut response, &mut info)? + { + Trigger::NextLink => { + RequestFormatter::new(request).format()?; + response = self.send_request_with_uri(request.uri().clone(), request)?; } - } else { - return Ok(response); + Trigger::Stop => return Ok(response), } } } @@ -334,7 +319,7 @@ impl ClientBuilder { Ok(Client { inner: ConnPool::new(connector), - client_config: self.client, + config: self.client, }) } } diff --git a/ylong_http_client/src/sync_impl/conn/http1.rs b/ylong_http_client/src/sync_impl/conn/http1.rs index bfa41f7..43a5437 100644 --- a/ylong_http_client/src/sync_impl/conn/http1.rs +++ b/ylong_http_client/src/sync_impl/conn/http1.rs @@ -15,14 +15,13 @@ use std::io::{Read, Write}; use ylong_http::body::sync_impl::Body; use ylong_http::h1::{RequestEncoder, ResponseDecoder}; -// TODO: Adapter, remove this later. +use ylong_http::request::Request; use ylong_http::response::Response; use crate::error::{ErrorKind, HttpClientError}; use crate::sync_impl::conn::StreamData; use crate::sync_impl::HttpBody; use crate::util::dispatcher::http1::Http1Conn; -use crate::Request; const TEMP_BUF_SIZE: usize = 16 * 1024; @@ -45,7 +44,7 @@ where if let Some(part) = encode_part.as_mut() { let size = part .encode(&mut buf[write..]) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::Request, e))?; write += size; if size == 0 { encode_part = None; @@ -55,9 +54,9 @@ where if write < buf.len() { if let Some(body) = encode_body.as_mut() { - let size = body.data(&mut buf[write..]).map_err(|e| { - HttpClientError::new_with_cause(ErrorKind::BodyTransfer, Some(e)) - })?; + let size = body + .data(&mut buf[write..]) + .map_err(|e| HttpClientError::from_error(ErrorKind::BodyTransfer, e))?; write += size; if size == 0 { encode_body = None; @@ -68,7 +67,7 @@ where if write == buf.len() { conn.raw_mut() .write_all(&buf[..write]) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::Request, e))?; write = 0; } } @@ -76,7 +75,7 @@ where if write != 0 { conn.raw_mut() .write_all(&buf[..write]) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::Request, e))?; } // Decodes response part. @@ -86,11 +85,11 @@ where let size = conn .raw_mut() .read(buf.as_mut_slice()) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::Request, e))?; match decoder.decode(&buf[..size]) { Ok(None) => {} Ok(Some((part, rem))) => break (part, rem), - Err(e) => return Err(HttpClientError::new_with_cause(ErrorKind::Request, Some(e))), + Err(e) => return err_from_other!(Request, e), } } }; @@ -100,13 +99,13 @@ where let chunked = part .headers .get("Transfer-Encoding") - .map(|v| v.to_str().unwrap_or_default()) + .map(|v| v.to_string().unwrap_or(String::new())) .and_then(|s| s.find("chunked")) .is_some(); let content_length = part .headers .get("Content-Length") - .map(|v| v.to_str().unwrap_or_default()) + .map(|v| v.to_string().unwrap_or(String::new())) .and_then(|s| s.parse::().ok()); let is_trailer = part.headers.get("Trailer").is_some(); @@ -116,10 +115,7 @@ where (false, Some(len), _) => HttpBody::text(len, pre, Box::new(conn)), (false, None, true) => HttpBody::empty(), _ => { - return Err(HttpClientError::new_with_message( - ErrorKind::Request, - "Invalid Response Format", - )) + return err_from_msg!(Request, "Invalid response format"); } } }; diff --git a/ylong_http_client/src/sync_impl/conn/mod.rs b/ylong_http_client/src/sync_impl/conn/mod.rs index 0c94355..a2f2d27 100644 --- a/ylong_http_client/src/sync_impl/conn/mod.rs +++ b/ylong_http_client/src/sync_impl/conn/mod.rs @@ -13,10 +13,7 @@ use std::io::{Read, Write}; -use ylong_http::body::sync_impl::Body; -use ylong_http::request::Request; -use ylong_http::response::Response; - +use super::{Body, Request, Response}; use crate::error::HttpClientError; use crate::sync_impl::HttpBody; use crate::util::dispatcher::Conn; diff --git a/ylong_http_client/src/sync_impl/connector.rs b/ylong_http_client/src/sync_impl/connector.rs index b535975..c19fe05 100644 --- a/ylong_http_client/src/sync_impl/connector.rs +++ b/ylong_http_client/src/sync_impl/connector.rs @@ -15,7 +15,7 @@ use std::io::{Read, Write}; use ylong_http::request::uri::Uri; -use crate::util::ConnectorConfig; +use crate::util::config::ConnectorConfig; /// `Connector` trait used by `Client`. `Connector` provides synchronous /// connection establishment interfaces. @@ -102,7 +102,7 @@ pub mod tls_conn { .proxy_info() .basic_auth .as_ref() - .and_then(|v| v.to_str().ok()); + .and_then(|v| v.to_string().ok()); is_proxy = true; } @@ -112,13 +112,14 @@ pub mod tls_conn { }; match *uri.scheme().unwrap() { - Scheme::HTTP => Ok(MixStream::Http(TcpStream::connect(addr).map_err(|e| { - HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)) - })?)), + Scheme::HTTP => { + Ok(MixStream::Http(TcpStream::connect(addr).map_err(|e| { + HttpClientError::from_error(ErrorKind::Connect, e) + })?)) + } Scheme::HTTPS => { - let tcp_stream = TcpStream::connect(addr).map_err(|e| { - HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)) - })?; + let tcp_stream = TcpStream::connect(addr) + .map_err(|e| HttpClientError::from_error(ErrorKind::Connect, e))?; let tcp_stream = if is_proxy { tunnel(tcp_stream, host, port, auth)? @@ -126,13 +127,16 @@ pub mod tls_conn { tcp_stream }; - let tls_ssl = self.config.tls.ssl_new(&host_name).map_err(|e| { - HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)) - })?; + let tls_ssl = self + .config + .tls + .ssl_new(&host_name) + .map_err(|e| HttpClientError::from_error(ErrorKind::Connect, e))?; - let stream = tls_ssl.into_inner().connect(tcp_stream).map_err(|e| { - HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)) - })?; + let stream = tls_ssl + .into_inner() + .connect(tcp_stream) + .map_err(|e| HttpClientError::from_error(ErrorKind::Connect, e))?; Ok(MixStream::Https(stream)) } } @@ -161,7 +165,7 @@ pub mod tls_conn { write!(&mut req, "\r\n").unwrap(); conn.write_all(&req) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::Connect, e))?; let mut buf = [0; 8192]; let mut pos = 0; @@ -169,10 +173,10 @@ pub mod tls_conn { loop { let n = conn .read(&mut buf[pos..]) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::Connect, e))?; if n == 0 { - return Err(HttpClientError::new_with_message( + return Err(HttpClientError::from_str( ErrorKind::Connect, "Error receiving from proxy", )); @@ -185,18 +189,18 @@ pub mod tls_conn { return Ok(conn); } if pos == buf.len() { - return Err(HttpClientError::new_with_message( + return Err(HttpClientError::from_str( ErrorKind::Connect, "proxy headers too long for tunnel", )); } } else if resp.starts_with(b"HTTP/1.1 407") { - return Err(HttpClientError::new_with_message( + return Err(HttpClientError::from_str( ErrorKind::Connect, "proxy authentication required", )); } else { - return Err(HttpClientError::new_with_message( + return Err(HttpClientError::from_str( ErrorKind::Connect, "unsuccessful tunnel", )); diff --git a/ylong_http_client/src/sync_impl/http_body.rs b/ylong_http_client/src/sync_impl/http_body.rs index 37ecaa7..4bafd28 100644 --- a/ylong_http_client/src/sync_impl/http_body.rs +++ b/ylong_http_client/src/sync_impl/http_body.rs @@ -27,8 +27,7 @@ use crate::sync_impl::conn::StreamData; /// # Examples /// /// ```no_run -/// use ylong_http_client::sync_impl::{Body, Client, HttpBody}; -/// use ylong_http_client::{EmptyBody, Request}; +/// use ylong_http_client::sync_impl::{Body, Client, EmptyBody, HttpBody, Request}; /// /// let mut client = Client::new(); /// @@ -111,7 +110,7 @@ impl Body for HttpBody { fn trailer(&mut self) -> Result, Self::Error> { match self.kind { Kind::Chunk(ref mut chunk) => chunk.decoder.get_trailer().map_err(|_| { - HttpClientError::new_with_message(ErrorKind::BodyDecode, "Get trailer failed") + HttpClientError::from_str(ErrorKind::BodyDecode, "Get trailer failed") }), _ => Ok(None), } @@ -140,10 +139,7 @@ impl Text { if let Some(io) = self.io.take() { io.shutdown(); }; - return Err(HttpClientError::new_with_message( - ErrorKind::BodyDecode, - "Not Eof", - )); + return Err(HttpClientError::from_str(ErrorKind::BodyDecode, "Not Eof")); } (true, true) => { self.io = None; @@ -160,7 +156,7 @@ impl Text { // Disconnected. Ok(0) => { io.shutdown(); - return Err(HttpClientError::new_with_message( + return Err(HttpClientError::from_str( ErrorKind::BodyDecode, "Response Body Incomplete", )); @@ -172,7 +168,7 @@ impl Text { match (text.is_complete(), rem.is_empty()) { (true, false) => { io.shutdown(); - return Err(HttpClientError::new_with_message( + return Err(HttpClientError::from_str( ErrorKind::BodyDecode, "Not Eof", )); @@ -182,12 +178,7 @@ impl Text { } self.io = Some(io); } - Err(e) => { - return Err(HttpClientError::new_with_cause( - ErrorKind::BodyTransfer, - Some(e), - )) - } + Err(e) => return Err(HttpClientError::from_error(ErrorKind::BodyTransfer, e)), } } } @@ -246,7 +237,7 @@ impl Chunk { Ok(filled) => { if filled == 0 { io.shutdown(); - return Err(HttpClientError::new_with_message( + return Err(HttpClientError::from_str( ErrorKind::BodyDecode, "Response Body Incomplete", )); @@ -263,12 +254,7 @@ impl Chunk { return Ok(read); } } - Err(e) => { - return Err(HttpClientError::new_with_cause( - ErrorKind::BodyTransfer, - Some(e), - )) - } + Err(e) => return Err(HttpClientError::from_error(ErrorKind::BodyTransfer, e)), } } Ok(read) @@ -293,7 +279,7 @@ impl Chunk { let (chunks, junk) = self .decoder .decode(buf) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::BodyDecode, e))?; let mut finished = false; let mut ptrs = Vec::new(); @@ -313,7 +299,7 @@ impl Chunk { } if finished && !junk.is_empty() { - return Err(HttpClientError::new_with_message( + return Err(HttpClientError::from_str( ErrorKind::BodyDecode, "Invalid Chunk Body", )); diff --git a/ylong_http_client/src/sync_impl/mod.rs b/ylong_http_client/src/sync_impl/mod.rs index f63e836..94414bb 100644 --- a/ylong_http_client/src/sync_impl/mod.rs +++ b/ylong_http_client/src/sync_impl/mod.rs @@ -23,7 +23,7 @@ //! synchronously. This module provides `Connector` trait and a `HttpConnector` //! which implements the trait. -// TODO: Reconstruct `sync_impl`. +// TODO: Reconstruct `sync_impl`, or reuse `async_impl`? mod client; mod conn; @@ -38,7 +38,8 @@ pub(crate) use connector::HttpConnector; pub use http_body::HttpBody; pub use reader::{BodyProcessError, BodyProcessor, BodyReader, DefaultBodyProcessor}; pub use ylong_http::body::sync_impl::Body; -// TODO: Adapter, remove this later. +pub use ylong_http::body::{EmptyBody, TextBody}; +pub use ylong_http::request::Request; pub use ylong_http::response::Response; #[cfg(feature = "__tls")] diff --git a/ylong_http_client/src/sync_impl/pool.rs b/ylong_http_client/src/sync_impl/pool.rs index afd2f53..db37b38 100644 --- a/ylong_http_client/src/sync_impl/pool.rs +++ b/ylong_http_client/src/sync_impl/pool.rs @@ -16,11 +16,12 @@ use std::io::{Read, Write}; use std::mem::take; use std::sync::{Arc, Mutex}; +use ylong_http::request::uri::Uri; + use crate::error::{ErrorKind, HttpClientError}; use crate::sync_impl::Connector; use crate::util::dispatcher::{Conn, ConnDispatcher, Dispatcher}; use crate::util::pool::{Pool, PoolKey}; -use crate::Uri; pub(crate) struct ConnPool { pool: Pool>, @@ -91,8 +92,7 @@ impl Conns { Ok(conn) } else { let dispatcher = ConnDispatcher::http1( - connect_fn() - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)))?, + connect_fn().map_err(|e| HttpClientError::from_error(ErrorKind::Connect, e))?, ); // We must be able to get the `Conn` here. let conn = dispatcher.dispatch().unwrap(); diff --git a/ylong_http_client/src/sync_impl/reader.rs b/ylong_http_client/src/sync_impl/reader.rs index 1dbd24c..38a8f87 100644 --- a/ylong_http_client/src/sync_impl/reader.rs +++ b/ylong_http_client/src/sync_impl/reader.rs @@ -26,8 +26,7 @@ use crate::ErrorKind; /// # Examples /// /// ``` -/// use ylong_http_client::sync_impl::{BodyProcessError, BodyProcessor, BodyReader}; -/// use ylong_http_client::TextBody; +/// use ylong_http_client::sync_impl::{BodyProcessError, BodyProcessor, BodyReader, TextBody}; /// /// // Defines a processor, which provides read and echo ability. /// struct Processor { @@ -89,7 +88,7 @@ impl BodyReader { /// /// ``` /// use ylong_http_client::sync_impl::{BodyReader, DefaultBodyProcessor}; - /// use ylong_http_client::util::Timeout; + /// use ylong_http_client::Timeout; /// /// let reader = BodyReader::new(DefaultBodyProcessor::new()).read_timeout(Timeout::none()); /// ``` @@ -108,8 +107,7 @@ impl BodyReader { /// # Examples /// /// ``` - /// use ylong_http_client::sync_impl::{BodyProcessor, BodyReader}; - /// use ylong_http_client::TextBody; + /// use ylong_http_client::sync_impl::{BodyProcessor, BodyReader, TextBody}; /// /// let mut body = TextBody::from_bytes(b"HelloWorld"); /// let _ = BodyReader::default().read_all(&mut body); @@ -125,18 +123,18 @@ impl BodyReader { loop { let read_len = body .data(&mut buf) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::BodyDecode, e))?; if read_len == 0 { self.processor .progress(written) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::BodyDecode, e))?; break; } self.processor .write(&buf[..read_len]) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::BodyDecode, e))?; written += read_len; @@ -144,7 +142,7 @@ impl BodyReader { if now.duration_since(last) >= Duration::from_secs(1) { self.processor .progress(written) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::BodyDecode, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::BodyDecode, e))?; } last = now; } diff --git a/ylong_http_client/src/util/c_openssl/adapter.rs b/ylong_http_client/src/util/c_openssl/adapter.rs index 66e7afb..587a7c0 100644 --- a/ylong_http_client/src/util/c_openssl/adapter.rs +++ b/ylong_http_client/src/util/c_openssl/adapter.rs @@ -15,22 +15,21 @@ use std::net::IpAddr; use std::path::Path; use std::sync::Arc; -use crate::error::HttpClientError; +use crate::error::{ErrorKind, HttpClientError}; use crate::util::c_openssl::error::ErrorStack; use crate::util::c_openssl::ssl::{ Ssl, SslContext, SslContextBuilder, SslFiletype, SslMethod, SslVersion, }; -use crate::util::c_openssl::x509::{X509Ref, X509Store, X509}; +use crate::util::c_openssl::x509::{X509Store, X509}; use crate::util::config::tls::DefaultCertVerifier; use crate::util::AlpnProtocolList; -use crate::{CertVerifier, ErrorKind, ServerCerts}; /// `TlsContextBuilder` implementation based on `SSL_CTX`. /// /// # Examples /// /// ``` -/// use ylong_http_client::util::{TlsConfigBuilder, TlsVersion}; +/// use ylong_http_client::{TlsConfigBuilder, TlsVersion}; /// /// let context = TlsConfigBuilder::new() /// .ca_file("ca.crt") @@ -55,7 +54,7 @@ impl TlsConfigBuilder { /// # Examples /// /// ``` - /// use ylong_http_client::util::TlsConfigBuilder; + /// use ylong_http_client::TlsConfigBuilder; /// /// let builder = TlsConfigBuilder::new(); /// ``` @@ -77,7 +76,7 @@ impl TlsConfigBuilder { /// # Examples /// /// ``` - /// use ylong_http_client::util::TlsConfigBuilder; + /// use ylong_http_client::TlsConfigBuilder; /// /// let builder = TlsConfigBuilder::new().ca_file("ca.crt"); /// ``` @@ -97,7 +96,7 @@ impl TlsConfigBuilder { /// # Examples /// /// ``` - /// use ylong_http_client::util::{TlsConfigBuilder, TlsVersion}; + /// use ylong_http_client::{TlsConfigBuilder, TlsVersion}; /// /// let builder = TlsConfigBuilder::new().max_proto_version(TlsVersion::TLS_1_2); /// ``` @@ -119,7 +118,7 @@ impl TlsConfigBuilder { /// # Examples /// /// ``` - /// use ylong_http_client::util::{TlsConfigBuilder, TlsVersion}; + /// use ylong_http_client::{TlsConfigBuilder, TlsVersion}; /// /// let builder = TlsConfigBuilder::new().min_proto_version(TlsVersion::TLS_1_2); /// ``` @@ -143,7 +142,7 @@ impl TlsConfigBuilder { /// # Examples /// /// ``` - /// use ylong_http_client::util::TlsConfigBuilder; + /// use ylong_http_client::TlsConfigBuilder; /// /// let builder = TlsConfigBuilder::new() /// .cipher_list("DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK"); @@ -168,7 +167,7 @@ impl TlsConfigBuilder { /// # Examples /// /// ``` - /// use ylong_http_client::util::TlsConfigBuilder; + /// use ylong_http_client::TlsConfigBuilder; /// /// let builder = TlsConfigBuilder::new() /// .cipher_suites("DEFAULT:!aNULL:!eNULL:!MD5:!3DES:!DES:!RC4:!IDEA:!SEED:!aDSS:!SRP:!PSK"); @@ -190,7 +189,7 @@ impl TlsConfigBuilder { /// # Examples /// /// ``` - /// use ylong_http_client::util::{TlsConfigBuilder, TlsFileType}; + /// use ylong_http_client::{TlsConfigBuilder, TlsFileType}; /// /// let builder = TlsConfigBuilder::new().certificate_file("cert.pem", TlsFileType::PEM); /// ``` @@ -212,7 +211,7 @@ impl TlsConfigBuilder { /// # Examples /// /// ``` - /// use ylong_http_client::util::TlsConfigBuilder; + /// use ylong_http_client::TlsConfigBuilder; /// /// let builder = TlsConfigBuilder::new().certificate_chain_file("cert.pem"); /// ``` @@ -261,7 +260,7 @@ impl TlsConfigBuilder { /// /// # Examples /// ``` - /// use ylong_http_client::util::TlsConfigBuilder; + /// use ylong_http_client::TlsConfigBuilder; /// /// let protocols = b"\x06spdy/1\x08http/1.1"; /// let builder = TlsConfigBuilder::new().alpn_protos(protocols); @@ -282,7 +281,7 @@ impl TlsConfigBuilder { /// /// # Examples /// ``` - /// use ylong_http_client::util::{AlpnProtocol, AlpnProtocolList, TlsConfigBuilder}; + /// use ylong_http_client::{AlpnProtocol, AlpnProtocolList, TlsConfigBuilder}; /// /// let protocols = AlpnProtocolList::new() /// .extend(AlpnProtocol::SPDY1) @@ -324,7 +323,7 @@ impl TlsConfigBuilder { /// # Examples /// /// ``` - /// use ylong_http_client::util::TlsConfigBuilder; + /// use ylong_http_client::TlsConfigBuilder; /// /// let builder = TlsConfigBuilder::new().danger_accept_invalid_certs(true); /// ``` @@ -353,7 +352,7 @@ impl TlsConfigBuilder { /// # Examples /// /// ``` - /// use ylong_http_client::util::TlsConfigBuilder; + /// use ylong_http_client::TlsConfigBuilder; /// /// let builder = TlsConfigBuilder::new().danger_accept_invalid_hostnames(true); /// ``` @@ -379,7 +378,7 @@ impl TlsConfigBuilder { /// # Examples /// /// ``` - /// use ylong_http_client::util::TlsConfigBuilder; + /// use ylong_http_client::TlsConfigBuilder; /// /// let builder = TlsConfigBuilder::new().sni(true); /// ``` @@ -394,7 +393,7 @@ impl TlsConfigBuilder { /// # Examples /// /// ``` - /// use ylong_http_client::util::{TlsConfigBuilder, TlsVersion}; + /// use ylong_http_client::{TlsConfigBuilder, TlsVersion}; /// /// let context = TlsConfigBuilder::new() /// .ca_file("ca.crt") @@ -421,7 +420,7 @@ impl TlsConfigBuilder { let ctx = self .inner .map(|builder| builder.build()) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Build, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::Build, e))?; Ok(TlsConfig { ctx, @@ -444,13 +443,14 @@ impl Default for TlsConfigBuilder { /// # Examples /// /// ``` -/// use ylong_http_client::util::TlsConfig; +/// use ylong_http_client::TlsConfig; /// /// let builder = TlsConfig::builder(); /// ``` #[derive(Clone)] pub struct TlsConfig { ctx: SslContext, + #[allow(dead_code)] cert_verifier: Option>, use_sni: bool, verify_hostname: bool, @@ -462,7 +462,7 @@ impl TlsConfig { /// # Examples /// /// ``` - /// use ylong_http_client::util::TlsConfig; + /// use ylong_http_client::TlsConfig; /// /// let builder = TlsConfig::builder(); /// ``` @@ -512,7 +512,7 @@ impl TlsSsl { /// # Examples /// /// ``` -/// use ylong_http_client::util::TlsVersion; +/// use ylong_http_client::TlsVersion; /// /// let version = TlsVersion::TLS_1_2; /// ``` @@ -538,7 +538,7 @@ impl TlsVersion { /// identifier of the format of a certificate or key file. /// /// ``` -/// use ylong_http_client::util::TlsFileType; +/// use ylong_http_client::TlsFileType; /// /// let file_type = TlsFileType::PEM; /// ``` @@ -592,7 +592,7 @@ impl Cert { /// ``` pub fn from_pem(pem: &[u8]) -> Result { Ok(Self(X509::from_pem(pem).map_err(|e| { - HttpClientError::new_with_cause(ErrorKind::Build, Some(e)) + HttpClientError::from_error(ErrorKind::Build, e) })?)) } @@ -609,28 +609,18 @@ impl Cert { /// ``` pub fn from_der(der: &[u8]) -> Result { Ok(Self(X509::from_der(der).map_err(|e| { - HttpClientError::new_with_cause(ErrorKind::Build, Some(e)) + HttpClientError::from_error(ErrorKind::Build, e) })?)) } /// Deserializes a list of PEM-formatted certificates. pub fn stack_from_pem(pem: &[u8]) -> Result, HttpClientError> { Ok(X509::stack_from_pem(pem) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Build, Some(e)))? + .map_err(|e| HttpClientError::from_error(ErrorKind::Build, e))? .into_iter() .map(Self) .collect()) } - - /// Gets a reference to `X509Ref`. - pub(crate) fn as_ref(&self) -> &X509Ref { - self.0.as_ref() - } - - /// Consumes `X509` and then takes `X509`. - pub(crate) fn into_inner(self) -> X509 { - self.0 - } } /// Represents a server X509 certificates. @@ -660,7 +650,7 @@ impl Certificate { /// Deserializes a list of PEM-formatted certificates. pub fn from_pem(pem: &[u8]) -> Result { let cert_list = X509::stack_from_pem(pem) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Build, Some(e)))? + .map_err(|e| HttpClientError::from_error(ErrorKind::Build, e))? .into_iter() .map(Cert) .collect(); @@ -684,9 +674,6 @@ impl Certificate { #[cfg(test)] mod ut_openssl_adapter { - use std::io::{Read, Write}; - use std::net::TcpStream; - use crate::util::c_openssl::adapter::CertificateList; use crate::util::{Cert, TlsConfigBuilder, TlsFileType, TlsVersion}; use crate::{AlpnProtocol, AlpnProtocolList, Certificate}; @@ -874,6 +861,9 @@ mod ut_openssl_adapter { #[cfg(feature = "sync")] #[test] fn ut_tls_ssl_verify_hostname() { + use std::io::{Read, Write}; + use std::net::TcpStream; + let config = TlsConfigBuilder::new() .sni(false) .danger_accept_invalid_hostnames(false) diff --git a/ylong_http_client/src/util/c_openssl/ffi/callback.rs b/ylong_http_client/src/util/c_openssl/ffi/callback.rs index 2f3627c..04d133c 100644 --- a/ylong_http_client/src/util/c_openssl/ffi/callback.rs +++ b/ylong_http_client/src/util/c_openssl/ffi/callback.rs @@ -13,7 +13,6 @@ use std::ffi::{c_int, c_void}; -use crate::util::c_openssl::ffi::ssl::SSL_CTX; use crate::util::c_openssl::ffi::x509::X509_STORE_CTX; use crate::util::c_openssl::foreign::ForeignRef; use crate::util::c_openssl::x509::X509StoreContextRef; diff --git a/ylong_http_client/src/util/c_openssl/ffi/ssl.rs b/ylong_http_client/src/util/c_openssl/ffi/ssl.rs index d34432d..f8a837f 100644 --- a/ylong_http_client/src/util/c_openssl/ffi/ssl.rs +++ b/ylong_http_client/src/util/c_openssl/ffi/ssl.rs @@ -14,7 +14,7 @@ use libc::{c_char, c_int, c_long, c_uchar, c_uint, c_void}; use super::bio::BIO; -use super::x509::{C_X509, X509_STORE, X509_STORE_CTX, X509_VERIFY_PARAM}; +use super::x509::{X509_STORE, X509_STORE_CTX, X509_VERIFY_PARAM}; /// This is the global context structure which is created by a server or client /// once per program life-time and which holds mainly default values for the diff --git a/ylong_http_client/src/util/c_openssl/ssl/ctx.rs b/ylong_http_client/src/util/c_openssl/ssl/ctx.rs index 8011401..dd8d36c 100644 --- a/ylong_http_client/src/util/c_openssl/ssl/ctx.rs +++ b/ylong_http_client/src/util/c_openssl/ssl/ctx.rs @@ -14,8 +14,6 @@ use core::{fmt, mem, ptr}; use std::ffi::CString; use std::path::Path; -use std::ptr::{null, null_mut}; -use std::sync::Weak; use libc::{c_int, c_long, c_uint, c_void}; @@ -34,12 +32,8 @@ use crate::util::c_openssl::ffi::ssl::{ SSL_CTX_use_certificate_file, SSL_CTX, }; use crate::util::c_openssl::foreign::{Foreign, ForeignRef}; -use crate::util::c_openssl::x509::{X509Ref, X509}; use crate::util::c_openssl::{cert_verify, check_ptr, check_ret, ssl_init}; use crate::util::config::tls::DefaultCertVerifier; -use crate::{CertVerifier, ServerCerts}; - -const SSL_CTRL_EXTRA_CHAIN_CERT: c_int = 14; const SSL_CTRL_SET_MIN_PROTO_VERSION: c_int = 123; const SSL_CTRL_SET_MAX_PROTO_VERSION: c_int = 124; diff --git a/ylong_http_client/src/util/c_openssl/ssl/error.rs b/ylong_http_client/src/util/c_openssl/ssl/error.rs index f291d5f..b32b70e 100644 --- a/ylong_http_client/src/util/c_openssl/ssl/error.rs +++ b/ylong_http_client/src/util/c_openssl/ssl/error.rs @@ -139,7 +139,9 @@ impl fmt::Display for SslErrorCode { #[derive(Debug)] pub(crate) enum HandshakeError { SetupFailure(ErrorStack), + #[allow(dead_code)] Failure(MidHandshakeSslStream), + #[allow(dead_code)] WouldBlock(MidHandshakeSslStream), } diff --git a/ylong_http_client/src/util/c_openssl/ssl/mod.rs b/ylong_http_client/src/util/c_openssl/ssl/mod.rs index 3559b6d..5e7e0d3 100644 --- a/ylong_http_client/src/util/c_openssl/ssl/mod.rs +++ b/ylong_http_client/src/util/c_openssl/ssl/mod.rs @@ -19,7 +19,7 @@ mod ssl_base; mod stream; mod version; -pub(crate) use ctx::{SslContext, SslContextBuilder, SSL_VERIFY_NONE, SSL_VERIFY_PEER}; +pub(crate) use ctx::{SslContext, SslContextBuilder, SSL_VERIFY_NONE}; pub(crate) use error::{InternalError, SslError, SslErrorCode}; pub(crate) use filetype::SslFiletype; pub(crate) use method::SslMethod; diff --git a/ylong_http_client/src/util/c_openssl/ssl/ssl_base.rs b/ylong_http_client/src/util/c_openssl/ssl/ssl_base.rs index 37251de..9eb5616 100644 --- a/ylong_http_client/src/util/c_openssl/ssl/ssl_base.rs +++ b/ylong_http_client/src/util/c_openssl/ssl/ssl_base.rs @@ -13,17 +13,21 @@ use core::{cmp, ffi, fmt, str}; use std::ffi::CString; +#[cfg(feature = "sync")] use std::io::{Read, Write}; use libc::{c_char, c_int, c_long, c_void}; +#[cfg(feature = "sync")] use super::error::HandshakeError; -use super::{MidHandshakeSslStream, SslContext, SslErrorCode, SslStream}; +#[cfg(feature = "sync")] +use super::SslStream; +use super::{SslContext, SslErrorCode}; use crate::c_openssl::check_ret; use crate::c_openssl::ffi::bio::BIO; use crate::c_openssl::ffi::ssl::{ - SSL_connect, SSL_ctrl, SSL_get0_param, SSL_get_error, SSL_get_rbio, SSL_get_verify_result, - SSL_read, SSL_state_string_long, SSL_write, + SSL_ctrl, SSL_get0_param, SSL_get_error, SSL_get_rbio, SSL_get_verify_result, SSL_read, + SSL_state_string_long, SSL_write, }; use crate::c_openssl::foreign::ForeignRef; use crate::c_openssl::x509::{ @@ -57,6 +61,9 @@ impl Ssl { where S: Read + Write, { + use super::MidHandshakeSslStream; + use crate::c_openssl::ffi::ssl::SSL_connect; + let mut stream = SslStream::new_base(self, stream)?; let ret = unsafe { SSL_connect(stream.ssl.as_ptr()) }; if ret > 0 { diff --git a/ylong_http_client/src/util/c_openssl/stack.rs b/ylong_http_client/src/util/c_openssl/stack.rs index 3f22216..43e961f 100644 --- a/ylong_http_client/src/util/c_openssl/stack.rs +++ b/ylong_http_client/src/util/c_openssl/stack.rs @@ -113,13 +113,6 @@ impl StackRef { } } } - - pub(crate) fn iter(&self) -> StackRefIter { - StackRefIter { - stack: self, - index: 0..self.len() as c_int, - } - } } impl ForeignRef for StackRef { diff --git a/ylong_http_client/src/util/c_openssl/x509.rs b/ylong_http_client/src/util/c_openssl/x509.rs index a60bbf3..c64dad5 100644 --- a/ylong_http_client/src/util/c_openssl/x509.rs +++ b/ylong_http_client/src/util/c_openssl/x509.rs @@ -16,7 +16,7 @@ use core::{ffi, fmt, ptr, str}; use std::ffi::CString; use std::net::IpAddr; -use libc::{c_char, c_int, c_long, c_uint}; +use libc::{c_int, c_long, c_uint}; use super::bio::BioSlice; use super::error::{error_get_lib, error_get_reason, ErrorStack}; diff --git a/ylong_http_client/src/util/config/http.rs b/ylong_http_client/src/util/config/http.rs index aff6586..fed252e 100644 --- a/ylong_http_client/src/util/config/http.rs +++ b/ylong_http_client/src/util/config/http.rs @@ -58,119 +58,31 @@ pub(crate) mod http2 { const DEFAULT_MAX_HEADER_LIST_SIZE: u32 = 16 << 20; /// Settings which can be used to configure a http2 connection. - /// - /// # Examples - /// - /// ``` - /// use ylong_http_client::util::H2Config; - /// - /// let config = H2Config::new() - /// .set_header_table_size(4096) - /// .set_max_header_list_size(16 << 20) - /// .set_max_frame_size(2 << 13); - /// ``` #[derive(Clone)] - pub struct H2Config { - max_frame_size: u32, - max_header_list_size: u32, - header_table_size: u32, + pub(crate) struct H2Config { + pub(crate) max_frame_size: u32, + pub(crate) max_header_list_size: u32, + pub(crate) header_table_size: u32, } impl H2Config { /// `H2Config` constructor. - /// - /// # Examples - /// - /// ``` - /// use ylong_http_client::util::H2Config; - /// - /// let config = H2Config::new(); - /// ``` - pub fn new() -> Self { + pub(crate) fn new() -> Self { Self::default() } - /// Sets the SETTINGS_MAX_FRAME_SIZE. - /// - /// # Examples - /// - /// ``` - /// use ylong_http_client::util::H2Config; - /// - /// let config = H2Config::new().set_max_frame_size(2 << 13); - /// ``` - pub fn set_max_frame_size(mut self, size: u32) -> Self { - self.max_frame_size = size; - self - } - - /// Sets the SETTINGS_MAX_HEADER_LIST_SIZE. - /// - /// # Examples - /// - /// ``` - /// use ylong_http_client::util::H2Config; - /// - /// let config = H2Config::new().set_max_header_list_size(16 << 20); - /// ``` - pub fn set_max_header_list_size(mut self, size: u32) -> Self { - self.max_header_list_size = size; - self - } - - /// Sets the SETTINGS_HEADER_TABLE_SIZE. - /// - /// # Examples - /// - /// ``` - /// use ylong_http_client::util::H2Config; - /// - /// let config = H2Config::new().set_max_header_list_size(4096); - /// ``` - pub fn set_header_table_size(mut self, size: u32) -> Self { - self.header_table_size = size; - self - } - /// Gets the SETTINGS_MAX_FRAME_SIZE. - /// - /// # Examples - /// - /// ``` - /// use ylong_http_client::util::H2Config; - /// - /// let config = H2Config::new().set_max_frame_size(2 << 13); - /// assert_eq!(config.max_frame_size(), 2 << 13); - /// ``` - pub fn max_frame_size(&self) -> u32 { + pub(crate) fn max_frame_size(&self) -> u32 { self.max_frame_size } /// Gets the SETTINGS_MAX_HEADER_LIST_SIZE. - /// - /// # Examples - /// - /// ``` - /// use ylong_http_client::util::H2Config; - /// - /// let config = H2Config::new().set_max_header_list_size(16 << 20); - /// assert_eq!(config.max_header_list_size(), 16 << 20); - /// ``` - pub fn max_header_list_size(&self) -> u32 { + pub(crate) fn max_header_list_size(&self) -> u32 { self.max_header_list_size } /// Gets the SETTINGS_MAX_FRAME_SIZE. - /// - /// # Examples - /// - /// ``` - /// use ylong_http_client::util::H2Config; - /// - /// let config = H2Config::new().set_header_table_size(4096); - /// assert_eq!(config.header_table_size(), 4096); - /// ``` - pub fn header_table_size(&self) -> u32 { + pub(crate) fn header_table_size(&self) -> u32 { self.header_table_size } } diff --git a/ylong_http_client/src/util/config/mod.rs b/ylong_http_client/src/util/config/mod.rs index f638f11..1b21d5d 100644 --- a/ylong_http_client/src/util/config/mod.rs +++ b/ylong_http_client/src/util/config/mod.rs @@ -24,7 +24,7 @@ pub use settings::{Proxy, ProxyBuilder, Redirect, Retry, SpeedLimit, Timeout}; #[cfg(feature = "__tls")] pub(crate) mod tls; #[cfg(feature = "http2")] -pub use http::http2::H2Config; +pub(crate) use http::http2::H2Config; #[cfg(feature = "__tls")] pub use tls::{AlpnProtocol, AlpnProtocolList, CertVerifier, ServerCerts}; #[cfg(feature = "tls_rust_ssl")] diff --git a/ylong_http_client/src/util/config/settings.rs b/ylong_http_client/src/util/config/settings.rs index d1a7dde..a2fe7aa 100644 --- a/ylong_http_client/src/util/config/settings.rs +++ b/ylong_http_client/src/util/config/settings.rs @@ -14,14 +14,8 @@ use core::cmp; use core::time::Duration; -use ylong_http::request::uri::Uri; -use ylong_http::request::Request; -use ylong_http::response::status::StatusCode; -use ylong_http::response::Response; - -use crate::error::{ErrorKind, HttpClientError}; -use crate::util::redirect::{RedirectStrategy, TriggerKind}; -use crate::util::{proxy, redirect as redirect_util}; +use crate::error::HttpClientError; +use crate::util::{proxy, redirect}; /// Redirects settings of requests. /// @@ -33,43 +27,27 @@ use crate::util::{proxy, redirect as redirect_util}; /// // The default maximum number of redirects is 10. /// let redirect = Redirect::default(); /// -/// // No redirect +/// // No redirect. /// let no_redirect = Redirect::none(); /// /// // Custom the number of redirects. /// let max = Redirect::limited(10); /// ``` -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct Redirect(Option); +#[derive(Clone, Debug, Eq, PartialEq, Default)] +pub struct Redirect(redirect::Redirect); impl Redirect { - /// Gets the strategy of redirects. - /// - /// # Examples - /// - /// ``` - /// # use ylong_http_client::util::Redirect; - /// - /// # let redirect = Redirect::limited(10); - /// let strategy = redirect.redirect_strategy(); - /// - /// # assert!(strategy.is_some()); - /// ``` - pub fn redirect_strategy(&self) -> Option<&redirect_util::RedirectStrategy> { - self.0.as_ref() - } - /// Sets max number of redirects. /// /// # Examples /// /// ``` - /// # use ylong_http_client::util::Redirect; + /// # use ylong_http_client::Redirect; /// /// let redirect = Redirect::limited(10); /// ``` pub fn limited(max: usize) -> Self { - Self(Some(RedirectStrategy::limited(max))) + Self(redirect::Redirect::limited(max)) } /// Sets unlimited number of redirects. @@ -77,12 +55,12 @@ impl Redirect { /// # Examples /// /// ``` - /// # use ylong_http_client::util::Redirect; + /// # use ylong_http_client::Redirect; /// /// let redirect = Redirect::no_limit(); /// ``` pub fn no_limit() -> Self { - Self(Some(RedirectStrategy::limited(usize::MAX))) + Self(redirect::Redirect::limited(usize::MAX)) } /// Stops redirects. @@ -95,34 +73,11 @@ impl Redirect { /// let redirect = Redirect::none(); /// ``` pub fn none() -> Self { - Self(Some(RedirectStrategy::none())) - } - - pub(crate) fn get_redirect( - dst_uri: &mut Uri, - redirect: &Redirect, - redirect_list: &[Uri], - response: &Response, - request: &mut Request, - ) -> Result { - redirect_util::Redirect::get_trigger_kind( - dst_uri, - redirect, - redirect_list, - response, - request, - ) + Self(redirect::Redirect::none()) } - pub(crate) fn is_redirect(status_code: StatusCode, request: &mut Request) -> bool { - redirect_util::Redirect::check_redirect(status_code, request) - } -} - -impl Default for Redirect { - // redirect default limit 10 times - fn default() -> Self { - Self(Some(RedirectStrategy::default())) + pub(crate) fn inner(&self) -> &redirect::Redirect { + &self.0 } } @@ -154,17 +109,14 @@ impl Retry { /// # Examples /// /// ``` - /// use ylong_http_client::util::Retry; + /// use ylong_http_client::Retry; /// /// assert!(Retry::new(1).is_ok()); /// assert!(Retry::new(10).is_err()); /// ``` pub fn new(times: usize) -> Result { if times >= Self::MAX_RETRIES { - return Err(HttpClientError::new_with_message( - ErrorKind::Build, - "Invalid Retry Times", - )); + return err_from_msg!(Build, "Invalid params"); } Ok(Self(Some(times))) } @@ -202,7 +154,7 @@ impl Retry { /// # Examples /// /// ``` - /// use ylong_http_client::util::Retry; + /// use ylong_http_client::Retry; /// /// assert!(Retry::default().times().is_none()); /// ``` @@ -512,118 +464,7 @@ impl ProxyBuilder { #[cfg(test)] mod ut_settings { - use ylong_http::h1::ResponseDecoder; - use ylong_http::request::uri::Uri; - use ylong_http::request::Request; - use ylong_http::response::status::StatusCode; - use ylong_http::response::Response; - - use crate::error::HttpClientError; - use crate::util::redirect as redirect_util; - use crate::util::redirect::TriggerKind; - use crate::{Redirect, Retry}; - - fn create_trigger( - redirect: &Redirect, - previous: &[Uri], - ) -> Result { - let redirect_status = redirect_util::RedirectStatus::new(previous); - redirect.0.as_ref().unwrap().get_trigger(redirect_status) - } - /// UT test cases for `Redirect::is_redirect`. - /// - /// # Brief - /// 1. Creates a `request` by calling `request::new`. - /// 2. Uses `redirect::is_redirect` to check whether is redirected. - /// 3. Checks if the result is true. - #[test] - fn ut_setting_is_redirect() { - let mut request = Request::new("this is a body"); - let code = StatusCode::MOVED_PERMANENTLY; - let res = Redirect::is_redirect(code, &mut request); - assert!(res); - } - /// UT test cases for `Redirect::get_redirect` error branch. - /// - /// # Brief - /// 1. Creates a `redirect` by calling `Redirect::default`. - /// 2. Uses `Redirect::get_redirect` to get redirected trigger kind. - /// 3. Checks if the results are error. - #[test] - fn ut_setting_get_redirect_kind_err() { - let response_str = "HTTP/1.1 304 \r\nAge: \t 270646 \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(); - let mut decoder = ResponseDecoder::new(); - let result = decoder.decode(response_str).unwrap().unwrap(); - let response = Response::from_raw_parts(result.0, result.1); - let mut request = Request::new("this is a body"); - let mut uri = Uri::default(); - let redirect = Redirect::default(); - let redirect_list: Vec = vec![]; - let res = - Redirect::get_redirect(&mut uri, &redirect, &redirect_list, &response, &mut request); - assert!(res.is_err()); - } - - /// UT test cases for `Redirect::default`. - /// - /// # Brief - /// 1. Creates a `Redirect` by calling `Redirect::default`. - /// 2. Uses `Redirect::create_trigger` to get redirected uri. - /// 3. Checks if the results are correct. - #[test] - fn ut_setting_redirect_default() { - let redirect = Redirect::default(); - let next = Uri::from_bytes(b"http://example.com").unwrap(); - let previous = (0..9) - .map(|i| Uri::from_bytes(format!("http://example{i}.com").as_bytes()).unwrap()) - .collect::>(); - - let redirect_uri = match create_trigger(&redirect, &previous).unwrap() { - TriggerKind::NextLink => next.to_string(), - TriggerKind::Stop => previous.get(9).unwrap().to_string(), - }; - assert_eq!(redirect_uri, "http://example.com".to_string()); - } - - /// UT test cases for `Redirect::max_limit`. - /// - /// # Brief - /// 1. Creates a `Redirect` by calling `Redirect::max_limit`. - /// 2. Sets redirect times which is over max limitation times. - /// 2. Uses `Redirect::create_trigger` to get redirected uri. - /// 3. Checks if the results are err. - #[test] - fn ut_setting_redirect_over_redirect_max() { - let redirect = Redirect::limited(10); - let previous = (0..10) - .map(|i| Uri::from_bytes(format!("http://example{i}.com").as_bytes()).unwrap()) - .collect::>(); - - if let Ok(other) = create_trigger(&redirect, &previous) { - panic!("unexpected {:?}", other); - }; - } - - /// UT test cases for `Redirect::no_redirect`. - /// - /// # Brief - /// 1. Creates a `Redirect` by calling `Redirect::no_redirect`. - /// 2. Uses `Redirect::create_trigger` but get origin uri. - /// 3. Checks if the results are correct. - #[test] - fn ut_setting_no_redirect() { - let redirect = Redirect::none(); - let next = Uri::from_bytes(b"http://example.com").unwrap(); - let previous = (0..1) - .map(|i| Uri::from_bytes(format!("http://example{i}.com").as_bytes()).unwrap()) - .collect::>(); - - let redirect_uri = match create_trigger(&redirect, &previous).unwrap() { - TriggerKind::NextLink => next.to_string(), - TriggerKind::Stop => previous.get(0).unwrap().to_string(), - }; - assert_eq!(redirect_uri, "http://example0.com".to_string()); - } + use crate::Retry; /// UT test cases for `Retry::new`. /// diff --git a/ylong_http_client/src/util/config/tls/alpn/mod.rs b/ylong_http_client/src/util/config/tls/alpn/mod.rs index cecefb4..ac26d4e 100644 --- a/ylong_http_client/src/util/config/tls/alpn/mod.rs +++ b/ylong_http_client/src/util/config/tls/alpn/mod.rs @@ -20,7 +20,7 @@ /// /// # Examples /// ``` -/// use ylong_http_client::util::AlpnProtocol; +/// use ylong_http_client::AlpnProtocol; /// /// let alpn = AlpnProtocol::HTTP11; /// assert_eq!(alpn.as_use_bytes(), b"\x08http/1.1"); @@ -115,7 +115,7 @@ impl AlpnProtocol { /// /// # Examples /// ``` -/// use ylong_http_client::util::{AlpnProtocol, AlpnProtocolList}; +/// use ylong_http_client::{AlpnProtocol, AlpnProtocolList}; /// /// let list = AlpnProtocolList::new() /// .extend(AlpnProtocol::SPDY1) diff --git a/ylong_http_client/src/util/config/tls/verifier/openssl.rs b/ylong_http_client/src/util/config/tls/verifier/openssl.rs index 842078d..2c93111 100644 --- a/ylong_http_client/src/util/config/tls/verifier/openssl.rs +++ b/ylong_http_client/src/util/config/tls/verifier/openssl.rs @@ -12,11 +12,10 @@ // limitations under the License. use std::mem::forget; -use std::ops::Deref; use libc::c_int; -use crate::util::c_openssl::x509::{X509Ref, X509Store, X509StoreContextRef, X509}; +use crate::util::c_openssl::x509::{X509StoreContextRef, X509}; use crate::{ErrorKind, HttpClientError}; /// ServerCerts is provided to fetch info from X509 pub struct ServerCerts<'a> { @@ -44,7 +43,7 @@ impl<'a> ServerCerts<'a> { let cert = self .inner .get_current_cert() - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::Connect, e))?; Ok(cert.get_cert_version() as usize) } @@ -64,10 +63,10 @@ impl<'a> ServerCerts<'a> { let cert = self .inner .get_current_cert() - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::Connect, e))?; let cert_name = cert .get_cert_name() - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::Connect, e))?; let mut buf = [0u8; 128]; let size = 128; let res = cert_name.get_x509_name_info(buf.as_mut(), size as c_int); @@ -91,10 +90,10 @@ impl<'a> ServerCerts<'a> { let cert = self .inner .get_current_cert() - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::Connect, e))?; let cert_issuer = cert .get_issuer_name() - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::Connect, e))?; let mut buf = [0u8; 128]; let size = 128; let res = cert_issuer.get_x509_name_info(buf.as_mut(), size as c_int); @@ -122,12 +121,12 @@ impl<'a> ServerCerts<'a> { let cert = self .inner .get_current_cert() - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::Connect, e))?; let cert_key = cert .get_cert() - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Connect, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::Connect, e))?; let target_cert = X509::from_pem(target_pem) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Build, Some(e)))?; + .map_err(|e| HttpClientError::from_error(ErrorKind::Build, e))?; Ok(target_cert.cmp_certs(cert_key) as usize) } } diff --git a/ylong_http_client/src/util/dispatcher.rs b/ylong_http_client/src/util/dispatcher.rs index 30c367e..b1d5f63 100644 --- a/ylong_http_client/src/util/dispatcher.rs +++ b/ylong_http_client/src/util/dispatcher.rs @@ -61,6 +61,7 @@ pub(crate) enum Conn { #[cfg(feature = "http1_1")] pub(crate) mod http1 { + use std::cell::UnsafeCell; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -79,7 +80,7 @@ pub(crate) mod http1 { } pub(crate) struct Inner { - pub(crate) io: S, + pub(crate) io: UnsafeCell, // `occupied` indicates that the connection is occupied. Only one coroutine // can get the handle at the same time. Once the handle is fetched, the flag // position is true. @@ -88,11 +89,13 @@ pub(crate) mod http1 { pub(crate) shutdown: AtomicBool, } + unsafe impl Sync for Inner {} + impl Http1Dispatcher { pub(crate) fn new(io: S) -> Self { Self { inner: Arc::new(Inner { - io, + io: UnsafeCell::new(io), occupied: AtomicBool::new(false), shutdown: AtomicBool::new(false), }), @@ -124,11 +127,10 @@ pub(crate) mod http1 { } impl Http1Conn { - // TODO: Use `UnsafeCell` instead when `Arc::get_mut_unchecked` become stable. pub(crate) fn raw_mut(&mut self) -> &mut S { // SAFETY: In the case of `HTTP1`, only one coroutine gets the handle // at the same time. - &mut unsafe { &mut *(Arc::as_ptr(&self.inner) as *mut Inner) }.io + unsafe { &mut *self.inner.io.get() } } pub(crate) fn shutdown(&self) { @@ -163,12 +165,12 @@ pub(crate) mod http2 { use super::{ConnDispatcher, Dispatcher}; use crate::dispatcher::http2::StreamState::Closed; - use crate::error::HttpClientError; - use crate::util::H2Config; - use crate::{ - unbounded_channel, AsyncMutex, AsyncRead, AsyncWrite, ErrorKind, MutexGuard, ReadBuf, - TryRecvError, UnboundedReceiver, UnboundedSender, + use crate::error::{ErrorKind, HttpClientError}; + use crate::runtime::{ + unbounded_channel, AsyncMutex, AsyncRead, AsyncWrite, MutexGuard, ReadBuf, TryRecvError, + UnboundedReceiver, UnboundedSender, }; + use crate::util::config::H2Config; impl ConnDispatcher { pub(crate) fn http2(config: H2Config, io: S) -> Self { @@ -616,17 +618,11 @@ pub(crate) mod http2 { let (tx, rx) = unbounded_channel::(); self.stream_info.receiver.set_receiver(rx); self.sender.send((Some((self.id, tx)), frame)).map_err(|_| { - HttpClientError::new_with_cause( - ErrorKind::Request, - Some(String::from("resend")), - ) + HttpClientError::from_error(ErrorKind::Request, String::from("resend")) }) } else { self.sender.send((None, frame)).map_err(|_| { - HttpClientError::new_with_cause( - ErrorKind::Request, - Some(String::from("resend")), - ) + HttpClientError::from_error(ErrorKind::Request, String::from("resend")) }) } } @@ -1459,7 +1455,7 @@ pub(crate) mod http2 { #[cfg(test)] mod ut_dispatch { - use crate::dispatcher::{Conn, ConnDispatcher, Dispatcher}; + use crate::dispatcher::{ConnDispatcher, Dispatcher}; /// UT test cases for `ConnDispatcher::is_shutdown`. /// diff --git a/ylong_http_client/src/util/mod.rs b/ylong_http_client/src/util/mod.rs index 49d3fbd..ce15ec9 100644 --- a/ylong_http_client/src/util/mod.rs +++ b/ylong_http_client/src/util/mod.rs @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Http client Util module. +//! Ylong http client utility module. //! //! A tool module that supports various functions of the http client. //! @@ -19,28 +19,21 @@ //! -[`HttpConfig`] is used to configure `HTTP` related logic. //! -[`HttpVersion`] is used to provide Http Version. -#![allow(dead_code)] -#![allow(unused_imports)] - +pub(crate) mod base64; pub(crate) mod config; -#[cfg(feature = "__tls")] -pub use config::{AlpnProtocol, AlpnProtocolList, CertVerifier, ServerCerts}; -pub(crate) use config::{ClientConfig, ConnectorConfig, HttpConfig, HttpVersion}; -pub use config::{Proxy, ProxyBuilder, Redirect, Retry, SpeedLimit, Timeout}; +pub(crate) mod normalizer; +pub(crate) mod pool; +pub(crate) mod proxy; +pub(crate) mod redirect; #[cfg(feature = "__c_openssl")] pub(crate) mod c_openssl; -#[cfg(feature = "__c_openssl")] -pub use c_openssl::{Cert, Certificate, TlsConfig, TlsConfigBuilder, TlsFileType, TlsVersion}; -#[cfg(feature = "http2")] -pub use config::H2Config; #[cfg(any(feature = "http1_1", feature = "http2"))] pub(crate) mod dispatcher; -pub(crate) mod normalizer; -pub(crate) mod pool; - -pub(crate) mod base64; -pub(crate) mod proxy; -pub(crate) mod redirect; +#[cfg(feature = "__c_openssl")] +pub use c_openssl::{Cert, Certificate, TlsConfig, TlsConfigBuilder, TlsFileType, TlsVersion}; +#[cfg(feature = "__tls")] +pub use config::{AlpnProtocol, AlpnProtocolList, CertVerifier, ServerCerts}; +pub use config::{Proxy, ProxyBuilder, Redirect, Retry, SpeedLimit, Timeout}; diff --git a/ylong_http_client/src/util/normalizer.rs b/ylong_http_client/src/util/normalizer.rs index d762e79..d2d4812 100644 --- a/ylong_http_client/src/util/normalizer.rs +++ b/ylong_http_client/src/util/normalizer.rs @@ -11,16 +11,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -// TODO: Remove this file later. - use ylong_http::request::method::Method; -use ylong_http::request::uri::{Host, Scheme}; +use ylong_http::request::uri::{Scheme, Uri}; use ylong_http::request::Request; use ylong_http::response::status::StatusCode; use ylong_http::response::ResponsePart; use ylong_http::version::Version; -use crate::{ErrorKind, HttpClientError, Uri}; +use crate::error::{ErrorKind, HttpClientError}; pub(crate) struct RequestFormatter<'a, T> { part: &'a mut Request, @@ -31,7 +29,13 @@ impl<'a, T> RequestFormatter<'a, T> { Self { part } } - pub(crate) fn normalize(&mut self) -> Result<(), HttpClientError> { + pub(crate) fn format(&mut self) -> Result<(), HttpClientError> { + if Version::HTTP1_0 == *self.part.version() && Method::CONNECT == *self.part.method() { + return Err(HttpClientError::from_str( + ErrorKind::Request, + "Unknown METHOD in HTTP/1.0", + )); + } // TODO Formatting the uri in the request doesn't seem necessary. let uri_formatter = UriFormatter::new(); uri_formatter.format(self.part.uri_mut())?; @@ -42,12 +46,10 @@ impl<'a, T> RequestFormatter<'a, T> { let _ = self.part.headers_mut().insert("Accept", "*/*"); } - if self.part.headers_mut().get("Host").is_none() { - let _ = self - .part - .headers_mut() - .insert("Host", host_value.as_bytes()); - } + let _ = self + .part + .headers_mut() + .insert("Host", host_value.as_bytes()); Ok(()) } @@ -63,12 +65,7 @@ impl UriFormatter { pub(crate) fn format(&self, uri: &mut Uri) -> Result<(), HttpClientError> { let host = match uri.host() { Some(host) => host.clone(), - None => { - return Err(HttpClientError::new_with_message( - ErrorKind::Request, - "No host in url", - )) - } + None => return err_from_msg!(Request, "No host in url"), }; #[cfg(feature = "__tls")] @@ -107,9 +104,9 @@ impl UriFormatter { new_uri = new_uri.query(query.clone()); } - *uri = new_uri.build().map_err(|_| { - HttpClientError::new_with_message(ErrorKind::Request, "Normalize url failed") - })?; + *uri = new_uri + .build() + .map_err(|_| HttpClientError::from_str(ErrorKind::Request, "Normalize url failed"))?; Ok(()) } @@ -145,13 +142,10 @@ impl<'a> BodyLengthParser<'a> { if transfer_encoding.is_some() { if self.part.version == Version::HTTP1_0 { - return Err(HttpClientError::new_with_message( - ErrorKind::Request, - "Illegal Transfer-Encoding in HTTP/1.0", - )); + return err_from_msg!(Request, "Illegal Transfer-Encoding in HTTP/1.0"); } let transfer_encoding_contains_chunk = transfer_encoding - .and_then(|v| v.to_str().ok()) + .and_then(|v| v.to_string().ok()) .and_then(|str| str.find("chunked")) .is_some(); @@ -167,7 +161,7 @@ impl<'a> BodyLengthParser<'a> { if content_length.is_some() { let content_length_valid = content_length - .and_then(|v| v.to_str().ok()) + .and_then(|v| v.to_string().ok()) .and_then(|s| s.parse::().ok()); return match content_length_valid { @@ -175,10 +169,7 @@ impl<'a> BodyLengthParser<'a> { // otherwise it will get stuck. Some(0) => Ok(BodyLength::Empty), Some(len) => Ok(BodyLength::Length(len)), - None => Err(HttpClientError::new_with_message( - ErrorKind::Request, - "Invalid response content-length", - )), + None => err_from_msg!(Request, "Invalid response content-length"), }; } @@ -199,7 +190,7 @@ pub(crate) fn format_host_value(uri: &Uri) -> Result { (Some(host), Some(port)) => { if port .as_u16() - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Request, Some(e)))? + .map_err(|e| HttpClientError::from_error(ErrorKind::Request, e))? == uri.scheme().unwrap_or(&Scheme::HTTP).default_port() { host.to_string() @@ -209,10 +200,7 @@ pub(crate) fn format_host_value(uri: &Uri) -> Result { } (Some(host), None) => host.to_string(), (None, _) => { - return Err(HttpClientError::new_with_message( - ErrorKind::Request, - "Request Uri lack host", - )); + return err_from_msg!(Request, "Request Uri lack host"); } }; Ok(host_value) @@ -224,7 +212,6 @@ mod ut_normalizer { use ylong_http::request::method::Method; use ylong_http::request::uri::{Uri, UriBuilder}; use ylong_http::request::Request; - use ylong_http::response::Response; use crate::normalizer::UriFormatter; use crate::util::normalizer::{ @@ -268,10 +255,10 @@ mod ut_normalizer { let request_uri = request.uri_mut(); *request_uri = Uri::from_bytes(b"http://example1.com").unwrap(); let mut formatter = RequestFormatter::new(&mut request); - let _ = formatter.normalize(); + let _ = formatter.format(); let (part, _) = request.into_parts(); let res = part.headers.get("Host").unwrap(); - assert_eq!(res.to_str().unwrap().as_bytes(), b"example1.com"); + assert_eq!(res.to_string().unwrap().as_bytes(), b"example1.com"); } /// UT test cases for `BodyLengthParser::parse`. diff --git a/ylong_http_client/src/util/pool.rs b/ylong_http_client/src/util/pool.rs index 4d9aa56..767f783 100644 --- a/ylong_http_client/src/util/pool.rs +++ b/ylong_http_client/src/util/pool.rs @@ -53,13 +53,13 @@ impl PoolKey { Self(scheme, authority) } } + #[cfg(test)] mod ut_pool { - use std::sync::{Arc, Mutex}; - use ylong_http::request::uri::Uri; use crate::pool::{Pool, PoolKey}; + /// UT test cases for `Pool::get`. /// /// # Brief diff --git a/ylong_http_client/src/util/proxy.rs b/ylong_http_client/src/util/proxy.rs index 3a7d5d9..09d8b28 100644 --- a/ylong_http_client/src/util/proxy.rs +++ b/ylong_http_client/src/util/proxy.rs @@ -16,14 +16,12 @@ use core::convert::TryFrom; use std::net::IpAddr; -use ylong_http::error::HttpError; use ylong_http::headers::HeaderValue; use ylong_http::request::uri::{Authority, Scheme, Uri}; use crate::error::HttpClientError; use crate::util::base64::encode; use crate::util::normalizer::UriFormatter; -use crate::ErrorKind; /// `Proxies` is responsible for managing a list of proxies. #[derive(Clone, Default)] @@ -158,7 +156,7 @@ impl ProxyInfo { let mut uri = match Uri::try_from(uri) { Ok(u) => u, Err(e) => { - return Err(HttpClientError::new_with_cause(ErrorKind::Build, Some(e))); + return err_from_other!(Build, e); } }; // Makes sure that all parts of uri exist. diff --git a/ylong_http_client/src/util/redirect.rs b/ylong_http_client/src/util/redirect.rs index a45a921..d25a41f 100644 --- a/ylong_http_client/src/util/redirect.rs +++ b/ylong_http_client/src/util/redirect.rs @@ -11,7 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use ylong_http::headers::Headers; use ylong_http::request::method::Method; use ylong_http::request::uri::Uri; use ylong_http::request::Request; @@ -19,409 +18,142 @@ use ylong_http::response::status::StatusCode; use ylong_http::response::Response; use crate::error::{ErrorKind, HttpClientError}; -use crate::util; -/// Redirect strategy supports limited times of redirection and no redirect -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct RedirectStrategy { - inner: StrategyKind, +#[derive(Debug, Clone, Eq, PartialEq)] +pub(crate) struct Redirect { + strategy: Strategy, } -#[derive(Clone, Debug, Eq, PartialEq)] -enum StrategyKind { - LimitTimes(usize), - NoRedirect, -} - -/// Redirect status supports to check response status and next -/// redirected uri -#[derive(Clone)] -pub struct RedirectStatus<'a> { - previous_uri: &'a [Uri], -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub(crate) struct Trigger { - inner: TriggerKind, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub(crate) enum TriggerKind { - NextLink, - Stop, -} - -impl RedirectStrategy { - pub(crate) fn limited(max: usize) -> Self { +impl Redirect { + pub(crate) fn limited(times: usize) -> Self { Self { - inner: StrategyKind::LimitTimes(max), + strategy: Strategy::LimitTimes(times), } } pub(crate) fn none() -> Self { Self { - inner: StrategyKind::NoRedirect, + strategy: Strategy::NoRedirect, } } - pub(crate) fn redirect(&self, status: RedirectStatus) -> Result { - match self.inner { - StrategyKind::LimitTimes(max) => { - if status.previous_uri.len() >= max { - Err(HttpClientError::new_with_message( - ErrorKind::Build, - "Over redirect max limit", - )) - } else { - Ok(status.transfer()) + pub(crate) fn redirect( + &self, + request: &mut Request, + response: &Response, + info: &mut RedirectInfo, + ) -> Result { + match response.status() { + StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND | StatusCode::SEE_OTHER => { + for header_name in UPDATED_HEADERS { + let _ = request.headers_mut().remove(header_name); + } + let method = request.method_mut(); + match *method { + Method::GET | Method::HEAD => {} + _ => *method = Method::GET, } } - StrategyKind::NoRedirect => Ok(status.stop()), + StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT => {} + _ => return Ok(Trigger::Stop), } - } - pub(crate) fn get_trigger( - &self, - redirect_status: RedirectStatus, - ) -> Result { - let trigger = self.redirect(redirect_status)?; - Ok(trigger.inner) - } -} - -impl Default for RedirectStrategy { - fn default() -> RedirectStrategy { - RedirectStrategy::limited(10) - } -} - -impl<'a> RedirectStatus<'a> { - pub(crate) fn new(previous_uri: &'a [Uri]) -> Self { - Self { previous_uri } - } - - fn transfer(self) -> Trigger { - Trigger { - inner: TriggerKind::NextLink, - } - } - - fn stop(self) -> Trigger { - Trigger { - inner: TriggerKind::Stop, + info.previous.push(request.uri().clone()); + + let mut location = response + .headers() + .get("Location") + .and_then(|value| value.to_string().ok()) + .and_then(|str| Uri::try_from(str.as_bytes()).ok()) + .ok_or(HttpClientError::from_str( + ErrorKind::Redirect, + "Illegal location header in response", + ))?; + + // If `location` doesn't have `scheme` or `authority`, adds scheme and + // authority of the origin request to it. + if location.scheme().is_none() || location.authority().is_none() { + let origin = request.uri(); + let scheme = origin.scheme().cloned(); + let authority = origin.authority().cloned(); + let (_, _, path, query) = location.into_parts(); + location = Uri::from_raw_parts(scheme, authority, path, query); } - } -} -pub(crate) struct Redirect; - -impl Redirect { - pub(crate) fn get_trigger_kind( - dst_uri: &mut Uri, - redirect: &util::Redirect, - redirect_list: &[Uri], - response: &Response, - request: &mut Request, - ) -> Result { - let location = match response.headers().get("location") { - Some(value) => value, - None => { - return Err(HttpClientError::new_with_message( - ErrorKind::Redirect, - "No location in response's headers", - )); + let trigger = self.strategy.trigger(info)?; + if let Trigger::NextLink = trigger { + if let Some(previous) = info.previous.last() { + if location.authority() != previous.authority() { + for header_name in SENSITIVE_HEADERS { + let _ = request.headers_mut().remove(header_name); + } + } } - }; - - let loc_str = location.to_str().unwrap(); - let loc_bytes = loc_str.as_str().trim_start_matches('/').as_bytes(); - - let mut loc_uri = Uri::from_bytes(loc_bytes) - .map_err(|e| HttpClientError::new_with_cause(ErrorKind::Redirect, Some(e)))?; - if loc_uri.scheme().is_none() || loc_uri.authority().is_none() { - // request uri is existed, so can use unwrap directly - let origin_scheme = request - .uri() - .scheme() - .ok_or_else(|| { - HttpClientError::new_with_message( - ErrorKind::Connect, - "No uri scheme in request", - ) - })? - .as_str(); - let auth = request - .uri() - .authority() - .ok_or_else(|| { - HttpClientError::new_with_message( - ErrorKind::Connect, - "No uri authority in request", - ) - })? - .to_str(); - let origin_auth = auth.as_str(); - loc_uri = Uri::builder() - .scheme(origin_scheme) - .authority(origin_auth) - // loc_uri is existed, so can use unwrap directly - .path( - loc_uri - .path() - .ok_or_else(|| { - HttpClientError::new_with_message( - ErrorKind::Connect, - "No loc_uri path in location", - ) - })? - .as_str(), - ) - .query( - loc_uri - .query() - .ok_or_else(|| { - HttpClientError::new_with_message( - ErrorKind::Connect, - "No loc_uri query in location", - ) - })? - .as_str(), - ) - .build() - .unwrap(); + *request.uri_mut() = location; } - let redirect_status = RedirectStatus::new(redirect_list); - let trigger = redirect - .redirect_strategy() - .unwrap() - .get_trigger(redirect_status)?; - - match trigger { - TriggerKind::NextLink => { - Self::remove_sensitive_headers(request.headers_mut(), &loc_uri, redirect_list); - *dst_uri = loc_uri.clone(); - *request.uri_mut() = loc_uri; - Ok(TriggerKind::NextLink) - } - TriggerKind::Stop => Ok(TriggerKind::Stop), - } + Ok(trigger) } +} - fn remove_sensitive_headers(headers: &mut Headers, next: &Uri, previous: &[Uri]) { - if let Some(previous) = previous.last() { - // TODO: Check this logic. - let cross_host = next.authority().unwrap() != previous.authority().unwrap(); - if cross_host { - let _ = headers.remove("authorization"); - let _ = headers.remove("cookie"); - let _ = headers.remove("cookie2"); - let _ = headers.remove("proxy_authorization"); - let _ = headers.remove("www_authenticate"); - } - } +impl Default for Redirect { + fn default() -> Self { + Self::limited(10) } +} - pub(crate) fn check_redirect(status_code: StatusCode, request: &mut Request) -> bool { - match status_code { - StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND | StatusCode::SEE_OTHER => { - Self::update_header_and_method(request); - true - } - StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT => true, - _ => false, - } - } +pub(crate) struct RedirectInfo { + previous: Vec, +} - fn update_header_and_method(request: &mut Request) { - for header_name in [ - "transfer_encoding", - "content_encoding", - "content_type", - "content_length", - "content_language", - "content_location", - "digest", - "last_modified", - ] { - let _ = request.headers_mut().remove(header_name); - } - let method = request.method_mut(); - match *method { - Method::GET | Method::HEAD => {} - _ => { - *method = Method::GET; - } +impl RedirectInfo { + pub(crate) fn new() -> Self { + Self { + previous: Vec::new(), } } } -#[cfg(test)] -mod ut_redirect { - use ylong_http::h1::ResponseDecoder; - use ylong_http::headers::Headers; - use ylong_http::request::uri::Uri; - use ylong_http::request::Request; - use ylong_http::response::status::StatusCode; - use ylong_http::response::Response; - - use crate::redirect::Redirect; - use crate::util::config::Redirect as setting_redirect; - use crate::util::redirect::{RedirectStatus, RedirectStrategy, TriggerKind}; - - /// UT test cases for `Redirect::remove_sensitive_headers`. - /// - /// # Brief - /// 1. Creates `next uri`, `pre uri` by calling `Headers::default`, - /// `Uri::from_bytes` - /// 2. Uses `redirect::remove_sensitive_headers` to check whether headers - /// remove sensitive info. - /// 3. Checks whether the authorization is removed. - #[test] - fn ut_remove_sensitive_headers() { - let mut headers = Headers::default(); - let next_uri = Uri::from_bytes(b"http://example1.com:80/foo?a=1").unwrap(); - let uri_content = Uri::from_bytes(b"http://example2.com:80/foo?a=1").unwrap(); - let pre_uri = vec![uri_content]; - Redirect::remove_sensitive_headers(&mut headers, &next_uri, &pre_uri); - let auth = headers.get("authorization"); - assert!(auth.is_none()); - } - - /// UT test cases for `Redirect::check_redirect`. - /// - /// # Brief - /// 1. Creates a `request` by calling `request::new`. - /// 2. Uses `redirect::check_redirect` to check whether is redirected. - /// 3. Checks if the result is true. - #[test] - fn ut_check_redirect() { - let mut request = Request::new("this is a body"); - let code = StatusCode::MOVED_PERMANENTLY; - let res = Redirect::check_redirect(code, &mut request); - assert!(res); - } - - /// UT test cases for `Redirect::get_trigger_kind`. - /// - /// # Brief - /// 1. Creates a `redirect` by calling `setting_redirect::default`. - /// 2. Uses `Redirect::get_trigger_kind` to get redirected trigger kind. - /// 3. Checks if the results are correct. - #[test] - fn ut_get_trigger_kind() { - 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(); - let mut decoder = ResponseDecoder::new(); - let result = decoder.decode(response_str).unwrap().unwrap(); - let response = Response::from_raw_parts(result.0, result.1); - let mut request = Request::new("this is a body"); - let request_uri = request.uri_mut(); - *request_uri = Uri::from_bytes(b"http://example1.com:80/foo?a=1").unwrap(); - let mut uri = Uri::default(); - let redirect = setting_redirect::default(); - let redirect_list: Vec = vec![]; - let res = Redirect::get_trigger_kind( - &mut uri, - &redirect, - &redirect_list, - &response, - &mut request, - ); - assert!(res.is_ok()); - } - - /// UT test cases for `Redirect::get_trigger_kind` err branch. - /// - /// # Brief - /// 1. Creates a `redirect` by calling `setting_redirect::default`. - /// 2. Uses `Redirect::get_trigger_kind` to get redirected trigger kind. - /// 3. Checks if the results are error. - #[test] - fn ut_get_trigger_kind_err() { - let response_str = "HTTP/1.1 304 \r\nAge: \t 270646 \t \t\r\nLocation: \t example3.com:80 \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(); - let mut decoder = ResponseDecoder::new(); - let result = decoder.decode(response_str).unwrap().unwrap(); - let response = Response::from_raw_parts(result.0, result.1); - let mut request = Request::new("this is a body"); - let request_uri = request.uri_mut(); - *request_uri = Uri::from_bytes(b"http://example1.com:80").unwrap(); - let mut uri = Uri::default(); - let redirect = setting_redirect::default(); - let redirect_list: Vec = vec![]; - let res = Redirect::get_trigger_kind( - &mut uri, - &redirect, - &redirect_list, - &response, - &mut request, - ); - assert!(res.is_err()); - } - - /// UT test cases for `RedirectStrategy::default`. - /// - /// # Brief - /// 1. Creates a `RedirectStrategy` by calling `RedirectStrategy::default`. - /// 2. Uses `RedirectStrategy::get_trigger` to get redirected uri. - /// 3. Checks if the results are correct. - #[test] - fn ut_redirect_default() { - let strategy = RedirectStrategy::default(); - let next = Uri::from_bytes(b"http://example.com").unwrap(); - let previous = (0..9) - .map(|i| Uri::from_bytes(format!("http://example{i}.com").as_bytes()).unwrap()) - .collect::>(); - - let redirect_uri = match strategy - .get_trigger(RedirectStatus::new(&previous)) - .unwrap() - { - TriggerKind::NextLink => next.to_string(), - TriggerKind::Stop => previous.get(9).unwrap().to_string(), - }; - assert_eq!(redirect_uri, "http://example.com".to_string()); - } - - /// UT test cases for `RedirectStrategy::limited`. - /// - /// # Brief - /// 1. Creates a `RedirectStrategy` by calling `RedirectStrategy::limited`. - /// 2. Sets redirect times which is over max limitation times. - /// 3. Uses `RedirectStrategy::get_trigger` to get redirected uri. - /// 4. Checks if the results are err. - #[test] - fn ut_redirect_over_redirect_max() { - let strategy = RedirectStrategy::limited(10); - let previous = (0..10) - .map(|i| Uri::from_bytes(format!("http://example{i}.com").as_bytes()).unwrap()) - .collect::>(); +#[derive(Debug, Clone, Eq, PartialEq)] +enum Strategy { + LimitTimes(usize), + NoRedirect, +} - if let Ok(other) = strategy.get_trigger(RedirectStatus::new(&previous)) { - panic!("unexpected {:?}", other); +impl Strategy { + fn trigger(&self, info: &RedirectInfo) -> Result { + match self { + Self::LimitTimes(max) => (info.previous.len() < *max) + .then_some(Trigger::NextLink) + .ok_or(HttpClientError::from_str( + ErrorKind::Build, + "Over redirect max limit", + )), + Self::NoRedirect => Ok(Trigger::Stop), } } +} - /// UT test cases for `RedirectStrategy::none`. - /// - /// # Brief - /// 1. Creates a `RedirectStrategy` by calling `RedirectStrategy::none`. - /// 2. Uses `RedirectStrategy::get_trigger` but get origin uri. - /// 3. Checks if the results are correct. - #[test] - fn ut_no_redirect() { - let strategy = RedirectStrategy::none(); - let next = Uri::from_bytes(b"http://example.com").unwrap(); - let previous = (0..1) - .map(|i| Uri::from_bytes(format!("http://example{i}.com").as_bytes()).unwrap()) - .collect::>(); - - let redirect_uri = match strategy - .get_trigger(RedirectStatus::new(&previous)) - .unwrap() - { - TriggerKind::NextLink => next.to_string(), - TriggerKind::Stop => previous.get(0).unwrap().to_string(), - }; - assert_eq!(redirect_uri, "http://example0.com".to_string()); - } +pub(crate) enum Trigger { + NextLink, + Stop, } + +const UPDATED_HEADERS: [&str; 8] = [ + "transfer_encoding", + "content_encoding", + "content_type", + "content_length", + "content_language", + "content_location", + "digest", + "last_modified", +]; + +const SENSITIVE_HEADERS: [&str; 5] = [ + "authorization", + "cookie", + "cookie2", + "proxy_authorization", + "www_authenticate", +]; diff --git a/ylong_http_client/tests/common/async_utils.rs b/ylong_http_client/tests/common/async_utils.rs index 458e64d..29c2fc5 100644 --- a/ylong_http_client/tests/common/async_utils.rs +++ b/ylong_http_client/tests/common/async_utils.rs @@ -59,7 +59,7 @@ macro_rules! async_client_test_case { let runtime = init_test_work_runtime($thread_num); // The number of servers may be variable based on the number of servers set by the user. - // However, cliipy checks that the variable does not need to be variable. + // However, clippy checks that the variable does not need to be variable. #[allow(unused_mut, unused_assignments)] let mut server_num = 1; $(server_num = $client_num;)? @@ -148,7 +148,7 @@ macro_rules! async_client_test_case { let runtime = init_test_work_runtime($thread_num); // The number of servers may be variable based on the number of servers set by the user. - // However, cliipy checks that the variable does not need to be variable. + // However, clippy checks that the variable does not need to be variable. #[allow(unused_mut, unused_assignments)] let mut server_num = 1; $(server_num = $client_num;)? @@ -328,23 +328,18 @@ macro_rules! async_client_assertions { Status: $status: expr, Version: $version: expr, $( - Header: $resp_n: expr, $resp_v: expr, + Header: $resp_n: expr, $resp_v: expr, )* Body: $resp_body: expr, },)* ) => { $( - let request = ylong_request!( - Request: { - Method: $method, - Host: $host, - Port: $handle.port, - $( - Header: $req_n, $req_v, - )* - Body: $req_body, - }, - ); + let request = ylong_http_client::async_impl::Request::builder() + .method($method) + .url(format!("{}:{}", $host, $handle.port).as_str()) + $(.header($req_n, $req_v))* + .body(ylong_http_client::async_impl::Body::slice($req_body)) + .expect("Request build failed"); let mut response = $client .request(request) @@ -358,7 +353,7 @@ macro_rules! async_client_assertions { .headers() .get($resp_n) .expect(format!("Get response header \"{}\" failed", $resp_n).as_str()) - .to_str() + .to_string() .expect(format!("Convert response header \"{}\"into string failed", $resp_n).as_str()), $resp_v, "Assert response header \"{}\" failed", $resp_n, @@ -367,7 +362,6 @@ macro_rules! async_client_assertions { let mut size = 0; loop { let read = response - .body_mut() .data(&mut buf[size..]).await .expect("Response body read failed"); if read == 0 { diff --git a/ylong_http_client/tests/common/mod.rs b/ylong_http_client/tests/common/mod.rs index 5c96606..702e595 100644 --- a/ylong_http_client/tests/common/mod.rs +++ b/ylong_http_client/tests/common/mod.rs @@ -211,6 +211,7 @@ macro_rules! start_http_server { /// Creates a `Request`. #[macro_export] +#[cfg(feature = "sync")] macro_rules! ylong_request { ( Request: { @@ -232,6 +233,30 @@ macro_rules! ylong_request { }; } +/// Creates a `Request`. +#[macro_export] +#[cfg(feature = "async")] +macro_rules! ylong_request { + ( + Request: { + Method: $method: expr, + Host: $host: expr, + Port: $port: expr, + $( + Header: $req_n: expr, $req_v: expr, + )* + Body: $req_body: expr, + }, + ) => { + ylong_http_client::async_impl::RequestBuilder::new() + .method($method) + .url(format!("{}:{}", $host, $port).as_str()) + $(.header($req_n, $req_v))* + .body(ylong_http_client::async_impl::Body::slice($req_body.as_bytes())) + .expect("Request build failed") + }; +} + /// Sets server async function. #[macro_export] macro_rules! set_server_fn { diff --git a/ylong_http_client/tests/common/sync_utils.rs b/ylong_http_client/tests/common/sync_utils.rs index aad7786..be65aae 100644 --- a/ylong_http_client/tests/common/sync_utils.rs +++ b/ylong_http_client/tests/common/sync_utils.rs @@ -354,7 +354,7 @@ macro_rules! sync_client_assertions { .headers() .get($resp_n) .expect(format!("Get response header \"{}\" failed", $resp_n).as_str()) - .to_str() + .to_string() .expect(format!("Convert response header \"{}\"into string failed", $resp_n).as_str()), $resp_v, "Assert response header \"{}\" failed", $resp_n, diff --git a/ylong_http_client/tests/sdv_async_http.rs b/ylong_http_client/tests/sdv_async_http.rs index 48eda03..49dcefb 100644 --- a/ylong_http_client/tests/sdv_async_http.rs +++ b/ylong_http_client/tests/sdv_async_http.rs @@ -16,8 +16,6 @@ #[macro_use] mod common; -use ylong_http::body::async_impl::Body as AsyncBody; - use crate::common::init_test_work_runtime; /// SDV test cases for `async::Client`. diff --git a/ylong_http_client/tests/sdv_async_http_on_tcp.rs b/ylong_http_client/tests/sdv_async_http_on_tcp.rs index f9c656a..30f132a 100644 --- a/ylong_http_client/tests/sdv_async_http_on_tcp.rs +++ b/ylong_http_client/tests/sdv_async_http_on_tcp.rs @@ -16,8 +16,6 @@ #[macro_use] pub mod tcp_server; -use ylong_http::body::async_impl::Body; - use crate::tcp_server::{format_header_str, TcpHandle}; /// SDV test cases for `async::Client`. @@ -83,21 +81,6 @@ fn sdv_async_client_send_request() { }, ); - // `HEAD` request without body. - async_client_test_on_tcp!( - HTTP; - Request: { - Method: "HEAD", - Path: "/data", - Body: "", - }, - Response: { - Status: 200, - Version: "HTTP/1.1", - Body: "", - }, - ); - // `PUT` request. async_client_test_on_tcp!( HTTP; diff --git a/ylong_http_client/tests/sdv_async_http_proxy.rs b/ylong_http_client/tests/sdv_async_http_proxy.rs index 92b623f..9de4902 100644 --- a/ylong_http_client/tests/sdv_async_http_proxy.rs +++ b/ylong_http_client/tests/sdv_async_http_proxy.rs @@ -97,7 +97,7 @@ fn sdv_async_client_send_request() { "Get response header \"{}\" failed", "Content-Length" )) - .to_str() + .to_string() .unwrap_or_else(|_| panic!( "Convert response header \"{}\"into string failed", "Content-Length" diff --git a/ylong_http_client/tests/sdv_async_https_c_ssl.rs b/ylong_http_client/tests/sdv_async_https_c_ssl.rs index 68bea96..5b39191 100644 --- a/ylong_http_client/tests/sdv_async_https_c_ssl.rs +++ b/ylong_http_client/tests/sdv_async_https_c_ssl.rs @@ -23,8 +23,6 @@ mod common; use std::path::PathBuf; -use ylong_http_client::Body; - use crate::common::init_test_work_runtime; // TODO: Add doc for sdv tests. diff --git a/ylong_http_client/tests/sdv_sync_http.rs b/ylong_http_client/tests/sdv_sync_http.rs index 6a4ceed..1879679 100644 --- a/ylong_http_client/tests/sdv_sync_http.rs +++ b/ylong_http_client/tests/sdv_sync_http.rs @@ -40,7 +40,6 @@ fn sdv_synchronized_client_send_request() { Request: { Method: "PUT", Host: "http://127.0.0.1", - Header: "Host", "127.0.0.1", Header: "Content-Length", "6", Body: "Hello!", }, @@ -70,7 +69,6 @@ fn sdv_synchronized_client_send_request_repeatedly() { Request: { Method: "GET", Host: "http://127.0.0.1", - Header: "Host", "127.0.0.1", Header: "Content-Length", "6", Body: "Hello!", }, @@ -83,7 +81,6 @@ fn sdv_synchronized_client_send_request_repeatedly() { Request: { Method: "POST", Host: "http://127.0.0.1", - Header: "Host", "127.0.0.1", Header: "Content-Length", "6", Body: "Hello!", }, diff --git a/ylong_http_client/tests/sdv_sync_https_c_ssl.rs b/ylong_http_client/tests/sdv_sync_https_c_ssl.rs index d838948..e186546 100644 --- a/ylong_http_client/tests/sdv_sync_https_c_ssl.rs +++ b/ylong_http_client/tests/sdv_sync_https_c_ssl.rs @@ -41,7 +41,6 @@ fn sdv_synchronized_client_send_request() { Request: { Method: "PUT", Host: "https://127.0.0.1", - Header: "Host", "127.0.0.1", Header: "Content-Length", "6", Body: "Hello!", }, @@ -67,7 +66,6 @@ fn sdv_synchronized_client_send_request_repeatedly() { Request: { Method: "GET", Host: "https://127.0.0.1", - Header: "Host", "127.0.0.1", Header: "Content-Length", "6", Body: "Hello!", }, @@ -80,7 +78,6 @@ fn sdv_synchronized_client_send_request_repeatedly() { Request: { Method: "POST", Host: "https://127.0.0.1", - Header: "Host", "127.0.0.1", Header: "Content-Length", "6", Body: "Hello!", }, diff --git a/ylong_http_client/tests/tcp_server/async_utils.rs b/ylong_http_client/tests/tcp_server/async_utils.rs index de523e4..6b37c78 100644 --- a/ylong_http_client/tests/tcp_server/async_utils.rs +++ b/ylong_http_client/tests/tcp_server/async_utils.rs @@ -35,7 +35,7 @@ macro_rules! async_client_test_on_tcp { ) => {{ // The number of servers may be variable based on the number of servers set by the user. - // However, cliipy checks that the variable does not need to be variable. + // However, clippy checks that the variable does not need to be variable. #[allow(unused_mut, unused_assignments)] let mut server_num = 1; $(server_num = $client_num;)? @@ -200,7 +200,7 @@ macro_rules! async_client_assertions_on_tcp { .headers() .get($resp_n) .expect(format!("Get response header \"{}\" failed", $resp_n).as_str()) - .to_str() + .to_string() .expect(format!("Convert response header \"{}\"into string failed", $resp_n).as_str()), $resp_v, "Assert response header \"{}\" failed", $resp_n, @@ -209,7 +209,6 @@ macro_rules! async_client_assertions_on_tcp { let mut size = 0; loop { let read = response - .body_mut() .data(&mut buf[size..]).await .expect("Response body read failed"); if read == 0 { diff --git a/ylong_http_client/tests/tcp_server/mod.rs b/ylong_http_client/tests/tcp_server/mod.rs index fc899f3..6ec0eec 100644 --- a/ylong_http_client/tests/tcp_server/mod.rs +++ b/ylong_http_client/tests/tcp_server/mod.rs @@ -239,8 +239,9 @@ macro_rules! start_tcp_server { }} } -/// Creates a `Request`. +/// Creates a sync `Request`. #[macro_export] +#[cfg(feature = "sync")] macro_rules! build_client_request { ( Request: { @@ -261,3 +262,27 @@ macro_rules! build_client_request { .expect("Request build failed") }}; } + +/// Creates a sync `Request`. +#[macro_export] +#[cfg(feature = "async")] +macro_rules! build_client_request { + ( + Request: { + Method: $method: expr, + Path: $path: expr, + Addr: $addr: expr, + $( + Header: $req_n: expr, $req_v: expr, + )* + Body: $req_body: expr, + }, + ) => {{ + ylong_http_client::async_impl::RequestBuilder::new() + .method($method) + .url(format!("http://{}{}",$addr, $path).as_str()) + $(.header($req_n, $req_v))* + .body(ylong_http_client::async_impl::Body::slice($req_body.as_bytes())) + .expect("Request build failed") + }}; +} diff --git a/ylong_http_client/tests/tcp_server/sync_utils.rs b/ylong_http_client/tests/tcp_server/sync_utils.rs index 27e17cb..5ab5349 100644 --- a/ylong_http_client/tests/tcp_server/sync_utils.rs +++ b/ylong_http_client/tests/tcp_server/sync_utils.rs @@ -197,7 +197,7 @@ macro_rules! sync_client_assertions_on_tcp { .headers() .get($resp_n) .expect(format!("Get response header \"{}\" failed", $resp_n).as_str()) - .to_str() + .to_string() .expect(format!("Convert response header \"{}\"into string failed", $resp_n).as_str()), $resp_v, "Assert response header \"{}\" failed", $resp_n, -- Gitee