diff --git a/ylong_http/src/h2/mod.rs b/ylong_http/src/h2/mod.rs
index f9c235d48e63a9c85ca58a5095ff2fdcff393017..b583ba64f4720cda4d328cb0ff86ab469d2fa169 100644
--- a/ylong_http/src/h2/mod.rs
+++ b/ylong_http/src/h2/mod.rs
@@ -57,7 +57,6 @@ mod frame;
mod hpack;
mod parts;
-pub use crate::pseudo::PseudoHeaders;
pub use decoder::{FrameDecoder, FrameKind, Frames, FramesIntoIter};
pub use encoder::FrameEncoder;
pub use error::{ErrorCode, H2Error};
@@ -67,3 +66,5 @@ pub use frame::{
};
pub(crate) use hpack::{HpackDecoder, HpackEncoder};
pub use parts::Parts;
+
+pub use crate::pseudo::PseudoHeaders;
diff --git a/ylong_http/src/h3/mod.rs b/ylong_http/src/h3/mod.rs
index d39e7b81b2574c8e444e058be3a798bc69af6d93..8b5db50b49f4a9da310e7c60c68236c7cd349a62 100644
--- a/ylong_http/src/h3/mod.rs
+++ b/ylong_http/src/h3/mod.rs
@@ -23,7 +23,6 @@ mod qpack;
// mod octets;
mod stream;
-pub use crate::pseudo::PseudoHeaders;
pub use decoder::FrameDecoder;
pub use encoder::FrameEncoder;
pub use error::{DecodeError, EncodeError, H3Error, H3ErrorCode};
@@ -37,6 +36,8 @@ pub use stream::{
QPACK_ENCODER_STREAM_TYPE,
};
+pub use crate::pseudo::PseudoHeaders;
+
pub(crate) fn is_bidirectional(id: u64) -> bool {
(id & 0x02) == 0
}
diff --git a/ylong_http/src/pseudo.rs b/ylong_http/src/pseudo.rs
index c8f4552ea0493c0886ed519736a84fc895b68a24..7fc818957ff82710f612e6a83c38aaea90af426e 100644
--- a/ylong_http/src/pseudo.rs
+++ b/ylong_http/src/pseudo.rs
@@ -12,8 +12,9 @@
// limitations under the License.
//! HTTP [`PseudoHeaders`], HTTP/2 uses a special pseudo-header file beginning
-//! with the “:” character (ASCII 0x3a) to replace the message start line in HTTP/1.x
-//! to convey the target URI, request method, and status code of the response.
+//! with the “:” character (ASCII 0x3a) to replace the message start line in
+//! HTTP/1.x to convey the target URI, request method, and status code of the
+//! response.
//!
//!
//! # Example
@@ -23,7 +24,6 @@
//! pseudo.set_method(Some("GET".to_string()));
//! assert_eq!(pseudo.method(), Some("GET"));
//! ```
-//!
/// [Pseudo-Header fields] that may appear in http2 and http3 header fields.
///
diff --git a/ylong_http_client/src/util/c_openssl/ffi/stack.rs b/ylong_http_client/src/util/c_openssl/ffi/stack.rs
index c5f9eb7c2d4e15ccf849187ff220f98464eeeb6e..35dfdad0a818c8091722d9179d30e32b8007d252 100644
--- a/ylong_http_client/src/util/c_openssl/ffi/stack.rs
+++ b/ylong_http_client/src/util/c_openssl/ffi/stack.rs
@@ -57,13 +57,14 @@ pub(crate) unsafe fn unified_sk_free(st: STACK) {
}
}
-/// Retrieves a pointer to a stack element from a stack allocated by OpenSSL or BoringSSL at the specified index.
+/// Retrieves a pointer to a stack element from a stack allocated by OpenSSL or
+/// BoringSSL at the specified index.
///
/// # Safety
/// - `st` must be a valid pointer to a stack allocated by the same library
/// (OpenSSL or BoringSSL) used in this crate.
-/// - `idx` must be a valid index within the bounds of the stack.
-/// if the index is out of range, the function will return `null`.
+/// - `idx` must be a valid index within the bounds of the stack. if the index
+/// is out of range, the function will return `null`.
pub(crate) unsafe fn unified_sk_value(st: STACK, idx: c_int) -> *mut c_void {
#[cfg(feature = "c_boringssl")]
{
@@ -99,8 +100,9 @@ pub(crate) unsafe fn unified_sk_num(st: STACK) -> c_int {
/// # Safety
/// - `st` must be a valid pointer to a stack allocated by the same library
/// (OpenSSL or BoringSSL) used in this crate.
-/// - The caller must check the return value of this function. If the stack is empty,
-/// the function will return `null`. The caller must handle this case appropriately.
+/// - The caller must check the return value of this function. If the stack is
+/// empty, the function will return `null`. The caller must handle this case
+/// appropriately.
pub(crate) unsafe fn unified_sk_pop(st: STACK) -> *mut c_void {
#[cfg(feature = "c_boringssl")]
{
diff --git a/ylong_http_client/src/util/c_openssl/ssl/stream.rs b/ylong_http_client/src/util/c_openssl/ssl/stream.rs
index 14ed1b40b04a8e5b088efdcf9e986795f3b51e19..01a7dc7b970f4debfb1dbfe7ece8f32306f7a895 100644
--- a/ylong_http_client/src/util/c_openssl/ssl/stream.rs
+++ b/ylong_http_client/src/util/c_openssl/ssl/stream.rs
@@ -26,7 +26,6 @@ use crate::c_openssl::error::ErrorStack;
use crate::c_openssl::ffi::ssl::{SSL_connect, SSL_set_bio, SSL_shutdown};
use crate::c_openssl::foreign::Foreign;
use crate::c_openssl::verify::PinsVerifyInfo;
-
use crate::util::base64::encode;
use crate::util::c_openssl::bio::BioMethod;
use crate::util::c_openssl::error::VerifyError;
@@ -256,8 +255,9 @@ pub(crate) enum ShutdownResult {
}
pub(crate) fn verify_server_root_cert(ssl: *const SSL, pinned_key: &str) -> Result<(), SslError> {
- use crate::c_openssl::ffi::{ssl::SSL_get_peer_cert_chain, ssl::X509_chain_up_ref};
- use crate::c_openssl::{stack::Stack, x509::X509};
+ use crate::c_openssl::ffi::ssl::{SSL_get_peer_cert_chain, X509_chain_up_ref};
+ use crate::c_openssl::stack::Stack;
+ use crate::c_openssl::x509::X509;
let cert_chain = unsafe { X509_chain_up_ref(SSL_get_peer_cert_chain(ssl)) };
if cert_chain.is_null() {
diff --git a/ylong_http_client/src/util/c_openssl/stack.rs b/ylong_http_client/src/util/c_openssl/stack.rs
index 5640b4bf40e87c5dce3555bd47eb3353ca576221..421c0ca3f0ea3353d92e5182b1a1dc96b1e28cbb 100644
--- a/ylong_http_client/src/util/c_openssl/stack.rs
+++ b/ylong_http_client/src/util/c_openssl/stack.rs
@@ -19,7 +19,6 @@ use core::ops::{Deref, DerefMut, Range};
use libc::c_int;
use super::ffi::stack::{unified_sk_free, unified_sk_num, unified_sk_pop, unified_sk_value, STACK};
-
use crate::c_openssl::foreign::{Foreign, ForeignRef, ForeignRefWrapper};
pub(crate) trait Stackof: Foreign {
diff --git a/ylong_http_client/src/util/c_openssl/verify/pinning.rs b/ylong_http_client/src/util/c_openssl/verify/pinning.rs
index d14bc32ae4cfe7297fe94241c9a0c12cb452a055..2b4984ab271321cb240719ec497214df1ab71c22 100644
--- a/ylong_http_client/src/util/c_openssl/verify/pinning.rs
+++ b/ylong_http_client/src/util/c_openssl/verify/pinning.rs
@@ -134,9 +134,9 @@ impl PubKeyPinsBuilder {
/// Sets a tuple of (server, public key digest) for `PubKeyPins`, using
/// the root certificate pinning strategy.
///
- /// Ensure that the server returns the complete certificate chain, including the root certificate;
- /// otherwise, the client's public key pinning validation will fail and return an error.
- ///
+ /// Ensure that the server returns the complete certificate chain, including
+ /// the root certificate; otherwise, the client's public key pinning
+ /// validation will fail and return an error.
///
/// # Examples
///
diff --git a/ylong_http_client/src/util/h2/streams.rs b/ylong_http_client/src/util/h2/streams.rs
index b1a395e9dc5d70b381d1a15027ca76570f27305a..cb4270a6b9bb2901f5dcd3c615d094709d04fb85 100644
--- a/ylong_http_client/src/util/h2/streams.rs
+++ b/ylong_http_client/src/util/h2/streams.rs
@@ -885,7 +885,8 @@ mod ut_streams {
/// # Brief
/// 1. Insert streams with different states and sends go_away with a stream
/// id.
- /// 2. Asserts that only streams with IDs greater than to the go_away ID are closed.
+ /// 2. Asserts that only streams with IDs greater than to the go_away ID are
+ /// closed.
#[test]
fn ut_streams_get_unset_streams() {
let mut streams = Streams::new(100, 100, FlowControl::new(100, 100));
diff --git a/ylong_http_client/tests/common/mod.rs b/ylong_http_client/tests/common/mod.rs
index bc271b61182c95cdd005c715b449ee7d39a2c715..1c6ef09ce8befb7ce6be285e27df7f60f1b03119 100644
--- a/ylong_http_client/tests/common/mod.rs
+++ b/ylong_http_client/tests/common/mod.rs
@@ -19,6 +19,7 @@ mod sync_utils;
use tokio::runtime::Runtime;
+#[macro_export]
macro_rules! define_service_handle {
(
HTTP;
@@ -198,6 +199,54 @@ macro_rules! start_http_server {
server_shutdown: server_rx,
}
}};
+ (
+ HTTP;
+ Ipv6;
+ $server_fn: ident
+ ) => {{
+ use hyper::service::{make_service_fn, service_fn};
+ use std::convert::Infallible;
+ use tokio::sync::mpsc::channel;
+
+ let (start_tx, start_rx) = channel::<()>(1);
+ let (client_tx, mut client_rx) = channel::<()>(1);
+ let (server_tx, server_rx) = channel::<()>(1);
+
+ let tcp_listener = std::net::TcpListener::bind("::1:0").expect("server bind port failed !");
+ let addr = tcp_listener.local_addr().expect("get server local address failed!");
+ let port = addr.port();
+ let server = hyper::Server::from_tcp(tcp_listener).expect("build hyper server from tcp listener failed !");
+
+ tokio::spawn(async move {
+ let make_svc =
+ make_service_fn(|_conn| async { Ok::<_, Infallible>(service_fn($server_fn)) });
+ server
+ .serve(make_svc)
+ .with_graceful_shutdown(async {
+ start_tx
+ .send(())
+ .await
+ .expect("Start channel (Client-Half) be closed unexpectedly");
+ client_rx
+ .recv()
+ .await
+ .expect("Client channel (Client-Half) be closed unexpectedly");
+ })
+ .await
+ .expect("Start server failed");
+ server_tx
+ .send(())
+ .await
+ .expect("Server channel (Client-Half) be closed unexpectedly");
+ });
+
+ HttpHandle {
+ port,
+ server_start: start_rx,
+ client_shutdown: client_tx,
+ server_shutdown: server_rx,
+ }
+ }};
(
HTTPS;
$service_fn: ident,
diff --git a/ylong_http_client/tests/file/invalid_cert.pem b/ylong_http_client/tests/file/invalid_cert.pem
new file mode 100644
index 0000000000000000000000000000000000000000..506c555e9ea513e94d47033b32f68735bb1070ae
--- /dev/null
+++ b/ylong_http_client/tests/file/invalid_cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHTCCAgWgAwIBAgIUdr7iGoCdo6bYOrzir7kSuzUapMswDQYJKoZIhvcNAQEL
+BQAwHjEcMBoGA1UEAwwTaW52YWxpZC5leGFtcGxlLmNvbTAeFw0yNDA0MjExMDU2
+NTFaFw0yNDA0MjIxMDU2NTFaMB4xHDAaBgNVBAMME2ludmFsaWQuZXhhbXBsZS5j
+b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8NNBIHOGKY7LbTe4j
+WstizazBBKkZpmi2uvRp6JpfCGF2S+sYdEAWQxhjI/iczJHRC5vKjFRTPGl+xWsn
+WcenF/+bnRvvYB7lzJZJi5GMd/hYEebWH3q4a/b6oVod7VuFUmbHRad/Soc067eG
+aukFWqLoLGfxPnrooA1hp+IXEkeYdXKkDuAoYC79GtdEz5I71ru5n1XqJwlijvNZ
++CH3BS7uSuxMuZ/5MpbxyGXcb6jsOKenWOIXtxSAfXAua5FAj0ELqOWdQIUcE0W5
+o8LnRd3tIG7ru5Au7qA24EFQfhOzrRXcCBe69lsaKsnAEisxGtAr2Npp5wunL0mT
+VfpxAgMBAAGjUzBRMB0GA1UdDgQWBBT1p0UvvayS6U2HbuIdsHhW9VELVzAfBgNV
+HSMEGDAWgBT1p0UvvayS6U2HbuIdsHhW9VELVzAPBgNVHRMBAf8EBTADAQH/MA0G
+CSqGSIb3DQEBCwUAA4IBAQAXKgfjJ9zSrpFWf7WkjX5TICdN71cnhKc74YBiOdCD
+ZHtPj/3yLmuBgEQx47I5HU5i2Pk1ZiFQa9GkTAv2NnUi9T14ZQbx0nUQr9VtqxER
+4lmelBxAm5nnLwm1zFGF27eKr1nE1CD9dBt5bD1Ud3QialtWPYnIi7UehjwgMvY5
+8fO334iUR3E0KrJqasAogb9CpivvsIXxfr5DcVdj84rfzUrSeZjy7b0iJ1cph/s3
+jhisIQkMTAlsEeeaicTaS6x9/9/SIWL8ukl6Ig9J+X09GWWF/3uTDAgdf2EVfoDX
+WKEylDkDJV7PICVFnSrV8/QA7ED63DFmLaaj/ASwyFmg
+-----END CERTIFICATE-----
diff --git a/ylong_http_client/tests/file/invalid_key.pem b/ylong_http_client/tests/file/invalid_key.pem
new file mode 100644
index 0000000000000000000000000000000000000000..80d38327b96f0d8f0ee14e38f422cb3684cc0e89
--- /dev/null
+++ b/ylong_http_client/tests/file/invalid_key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC8NNBIHOGKY7Lb
+Te4jWstizazBBKkZpmi2uvRp6JpfCGF2S+sYdEAWQxhjI/iczJHRC5vKjFRTPGl+
+xWsnWcenF/+bnRvvYB7lzJZJi5GMd/hYEebWH3q4a/b6oVod7VuFUmbHRad/Soc0
+67eGaukFWqLoLGfxPnrooA1hp+IXEkeYdXKkDuAoYC79GtdEz5I71ru5n1XqJwli
+jvNZ+CH3BS7uSuxMuZ/5MpbxyGXcb6jsOKenWOIXtxSAfXAua5FAj0ELqOWdQIUc
+E0W5o8LnRd3tIG7ru5Au7qA24EFQfhOzrRXcCBe69lsaKsnAEisxGtAr2Npp5wun
+L0mTVfpxAgMBAAECggEAFoMtv9Z+jwZ2dD+Z4u4/IlouVidHz+rlHyh1SH7bik8P
+E26o2DO9n7wWXHOGz0C0F4CPOdb31j4Fc3XLZ7lYRa878sID/6nsgDns5y6z8gy+
+rtAzX70DubAmapvm0uobtrKg2yjEjJgeXLDucrxyk6KgFmwLYeLConVUrG//vhkL
+wafnh6+NPOHGZJrFp0nKCoQ24YMQkW3hfyV3sOB9J326t+2TenYHi5gaiZCpSMRE
+/P7Xt33GQf03uwYdcFKjIFi3eNyA+lC14GMEIDjpSjt7pyCSUNGmVoqq3g51qqHk
+LJlA7anDfxg7FBWma3dLKGsWYo9rk6/+bDt11ECCUQKBgQDLzTkyeXjSQbqXiy9F
+Rcb8oKj1DM4wAXzA8tVPbjCaVGxwcUAI7BK8Fa/owV0jcQ5vZagCGu3aBVYRHvMm
+6QyOd7xD4lFarzkd/6SaohCaIZy9F7Lmy0/WvjjNBwSrFQgE6wZyVFx6PgfSjGA7
+8Zpzu9B3K3CfPOjDeDXFMkDpWQKBgQDsaQqGiZi+Jgg2YvdobEU1QJUL4//id0KT
+1rMOp5h4o3D8i+AJxZeIjbF9IDkjGsIRA+FoGmhQHGONn4KlWN1JLOd4YxZI2QZc
+FAj10WdVkltm8WP7Tv9wg0RW6iil7cD+Ye7qCZIGG1K7tz+7ewYs2ajLt8tYqz95
+9/gaS/3e2QKBgDaTWHPJz68OZvdaEGiOjQDtzRECQtzcKRpKbRD+Qwmw/fEGgs1g
+2ot/i0HbNiPdjK4MHPejWnp931RqY1bSJcipC8NqAbs1HSiJYvz91VBRG/YPOJtR
+aqno+rD85iUiVqlqfqlzKeiaX+HkMYkb4+6GJwcinOAnoXVm5Y9PK87RAoGAOiCx
+t9bJOae8STMYRhKpCajxfNT4q8RLGHKn4CiRMshtFYuG8KAm3CqKw4tSwTDpGqnr
+mF812szAJahcxS8OadmiqFUku5CxIZ7DfT2ZvM6ZrrwXq0kHQbDk8sHscK/+zg1i
+YyUrRFkfhIxQHVboc8qo2D4+v3SLzP+Nf1nD+DECgYAZ/QcuOw0aHPHm7mr27JKY
+NXCvJ25z4xFN3u6vLSrSffyFNpHDNw7is2KAeOiZeNOXJKwv+bsU9O13dhI6sYEM
+ibWVegJXHujHojD7xEnfKkjDtvGkYbrV9rH7g3H1iUPfU2Y26/RmgJYNxjohGKmu
+K1wAe1PqQ+WMGTZgDoGLDw==
+-----END PRIVATE KEY-----
diff --git a/ylong_http_client/tests/sdv_async_certs_adapter.rs b/ylong_http_client/tests/sdv_async_certs_adapter.rs
new file mode 100644
index 0000000000000000000000000000000000000000..dbf02d1557f78bebcaaabc40e916b67d00f97a92
--- /dev/null
+++ b/ylong_http_client/tests/sdv_async_certs_adapter.rs
@@ -0,0 +1,268 @@
+// Copyright (c) 2025 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.
+
+#![cfg(all(feature = "async", feature = "tokio_base", feature = "__tls"))]
+
+mod common;
+
+use std::convert::Infallible;
+
+use ylong_http::response::status::StatusCode;
+use ylong_http_client::async_impl::{Body, Client, Request};
+use ylong_http_client::{CertVerifier, ServerCerts};
+
+use crate::common::init_test_work_runtime;
+
+async fn server_fn(
+ _req: hyper::Request,
+) -> Result, Infallible> {
+ let response = hyper::Response::builder()
+ .status(hyper::StatusCode::OK)
+ .body(hyper::Body::empty())
+ .expect("build hyper response failed");
+ Ok(response)
+}
+
+#[test]
+#[cfg(feature = "http1_1")]
+fn sdv_client_request_http1_verify_true() {
+ struct Verifier;
+ impl CertVerifier for Verifier {
+ fn verify(&self, _certs: &ServerCerts) -> bool {
+ true
+ }
+ }
+
+ define_service_handle!(HTTPS;);
+
+ let rt = init_test_work_runtime(4);
+
+ let mut handles_vec = vec![];
+ start_server!(
+ HTTPS;
+ ServerNum: 1,
+ Runtime: rt,
+ Handles: handles_vec,
+ ServeFnName: server_fn,
+ );
+ let handle = handles_vec.pop().expect("No more handles !");
+
+ let client = Client::builder()
+ .cert_verifier(Verifier)
+ .build()
+ .expect("Build Client failed.");
+
+ let request = Request::builder()
+ .url(format!("{}:{}", "127.0.0.1", handle.port).as_str())
+ .version("HTTP/1.1")
+ .method("GET")
+ .body(Body::empty())
+ .expect("Client build Request failed.");
+
+ rt.block_on(async move {
+ let response = client.request(request).await.unwrap();
+ assert_eq!(response.status(), StatusCode::OK);
+ });
+}
+
+#[test]
+#[cfg(feature = "http2")]
+fn sdv_client_request_http2_verify_true() {
+ struct Verifier;
+ impl CertVerifier for Verifier {
+ fn verify(&self, _certs: &ServerCerts) -> bool {
+ true
+ }
+ }
+
+ define_service_handle!(HTTPS;);
+
+ let rt = init_test_work_runtime(4);
+
+ let key_path = std::path::PathBuf::from("tests/file/key.pem");
+ let cert_path = std::path::PathBuf::from("tests/file/cert.pem");
+
+ let (tx, rx) = std::sync::mpsc::channel();
+ let server_handle = rt.spawn(async move {
+ let handle = {
+ let mut port = 10000;
+ let listener = loop {
+ let addr = std::net::SocketAddr::from(([127, 0, 0, 1], port));
+ match tokio::net::TcpListener::bind(addr).await {
+ Ok(listener) => break listener,
+ Err(_) => {
+ port += 1;
+ if port == u16::MAX {
+ port = 10000;
+ }
+ continue;
+ }
+ }
+ };
+ let port = listener.local_addr().unwrap().port();
+
+ tokio::spawn(async move {
+ let mut acceptor =
+ openssl::ssl::SslAcceptor::mozilla_intermediate(openssl::ssl::SslMethod::tls())
+ .expect("SslAcceptorBuilder error");
+ acceptor
+ .set_session_id_context(b"test")
+ .expect("Set session id error");
+ acceptor
+ .set_private_key_file(key_path, openssl::ssl::SslFiletype::PEM)
+ .expect("Set private key error");
+ acceptor
+ .set_certificate_chain_file(cert_path)
+ .expect("Set cert error");
+ acceptor.set_alpn_protos(b"\x02h2").unwrap();
+ acceptor.set_alpn_select_callback(|_, client| {
+ openssl::ssl::select_next_proto(b"\x02h2", client)
+ .ok_or(openssl::ssl::AlpnError::NOACK)
+ });
+
+ let acceptor = acceptor.build();
+
+ let (stream, _) = listener.accept().await.expect("TCP listener accept error");
+ let ssl = openssl::ssl::Ssl::new(acceptor.context()).expect("Ssl Error");
+ let mut stream =
+ tokio_openssl::SslStream::new(ssl, stream).expect("SslStream Error");
+ core::pin::Pin::new(&mut stream).accept().await.unwrap();
+
+ hyper::server::conn::Http::new()
+ .serve_connection(stream, hyper::service::service_fn(server_fn))
+ .await
+ });
+
+ TlsHandle { port }
+ };
+ tx.send(handle)
+ .expect("Failed to send the handle to the test thread.");
+ });
+ rt.block_on(server_handle)
+ .expect("Runtime start server coroutine failed");
+ let handle = rx
+ .recv()
+ .expect("Handle send channel (Server-Half) be closed unexpectedly");
+
+ let client = Client::builder()
+ .http2_prior_knowledge()
+ .cert_verifier(Verifier)
+ .build()
+ .expect("Build Client failed.");
+
+ let request = Request::builder()
+ .url(format!("{}:{}", "127.0.0.1", handle.port).as_str())
+ .version("HTTP/2.0")
+ .method("GET")
+ .body(Body::empty())
+ .expect("Client build Request failed.");
+
+ rt.block_on(async move {
+ let response = client.request(request).await.unwrap();
+ assert_eq!(response.status(), StatusCode::OK);
+ });
+}
+
+#[test]
+#[cfg(feature = "http1_1")]
+fn sdv_client_request_http1_verify_false() {
+ struct Verifier;
+ impl CertVerifier for Verifier {
+ fn verify(&self, _certs: &ServerCerts) -> bool {
+ false
+ }
+ }
+
+ define_service_handle!(HTTPS;);
+
+ let rt = init_test_work_runtime(4);
+
+ let mut handles_vec = vec![];
+ start_server!(
+ HTTPS;
+ ServerNum: 1,
+ Runtime: rt,
+ Handles: handles_vec,
+ ServeFnName: server_fn,
+ );
+ let handle = handles_vec.pop().expect("No more handles !");
+
+ let client = Client::builder()
+ .cert_verifier(Verifier)
+ .build()
+ .expect("Build Client failed.");
+
+ let request = Request::builder()
+ .url(format!("{}:{}", "127.0.0.1", handle.port).as_str())
+ .version("HTTP/1.1")
+ .method("GET")
+ .body(Body::empty())
+ .expect("Client build Request failed.");
+
+ rt.block_on(async move {
+ let response = client.request(request).await;
+ assert!(response.is_err());
+ });
+}
+
+#[test]
+#[cfg(feature = "http1_1")]
+fn sdv_client_request_http1_verify_true_invalid_cert() {
+ struct Verifier;
+ impl CertVerifier for Verifier {
+ fn verify(&self, _certs: &ServerCerts) -> bool {
+ true
+ }
+ }
+
+ define_service_handle!(HTTPS;);
+
+ let rt = init_test_work_runtime(4);
+
+ let key_path = std::path::PathBuf::from("tests/file/invalid_key.pem");
+ let cert_path = std::path::PathBuf::from("tests/file/invalid_cert.pem");
+
+ let (tx, rx) = std::sync::mpsc::channel();
+ let server_handle = rt.spawn(async move {
+ let handle = start_http_server!(
+ HTTPS ;
+ server_fn ,
+ key_path ,
+ cert_path
+ );
+ tx.send(handle)
+ .expect("Failed to send the handle to the test thread.");
+ });
+ rt.block_on(server_handle)
+ .expect("Runtime start server coroutine failed");
+ let handle = rx
+ .recv()
+ .expect("Handle send channel (Server-Half) be closed unexpectedly");
+
+ let client = Client::builder()
+ .cert_verifier(Verifier)
+ .build()
+ .expect("Build Client failed.");
+
+ let request = Request::builder()
+ .url(format!("{}:{}", "127.0.0.1", handle.port).as_str())
+ .version("HTTP/1.1")
+ .method("GET")
+ .body(Body::empty())
+ .expect("Client build Request failed.");
+
+ rt.block_on(async move {
+ let response = client.request(request).await.unwrap();
+ assert_eq!(response.status(), StatusCode::OK);
+ });
+}
diff --git a/ylong_http_client/tests/sdv_async_dns_resolver.rs b/ylong_http_client/tests/sdv_async_dns_resolver.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b0ee33a9e9b6a167eb460c2ab0141e1e08c420e5
--- /dev/null
+++ b/ylong_http_client/tests/sdv_async_dns_resolver.rs
@@ -0,0 +1,592 @@
+// Copyright (c) 2025 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.
+
+#![cfg(all(
+ feature = "async",
+ feature = "tokio_base",
+ feature = "http1_1",
+ not(feature = "__tls")
+))]
+
+mod common;
+
+use std::convert::Infallible;
+use std::io;
+use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
+
+use ylong_http_client::async_impl::{Body, Client, Request, Resolver, SocketFuture};
+
+use crate::common::init_test_work_runtime;
+
+async fn server_fn(
+ _req: hyper::Request,
+) -> Result, Infallible> {
+ let response = hyper::Response::builder()
+ .status(hyper::StatusCode::OK)
+ .body(hyper::Body::empty())
+ .expect("build hyper response failed");
+ Ok(response)
+}
+
+macro_rules! dns_test {
+ (
+ Ipv4;
+ Success;
+ $resolver: ident
+ ) => {
+ define_service_handle!(HTTP;);
+ let rt = init_test_work_runtime(4);
+ let (tx, rx) = std::sync::mpsc::channel();
+
+ rt.block_on(async move {
+ let mut handle = start_http_server!(HTTP; server_fn);
+ handle
+ .server_start
+ .recv()
+ .await
+ .expect("recv server start msg failed !");
+ tx.send(handle)
+ .expect("send Handle out the server coroutine failed !");
+ });
+
+ let mut handle = rx.recv().expect("recv Handle failed !");
+
+ let client = Client::builder()
+ .dns_resolver($resolver)
+ .build()
+ .expect("Build Client failed.");
+
+ let request = Request::builder()
+ .url(format!("localhost:{}", handle.port).as_str())
+ .body(Body::empty())
+ .expect("Client build Request failed.");
+
+ rt.block_on(async move {
+ let response = client.request(request).await;
+ assert!(response.is_ok());
+
+ handle
+ .client_shutdown
+ .send(())
+ .await
+ .expect("send client shutdown");
+ handle
+ .server_shutdown
+ .recv()
+ .await
+ .expect("server shutdown");
+ })
+ };
+ (
+ Ipv6;
+ Success;
+ $resolver: ident
+ ) => {
+ define_service_handle!(HTTP;);
+ let rt = init_test_work_runtime(4);
+ let (tx, rx) = std::sync::mpsc::channel();
+
+ rt.block_on(async move {
+ let mut handle = start_http_server!(HTTP; Ipv6; server_fn);
+ handle
+ .server_start
+ .recv()
+ .await
+ .expect("recv server start msg failed !");
+ tx.send(handle)
+ .expect("send Handle out the server coroutine failed !");
+ });
+
+ let mut handle = rx.recv().expect("recv Handle failed !");
+
+ let client = Client::builder()
+ .dns_resolver($resolver)
+ .build()
+ .expect("Build Client failed.");
+
+ let request = Request::builder()
+ .url(format!("localhost:{}", handle.port).as_str())
+ .body(Body::empty())
+ .expect("Client build Request failed.");
+
+ rt.block_on(async move {
+ let response = client.request(request).await;
+ assert!(response.is_ok());
+
+ handle
+ .client_shutdown
+ .send(())
+ .await
+ .expect("send client shutdown");
+ handle
+ .server_shutdown
+ .recv()
+ .await
+ .expect("server shutdown");
+ })
+ };
+ (
+ Ipv4;
+ Fail;
+ $resolver: ident
+ ) => {
+ define_service_handle!(HTTP;);
+ let rt = init_test_work_runtime(4);
+ let (tx, rx) = std::sync::mpsc::channel();
+
+ rt.block_on(async move {
+ let mut handle = start_http_server!(HTTP; server_fn);
+ handle
+ .server_start
+ .recv()
+ .await
+ .expect("recv server start msg failed !");
+ tx.send(handle)
+ .expect("send Handle out the server coroutine failed !");
+ });
+
+ let mut handle = rx.recv().expect("recv Handle failed !");
+
+ let client = Client::builder()
+ .dns_resolver($resolver)
+ .build()
+ .expect("Build Client failed.");
+
+ let request = Request::builder()
+ .url(format!("localhost:{}", handle.port).as_str())
+ .body(Body::empty())
+ .expect("Client build Request failed.");
+
+ rt.block_on(async move {
+ let response = client.request(request).await;
+ assert!(response.is_err());
+
+ handle
+ .client_shutdown
+ .send(())
+ .await
+ .expect("send client shutdown");
+ handle
+ .server_shutdown
+ .recv()
+ .await
+ .expect("server shutdown");
+ })
+ };
+ (
+ Ipv6;
+ Fail;
+ $resolver: ident
+ ) => {
+ define_service_handle!(HTTP;);
+ let rt = init_test_work_runtime(4);
+ let (tx, rx) = std::sync::mpsc::channel();
+
+ rt.block_on(async move {
+ let mut handle = start_http_server!(HTTP; Ipv6; server_fn);
+ handle
+ .server_start
+ .recv()
+ .await
+ .expect("recv server start msg failed !");
+ tx.send(handle)
+ .expect("send Handle out the server coroutine failed !");
+ });
+
+ let mut handle = rx.recv().expect("recv Handle failed !");
+
+ let client = Client::builder()
+ .dns_resolver($resolver)
+ .build()
+ .expect("Build Client failed.");
+
+ let request = Request::builder()
+ .url(format!("localhost:{}", handle.port).as_str())
+ .body(Body::empty())
+ .expect("Client build Request failed.");
+
+ rt.block_on(async move {
+ let response = client.request(request).await;
+ assert!(response.is_err());
+
+ handle
+ .client_shutdown
+ .send(())
+ .await
+ .expect("send client shutdown");
+ handle
+ .server_shutdown
+ .recv()
+ .await
+ .expect("server shutdown");
+ })
+ };
+}
+
+#[test]
+fn sdv_client_request_dns_resolver_ipv4() {
+ struct ExampleDnsResolver;
+ impl Resolver for ExampleDnsResolver {
+ fn resolve(&self, authority: &str) -> SocketFuture {
+ let port = authority.trim_start_matches("localhost:").to_string();
+ Box::pin(async move {
+ if port.is_empty() {
+ Err(io::Error::new(io::ErrorKind::Other, "").into())
+ } else {
+ let ip = Ipv4Addr::new(127, 0, 0, 1);
+ let addr = SocketAddr::from((ip, port.parse().unwrap()));
+ let addrs = vec![addr];
+ Ok(Box::new(addrs.into_iter())
+ as Box + Sync + Send>)
+ }
+ })
+ }
+ }
+ dns_test!(Ipv4; Success; ExampleDnsResolver);
+}
+
+#[test]
+fn sdv_client_request_dns_resolver_ipv6() {
+ struct ExampleDnsResolver;
+ impl Resolver for ExampleDnsResolver {
+ fn resolve(&self, authority: &str) -> SocketFuture {
+ let port = authority.trim_start_matches("localhost:").to_string();
+ Box::pin(async move {
+ if port.is_empty() {
+ Err(io::Error::new(io::ErrorKind::Other, "").into())
+ } else {
+ let ip = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1);
+ let addr = SocketAddr::from((ip, port.parse().unwrap()));
+ let addrs = vec![addr];
+ Ok(Box::new(addrs.into_iter())
+ as Box + Sync + Send>)
+ }
+ })
+ }
+ }
+ dns_test!(Ipv6; Success; ExampleDnsResolver);
+}
+
+#[test]
+fn sdv_client_request_dns_resolver_ipv4_invalid() {
+ struct ExampleDnsResolver;
+ impl Resolver for ExampleDnsResolver {
+ fn resolve(&self, authority: &str) -> SocketFuture {
+ let port = authority.trim_start_matches("localhost:").to_string();
+ Box::pin(async move {
+ if port.is_empty() {
+ Err(io::Error::new(io::ErrorKind::Other, "").into())
+ } else {
+ let ip = Ipv4Addr::new(127, 0, 0, 2);
+ let addr = SocketAddr::from((ip, port.parse().unwrap()));
+ let addrs = vec![addr];
+ Ok(Box::new(addrs.into_iter())
+ as Box + Sync + Send>)
+ }
+ })
+ }
+ }
+ dns_test!(Ipv4; Fail; ExampleDnsResolver);
+}
+
+#[test]
+fn sdv_client_request_dns_resolver_ipv6_invalid() {
+ struct ExampleDnsResolver;
+ impl Resolver for ExampleDnsResolver {
+ fn resolve(&self, authority: &str) -> SocketFuture {
+ let port = authority.trim_start_matches("localhost:").to_string();
+ Box::pin(async move {
+ if port.is_empty() {
+ Err(io::Error::new(io::ErrorKind::Other, "").into())
+ } else {
+ let ip = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 2);
+ let addr = SocketAddr::from((ip, port.parse().unwrap()));
+ let addrs = vec![addr];
+ Ok(Box::new(addrs.into_iter())
+ as Box + Sync + Send>)
+ }
+ })
+ }
+ }
+ dns_test!(Ipv6; Fail; ExampleDnsResolver);
+}
+
+#[test]
+fn sdv_client_request_dns_resolver_ipv4_multiple1() {
+ struct ExampleDnsResolver;
+ impl Resolver for ExampleDnsResolver {
+ fn resolve(&self, authority: &str) -> SocketFuture {
+ let port = authority.trim_start_matches("localhost:").to_string();
+ Box::pin(async move {
+ if port.is_empty() {
+ Err(io::Error::new(io::ErrorKind::Other, "").into())
+ } else {
+ let ip1 = Ipv4Addr::new(127, 0, 0, 1);
+ let addr1 = SocketAddr::from((ip1, port.parse().unwrap()));
+ let ip2 = Ipv4Addr::new(127, 0, 0, 2);
+ let addr2 = SocketAddr::from((ip2, port.parse().unwrap()));
+ let addrs = vec![addr1, addr2];
+ Ok(Box::new(addrs.into_iter())
+ as Box + Sync + Send>)
+ }
+ })
+ }
+ }
+ dns_test!(Ipv4; Success; ExampleDnsResolver);
+}
+
+#[test]
+fn sdv_client_request_dns_resolver_ipv6_multiple1() {
+ struct ExampleDnsResolver;
+ impl Resolver for ExampleDnsResolver {
+ fn resolve(&self, authority: &str) -> SocketFuture {
+ let port = authority.trim_start_matches("localhost:").to_string();
+ Box::pin(async move {
+ if port.is_empty() {
+ Err(io::Error::new(io::ErrorKind::Other, "").into())
+ } else {
+ let ip1 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1);
+ let addr1 = SocketAddr::from((ip1, port.parse().unwrap()));
+ let ip2 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 2);
+ let addr2 = SocketAddr::from((ip2, port.parse().unwrap()));
+ let addrs = vec![addr1, addr2];
+ Ok(Box::new(addrs.into_iter())
+ as Box + Sync + Send>)
+ }
+ })
+ }
+ }
+ dns_test!(Ipv6; Success; ExampleDnsResolver);
+}
+
+#[test]
+fn sdv_client_request_dns_resolver_ipv4_multiple2() {
+ struct ExampleDnsResolver;
+ impl Resolver for ExampleDnsResolver {
+ fn resolve(&self, authority: &str) -> SocketFuture {
+ let port = authority.to_string();
+ Box::pin(async move {
+ if port.is_empty() {
+ Err(io::Error::new(io::ErrorKind::Other, "").into())
+ } else {
+ let (port1, port2) = port.split_once(':').unwrap();
+ let ip1 = Ipv4Addr::new(127, 0, 0, 1);
+ let addr1 = SocketAddr::from((ip1, port1.parse().unwrap()));
+ let ip2 = Ipv4Addr::new(127, 0, 0, 1);
+ let addr2 = SocketAddr::from((ip2, port2.parse().unwrap()));
+ let addrs = vec![addr1, addr2];
+ Ok(Box::new(addrs.into_iter())
+ as Box + Sync + Send>)
+ }
+ })
+ }
+ }
+ define_service_handle!( HTTP ; );
+ let rt = init_test_work_runtime(4);
+ let (mut handle1, mut handle2) = rt.block_on(async move {
+ let mut handle1 = start_http_server!( HTTP ; server_fn );
+ let mut handle2 = start_http_server!( HTTP ; server_fn );
+ handle1
+ .server_start
+ .recv()
+ .await
+ .expect("recv server start msg failed !");
+ handle2
+ .server_start
+ .recv()
+ .await
+ .expect("recv server start msg failed !");
+ (handle1, handle2)
+ });
+ let client = Client::builder()
+ .dns_resolver(ExampleDnsResolver)
+ .build()
+ .expect("Build Client failed.");
+ let request = Request::builder()
+ .url(format!("{}:{}", handle1.port, handle2.port).as_str())
+ .body(Body::empty())
+ .expect("Client build Request failed.");
+ rt.block_on(async move {
+ let response = client.request(request).await;
+ assert!(response.is_ok());
+
+ handle1
+ .client_shutdown
+ .send(())
+ .await
+ .expect("send client shutdown");
+ handle1
+ .server_shutdown
+ .recv()
+ .await
+ .expect("server shutdown");
+ handle2
+ .client_shutdown
+ .send(())
+ .await
+ .expect("send client shutdown");
+ handle2
+ .server_shutdown
+ .recv()
+ .await
+ .expect("server shutdown");
+ })
+}
+
+#[test]
+fn sdv_client_request_dns_resolver_ipv6_multiple2() {
+ struct ExampleDnsResolver;
+ impl Resolver for ExampleDnsResolver {
+ fn resolve(&self, authority: &str) -> SocketFuture {
+ let port = authority.to_string();
+ Box::pin(async move {
+ if port.is_empty() {
+ Err(io::Error::new(io::ErrorKind::Other, "").into())
+ } else {
+ let (port1, port2) = port.split_once(':').unwrap();
+ let ip1 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1);
+ let addr1 = SocketAddr::from((ip1, port1.parse().unwrap()));
+ let ip2 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1);
+ let addr2 = SocketAddr::from((ip2, port2.parse().unwrap()));
+ let addrs = vec![addr1, addr2];
+ Ok(Box::new(addrs.into_iter())
+ as Box + Sync + Send>)
+ }
+ })
+ }
+ }
+ define_service_handle!( HTTP ; );
+ let rt = init_test_work_runtime(4);
+ let (mut handle1, mut handle2) = rt.block_on(async move {
+ let mut handle1 = start_http_server!( HTTP ; Ipv6; server_fn );
+ let mut handle2 = start_http_server!( HTTP ; Ipv6; server_fn );
+ handle1
+ .server_start
+ .recv()
+ .await
+ .expect("recv server start msg failed !");
+ handle2
+ .server_start
+ .recv()
+ .await
+ .expect("recv server start msg failed !");
+ (handle1, handle2)
+ });
+ let client = Client::builder()
+ .dns_resolver(ExampleDnsResolver)
+ .build()
+ .expect("Build Client failed.");
+ let request = Request::builder()
+ .url(format!("{}:{}", handle1.port, handle2.port).as_str())
+ .body(Body::empty())
+ .expect("Client build Request failed.");
+ rt.block_on(async move {
+ let response = client.request(request).await;
+ assert!(response.is_ok());
+
+ handle1
+ .client_shutdown
+ .send(())
+ .await
+ .expect("send client shutdown");
+ handle1
+ .server_shutdown
+ .recv()
+ .await
+ .expect("server shutdown");
+ handle2
+ .client_shutdown
+ .send(())
+ .await
+ .expect("send client shutdown");
+ handle2
+ .server_shutdown
+ .recv()
+ .await
+ .expect("server shutdown");
+ })
+}
+
+#[test]
+fn sdv_client_request_dns_resolver_ipv4_ipv6() {
+ struct ExampleDnsResolver;
+ impl Resolver for ExampleDnsResolver {
+ fn resolve(&self, authority: &str) -> SocketFuture {
+ let port = authority.to_string();
+ Box::pin(async move {
+ if port.is_empty() {
+ Err(io::Error::new(io::ErrorKind::Other, "").into())
+ } else {
+ let (port1, port2) = port.split_once(':').unwrap();
+ let ip1 = Ipv4Addr::new(0, 0, 0, 1);
+ let addr1 = SocketAddr::from((ip1, port1.parse().unwrap()));
+ let ip2 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1);
+ let addr2 = SocketAddr::from((ip2, port2.parse().unwrap()));
+ let addrs = vec![addr1, addr2];
+ Ok(Box::new(addrs.into_iter())
+ as Box + Sync + Send>)
+ }
+ })
+ }
+ }
+ define_service_handle!( HTTP ; );
+ let rt = init_test_work_runtime(4);
+ let (mut handle1, mut handle2) = rt.block_on(async move {
+ let mut handle1 = start_http_server!( HTTP ; server_fn );
+ let mut handle2 = start_http_server!( HTTP ; Ipv6; server_fn );
+ handle1
+ .server_start
+ .recv()
+ .await
+ .expect("recv server start msg failed !");
+ handle2
+ .server_start
+ .recv()
+ .await
+ .expect("recv server start msg failed !");
+ (handle1, handle2)
+ });
+ let client = Client::builder()
+ .dns_resolver(ExampleDnsResolver)
+ .build()
+ .expect("Build Client failed.");
+ let request = Request::builder()
+ .url(format!("{}:{}", handle1.port, handle2.port).as_str())
+ .body(Body::empty())
+ .expect("Client build Request failed.");
+ rt.block_on(async move {
+ let response = client.request(request).await;
+ assert!(response.is_ok());
+
+ handle1
+ .client_shutdown
+ .send(())
+ .await
+ .expect("send client shutdown");
+ handle1
+ .server_shutdown
+ .recv()
+ .await
+ .expect("server shutdown");
+ handle2
+ .client_shutdown
+ .send(())
+ .await
+ .expect("send client shutdown");
+ handle2
+ .server_shutdown
+ .recv()
+ .await
+ .expect("server shutdown");
+ })
+}
diff --git a/ylong_http_client/tests/sdv_async_https_pinning.rs b/ylong_http_client/tests/sdv_async_https_pinning.rs
index 963d258dd50ed6c4c84bc67c1e0880aa82e0b3af..c4bf5c4f8bb58dc4e271adc932a0f031b4d30d3c 100644
--- a/ylong_http_client/tests/sdv_async_https_pinning.rs
+++ b/ylong_http_client/tests/sdv_async_https_pinning.rs
@@ -397,7 +397,8 @@ fn sdv_client_public_key_root_pinning() {
.expect("Runtime block on server shutdown failed");
}
- // Root certificate pinning strategy, but using the server certificate public key hash
+ // Root certificate pinning strategy, but using the server certificate public
+ // key hash
{
start_server!(
HTTPS;