diff --git a/ylong_http_client/Cargo.toml b/ylong_http_client/Cargo.toml index e9641499003c01e004d09d2b6d102b40ee69df58..e70bd740d510207b5df4c2acc0557964b21b4d61 100644 --- a/ylong_http_client/Cargo.toml +++ b/ylong_http_client/Cargo.toml @@ -114,3 +114,13 @@ required-features = ["async", "http1_1", "__tls", "tokio_base"] name = "sdv_sync_https_c_ssl" path = "./tests/sdv_sync_https_c_ssl.rs" required-features = ["sync", "http1_1", "__tls", "tokio_base"] + +[[test]] +name = "sdv_async_http_on_tcp" +path = "./tests/sdv_async_http_on_tcp.rs" +required-features = ["async", "http1_1", "tokio_base"] + +[[test]] +name = "sdv_sync_http_on_tcp" +path = "./tests/sdv_sync_http_on_tcp.rs" +required-features = ["sync", "http1_1"] diff --git a/ylong_http_client/tests/common/async_utils.rs b/ylong_http_client/tests/common/async_utils.rs index dd5d704730055683eca3c774366f0e4124df2e74..458e64dcd28b6684df5e993a1c6588bb09db7dcf 100644 --- a/ylong_http_client/tests/common/async_utils.rs +++ b/ylong_http_client/tests/common/async_utils.rs @@ -11,10 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::sync::Arc; - -use ylong_http_client::async_impl::Client; - #[macro_export] macro_rules! async_client_test_case { ( @@ -40,6 +36,7 @@ macro_rules! async_client_test_case { Body: $resp_body: expr, },)* ) => {{ + define_service_handle!(HTTPS;); set_server_fn!( ASYNC; $server_fn_name, @@ -128,6 +125,7 @@ macro_rules! async_client_test_case { Body: $resp_body: expr, },)* ) => {{ + define_service_handle!(HTTP;); set_server_fn!( ASYNC; $server_fn_name, @@ -222,7 +220,12 @@ macro_rules! async_client_assert { Body: $resp_body: expr, },)* ) => {{ - let client = async_build_https_client($tls_config); + let client = ylong_http_client::async_impl::Client::builder() + .tls_ca_file($tls_config) + .danger_accept_invalid_hostnames(true) + .build() + .unwrap(); + let client = std::sync::Arc::new(client); for _i in 0..$server_num { let handle = $handle_vec.pop().expect("No more handles !"); let client = std::sync::Arc::clone(&client); @@ -274,7 +277,9 @@ macro_rules! async_client_assert { Body: $resp_body: expr, },)* ) => {{ - let client = async_build_http_client(); + //let client = async_build_http_client(); + let client = ylong_http_client::async_impl::Client::new(); + let client = std::sync::Arc::new(client); for _i in 0..$server_num { let mut handle = $handle_vec.pop().expect("No more handles !"); let client = std::sync::Arc::clone(&client); @@ -374,19 +379,3 @@ macro_rules! async_client_assertions { )* } } - -#[cfg(feature = "__tls")] -pub fn async_build_https_client(tls_config: &str) -> Arc { - let client = ylong_http_client::async_impl::Client::builder() - .tls_ca_file(tls_config) - .danger_accept_invalid_hostnames(true) // The root-ca is not have SAN hostname. - .build() - .unwrap(); - std::sync::Arc::new(client) -} - -#[cfg(not(feature = "__tls"))] -pub fn async_build_http_client() -> Arc { - let client = ylong_http_client::async_impl::Client::new(); - Arc::new(client) -} diff --git a/ylong_http_client/tests/common/mod.rs b/ylong_http_client/tests/common/mod.rs index 8dd76a0780d9a05b4154b5c65d1236a2625dac0b..3a0b6dce05ad246538aa28167ebfda17e46f0fc3 100644 --- a/ylong_http_client/tests/common/mod.rs +++ b/ylong_http_client/tests/common/mod.rs @@ -17,62 +17,115 @@ mod async_utils; #[cfg(feature = "sync")] mod sync_utils; -#[cfg(all(feature = "async", not(feature = "__tls")))] -pub use async_utils::async_build_http_client; -#[cfg(all(feature = "async", feature = "__tls"))] -pub use async_utils::async_build_https_client; use tokio::runtime::Runtime; -#[cfg(not(feature = "__tls"))] -use tokio::sync::mpsc::{Receiver, Sender}; -/// Server handle. -#[cfg(feature = "__tls")] -pub struct TlsHandle { - pub port: u16, -} +macro_rules! define_service_handle { + ( + HTTP; + ) => { + use tokio::sync::mpsc::{Receiver, Sender}; -#[cfg(not(feature = "__tls"))] -pub struct HttpHandle { - pub port: u16, + pub struct HttpHandle { + pub port: u16, - // This channel allows the server to notify the client when it is up and running. - pub server_start: Receiver<()>, + // This channel allows the server to notify the client when it is up and running. + pub server_start: Receiver<()>, - // This channel allows the client to notify the server when it is ready to shut down. - pub client_shutdown: Sender<()>, + // This channel allows the client to notify the server when it is ready to shut down. + pub client_shutdown: Sender<()>, - // This channel allows the server to notify the client when it has shut down. - pub server_shutdown: Receiver<()>, + // This channel allows the server to notify the client when it has shut down. + pub server_shutdown: Receiver<()>, + } + }; + ( + HTTPS; + ) => { + pub struct TlsHandle { + pub port: u16, + } + }; } #[macro_export] -macro_rules! start_http_server { - ($server_fn: ident) => {{ - use std::convert::Infallible; - use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +macro_rules! start_server { + ( + HTTPS; + ServerNum: $server_num: expr, + Runtime: $runtime: expr, + Handles: $handle_vec: expr, + ServeFnName: $service_fn: ident, + ) => {{ + for _i in 0..$server_num { + let (tx, rx) = std::sync::mpsc::channel(); + let server_handle = $runtime.spawn(async move { + let handle = start_http_server!( + HTTPS; + $service_fn + ); + tx.send(handle) + .expect("Failed to send the handle to the test thread."); + }); + $runtime + .block_on(server_handle) + .expect("Runtime start server coroutine failed"); + let handle = rx + .recv() + .expect("Handle send channel (Server-Half) be closed unexpectedly"); + $handle_vec.push(handle); + } + }}; + ( + HTTP; + ServerNum: $server_num: expr, + Runtime: $runtime: expr, + Handles: $handle_vec: expr, + ServeFnName: $service_fn: ident, + ) => {{ + for _i in 0..$server_num { + let (tx, rx) = std::sync::mpsc::channel(); + let server_handle = $runtime.spawn(async move { + let mut handle = start_http_server!( + HTTP; + $service_fn + ); + handle + .server_start + .recv() + .await + .expect("Start channel (Server-Half) be closed unexpectedly"); + tx.send(handle) + .expect("Failed to send the handle to the test thread."); + }); + $runtime + .block_on(server_handle) + .expect("Runtime start server coroutine failed"); + let handle = rx + .recv() + .expect("Handle send channel (Server-Half) be closed unexpectedly"); + $handle_vec.push(handle); + } + }}; +} +#[macro_export] +macro_rules! start_http_server { + ( + HTTP; + $server_fn: ident + ) => {{ use hyper::service::{make_service_fn, service_fn}; - use hyper::Server; + 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 mut port = 10000; - let server = loop { - let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port); - match Server::try_bind(&addr) { - Ok(server) => break server, - Err(_) => { - port += 1; - if port == u16::MAX { - port = 10000; - } - continue; - } - } - }; + let tcp_listener = std::net::TcpListener::bind("127.0.0.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 = @@ -104,6 +157,56 @@ macro_rules! start_http_server { server_shutdown: server_rx, } }}; + ( + HTTPS; + $service_fn: ident + ) => {{ + 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("tests/file/key.pem", openssl::ssl::SslFiletype::PEM) + .expect("Set private key error"); + acceptor + .set_certificate_chain_file("tests/file/cert.pem") + .expect("Set cert error"); + let acceptor = acceptor.build(); + + let (stream, _) = listener.accept().await.expect("TCP listener accpet 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(); // SSL negotiation finished successfully + + hyper::server::conn::Http::new() + .http1_only(true) + .http1_keep_alive(true) + .serve_connection(stream, hyper::service::service_fn($service_fn)) + .await + }); + + TlsHandle { + port, + } + }}; } /// Creates a `Request`. @@ -225,7 +328,7 @@ macro_rules! set_server_fn { assert_eq!( $host, - request.uri().host().expect("Uri in request do not have a host."), + format!("{}://{}", request.uri().scheme().expect("assert uri scheme failed !").as_str(), request.uri().host().expect("assert uri host failed !")), "Assert request host failed", ); assert_eq!( @@ -263,117 +366,6 @@ macro_rules! set_server_fn { }; } -#[cfg(feature = "__tls")] -macro_rules! start_tls_server { - ($service_fn: ident) => {{ - 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("tests/file/key.pem", openssl::ssl::SslFiletype::PEM) - .expect("Set private key error"); - acceptor - .set_certificate_chain_file("tests/file/cert.pem") - .expect("Set cert error"); - let acceptor = acceptor.build(); - - // start_tx - // .send(()) - // .await - // .expect("Start channel (Client-Half) be closed unexpectedly"); - - let (stream, _) = listener.accept().await.expect("TCP listener accpet error"); - let ssl = openssl::ssl::Ssl::new(acceptor.context()).expect("Ssl Error"); - let mut stream = tokio_openssl::SslStream::new(ssl, stream).expect("SslStream Error"); - // SSL negotiation finished successfully - core::pin::Pin::new(&mut stream).accept().await.unwrap(); - - hyper::server::conn::Http::new() - .http1_only(true) - .http1_keep_alive(true) - .serve_connection(stream, hyper::service::service_fn($service_fn)) - .await - }); - - TlsHandle { port } - }}; -} - -#[macro_export] -macro_rules! start_server { - ( - HTTPS; - ServerNum: $server_num: expr, - Runtime: $runtime: expr, - Handles: $handle_vec: expr, - ServeFnName: $service_fn: ident, - ) => {{ - for _i in 0..$server_num { - let (tx, rx) = std::sync::mpsc::channel(); - let server_handle = $runtime.spawn(async move { - let handle = start_tls_server!($service_fn); - tx.send(handle) - .expect("Failed to send the handle to the test thread."); - }); - $runtime - .block_on(server_handle) - .expect("Runtime start server coroutine failed"); - let handle = rx - .recv() - .expect("Handle send channel (Server-Half) be closed unexpectedly"); - $handle_vec.push(handle); - } - }}; - ( - HTTP; - ServerNum: $server_num: expr, - Runtime: $runtime: expr, - Handles: $handle_vec: expr, - ServeFnName: $service_fn: ident, - ) => {{ - for _i in 0..$server_num { - let (tx, rx) = std::sync::mpsc::channel(); - let server_handle = $runtime.spawn(async move { - let mut handle = start_http_server!($service_fn); - handle - .server_start - .recv() - .await - .expect("Start channel (Server-Half) be closed unexpectedly"); - tx.send(handle) - .expect("Failed to send the handle to the test thread."); - }); - $runtime - .block_on(server_handle) - .expect("Runtime start server coroutine failed"); - let handle = rx - .recv() - .expect("Handle send channel (Server-Half) be closed unexpectedly"); - $handle_vec.push(handle); - } - }}; -} - #[macro_export] macro_rules! ensure_server_shutdown { (ServerHandle: $handle:expr) => { diff --git a/ylong_http_client/tests/common/sync_utils.rs b/ylong_http_client/tests/common/sync_utils.rs index cbedb9cb9de1277a995418b49287e87f4d0dce58..b9a1a6c4db99300ea53954607e308e403a023bd6 100644 --- a/ylong_http_client/tests/common/sync_utils.rs +++ b/ylong_http_client/tests/common/sync_utils.rs @@ -35,6 +35,7 @@ macro_rules! sync_client_test_case { Body: $resp_body: expr, },)* ) => {{ + define_service_handle!(HTTPS;); set_server_fn!( SYNC; ylong_server_fn, @@ -123,6 +124,7 @@ macro_rules! sync_client_test_case { Body: $resp_body: expr, },)* ) => {{ + define_service_handle!(HTTP;); set_server_fn!( SYNC; ylong_server_fn, diff --git a/ylong_http_client/tests/sdv_async_http.rs b/ylong_http_client/tests/sdv_async_http.rs index edf98d56f8ea5f6382844368383888539126add8..78dd6bd8ec014612885845bf2b7f6bfdc1d12b69 100644 --- a/ylong_http_client/tests/sdv_async_http.rs +++ b/ylong_http_client/tests/sdv_async_http.rs @@ -11,14 +11,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![cfg(all(feature = "async", not(feature = "__tls")))] +#![cfg(feature = "async")] #[macro_use] mod common; use ylong_http::body::async_impl::Body as AsyncBody; -use crate::common::{async_build_http_client, init_test_work_runtime, HttpHandle}; +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 new file mode 100644 index 0000000000000000000000000000000000000000..50cfde0567412d2015904bc40dcfde7f6982f3c8 --- /dev/null +++ b/ylong_http_client/tests/sdv_async_http_on_tcp.rs @@ -0,0 +1,200 @@ +// 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. + +#![cfg(feature = "async")] + +#[macro_use] +pub mod tcp_server; + +use tokio::runtime::Runtime; +use ylong_http::body::async_impl::Body; + +use crate::tcp_server::{format_header_str, TcpHandle}; + +fn init_test_work_runtime(thread_num: usize) -> Runtime { + tokio::runtime::Builder::new_multi_thread() + .worker_threads(thread_num) + .enable_all() + .build() + .expect("Build runtime failed.") +} + +/// SDV test cases for `async::Client`. +/// +/// # Brief +/// 1. Starts a hyper server with the tokio coroutine. +/// 2. Creates an async::Client. +/// 3. The client sends a request message. +/// 4. Verifies the received request on the server. +/// 5. The server sends a response message. +/// 6. Verifies the received response on the client. +/// 7. Shuts down the server. +/// 8. Repeats the preceding operations to start the next test case. +#[test] +fn sdv_async_client_send_request() { + // `GET` request + async_client_test_on_tcp!( + HTTP; + RuntimeThreads: 2, + Request: { + Method: "GET", + Path: "/data", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 200, + Version: "HTTP/1.1", + Header: "Content-Length", "3", + Body: "Hi!", + }, + ); + + // `HEAD` request. + async_client_test_on_tcp!( + HTTP; + RuntimeThreads: 2, + Request: { + Method: "HEAD", + Path: "/data", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 200, + Version: "HTTP/1.1", + Body: "", + }, + ); + + // `Post` Request. + async_client_test_on_tcp!( + HTTP; + RuntimeThreads: 2, + Request: { + Method: "POST", + Path: "/data", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 201, + Version: "HTTP/1.1", + Header: "Content-Length", "3", + Body: "Hi!", + }, + ); + + // `HEAD` request without body. + async_client_test_on_tcp!( + HTTP; + RuntimeThreads: 2, + Request: { + Method: "HEAD", + Path: "/data", + Body: "", + }, + Response: { + Status: 200, + Version: "HTTP/1.1", + Body: "", + }, + ); + + // `PUT` request. + async_client_test_on_tcp!( + HTTP; + RuntimeThreads: 2, + Request: { + Method: "PUT", + Path: "/data", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 200, + Version: "HTTP/1.1", + Header: "Content-Length", "3", + Body: "Hi!", + }, + ); +} + +/// SDV test cases for `async::Client`. +/// +/// # Brief +/// 1. Creates a hyper server with the tokio coroutine. +/// 2. Creates an async::Client. +/// 3. The client repeatedly sends requests to the the server. +/// 4. Verifies each response returned by the server. +/// 5. Shuts down the server. +#[test] +fn sdv_client_send_request_repeatedly() { + async_client_test_on_tcp!( + HTTP; + RuntimeThreads: 2, + Request: { + Method: "GET", + Path: "/data", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 201, + Version: "HTTP/1.1", + Header: "Content-Length", "11", + Body: "METHOD GET!", + }, + Request: { + Method: "POST", + Path: "/data", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 201, + Version: "HTTP/1.1", + Header: "Content-Length", "12", + Body: "METHOD POST!", + }, + ); +} + +/// SDV test cases for `async::Client`. +/// +/// # Brief +/// 1. Creates an async::Client. +/// 2. Creates five servers and five coroutine sequentially. +/// 3. The client sends requests to the created servers in five coroutines. +/// 4. Verifies the responses returned by each server. +/// 5. Shuts down the servers. +#[test] +fn sdv_client_making_multiple_connections() { + async_client_test_on_tcp!( + HTTP; + RuntimeThreads: 10, + ClientNum: 5, + Request: { + Method: "GET", + Path: "/data", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 201, + Version: "HTTP/1.1", + Header: "Content-Length", "11", + Body: "METHOD GET!", + }, + ); +} 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 99033c48bd6087541b6f7bd04e83372ca96d59c0..0cf98c380038ddf7e6eecde24c67c87267af4fa0 100644 --- a/ylong_http_client/tests/sdv_async_https_c_ssl.rs +++ b/ylong_http_client/tests/sdv_async_https_c_ssl.rs @@ -18,10 +18,9 @@ mod common; use std::path::PathBuf; -use common::TlsHandle; use ylong_http_client::Body; -use crate::common::{async_build_https_client, init_test_work_runtime}; +use crate::common::init_test_work_runtime; // TODO: Add doc for sdv tests. #[test] diff --git a/ylong_http_client/tests/sdv_sync_http.rs b/ylong_http_client/tests/sdv_sync_http.rs index 4defc6f2c1fd8e33aca49dd371b4cc8d6ffc8fc8..65cf63da39a1a5210ac9a2e36627259b10836b34 100644 --- a/ylong_http_client/tests/sdv_sync_http.rs +++ b/ylong_http_client/tests/sdv_sync_http.rs @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![cfg(all(not(feature = "__tls"), feature = "sync"))] +#![cfg(feature = "sync")] #[macro_use] mod common; @@ -20,7 +20,7 @@ use std::sync::Arc; use ylong_http_client::sync_impl::Body; -use crate::common::{init_test_work_runtime, HttpHandle}; +use crate::common::init_test_work_runtime; /// SDV test cases for `sync::Client`. /// @@ -39,7 +39,7 @@ fn sdv_synchronized_client_send_request() { RuntimeThreads: 2, Request: { Method: "PUT", - Host: "127.0.0.1", + Host: "http://127.0.0.1", Header: "Host", "127.0.0.1", Header: "Content-Length", "6", Body: "Hello!", @@ -69,7 +69,7 @@ fn sdv_synchronized_client_send_request_repeatedly() { RuntimeThreads: 2, Request: { Method: "GET", - Host: "127.0.0.1", + Host: "http://127.0.0.1", Header: "Host", "127.0.0.1", Header: "Content-Length", "6", Body: "Hello!", @@ -82,7 +82,7 @@ fn sdv_synchronized_client_send_request_repeatedly() { }, Request: { Method: "POST", - Host: "127.0.0.1", + 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_http_on_tcp.rs b/ylong_http_client/tests/sdv_sync_http_on_tcp.rs new file mode 100644 index 0000000000000000000000000000000000000000..2d489163e7e2b88733331dd01719d38ee157666c --- /dev/null +++ b/ylong_http_client/tests/sdv_sync_http_on_tcp.rs @@ -0,0 +1,118 @@ +// 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. + +#![cfg(feature = "sync")] + +#[macro_use] +mod tcp_server; + +use ylong_http_client::sync_impl::Body; + +use crate::tcp_server::{format_header_str, TcpHandle}; + +/// SDV test cases for `sync::Client`. +/// +/// # Brief +/// 1. Creates a runtime to host the server. +/// 2. Creates a server within the runtime coroutine. +/// 3. Creates a sync::Client. +/// 4. The client sends a request to the the server. +/// 5. Verifies the response returned by the server. +/// 6. Shuts down the server. +#[test] +fn sdv_synchronized_client_send_request_to_tcp() { + // `PUT` request. + sync_client_test_on_tcp!( + HTTP; + Request: { + Method: "PUT", + Path: "/data", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 200, + Version: "HTTP/1.1", + Header: "Content-Length", "3", + Body: "Hi!", + }, + ); +} + +/// SDV test cases for `sync::Client`. +/// +/// # Brief +/// 1. Creates a runtime to host the server. +/// 2. Creates a server within the runtime coroutine. +/// 3. Creates a sync::Client. +/// 4. The client sends requests to the the server repeatedly. +/// 5. Verifies each response returned by the server. +/// 6. Shuts down the server. +#[test] +fn sdv_synchronized_client_send_request_repeatedly_to_tcp() { + sync_client_test_on_tcp!( + HTTP; + Request: { + Method: "GET", + Path: "/data", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 201, + Version: "HTTP/1.1", + Header: "Content-Length", "11", + Body: "METHOD GET!", + }, + Request: { + Method: "POST", + Path: "/data", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 201, + Version: "HTTP/1.1", + Header: "Content-Length", "12", + Body: "METHOD POST!", + }, + ); +} + +/// SDV test cases for `sync::Client`. +/// +/// # Brief +/// 1. Creates a sync::Client. +/// 2. Creates five servers and five client thread sequentially. +/// 3. The client sends requests to the created servers in five thread. +/// 4. Verifies the responses returned by each server. +/// 5. Shuts down the servers. +#[test] +fn sdv_client_making_multiple_connections() { + sync_client_test_on_tcp!( + HTTP; + ClientNum: 5, + Request: { + Method: "GET", + Path: "/data", + Header: "Content-Length", "6", + Body: "Hello!", + }, + Response: { + Status: 201, + Version: "HTTP/1.1", + Header: "Content-Length", "11", + Body: "METHOD GET!", + }, + ); +} 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 2b17d9d9d60af9406092b34b152f0202588ded76..68bec7fa1318c5ba48ff63ca52d37bde6a3cdd1c 100644 --- a/ylong_http_client/tests/sdv_sync_https_c_ssl.rs +++ b/ylong_http_client/tests/sdv_sync_https_c_ssl.rs @@ -15,7 +15,6 @@ use std::path::PathBuf; -use common::TlsHandle; use ylong_http_client::sync_impl::Body; use crate::common::init_test_work_runtime; @@ -36,7 +35,7 @@ fn sdv_synchronized_client_send_request() { RuntimeThreads: 2, Request: { Method: "PUT", - Host: "127.0.0.1", + Host: "https://127.0.0.1", Header: "Host", "127.0.0.1", Header: "Content-Length", "6", Body: "Hello!", @@ -62,7 +61,7 @@ fn sdv_synchronized_client_send_request_repeatedly() { RuntimeThreads: 2, Request: { Method: "GET", - Host: "127.0.0.1", + Host: "https://127.0.0.1", Header: "Host", "127.0.0.1", Header: "Content-Length", "6", Body: "Hello!", @@ -75,7 +74,7 @@ fn sdv_synchronized_client_send_request_repeatedly() { }, Request: { Method: "POST", - Host: "127.0.0.1", + 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 new file mode 100644 index 0000000000000000000000000000000000000000..8734ddaf431248f4fd5e159546af1a5165075846 --- /dev/null +++ b/ylong_http_client/tests/tcp_server/async_utils.rs @@ -0,0 +1,228 @@ +// 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. + +#[macro_export] +macro_rules! async_client_test_on_tcp { + ( + HTTP; + RuntimeThreads: $thread_num: expr, + $(ClientNum: $client_num: expr,)? + $(Request: { + Method: $method: expr, + Path: $path: expr, + $( + Header: $req_n: expr, $req_v: expr, + )* + Body: $req_body: expr, + }, + Response: { + Status: $status: expr, + Version: $version: expr, + $( + Header: $resp_n: expr, $resp_v: expr, + )* + Body: $resp_body: expr, + },)* + ) => {{ + + 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. + #[allow(unused_mut, unused_assignments)] + let mut server_num = 1; + $(server_num = $client_num;)? + let mut handles_vec = vec![]; + + start_tcp_server!( + ASYNC; + ServerNum: server_num, + Runtime: runtime, + Handles: handles_vec, + $(Request: { + Method: $method, + Path: $path, + $( + Header: $req_n, $req_v, + )* + Body: $req_body, + }, + Response: { + Status: $status, + Version: $version, + $( + Header: $resp_n, $resp_v, + )* + Body: $resp_body, + },)* + ); + + let mut shut_downs = vec![]; + async_client_assert_on_tcp!( + HTTP; + Runtime: runtime, + ServerNum: server_num, + Handles: handles_vec, + ShutDownHandles: shut_downs, + $(Request: { + Method: $method, + Path: $path, + $( + Header: $req_n, $req_v, + )* + Body: $req_body, + }, + Response: { + Status: $status, + Version: $version, + $( + Header: $resp_n, $resp_v, + )* + Body: $resp_body, + },)* + ); + + for shutdown_handle in shut_downs { + runtime.block_on(shutdown_handle).expect("Runtime wait for server shutdown failed"); + } + }}; + +} + +#[macro_export] +macro_rules! async_client_assert_on_tcp { + ( + HTTP; + Runtime: $runtime: expr, + ServerNum: $server_num: expr, + Handles: $handle_vec: expr, + ShutDownHandles: $shut_downs: expr, + $(Request: { + Method: $method: expr, + Path: $path: expr, + $( + Header: $req_n: expr, $req_v: expr, + )* + Body: $req_body: expr, + }, + Response: { + Status: $status: expr, + Version: $version: expr, + $( + Header: $resp_n: expr, $resp_v: expr, + )* + Body: $resp_body: expr, + },)* + ) => {{ + let client = ylong_http_client::async_impl::Client::new(); + let client = std::sync::Arc::new(client); + for _i in 0..$server_num { + let handle = $handle_vec.pop().expect("No more handles !"); + let client = std::sync::Arc::clone(&client); + let shutdown_handle = $runtime.spawn(async move { + async_client_assertions_on_tcp!( + ServerHandle: handle, + ClientRef: client, + $(Request: { + Method: $method, + Path: $path, + $( + Header: $req_n, $req_v, + )* + Body: $req_body, + }, + Response: { + Status: $status, + Version: $version, + $( + Header: $resp_n, $resp_v, + )* + Body: $resp_body, + },)* + ); + handle.server_shutdown.recv().expect("server send order failed !"); + }); + $shut_downs.push(shutdown_handle); + } + }} +} + +#[macro_export] +macro_rules! async_client_assertions_on_tcp { + ( + ServerHandle: $handle:expr, + ClientRef: $client:expr, + $(Request: { + Method: $method: expr, + Path: $path: expr, + $( + Header: $req_n: expr, $req_v: expr, + )* + Body: $req_body: expr, + }, + Response: { + Status: $status: expr, + Version: $version: expr, + $( + Header: $resp_n: expr, $resp_v: expr, + )* + Body: $resp_body: expr, + },)* + ) => { + $( + { + let request = build_client_request!( + Request: { + Method: $method, + Path: $path, + Addr: $handle.addr.as_str(), + $( + Header: $req_n, $req_v, + )* + Body: $req_body, + }, + ); + + let mut response = $client + .request(request) + .await + .expect("Request send failed"); + + assert_eq!(response.status().as_u16(), $status, "Assert response status code failed") ; + assert_eq!(response.version().as_str(), $version, "Assert response version failed"); + $(assert_eq!( + response + .headers() + .get($resp_n) + .expect(format!("Get response header \"{}\" failed", $resp_n).as_str()) + .to_str() + .expect(format!("Convert response header \"{}\"into string failed", $resp_n).as_str()), + $resp_v, + "Assert response header \"{}\" failed", $resp_n, + );)* + let mut buf = [0u8; 4096]; + let mut size = 0; + loop { + let read = response + .body_mut() + .data(&mut buf[size..]).await + .expect("Response body read failed"); + if read == 0 { + break; + } + size += read; + } + assert_eq!(&buf[..size], $resp_body.as_bytes(), "Assert response body failed"); + } + )* + } +} diff --git a/ylong_http_client/tests/tcp_server/mod.rs b/ylong_http_client/tests/tcp_server/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..931ced330011d77d52e44ed88d73c7327eb8dc6a --- /dev/null +++ b/ylong_http_client/tests/tcp_server/mod.rs @@ -0,0 +1,259 @@ +// 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::sync::mpsc::Receiver; + +#[cfg(feature = "async")] +mod async_utils; + +#[cfg(feature = "sync")] +mod sync_utils; + +pub struct TcpHandle { + pub addr: String, + + // This channel allows the server to notify the client when it has shut down. + pub server_shutdown: Receiver<()>, +} + +pub fn format_header_str(key: &str, value: &str) -> String { + format!("{}:{}\r\n", key.to_ascii_lowercase(), value) +} + +#[macro_export] +macro_rules! start_tcp_server { + ( + ASYNC; + ServerNum: $server_num: expr, + Runtime: $runtime: expr, + Handles: $handle_vec: expr, + $(Request: { + Method: $method: expr, + Path: $path: expr, + $( + Header: $req_n: expr, $req_v: expr, + )* + Body: $req_body: expr, + }, + Response: { + Status: $status: expr, + Version: $version: expr, + $( + Header: $resp_n: expr, $resp_v: expr, + )* + Body: $resp_body: expr, + },)* + + ) => {{ + use std::sync::mpsc::channel; + use tokio::net::TcpListener; + use tokio::io::{AsyncReadExt, AsyncWriteExt}; + + for _i in 0..$server_num { + let (rx, tx) = channel(); + let (rx2, tx2) = channel(); + + $runtime.spawn(async move { + + let server = TcpListener::bind("127.0.0.1:0").await.expect("server is failed to bind a address !"); + let addr = server.local_addr().expect("failed to get server address !"); + let handle = TcpHandle { + addr: addr.to_string(), + server_shutdown: tx, + }; + rx2.send(handle).expect("send TcpHandle out coroutine failed !"); + + let (mut stream, _client) = server.accept().await.expect("failed to build a tcp stream"); + + $( + { + let mut buf = [0u8; 4096]; + + let size = stream.read(&mut buf).await.expect("tcp stream read error !"); + let mut length = 0; + let crlf = "\r\n"; + let request_str = String::from_utf8_lossy(&buf[..size]); + + let request_line = format!("{} {} {}{}", $method, $path, "HTTP/1.1", crlf); + assert!(&buf[..size].starts_with(request_line.as_bytes()), "Incorrect Request-Line!"); + length += request_line.len(); + + let accept = format_header_str("accept", "*/*"); + assert!(request_str.contains(accept.as_str()), "Incorrect accept header!"); + length += accept.len(); + + let host = format_header_str("host", addr.to_string().as_str()); + assert!(request_str.contains(host.as_str()), "Incorrect host header!"); + length += host.len(); + + $( + let header_str = format_header_str($req_n, $req_v); + assert!(request_str.contains(header_str.as_str()), "Incorrect {} header!", $req_n); + length += header_str.len(); + )* + + length += crlf.len(); + length += $req_body.len(); + + if length > size { + let size2 = stream.read(&mut buf).await.expect("tcp stream read error2 !"); + assert_eq!(&buf[..size2], $req_body.as_bytes()); + assert_eq!(size + size2, length, "Incorrect total request bytes !"); + } else { + assert_eq!(size, length, "Incorrect total request bytes !"); + } + + let mut resp_str = String::from(format!("{} {} OK\r\n", $version, $status)); + $( + let header = format_header_str($resp_n, $resp_v); + resp_str.push_str(header.as_str()); + )* + resp_str.push_str(crlf); + resp_str.push_str($resp_body); + + stream.write_all(resp_str.as_bytes()).await.expect("server write response failed"); + } + )* + rx.send(()).expect("server send order failed !"); + + }); + + let handle = tx2.recv().expect("recv server handle failed !"); + + $handle_vec.push(handle); + } + }}; + + ( + SYNC; + ServerNum: $server_num: expr, + Handles: $handle_vec: expr, + $(Request: { + Method: $method: expr, + Path: $path: expr, + $( + Header: $req_n: expr, $req_v: expr, + )* + Body: $req_body: expr, + }, + Response: { + Status: $status: expr, + Version: $version: expr, + $( + Header: $resp_n: expr, $resp_v: expr, + )* + Body: $resp_body: expr, + },)* + + ) => {{ + use std::net::TcpListener; + use std::io::{Read, Write}; + use std::sync::mpsc::channel; + use std::time::Duration; + + for _i in 0..$server_num { + let server = TcpListener::bind("127.0.0.1:0").expect("server is failed to bind a address !"); + let addr = server.local_addr().expect("failed to get server address !"); + let (rx, tx) = channel(); + + std::thread::spawn( move || { + + let (mut stream, _client) = server.accept().expect("failed to build a tcp stream"); + stream.set_read_timeout(Some(Duration::from_secs(10))).expect("tcp stream set read time out error !"); + stream.set_write_timeout(Some(Duration::from_secs(10))).expect("tcp stream set write time out error !"); + + $( + { + let mut buf = [0u8; 4096]; + + let size = stream.read(&mut buf).expect("tcp stream read error !"); + let mut length = 0; + let crlf = "\r\n"; + let request_str = String::from_utf8_lossy(&buf[..size]); + let request_line = format!("{} http://{}{} {}{}", $method, addr.to_string().as_str(), $path, "HTTP/1.1", crlf); + assert!(&buf[..size].starts_with(request_line.as_bytes()), "Incorrect Request-Line!"); + + length += request_line.len(); + + let accept = format_header_str("accept", "*/*"); + assert!(request_str.contains(accept.as_str()), "Incorrect accept header!"); + length += accept.len(); + + let host = format_header_str("host", addr.to_string().as_str()); + assert!(request_str.contains(host.as_str()), "Incorrect host header!"); + length += host.len(); + + $( + let header_str = format_header_str($req_n, $req_v); + assert!(request_str.contains(header_str.as_str()), "Incorrect {} header!", $req_n); + length += header_str.len(); + )* + + length += crlf.len(); + length += $req_body.len(); + + if length > size { + let size2 = stream.read(&mut buf).expect("tcp stream read error2 !"); + assert_eq!(&buf[..size2], $req_body.as_bytes()); + assert_eq!(size + size2, length, "Incorrect total request bytes !"); + } else { + assert_eq!(size, length, "Incorrect total request bytes !"); + } + + let mut resp_str = String::from(format!("{} {} OK\r\n", $version, $status)); + $( + let header = format_header_str($resp_n, $resp_v); + resp_str.push_str(header.as_str()); + )* + resp_str.push_str(crlf); + resp_str.push_str($resp_body); + + stream.write_all(resp_str.as_bytes()).expect("server write response failed"); + } + )* + rx.send(()).expect("server send order failed !"); + + }); + + let handle = TcpHandle { + addr: addr.to_string(), + server_shutdown: tx, + }; + $handle_vec.push(handle); + } + + }} +} + +/// Creates a `Request`. +#[macro_export] +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::request::RequestBuilder::new() + .method($method) + .url(format!("http://{}{}",$addr, $path).as_str()) + $(.header($req_n, $req_v))* + .body(ylong_http::body::TextBody::from_bytes($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 new file mode 100644 index 0000000000000000000000000000000000000000..27e17cb225082c5d45deefcacb6818c4f8093d39 --- /dev/null +++ b/ylong_http_client/tests/tcp_server/sync_utils.rs @@ -0,0 +1,221 @@ +// 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. + +#[macro_export] +macro_rules! sync_client_test_on_tcp { + ( + HTTP; + $(ClientNum: $client_num: expr,)? + $(Request: { + Method: $method: expr, + Path: $path: expr, + $( + Header: $req_n: expr, $req_v: expr, + )* + Body: $req_body: expr, + }, + Response: { + Status: $status: expr, + Version: $version: expr, + $( + Header: $resp_n: expr, $resp_v: expr, + )* + Body: $resp_body: expr, + },)* + ) => {{ + + // 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. + #[allow(unused_mut, unused_assignments)] + let mut server_num = 1; + $(server_num = $client_num;)? + let mut handles_vec = vec![]; + + start_tcp_server!( + SYNC; + ServerNum: server_num, + Handles: handles_vec, + $(Request: { + Method: $method, + Path: $path, + $( + Header: $req_n, $req_v, + )* + Body: $req_body, + }, + Response: { + Status: $status, + Version: $version, + $( + Header: $resp_n, $resp_v, + )* + Body: $resp_body, + },)* + ); + + let mut shut_downs = vec![]; + sync_client_assert_on_tcp!( + HTTP; + ServerNum: server_num, + Handles: handles_vec, + ShutDownHandles: shut_downs, + $(Request: { + Method: $method, + Path: $path, + $( + Header: $req_n, $req_v, + )* + Body: $req_body, + }, + Response: { + Status: $status, + Version: $version, + $( + Header: $resp_n, $resp_v, + )* + Body: $resp_body, + },)* + ); + + for shutdown_handle in shut_downs { + shutdown_handle.join().expect("join to wait client thread finish failed !"); + } + }}; + +} + +#[macro_export] +macro_rules! sync_client_assert_on_tcp { + ( + HTTP; + ServerNum: $server_num: expr, + Handles: $handle_vec: expr, + ShutDownHandles: $shut_downs: expr, + $(Request: { + Method: $method: expr, + Path: $path: expr, + $( + Header: $req_n: expr, $req_v: expr, + )* + Body: $req_body: expr, + }, + Response: { + Status: $status: expr, + Version: $version: expr, + $( + Header: $resp_n: expr, $resp_v: expr, + )* + Body: $resp_body: expr, + },)* + ) => {{ + let client = ylong_http_client::sync_impl::Client::new(); + let client = std::sync::Arc::new(client); + for _i in 0..$server_num { + let handle = $handle_vec.pop().expect("No more handles !"); + let client = std::sync::Arc::clone(&client); + let shutdown_handle = std::thread::spawn(move || { + sync_client_assertions_on_tcp!( + ServerHandle: handle, + ClientRef: client, + $(Request: { + Method: $method, + Path: $path, + $( + Header: $req_n, $req_v, + )* + Body: $req_body, + }, + Response: { + Status: $status, + Version: $version, + $( + Header: $resp_n, $resp_v, + )* + Body: $resp_body, + },)* + ); + handle.server_shutdown.recv().expect("server send order failed !"); + }); + $shut_downs.push(shutdown_handle); + }; + }} +} + +#[macro_export] +macro_rules! sync_client_assertions_on_tcp { + ( + ServerHandle: $handle:expr, + ClientRef: $client:expr, + $(Request: { + Method: $method: expr, + Path: $path: expr, + $( + Header: $req_n: expr, $req_v: expr, + )* + Body: $req_body: expr, + }, + Response: { + Status: $status: expr, + Version: $version: expr, + $( + Header: $resp_n: expr, $resp_v: expr, + )* + Body: $resp_body: expr, + },)* + ) => { + $( + { + let request = build_client_request!( + Request: { + Method: $method, + Path: $path, + Addr: $handle.addr.as_str(), + $( + Header: $req_n, $req_v, + )* + Body: $req_body, + }, + ); + let mut response = $client + .request(request) + .expect("Request send failed"); + + assert_eq!(response.status().as_u16(), $status, "Assert response status code failed") ; + assert_eq!(response.version().as_str(), $version, "Assert response version failed"); + $(assert_eq!( + response + .headers() + .get($resp_n) + .expect(format!("Get response header \"{}\" failed", $resp_n).as_str()) + .to_str() + .expect(format!("Convert response header \"{}\"into string failed", $resp_n).as_str()), + $resp_v, + "Assert response header \"{}\" failed", $resp_n, + );)* + let mut buf = [0u8; 4096]; + let mut size = 0; + loop { + let read = response + .body_mut() + .data(&mut buf[size..]) + .expect("Response body read failed"); + if read == 0 { + break; + } + size += read; + } + assert_eq!(&buf[..size], $resp_body.as_bytes(), "Assert response body failed"); + } + )* + } +}