diff --git a/ylong_http_client/src/async_impl/client.rs b/ylong_http_client/src/async_impl/client.rs index 7854e2d1cc1ff6e98a71657ff093cd2b6e69f6cb..b505020ddddce7e400560e771350c4bbdc3a643c 100644 --- a/ylong_http_client/src/async_impl/client.rs +++ b/ylong_http_client/src/async_impl/client.rs @@ -19,7 +19,6 @@ use super::pool::ConnPool; use super::timeout::TimeoutFuture; use super::{conn, Connector, HttpConnector, Request, Response}; use crate::async_impl::dns::{DefaultDnsResolver, Resolver}; -use crate::async_impl::interceptor::{IdleInterceptor, Interceptor, Interceptors}; use crate::async_impl::request::Message; use crate::error::HttpClientError; use crate::runtime::timeout; @@ -30,7 +29,8 @@ use crate::util::config::FchownConfig; use crate::util::config::{ ClientConfig, ConnectorConfig, HttpConfig, HttpVersion, Proxy, Redirect, Timeout, }; -use crate::util::dispatcher::Conn; +use crate::util::dispatcher::{Conn, TimeInfoConn}; +use crate::util::interceptor::{IdleInterceptor, Interceptor, Interceptors}; use crate::util::normalizer::RequestFormatter; use crate::util::proxy::Proxies; use crate::util::redirect::{RedirectInfo, Trigger}; @@ -188,11 +188,17 @@ impl Client { mut request: RequestArc, ) -> Result { RequestFormatter::new(request.ref_mut()).format()?; - let conn = self.connect_to(request.ref_mut().uri()).await?; + let mut info_conn = self.connect_to(request.ref_mut().uri()).await?; + request + .ref_mut() + .time_group_mut() + .update_transport_conn_time(info_conn.time_group()); + let mut conn = info_conn.connection(); + self.interceptors.intercept_connection(conn.get_detail())?; self.send_request_on_conn(conn, request).await } - async fn connect_to(&self, uri: &Uri) -> Result, HttpClientError> { + 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), @@ -285,6 +291,7 @@ pub struct ClientBuilder { /// Fchown configuration. fchown: Option, + /// Interceptor for all stages. interceptors: Arc, /// Resolver to http DNS. resolver: Arc, @@ -443,8 +450,8 @@ impl ClientBuilder { /// # Examples /// /// ``` - /// # use ylong_http_client::async_impl::{ClientBuilder, Interceptor}; - /// # use ylong_http_client::HttpClientError; + /// # use ylong_http_client::async_impl::ClientBuilder; + /// # use ylong_http_client::{HttpClientError, Interceptor}; /// /// # fn add_interceptor(interceptor: T) /// # where T: Interceptor + Sync + Send + 'static, @@ -974,8 +981,8 @@ mod ut_async_impl_client { use ylong_http::h1::ResponseDecoder; use ylong_http::response::Response as HttpResponse; - use crate::async_impl::interceptor::IdleInterceptor; use crate::async_impl::{ClientBuilder, HttpBody}; + use crate::util::interceptor::IdleInterceptor; use crate::util::normalizer::BodyLength; use crate::util::request::RequestArc; use crate::util::Redirect; @@ -1066,7 +1073,11 @@ HJMRZVCQpSMzvHlofHSNgzWV1MX5h1CP4SGZdBDTfA== #[cfg(all(feature = "__tls", feature = "ylong_base"))] async fn client_request_verify() { // Creates a `async_impl::Client` - let client = Client::builder().cert_verifier(Verifier).build().unwrap(); + let client = Client::builder() + .cert_verifier(Verifier) + .connect_timeout(Timeout::from_secs(10)) + .build() + .unwrap(); // Creates a `Request`. let request = Request::builder() .url("https://www.example.com") diff --git a/ylong_http_client/src/async_impl/conn/http1.rs b/ylong_http_client/src/async_impl/conn/http1.rs index a582706330fe575774d18fbe82fc9109e7d352cb..0c66960527f92bc55483a7309e4b58088fab3816 100644 --- a/ylong_http_client/src/async_impl/conn/http1.rs +++ b/ylong_http_client/src/async_impl/conn/http1.rs @@ -11,9 +11,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::mem::take; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; +use std::time::Instant; use ylong_http::body::async_impl::Body; use ylong_http::body::{ChunkBody, TextBody}; @@ -23,13 +25,13 @@ use ylong_http::response::ResponsePart; use ylong_http::version::Version; use super::StreamData; -use crate::async_impl::connector::ConnInfo; -use crate::async_impl::interceptor::Interceptors; use crate::async_impl::request::Message; 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::information::ConnInfo; +use crate::util::interceptor::Interceptors; use crate::util::normalizer::BodyLengthParser; const TEMP_BUF_SIZE: usize = 16 * 1024; @@ -41,14 +43,16 @@ pub(crate) async fn request( where S: AsyncRead + AsyncWrite + ConnInfo + Sync + Send + Unpin + 'static, { - message - .interceptor - .intercept_connection(conn.raw_mut().conn_detail())?; message .interceptor .intercept_request(message.request.ref_mut())?; let mut buf = vec![0u8; TEMP_BUF_SIZE]; + message + .request + .ref_mut() + .time_group_mut() + .set_transfer_start(Instant::now()); encode_request_part( message.request.ref_mut(), &message.interceptor, @@ -57,7 +61,6 @@ where ) .await?; encode_various_body(message.request.ref_mut(), &mut conn, &mut buf).await?; - // Decodes response part. let (part, pre) = { let mut decoder = ResponseDecoder::new(); @@ -67,7 +70,22 @@ where conn.shutdown(); return err_from_msg!(Request, "Tcp closed"); } - Ok(size) => size, + Ok(size) => { + if message + .request + .ref_mut() + .time_group_mut() + .transfer_end_time() + .is_none() + { + message + .request + .ref_mut() + .time_group_mut() + .set_transfer_end(Instant::now()) + } + size + } Err(e) => { conn.shutdown(); return err_from_io!(Request, e); @@ -214,10 +232,11 @@ where } }; + let time_group = take(message.request.ref_mut().time_group_mut()); let body = HttpBody::new(message.interceptor, length, Box::new(conn), pre)?; - Ok(Response::new( - ylong_http::response::Response::from_raw_parts(part, body), - )) + let mut response = Response::new(ylong_http::response::Response::from_raw_parts(part, body)); + response.set_time_group(time_group); + Ok(response) } async fn encode_body( diff --git a/ylong_http_client/src/async_impl/conn/http2.rs b/ylong_http_client/src/async_impl/conn/http2.rs index d6e3df965b626334a33eebdc981fb2d6d463c61c..7399e62a3ef99a837de0cbaa6374fd7bc9629a17 100644 --- a/ylong_http_client/src/async_impl/conn/http2.rs +++ b/ylong_http_client/src/async_impl/conn/http2.rs @@ -12,10 +12,12 @@ // limitations under the License. use std::cmp::min; +use std::mem::take; use std::ops::Deref; use std::pin::Pin; use std::sync::atomic::Ordering; use std::task::{Context, Poll}; +use std::time::Instant; use ylong_http::error::HttpError; use ylong_http::h2; @@ -59,8 +61,18 @@ where payload, data, }; + message + .request + .ref_mut() + .time_group_mut() + .set_transfer_start(Instant::now()); conn.send_frame_to_controller(stream)?; let frame = conn.receiver.recv().await?; + message + .request + .ref_mut() + .time_group_mut() + .set_transfer_end(Instant::now()); frame_2_response(conn, frame, message) } @@ -115,19 +127,18 @@ where }; let text_io = TextIo::new(conn); - // TODO Can http2 have no content-length header field and rely only on the - // end_stream flag? flag has a Body let length = match BodyLengthParser::new(message.request.ref_mut().method(), &part).parse() { Ok(length) => length, Err(e) => { return Err(e); } }; + let time_group = take(message.request.ref_mut().time_group_mut()); let body = HttpBody::new(message.interceptor, length, Box::new(text_io), &[0u8; 0])?; - Ok(Response::new( - ylong_http::response::Response::from_raw_parts(part, body), - )) + let mut response = Response::new(ylong_http::response::Response::from_raw_parts(part, body)); + response.set_time_group(time_group); + Ok(response) } pub(crate) fn build_headers_payload( @@ -448,6 +459,7 @@ mod ut_http2 { #[cfg(feature = "ylong_base")] #[test] fn ut_http2_body_poll_read() { + use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::pin::Pin; use std::sync::atomic::AtomicBool; use std::sync::Arc; @@ -458,11 +470,18 @@ mod ut_http2 { use crate::async_impl::conn::http2::TextIo; use crate::util::dispatcher::http2::Http2Conn; + use crate::{ConnDetail, ConnProtocol}; let (resp_tx, resp_rx) = ylong_runtime::sync::mpsc::bounded_channel(20); let (req_tx, _req_rx) = crate::runtime::unbounded_channel(); let shutdown = Arc::new(AtomicBool::new(false)); - let mut conn: Http2Conn<()> = Http2Conn::new(20, shutdown, req_tx); + let detail = ConnDetail { + protocol: ConnProtocol::Tcp, + local: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080), + peer: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 443), + addr: "localhost".to_string(), + }; + let mut conn: Http2Conn<()> = Http2Conn::new(20, shutdown, req_tx, detail); conn.receiver.set_receiver(resp_rx); let mut text_io = TextIo::new(conn); let data_1 = Frame::new( diff --git a/ylong_http_client/src/async_impl/conn/mod.rs b/ylong_http_client/src/async_impl/conn/mod.rs index e6f2590aa76e35e28b796cde80b9a865b3e064ab..b6d4b5472ed668d7fcc6d2cb99de104a697e6dd0 100644 --- a/ylong_http_client/src/async_impl/conn/mod.rs +++ b/ylong_http_client/src/async_impl/conn/mod.rs @@ -20,12 +20,12 @@ mod http2; #[cfg(feature = "http3")] mod http3; -use crate::async_impl::connector::ConnInfo; use crate::async_impl::request::Message; use crate::async_impl::Response; use crate::error::HttpClientError; use crate::runtime::{AsyncRead, AsyncWrite}; use crate::util::dispatcher::Conn; +use crate::util::ConnInfo; pub(crate) trait StreamData: AsyncRead { fn shutdown(&self); diff --git a/ylong_http_client/src/async_impl/connector/mod.rs b/ylong_http_client/src/async_impl/connector/mod.rs index 3dfe57ccffdab2798b74ca4a7ea0d641069e4f0d..b41727cfe49a73b4956b586b1fbb4720707b0dc8 100644 --- a/ylong_http_client/src/async_impl/connector/mod.rs +++ b/ylong_http_client/src/async_impl/connector/mod.rs @@ -17,10 +17,9 @@ mod stream; use core::future::Future; use std::io::{Error, ErrorKind}; +use std::net::SocketAddr; use std::sync::Arc; -/// Information of an IO. -pub use stream::ConnInfo; use ylong_http::request::uri::Uri; #[cfg(feature = "http3")] use ylong_runtime::net::{ConnectedUdpSocket, UdpSocket}; @@ -28,6 +27,8 @@ use ylong_runtime::net::{ConnectedUdpSocket, UdpSocket}; use crate::async_impl::dns::{DefaultDnsResolver, EyeBallConfig, HappyEyeballs, Resolver}; use crate::runtime::{AsyncRead, AsyncWrite, TcpStream}; use crate::util::config::{ConnectorConfig, HttpVersion}; +/// Information of an IO. +use crate::util::ConnInfo; use crate::{HttpClientError, Timeout}; /// `Connector` trait used by `async_impl::Client`. `Connector` provides @@ -106,11 +107,10 @@ async fn tcp_stream(eyeballs: HappyEyeballs) -> Result, addr: &str, - timeout: Timeout, -) -> Result { +) -> Result, HttpClientError> { let addr_fut = resolver.resolve(addr); let socket_addr = addr_fut.await.map_err(|e| { HttpClientError::from_dns_error( @@ -118,8 +118,13 @@ async fn eyeballs_connect( Error::new(ErrorKind::Interrupted, e), ) })?; + Ok(socket_addr.collect::>()) +} - let addrs = socket_addr.collect::>(); +async fn eyeballs_connect( + addrs: Vec, + timeout: Timeout, +) -> Result { let eyeball_config = EyeBallConfig::new(timeout.inner(), None); let happy_eyeballs = HappyEyeballs::new(addrs, eyeball_config); tcp_stream(happy_eyeballs).await @@ -145,15 +150,17 @@ pub(crate) async fn udp_stream( mod no_tls { use core::future::Future; use core::pin::Pin; + use std::time::Instant; use ylong_http::request::uri::Uri; use super::{eyeballs_connect, Connector, HttpConnector}; + use crate::async_impl::connector::dns_query; use crate::async_impl::connector::stream::HttpStream; - use crate::async_impl::interceptor::{ConnDetail, ConnProtocol}; use crate::runtime::TcpStream; use crate::util::config::HttpVersion; - use crate::HttpClientError; + use crate::util::interceptor::ConnProtocol; + use crate::{ConnData, ConnDetail, HttpClientError, TimeGroup}; impl Connector for HttpConnector { type Stream = HttpStream; @@ -172,7 +179,13 @@ mod no_tls { let resolver = self.resolver.clone(); let timeout = self.config.timeout.clone(); Box::pin(async move { - let stream = eyeballs_connect(resolver, addr.as_str(), timeout).await?; + let mut time_group = TimeGroup::default(); + time_group.set_dns_start(Instant::now()); + let socket_addrs = dns_query(resolver, addr.as_str()).await?; + time_group.set_dns_end(Instant::now()); + time_group.set_tcp_start(Instant::now()); + let stream = eyeballs_connect(socket_addrs, timeout).await?; + time_group.set_tcp_end(Instant::now()); let local = stream .local_addr() .map_err(|e| HttpClientError::from_io_error(crate::ErrorKind::Connect, e))?; @@ -181,13 +194,16 @@ mod no_tls { .map_err(|e| HttpClientError::from_io_error(crate::ErrorKind::Connect, e))?; let detail = ConnDetail { protocol: ConnProtocol::Tcp, - alpn: None, local, peer, addr, - proxy: is_proxy, }; - Ok(HttpStream::new(stream, detail)) + + let data = ConnData::builder() + .time_group(time_group) + .proxy(is_proxy) + .build(detail); + Ok(HttpStream::new(stream, data)) }) } } @@ -200,12 +216,13 @@ mod tls { use std::error; use std::fmt::{Debug, Display, Formatter}; use std::io::{Error, ErrorKind, Write}; + use std::time::Instant; use ylong_http::request::uri::{Scheme, Uri}; use super::{eyeballs_connect, Connector, HttpConnector}; + use crate::async_impl::connector::dns_query; use crate::async_impl::connector::stream::HttpStream; - use crate::async_impl::interceptor::{ConnDetail, ConnProtocol}; use crate::async_impl::mix::MixStream; #[cfg(feature = "http3")] use crate::async_impl::quic::QuicConn; @@ -214,7 +231,10 @@ mod tls { use crate::config::FchownConfig; use crate::runtime::{AsyncReadExt, AsyncWriteExt, TcpStream}; use crate::util::config::HttpVersion; - use crate::{HttpClientError, TlsConfig}; + #[cfg(feature = "http2")] + use crate::util::information::NegotiateInfo; + use crate::util::interceptor::ConnProtocol; + use crate::{ConnData, ConnDetail, HttpClientError, TimeGroup, TlsConfig}; impl Connector for HttpConnector { type Stream = HttpStream; @@ -243,8 +263,13 @@ mod tls { let timeout = self.config.timeout.clone(); match *uri.scheme().unwrap() { Scheme::HTTP => Box::pin(async move { - let stream = eyeballs_connect(resolver, addr.as_str(), timeout).await?; - + let mut time_group = TimeGroup::default(); + time_group.set_dns_start(Instant::now()); + let socket_addrs = dns_query(resolver, addr.as_str()).await?; + time_group.set_dns_end(Instant::now()); + time_group.set_tcp_start(Instant::now()); + let stream = eyeballs_connect(socket_addrs, timeout).await?; + time_group.set_tcp_end(Instant::now()); #[cfg(all(target_os = "linux", feature = "ylong_base", feature = "__tls"))] if let Some(fchown) = fchown { let _ = stream.fchown(fchown.uid, fchown.gid); @@ -258,14 +283,16 @@ mod tls { })?; let detail = ConnDetail { protocol: ConnProtocol::Tcp, - alpn: None, local, peer, addr, - proxy: is_proxy, }; + let data = ConnData::builder() + .time_group(time_group) + .proxy(is_proxy) + .build(detail); - Ok(HttpStream::new(MixStream::Http(stream), detail)) + Ok(HttpStream::new(MixStream::Http(stream), data)) }), Scheme::HTTPS => { let host = uri.host().unwrap().to_string(); @@ -274,6 +301,8 @@ mod tls { #[cfg(feature = "http3")] if _http_version == HttpVersion::Http3 { return Box::pin(async move { + let mut time_group = TimeGroup::default(); + time_group.set_dns_start(Instant::now()); let addr_fut = resolver.resolve(&addr); let addrs = addr_fut.await.map_err(|e| { HttpClientError::from_dns_error( @@ -281,7 +310,8 @@ mod tls { Error::new(ErrorKind::Interrupted, e), ) })?; - + time_group.set_dns_end(Instant::now()); + time_group.set_quic_start(Instant::now()); let mut last_e = None; for addr_it in addrs { let udp_socket = match super::udp_stream(&addr_it).await { @@ -298,21 +328,27 @@ mod tls { HttpClientError::from_io_error(crate::ErrorKind::Connect, e) })?; let detail = ConnDetail { - protocol: ConnProtocol::Udp, - alpn: None, + protocol: ConnProtocol::Quic, local, peer, addr: addr.clone(), - proxy: false, }; + + let mut data = ConnData::builder() + .time_group(time_group.clone()) + .proxy(is_proxy) + .build(detail); + let mut stream = - HttpStream::new(MixStream::Udp(udp_socket), detail); + HttpStream::new(MixStream::Udp(udp_socket), data.clone()); let Ok(quic_conn) = QuicConn::connect(&mut stream, &config, &host).await else { continue; }; stream.set_quic_conn(quic_conn); + data.time_group_mut().set_quic_end(Instant::now()); + stream.set_conn_data(data); return Ok(stream); } @@ -323,7 +359,13 @@ mod tls { }); } Box::pin(async move { - let stream = eyeballs_connect(resolver, addr.as_str(), timeout).await?; + let mut time_group = TimeGroup::default(); + time_group.set_dns_start(Instant::now()); + let socket_addrs = dns_query(resolver, addr.as_str()).await?; + time_group.set_dns_end(Instant::now()); + time_group.set_tcp_start(Instant::now()); + let stream = eyeballs_connect(socket_addrs, timeout).await?; + time_group.set_tcp_end(Instant::now()); #[cfg(all(target_os = "linux", feature = "ylong_base", feature = "__tls"))] { https_connect( @@ -331,9 +373,9 @@ mod tls { addr, stream, is_proxy, - auth, - (host, port), + (auth, host, port), fchown, + time_group, ) .await } @@ -343,7 +385,15 @@ mod tls { feature = "__tls" )))] { - https_connect(config, addr, stream, is_proxy, auth, (host, port)).await + https_connect( + config, + addr, + stream, + is_proxy, + (auth, host, port), + time_group, + ) + .await } }) } @@ -356,11 +406,11 @@ mod tls { addr: String, tcp_stream: TcpStream, is_proxy: bool, - auth: Option, - (host, port): (String, u16), + (auth, host, port): (Option, String, u16), #[cfg(all(target_os = "linux", feature = "ylong_base", feature = "__tls"))] fchown: Option< FchownConfig, >, + mut time_group: TimeGroup, ) -> Result, HttpClientError> { let mut tcp = tcp_stream; #[cfg(all(target_os = "linux", feature = "ylong_base", feature = "__tls"))] @@ -390,24 +440,38 @@ mod tls { ) })?; + time_group.set_tls_start(Instant::now()); Pin::new(&mut stream).connect().await.map_err(|e| { HttpClientError::from_tls_error( crate::ErrorKind::Connect, Error::new(ErrorKind::Other, e), ) })?; + time_group.set_tls_end(Instant::now()); + #[cfg(feature = "http2")] let alpn = stream.negotiated_alpn_protocol().map(Vec::from); let detail = ConnDetail { protocol: ConnProtocol::Tcp, - alpn, local, peer, addr, - proxy: is_proxy, }; - Ok(HttpStream::new(MixStream::Https(stream), detail)) + #[cfg(feature = "http2")] + let data = ConnData::builder() + .time_group(time_group) + .proxy(is_proxy) + .negotiate(NegotiateInfo::from_alpn(alpn)) + .build(detail); + + #[cfg(not(feature = "http2"))] + let data = ConnData::builder() + .time_group(time_group) + .proxy(is_proxy) + .build(detail); + + Ok(HttpStream::new(MixStream::Https(stream), data)) } async fn tunnel( diff --git a/ylong_http_client/src/async_impl/connector/stream.rs b/ylong_http_client/src/async_impl/connector/stream.rs index cd0419a6f090541be949f9c0d860e69680445531..1bb6640e24a41c146a130b70b29442f178ed7baf 100644 --- a/ylong_http_client/src/async_impl/connector/stream.rs +++ b/ylong_http_client/src/async_impl/connector/stream.rs @@ -16,28 +16,14 @@ use std::pin::Pin; use std::task::{Context, Poll}; -use crate::async_impl::interceptor::ConnDetail; #[cfg(feature = "http3")] use crate::async_impl::quic::QuicConn; use crate::runtime::{AsyncRead, AsyncWrite, ReadBuf}; - -/// `ConnDetail` trait, which is used to obtain information about the current -/// connection. -pub trait ConnInfo { - /// Whether the current connection is a proxy. - fn is_proxy(&self) -> bool; - - /// Gets connection information. - fn conn_detail(&self) -> ConnDetail; - - /// Gets quic information - #[cfg(feature = "http3")] - fn quic_conn(&mut self) -> Option; -} +use crate::util::{ConnData, ConnInfo}; /// A connection wrapper containing io and io information. pub struct HttpStream { - detail: ConnDetail, + conn_data: ConnData, stream: T, #[cfg(feature = "http3")] quic_conn: Option, @@ -81,11 +67,11 @@ where impl ConnInfo for HttpStream { fn is_proxy(&self) -> bool { - self.detail.proxy + self.conn_data.is_proxy() } - fn conn_detail(&self) -> ConnDetail { - self.detail.clone() + fn conn_data(&self) -> ConnData { + self.conn_data.clone() } #[cfg(feature = "http3")] @@ -96,9 +82,9 @@ impl ConnInfo for HttpStream { impl HttpStream { /// HttpStream constructor. - pub fn new(io: T, detail: ConnDetail) -> HttpStream { + pub(crate) fn new(io: T, conn_data: ConnData) -> HttpStream { HttpStream { - detail, + conn_data, stream: io, #[cfg(feature = "http3")] quic_conn: None, @@ -106,7 +92,12 @@ impl HttpStream { } #[cfg(feature = "http3")] - pub fn set_quic_conn(&mut self, conn: QuicConn) { + pub(crate) fn set_quic_conn(&mut self, conn: QuicConn) { self.quic_conn = Some(conn); } + + #[cfg(feature = "http3")] + pub(crate) fn set_conn_data(&mut self, conn_data: ConnData) { + self.conn_data = conn_data + } } diff --git a/ylong_http_client/src/async_impl/downloader/mod.rs b/ylong_http_client/src/async_impl/downloader/mod.rs index 6eb694e70a14d9575f6ea81d621fb7a1c785844d..2c2e8c1396956298af7ae09bbbedbfa076ed29e3 100644 --- a/ylong_http_client/src/async_impl/downloader/mod.rs +++ b/ylong_http_client/src/async_impl/downloader/mod.rs @@ -259,8 +259,8 @@ mod ut_downloader { use ylong_http::response::Response; use crate::async_impl::conn::StreamData; - use crate::async_impl::interceptor::IdleInterceptor; use crate::async_impl::{Downloader, HttpBody, Response as adpater_resp}; + use crate::util::interceptor::IdleInterceptor; use crate::util::normalizer::BodyLength; impl StreamData for &[u8] { diff --git a/ylong_http_client/src/async_impl/http_body.rs b/ylong_http_client/src/async_impl/http_body.rs index 91c2da28e64400c97f8c5ccc5ef2fcdcf2aee807..d7ee09738106a4a4a9eb4744474729b31d12ba8c 100644 --- a/ylong_http_client/src/async_impl/http_body.rs +++ b/ylong_http_client/src/async_impl/http_body.rs @@ -24,9 +24,9 @@ use ylong_http::body::{ChunkBodyDecoder, ChunkState}; use ylong_http::headers::Headers; use super::conn::StreamData; -use crate::async_impl::interceptor::Interceptors; use crate::error::{ErrorKind, HttpClientError}; use crate::runtime::{AsyncRead, ReadBuf, Sleep}; +use crate::util::interceptor::Interceptors; use crate::util::normalizer::BodyLength; const TRAILER_SIZE: usize = 1024; @@ -564,8 +564,8 @@ mod ut_async_http_body { use ylong_http::body::async_impl; - use crate::async_impl::interceptor::IdleInterceptor; use crate::async_impl::HttpBody; + use crate::util::interceptor::IdleInterceptor; use crate::util::normalizer::BodyLength; use crate::ErrorKind; diff --git a/ylong_http_client/src/async_impl/mod.rs b/ylong_http_client/src/async_impl/mod.rs index 98334473229087e6ef4742f0710c504960a0259c..572a6360c3467eb885b7fc58e106767bee6fdf1d 100644 --- a/ylong_http_client/src/async_impl/mod.rs +++ b/ylong_http_client/src/async_impl/mod.rs @@ -29,7 +29,6 @@ mod connector; mod dns; mod downloader; mod http_body; -mod interceptor; mod request; mod response; mod timeout; @@ -47,10 +46,9 @@ pub(crate) mod pool; mod quic; pub use client::ClientBuilder; -pub use connector::{ConnInfo, Connector, HttpConnector}; +pub use connector::{Connector, HttpConnector}; pub use downloader::{DownloadOperator, Downloader, DownloaderBuilder}; pub use http_body::HttpBody; -pub use interceptor::{ConnDetail, ConnProtocol, Interceptor}; #[cfg(feature = "http3")] pub use quic::QuicConn; pub use request::{Body, PercentEncoder, Request, RequestBuilder}; diff --git a/ylong_http_client/src/async_impl/pool.rs b/ylong_http_client/src/async_impl/pool.rs index 0403c08bac85671c9c99e860a3a73454b7bc1472..785931d61eb59f66de276ea8f2f1add4e8ceddd8 100644 --- a/ylong_http_client/src/async_impl/pool.rs +++ b/ylong_http_client/src/async_impl/pool.rs @@ -13,6 +13,7 @@ use std::mem::take; use std::sync::{Arc, Mutex}; +use std::time::Instant; #[cfg(feature = "http3")] use ylong_http::request::uri::Authority; @@ -20,7 +21,6 @@ use ylong_http::request::uri::Authority; use ylong_http::request::uri::Scheme; use ylong_http::request::uri::Uri; -use crate::async_impl::connector::ConnInfo; #[cfg(feature = "http3")] use crate::async_impl::quic::QuicConn; use crate::async_impl::Connector; @@ -35,10 +35,14 @@ use crate::util::config::H2Config; #[cfg(feature = "http3")] use crate::util::config::H3Config; use crate::util::config::{HttpConfig, HttpVersion}; -use crate::util::dispatcher::{Conn, ConnDispatcher, Dispatcher}; +use crate::util::dispatcher::{Conn, ConnDispatcher, Dispatcher, TimeInfoConn}; use crate::util::pool::{Pool, PoolKey}; #[cfg(feature = "http3")] use crate::util::request::RequestArc; +use crate::util::ConnInfo; +#[cfg(feature = "http2")] +use crate::ConnDetail; +use crate::TimeGroup; pub(crate) struct ConnPool { pool: Pool>, @@ -59,7 +63,10 @@ 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(), @@ -131,46 +138,57 @@ impl Conns connector: Arc, url: &Uri, #[cfg(feature = "http3")] alt_svc: Option>, - ) -> Result, HttpClientError> + ) -> Result, HttpClientError> where C: Connector, { - match config.version { + let conn_start = Instant::now(); + let mut conn = match config.version { #[cfg(feature = "http3")] HttpVersion::Http3 => self.conn_h3(connector, url, config.http3_config).await, #[cfg(feature = "http2")] HttpVersion::Http2 => self.conn_h2(connector, url, config.http2_config).await, #[cfg(feature = "http1_1")] HttpVersion::Http1 => self.conn_h1(connector, url).await, + #[cfg(all(feature = "http1_1", not(feature = "http2")))] + HttpVersion::Negotiate => self.conn_h1(connector, url).await, + #[cfg(all(feature = "http1_1", feature = "http2"))] HttpVersion::Negotiate => { #[cfg(feature = "http3")] - if let Some(conn) = self + if let Some(mut conn) = self .conn_alt_svc(&connector, url, alt_svc, config.http3_config) .await { + conn.time_group_mut().set_connect_start(conn_start); + conn.time_group_mut().set_connect_end(Instant::now()); return Ok(conn); } - - #[cfg(all(feature = "http1_1", not(feature = "http2")))] - return self.conn_h1(connector, url).await; - - #[cfg(all(feature = "http2", feature = "http1_1"))] - return self - .conn_negotiate(connector, url, config.http2_config) - .await; + self.conn_negotiate(connector, url, config.http2_config) + .await } - } + }?; + conn.time_group_mut().set_connect_start(conn_start); + conn.time_group_mut().set_connect_end(Instant::now()); + Ok(conn) } - async fn conn_h1(&self, connector: Arc, url: &Uri) -> Result, HttpClientError> + async fn conn_h1( + &self, + connector: Arc, + url: &Uri, + ) -> Result, HttpClientError> where C: Connector, { if let Some(conn) = self.exist_h1_conn() { - return Ok(conn); + return Ok(TimeInfoConn::new(conn, TimeGroup::default())); } - let dispatcher = ConnDispatcher::http1(connector.connect(url, HttpVersion::Http1).await?); - Ok(self.dispatch_h1_conn(dispatcher)) + let stream = connector.connect(url, HttpVersion::Http1).await?; + let time_group = take(stream.conn_data().time_group_mut()); + + let dispatcher = ConnDispatcher::http1(stream); + let conn = self.dispatch_h1_conn(dispatcher); + Ok(TimeInfoConn::new(conn, time_group)) } #[cfg(feature = "http2")] @@ -179,7 +197,7 @@ impl Conns connector: Arc, url: &Uri, config: H2Config, - ) -> Result, HttpClientError> + ) -> Result, HttpClientError> where C: Connector, { @@ -189,24 +207,25 @@ impl Conns let mut lock = self.h2_conn.lock().await; if let Some(conn) = Self::exist_h2_conn(&mut lock) { - return Ok(conn); + return Ok(TimeInfoConn::new(conn, TimeGroup::default())); } let stream = connector.connect(url, HttpVersion::Http2).await?; - let details = stream.conn_detail(); + let mut data = stream.conn_data(); let tls = if let Some(scheme) = url.scheme() { *scheme == Scheme::HTTPS } else { false }; - match details.alpn() { + match data.negotiate().alpn() { None if tls => return err_from_msg!(Connect, "The peer does not support http/2."), Some(protocol) if protocol != b"h2" => { return err_from_msg!(Connect, "Alpn negotiate a wrong protocol version.") } _ => {} } - - Ok(Self::dispatch_h2_conn(config, stream, &mut lock)) + let time_group = take(data.time_group_mut()); + let conn = Self::dispatch_h2_conn(data.detail(), config, stream, &mut lock); + Ok(TimeInfoConn::new(conn, time_group)) } #[cfg(feature = "http3")] @@ -215,22 +234,28 @@ impl Conns connector: Arc, url: &Uri, config: H3Config, - ) -> Result, HttpClientError> + ) -> Result, HttpClientError> where C: Connector, { let mut lock = self.h3_conn.lock().await; if let Some(conn) = Self::exist_h3_conn(&mut lock) { - return Ok(conn); + return Ok(TimeInfoConn::new(conn, TimeGroup::default())); } let mut stream = connector.connect(url, HttpVersion::Http3).await?; + let quic_conn = stream.quic_conn().ok_or(HttpClientError::from_str( crate::ErrorKind::Connect, "QUIC connect failed", ))?; - Ok(Self::dispatch_h3_conn(config, stream, quic_conn, &mut lock)) + let mut data = stream.conn_data(); + let time_group = take(data.time_group_mut()); + Ok(TimeInfoConn::new( + Self::dispatch_h3_conn(data.detail(), config, stream, quic_conn, &mut lock), + time_group, + )) } #[cfg(all(feature = "http2", feature = "http1_1"))] @@ -239,7 +264,7 @@ impl Conns connector: Arc, url: &Uri, h2_config: H2Config, - ) -> Result, HttpClientError> + ) -> Result, HttpClientError> where C: Connector, { @@ -247,28 +272,35 @@ impl Conns Scheme::HTTPS => { let mut lock = self.h2_conn.lock().await; if let Some(conn) = Self::exist_h2_conn(&mut lock) { - return Ok(conn); + return Ok(TimeInfoConn::new(conn, TimeGroup::default())); } if let Some(conn) = self.exist_h1_conn() { - return Ok(conn); + return Ok(TimeInfoConn::new(conn, TimeGroup::default())); } - let stream = connector.connect(url, HttpVersion::Negotiate).await?; - let details = stream.conn_detail(); + let mut data = stream.conn_data(); + let time_group = take(data.time_group_mut()); - let protocol = if let Some(bytes) = details.alpn() { + let protocol = if let Some(bytes) = data.negotiate().alpn() { bytes } else { let dispatcher = ConnDispatcher::http1(stream); - return Ok(self.dispatch_h1_conn(dispatcher)); + return Ok(TimeInfoConn::new( + self.dispatch_h1_conn(dispatcher), + time_group, + )); }; if protocol == b"http/1.1" { let dispatcher = ConnDispatcher::http1(stream); - Ok(self.dispatch_h1_conn(dispatcher)) + Ok(TimeInfoConn::new( + self.dispatch_h1_conn(dispatcher), + time_group, + )) } else if protocol == b"h2" { - Ok(Self::dispatch_h2_conn(h2_config, stream, &mut lock)) + let conn = Self::dispatch_h2_conn(data.detail(), h2_config, stream, &mut lock); + Ok(TimeInfoConn::new(conn, time_group)) } else { err_from_msg!(Connect, "Alpn negotiate a wrong protocol version.") } @@ -284,13 +316,13 @@ impl Conns url: &Uri, alt_svcs: Option>, h3_config: H3Config, - ) -> Option> + ) -> Option> where C: Connector, { let mut lock = self.h3_conn.lock().await; if let Some(conn) = Self::exist_h3_conn(&mut lock) { - return Some(conn); + return Some(TimeInfoConn::new(conn, TimeGroup::default())); } if let Some(alt_svcs) = alt_svcs { for alt_svc in alt_svcs { @@ -312,11 +344,17 @@ impl Conns let alt_url = Uri::from_raw_parts(Some(scheme), Some(authority), path, query); let mut stream = connector.connect(&alt_url, HttpVersion::Http3).await.ok()?; let quic_conn = stream.quic_conn().unwrap(); - return Some(Self::dispatch_h3_conn( - h3_config.clone(), - stream, - quic_conn, - &mut lock, + let mut data = stream.conn_data(); + let time_group = take(data.time_group_mut()); + return Some(TimeInfoConn::new( + Self::dispatch_h3_conn( + data.detail(), + h3_config.clone(), + stream, + quic_conn, + &mut lock, + ), + time_group, )); } } @@ -328,17 +366,17 @@ impl Conns let conn = dispatcher.dispatch().unwrap(); let mut list = self.list.lock().unwrap(); list.push(dispatcher); - conn } #[cfg(feature = "http2")] fn dispatch_h2_conn( + detail: ConnDetail, config: H2Config, stream: S, lock: &mut crate::runtime::MutexGuard>>, ) -> Conn { - let dispatcher = ConnDispatcher::http2(config, stream); + let dispatcher = ConnDispatcher::http2(detail, config, stream); let conn = dispatcher.dispatch().unwrap(); lock.push(dispatcher); conn @@ -346,12 +384,13 @@ impl Conns #[cfg(feature = "http3")] fn dispatch_h3_conn( + detail: ConnDetail, config: H3Config, stream: S, quic_connection: QuicConn, lock: &mut crate::runtime::MutexGuard>>, ) -> Conn { - let dispatcher = ConnDispatcher::http3(config, stream, quic_connection); + let dispatcher = ConnDispatcher::http3(detail, config, stream, quic_connection); let conn = dispatcher.dispatch().unwrap(); lock.push(dispatcher); conn diff --git a/ylong_http_client/src/async_impl/quic/mod.rs b/ylong_http_client/src/async_impl/quic/mod.rs index 223a14c2a4147d6699a223594035f2bedd02a9d8..b7cd93a64f178e83e150480fed30fada9bd4fa5e 100644 --- a/ylong_http_client/src/async_impl/quic/mod.rs +++ b/ylong_http_client/src/async_impl/quic/mod.rs @@ -25,10 +25,10 @@ use libc::{ use ylong_runtime::fastrand::fast_random; use ylong_runtime::time::timeout; -use crate::async_impl::connector::ConnInfo; use crate::c_openssl::ssl::verify_server_cert; use crate::runtime::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use crate::util::c_openssl::ssl::Ssl; +use crate::util::ConnInfo; use crate::{ErrorKind, HttpClientError, TlsConfig}; const MAX_DATAGRAM_SIZE: usize = 1350; @@ -77,8 +77,8 @@ impl QuicConn { } let scid = quiche::ConnectionId::from_ref(&scid); - let local = stream.conn_detail().local(); - let peer = stream.conn_detail().peer(); + let local = stream.conn_data().detail().local(); + let peer = stream.conn_data().detail().peer(); let mut c_local: sockaddr_storage = unsafe { std::mem::zeroed() }; let c_local_size = Self::std_addr_to_c(&local, &mut c_local); let mut c_peer: sockaddr_storage = unsafe { std::mem::zeroed() }; @@ -142,7 +142,8 @@ impl QuicConn { break; } if self.is_established() { - let Some(key) = tls_config.pinning_host_match(stream.conn_detail().addr()) else { + let Some(key) = tls_config.pinning_host_match(stream.conn_data().detail().addr()) + else { break; }; @@ -188,8 +189,8 @@ impl QuicConn { S: AsyncRead + AsyncWrite + ConnInfo + Unpin + Sync + Send + 'static, { let recv_info = quiche::RecvInfo { - to: stream.conn_detail().local(), - from: stream.conn_detail().peer(), + to: stream.conn_data().detail().local(), + from: stream.conn_data().detail().peer(), }; let mut recv_size = 0; let mut len = 0; diff --git a/ylong_http_client/src/async_impl/request.rs b/ylong_http_client/src/async_impl/request.rs index 9c41c6e294ceca5b63a7d620e81e3827510d382a..8ef446ffc12d4f0b2143374fc66ba8787417b4b9 100644 --- a/ylong_http_client/src/async_impl/request.rs +++ b/ylong_http_client/src/async_impl/request.rs @@ -24,9 +24,10 @@ use ylong_http::body::MultiPartBase; use ylong_http::request::uri::PercentEncoder as PerEncoder; use ylong_http::request::{Request as Req, RequestBuilder as ReqBuilder}; -use crate::async_impl::interceptor::Interceptors; use crate::error::{ErrorKind, HttpClientError}; use crate::runtime::{AsyncRead, ReadBuf}; +use crate::util::interceptor::Interceptors; +use crate::util::monitor::TimeGroup; use crate::util::request::RequestArc; /// A structure that represents an HTTP `Request`. It contains a request line, @@ -46,6 +47,7 @@ use crate::util::request::RequestArc; /// ``` pub struct Request { pub(crate) inner: Req, + pub(crate) time_group: TimeGroup, } impl Request { @@ -68,6 +70,10 @@ impl Request { pub fn builder() -> RequestBuilder { RequestBuilder::new() } + + pub(crate) fn time_group_mut(&mut self) -> &mut TimeGroup { + &mut self.time_group + } } impl Deref for Request { @@ -228,7 +234,10 @@ impl RequestBuilder { builder .0 .body(body) - .map(|inner| Request { inner }) + .map(|inner| Request { + inner, + time_group: TimeGroup::default(), + }) .map_err(|e| HttpClientError::from_error(ErrorKind::Build, e)) } } diff --git a/ylong_http_client/src/async_impl/response.rs b/ylong_http_client/src/async_impl/response.rs index a611fd62c00ce6d9a4b088d4870e81eecae61e21..b2962700a9e3cb842e88f133e3460831eb28548a 100644 --- a/ylong_http_client/src/async_impl/response.rs +++ b/ylong_http_client/src/async_impl/response.rs @@ -18,16 +18,21 @@ use ylong_http::response::Response as Resp; use crate::async_impl::HttpBody; use crate::error::HttpClientError; +use crate::util::monitor::TimeGroup; use crate::ErrorKind; /// A structure that represents an HTTP `Response`. pub struct Response { pub(crate) inner: Resp, + pub(crate) time_group: TimeGroup, } impl Response { pub(crate) fn new(response: Resp) -> Self { - Self { inner: response } + Self { + inner: response, + time_group: TimeGroup::default(), + } } /// Reads the data of the `HttpBody`. @@ -49,6 +54,15 @@ impl Response { String::from_utf8(vec).map_err(|e| HttpClientError::from_error(ErrorKind::BodyDecode, e)) } + + /// Gets the time spent on each stage of the request. + pub fn time_group(&self) -> &TimeGroup { + &self.time_group + } + + pub(crate) fn set_time_group(&mut self, time_group: TimeGroup) { + self.time_group = time_group + } } impl Deref for Response { 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 75aa3ce0928f2d70a19589ec39dad5a4f87ba45c..ad92d5b62710958dc78041ffa6a8e2c54d2b16ba 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 @@ -47,6 +47,7 @@ impl AsyncSslStream { unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0.get_mut().stream) } } + #[cfg(feature = "http2")] pub(crate) fn negotiated_alpn_protocol(&self) -> Option<&[u8]> { self.0.ssl().negotiated_alpn_protocol() } diff --git a/ylong_http_client/src/async_impl/timeout.rs b/ylong_http_client/src/async_impl/timeout.rs index a555c390518186dc79eb4cc0ef8ae6ef321fd1fa..e13a72198cab28dcf4b0b8dc4ece72251239e814 100644 --- a/ylong_http_client/src/async_impl/timeout.rs +++ b/ylong_http_client/src/async_impl/timeout.rs @@ -67,9 +67,9 @@ mod ut_timeout { use ylong_http::response::{Response, ResponsePart}; use ylong_http::version::Version; - use crate::async_impl::interceptor::IdleInterceptor; use crate::async_impl::timeout::TimeoutFuture; use crate::async_impl::HttpBody; + use crate::util::interceptor::IdleInterceptor; use crate::util::normalizer::BodyLength; use crate::HttpClientError; 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 7f516a091af944a0808e828bf1851f78cbff3558..cd5c952c7986afad569c445a65e3d192fd72528b 100644 --- a/ylong_http_client/src/util/c_openssl/ffi/ssl.rs +++ b/ylong_http_client/src/util/c_openssl/ffi/ssl.rs @@ -83,6 +83,7 @@ extern "C" { ) -> c_int; /// returns the selected protocol. It is not NUL-terminated. + #[cfg(feature = "http2")] pub(crate) fn SSL_get0_alpn_selected( ssl: *const SSL, data: *mut *const c_uchar, 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 467af18fa847f4ee4101a547afd20303344ab51d..b3ac46330889e1a0ebf2b6f2c0da9c66b029b841 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 @@ -12,11 +12,9 @@ // limitations under the License. use core::{cmp, ffi, fmt, str}; -use std::ffi::{c_uchar, CString}; +use std::ffi::CString; #[cfg(feature = "sync")] use std::io::{Read, Write}; -use std::ptr::null; -use std::slice::from_raw_parts; use libc::{c_char, c_int, c_long, c_void}; @@ -37,7 +35,7 @@ use crate::c_openssl::x509::{ }; use crate::util::c_openssl::check_ptr; use crate::util::c_openssl::error::ErrorStack; -use crate::util::c_openssl::ffi::ssl::{SSL_free, SSL_get0_alpn_selected, SSL_new, SSL}; +use crate::util::c_openssl::ffi::ssl::{SSL_free, SSL_new, SSL}; use crate::util::c_openssl::foreign::Foreign; foreign_type!( @@ -148,7 +146,14 @@ impl SslRef { } } + #[cfg(feature = "http2")] pub(crate) fn negotiated_alpn_protocol(&self) -> Option<&[u8]> { + use std::ffi::c_uchar; + use std::ptr::null; + use std::slice::from_raw_parts; + + use crate::util::c_openssl::ffi::ssl::SSL_get0_alpn_selected; + let mut data = null() as *const c_uchar; let mut len = 0_u32; unsafe { diff --git a/ylong_http_client/src/util/dispatcher.rs b/ylong_http_client/src/util/dispatcher.rs index 74721c755d5c7628786d88c692065006675f9f5e..fb0da81504492ed0d81dae7974e4a90778e4e5b4 100644 --- a/ylong_http_client/src/util/dispatcher.rs +++ b/ylong_http_client/src/util/dispatcher.rs @@ -11,6 +11,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::util::ConnInfo; +use crate::{ConnDetail, TimeGroup}; + pub(crate) trait Dispatcher { type Handle; @@ -87,6 +90,42 @@ pub(crate) enum Conn { Http3(http3::Http3Conn), } +impl Conn { + pub(crate) fn get_detail(&mut self) -> ConnDetail { + match self { + #[cfg(feature = "http1_1")] + Conn::Http1(io) => io.raw_mut().conn_data().detail(), + #[cfg(feature = "http2")] + Conn::Http2(io) => io.detail.clone(), + #[cfg(feature = "http3")] + Conn::Http3(io) => io.detail.clone(), + } + } +} + +pub(crate) struct TimeInfoConn { + conn: Conn, + time_group: TimeGroup, +} + +impl TimeInfoConn { + pub(crate) fn new(conn: Conn, time_group: TimeGroup) -> Self { + Self { conn, time_group } + } + + pub(crate) fn time_group_mut(&mut self) -> &mut TimeGroup { + &mut self.time_group + } + + pub(crate) fn time_group(&mut self) -> &TimeGroup { + &self.time_group + } + + pub(crate) fn connection(self) -> Conn { + self.conn + } +} + #[cfg(feature = "http1_1")] pub(crate) mod http1 { use std::cell::UnsafeCell; @@ -204,7 +243,7 @@ pub(crate) mod http2 { StreamEndState, Streams, }; use crate::ErrorKind::Request; - use crate::{ErrorKind, HttpClientError}; + use crate::{ConnDetail, ErrorKind, HttpClientError}; const DEFAULT_MAX_FRAME_SIZE: usize = 2 << 13; const DEFAULT_WINDOW_SIZE: u32 = 65535; @@ -237,6 +276,7 @@ pub(crate) mod http2 { // HTTP2-based connection manager, which can dispatch connections to other // threads according to HTTP2 syntax. pub(crate) struct Http2Dispatcher { + pub(crate) detail: ConnDetail, pub(crate) allowed_cache: usize, pub(crate) sender: UnboundedSender, pub(crate) io_shutdown: Arc, @@ -250,6 +290,7 @@ pub(crate) mod http2 { pub(crate) sender: UnboundedSender, pub(crate) receiver: RespReceiver, pub(crate) io_shutdown: Arc, + pub(crate) detail: ConnDetail, pub(crate) _mark: PhantomData, } @@ -294,8 +335,8 @@ pub(crate) mod http2 { where S: AsyncRead + AsyncWrite + Sync + Send + Unpin + 'static, { - pub(crate) fn http2(config: H2Config, io: S) -> Self { - Self::Http2(Http2Dispatcher::new(config, io)) + pub(crate) fn http2(detail: ConnDetail, config: H2Config, io: S) -> Self { + Self::Http2(Http2Dispatcher::new(detail, config, io)) } } @@ -303,7 +344,7 @@ pub(crate) mod http2 { where S: AsyncRead + AsyncWrite + Sync + Send + Unpin + 'static, { - pub(crate) fn new(config: H2Config, io: S) -> Self { + pub(crate) fn new(detail: ConnDetail, config: H2Config, io: S) -> Self { let settings = create_initial_settings(&config); let mut flow = FlowControl::new(DEFAULT_WINDOW_SIZE, DEFAULT_WINDOW_SIZE); @@ -331,6 +372,7 @@ pub(crate) mod http2 { ); } Self { + detail, allowed_cache: config.allowed_cache_frame_size(), sender: req_tx, io_shutdown: shutdown_flag, @@ -385,7 +427,12 @@ pub(crate) mod http2 { fn dispatch(&self) -> Option { let sender = self.sender.clone(); - let handle = Http2Conn::new(self.allowed_cache, self.io_shutdown.clone(), sender); + let handle = Http2Conn::new( + self.allowed_cache, + self.io_shutdown.clone(), + sender, + self.detail.clone(), + ); Some(handle) } @@ -415,12 +462,14 @@ pub(crate) mod http2 { allow_cached_num: usize, io_shutdown: Arc, sender: UnboundedSender, + detail: ConnDetail, ) -> Self { Self { allow_cached_frames: allow_cached_num, sender, receiver: RespReceiver::default(), io_shutdown, + detail, _mark: PhantomData, } } @@ -678,7 +727,7 @@ pub(crate) mod http3 { use ylong_http::error::HttpError; use ylong_http::h3::{Frame, FrameDecoder, H3Error}; - use crate::async_impl::{ConnInfo, QuicConn}; + use crate::async_impl::QuicConn; use crate::runtime::{ bounded_channel, unbounded_channel, AsyncRead, AsyncWrite, BoundedReceiver, BoundedSender, UnboundedSender, @@ -689,9 +738,10 @@ pub(crate) mod http3 { use crate::util::h3::io_manager::IOManager; use crate::util::h3::stream_manager::StreamManager; use crate::ErrorKind::Request; - use crate::{ErrorKind, HttpClientError}; + use crate::{ConnDetail, ConnInfo, ErrorKind, HttpClientError}; pub(crate) struct Http3Dispatcher { + pub(crate) detail: ConnDetail, pub(crate) req_tx: UnboundedSender, pub(crate) handles: Vec>, pub(crate) _mark: PhantomData, @@ -704,6 +754,7 @@ pub(crate) mod http3 { pub(crate) resp_receiver: BoundedReceiver, pub(crate) resp_sender: BoundedSender, pub(crate) io_shutdown: Arc, + pub(crate) detail: ConnDetail, pub(crate) _mark: PhantomData, } @@ -738,7 +789,12 @@ pub(crate) mod http3 { where S: AsyncRead + AsyncWrite + ConnInfo + Sync + Send + Unpin + 'static, { - pub(crate) fn new(config: H3Config, io: S, quic_connection: QuicConn) -> Self { + pub(crate) fn new( + detail: ConnDetail, + config: H3Config, + io: S, + quic_connection: QuicConn, + ) -> Self { let (req_tx, req_rx) = unbounded_channel(); let (io_manager_tx, io_manager_rx) = unbounded_channel(); let (stream_manager_tx, stream_manager_rx) = unbounded_channel(); @@ -774,6 +830,7 @@ pub(crate) mod http3 { // read_rx gets readable stream ids and writable client channels, then read // stream and send to the corresponding channel Self { + detail, req_tx, handles, _mark: PhantomData, @@ -785,6 +842,7 @@ pub(crate) mod http3 { impl Http3Conn { pub(crate) fn new( + detail: ConnDetail, sender: UnboundedSender, io_shutdown: Arc, ) -> Self { @@ -796,6 +854,7 @@ pub(crate) mod http3 { resp_receiver, _mark: PhantomData, io_shutdown, + detail, } } @@ -838,8 +897,13 @@ pub(crate) mod http3 { where S: AsyncRead + AsyncWrite + ConnInfo + Sync + Send + Unpin + 'static, { - pub(crate) fn http3(config: H3Config, io: S, quic_connection: QuicConn) -> Self { - Self::Http3(Http3Dispatcher::new(config, io, quic_connection)) + pub(crate) fn http3( + detail: ConnDetail, + config: H3Config, + io: S, + quic_connection: QuicConn, + ) -> Self { + Self::Http3(Http3Dispatcher::new(detail, config, io, quic_connection)) } } @@ -848,7 +912,11 @@ pub(crate) mod http3 { fn dispatch(&self) -> Option { let sender = self.req_tx.clone(); - Some(Http3Conn::new(sender, self.io_shutdown.clone())) + Some(Http3Conn::new( + self.detail.clone(), + sender, + self.io_shutdown.clone(), + )) } fn is_shutdown(&self) -> bool { diff --git a/ylong_http_client/src/util/h3/io_manager.rs b/ylong_http_client/src/util/h3/io_manager.rs index f71b122824aedc66aac4470ec5608b9fdbac9dc9..17f5aaebc42cd69b469fe5e5a4a1a41bc4972a5e 100644 --- a/ylong_http_client/src/util/h3/io_manager.rs +++ b/ylong_http_client/src/util/h3/io_manager.rs @@ -18,10 +18,11 @@ use std::task::{Context, Poll}; use ylong_runtime::time::{sleep, Sleep}; -use crate::async_impl::{ConnInfo, QuicConn}; +use crate::async_impl::QuicConn; use crate::runtime::{AsyncRead, AsyncWrite, ReadBuf, UnboundedReceiver, UnboundedSender}; use crate::util::dispatcher::http3::DispatchErrorKind; use crate::util::h3::stream_manager::UPD_RECV_BUF_SIZE; +use crate::util::ConnInfo; const UDP_SEND_BUF_SIZE: usize = 1350; @@ -97,7 +98,7 @@ impl IOMan } match Pin::new(&mut self.io).poll_read(cx, &mut buf) { Poll::Ready(Ok(())) => { - let info = self.io.conn_detail(); + let info = self.io.conn_data().detail(); self.recv_timeout = None; let recv_info = quiche::RecvInfo { to: info.local, diff --git a/ylong_http_client/src/util/information.rs b/ylong_http_client/src/util/information.rs new file mode 100644 index 0000000000000000000000000000000000000000..2f5c6fcc317733c2b24ea674b00cd96927dfa0b3 --- /dev/null +++ b/ylong_http_client/src/util/information.rs @@ -0,0 +1,161 @@ +// 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::net::SocketAddr; + +#[cfg(feature = "http3")] +use crate::async_impl::QuicConn; +use crate::{ConnProtocol, TimeGroup}; + +/// `ConnDetail` trait, which is used to obtain information about the current +/// connection. +pub trait ConnInfo { + /// Whether the current connection is a proxy. + fn is_proxy(&self) -> bool; + + /// Gets connection information data. + fn conn_data(&self) -> ConnData; + + /// Gets quic information + #[cfg(feature = "http3")] + fn quic_conn(&mut self) -> Option; +} + +/// Tcp connection information. +#[derive(Clone)] +pub struct ConnDetail { + /// Transport layer protocol type. + pub(crate) protocol: ConnProtocol, + /// local socket address. + pub(crate) local: SocketAddr, + /// peer socket address. + pub(crate) peer: SocketAddr, + /// peer domain information. + pub(crate) addr: String, +} + +impl ConnDetail { + /// Gets the transport layer protocol for the connection. + pub fn protocol(&self) -> &ConnProtocol { + &self.protocol + } + + /// Gets the local socket address of the connection. + pub fn local(&self) -> SocketAddr { + self.local + } + + /// Gets the peer socket address of the connection. + pub fn peer(&self) -> SocketAddr { + self.peer + } + + /// Gets the peer domain address of the connection. + pub fn addr(&self) -> &str { + &self.addr + } +} + +/// Negotiated http version information. +#[derive(Default, Clone)] +pub struct NegotiateInfo { + alpn: Option>, +} + +impl NegotiateInfo { + /// Constructs NegotiateInfo with apln extensions. + #[cfg(feature = "__tls")] + pub fn from_alpn(alpn: Option>) -> Self { + Self { alpn } + } + + /// tls alpn Indicates extended information. + pub fn alpn(&self) -> Option<&[u8]> { + self.alpn.as_deref() + } +} + +/// Transport layer connection establishment information data. +#[derive(Clone)] +pub struct ConnData { + detail: ConnDetail, + #[cfg(feature = "http2")] + negotiate: NegotiateInfo, + proxy: bool, + time_group: TimeGroup, +} + +impl ConnData { + /// Construct a `ConnDataBuilder`. + pub fn builder() -> ConnDataBuilder { + ConnDataBuilder::default() + } + + pub(crate) fn detail(self) -> ConnDetail { + self.detail + } + + #[cfg(feature = "http2")] + pub(crate) fn negotiate(&self) -> &NegotiateInfo { + &self.negotiate + } + + pub(crate) fn is_proxy(&self) -> bool { + self.proxy + } + + pub(crate) fn time_group_mut(&mut self) -> &mut TimeGroup { + &mut self.time_group + } +} + +/// ConnData's builder, which builds ConnData through cascading calls. +#[derive(Default)] +pub struct ConnDataBuilder { + #[cfg(feature = "http2")] + negotiate: NegotiateInfo, + proxy: bool, + time_group: TimeGroup, +} + +impl ConnDataBuilder { + /// Sets the http negotiation result. + #[cfg(all(feature = "__tls", feature = "http2"))] + pub fn negotiate(mut self, negotiate: NegotiateInfo) -> Self { + self.negotiate = negotiate; + self + } + + /// Sets whether the peer is a proxy. + pub fn proxy(mut self, proxy: bool) -> Self { + self.proxy = proxy; + self + } + + /// Set the time required for each phase of connection establishment. + pub fn time_group(mut self, time_group: TimeGroup) -> Self { + self.time_group = time_group; + self + } + + /// Construct ConnData by setting the individual endpoint information. + pub fn build(self, detail: ConnDetail) -> ConnData { + ConnData { + detail, + #[cfg(feature = "http2")] + negotiate: self.negotiate, + proxy: self.proxy, + time_group: self.time_group, + } + } +} diff --git a/ylong_http_client/src/async_impl/interceptor/mod.rs b/ylong_http_client/src/util/interceptor/mod.rs similarity index 69% rename from ylong_http_client/src/async_impl/interceptor/mod.rs rename to ylong_http_client/src/util/interceptor/mod.rs index b0f1a4c6a06227011e7706ff5ad0fa0253ed8039..449dcb62bb194358da5eada1f4c1e83bc1823a50 100644 --- a/ylong_http_client/src/async_impl/interceptor/mod.rs +++ b/ylong_http_client/src/util/interceptor/mod.rs @@ -13,12 +13,10 @@ //! Http network interceptor. -use std::net::SocketAddr; - use ylong_http::response::Response as HttpResp; use crate::async_impl::{HttpBody, Request, Response}; -use crate::HttpClientError; +use crate::{ConnDetail, HttpClientError}; pub(crate) type Interceptors = dyn Interceptor + Sync + Send + 'static; @@ -29,54 +27,8 @@ pub enum ConnProtocol { Tcp, /// Udp Protocol. Udp, -} - -/// Connection information. -#[derive(Clone)] -pub struct ConnDetail { - /// Transport layer protocol type. - pub(crate) protocol: ConnProtocol, - pub(crate) alpn: Option>, - /// local socket address. - pub(crate) local: SocketAddr, - /// peer socket address. - pub(crate) peer: SocketAddr, - /// peer domain information. - pub(crate) addr: String, - /// Whether to use proxy. - pub(crate) proxy: bool, -} - -impl ConnDetail { - /// Gets the transport layer protocol for the connection. - pub fn protocol(&self) -> &ConnProtocol { - &self.protocol - } - - /// Gets the local socket address of the connection. - pub fn local(&self) -> SocketAddr { - self.local - } - - /// Gets the peer socket address of the connection. - pub fn peer(&self) -> SocketAddr { - self.peer - } - - /// Gets the peer domain address of the connection. - pub fn addr(&self) -> &str { - &self.addr - } - - /// Whether to use proxy. - pub fn proxy(&self) -> bool { - self.proxy - } - - /// Whether to use tls. - pub fn alpn(&self) -> Option<&[u8]> { - self.alpn.as_deref() - } + /// Quic Protocol + Quic, } /// Network interceptor. diff --git a/ylong_http_client/src/util/mod.rs b/ylong_http_client/src/util/mod.rs index f4a978cf8ff665746e258f38f590690c341724c9..700aa0324f0cb3f03dbe36dbc92a5dc0b07902c8 100644 --- a/ylong_http_client/src/util/mod.rs +++ b/ylong_http_client/src/util/mod.rs @@ -43,6 +43,9 @@ pub(crate) mod data_ref; pub(crate) mod h2; #[cfg(feature = "http3")] pub(crate) mod h3; +pub(crate) mod information; +pub(crate) mod interceptor; +pub(crate) mod monitor; #[cfg(all(test, feature = "ylong_base"))] pub(crate) mod test_utils; @@ -58,3 +61,6 @@ pub use config::{CertVerifier, ServerCerts}; pub use config::{Proxy, ProxyBuilder, Redirect, Retry, SpeedLimit, Timeout}; #[cfg(all(feature = "async", feature = "ylong_base", feature = "http2"))] pub(crate) use h2::{split, Reader, Writer}; +pub use information::{ConnData, ConnDataBuilder, ConnDetail, ConnInfo, NegotiateInfo}; +pub use interceptor::{ConnProtocol, Interceptor}; +pub use monitor::TimeGroup; diff --git a/ylong_http_client/src/util/monitor/mod.rs b/ylong_http_client/src/util/monitor/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..979346b770263b451c31549cb6f8e241cba53f3d --- /dev/null +++ b/ylong_http_client/src/util/monitor/mod.rs @@ -0,0 +1,17 @@ +// 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 add Monitor trait for monitor all stage's statistic data. +mod time_group; + +pub use time_group::TimeGroup; diff --git a/ylong_http_client/src/util/monitor/time_group.rs b/ylong_http_client/src/util/monitor/time_group.rs new file mode 100644 index 0000000000000000000000000000000000000000..1dadb70a08f9ca56ef96adabec0d309dc460a919 --- /dev/null +++ b/ylong_http_client/src/util/monitor/time_group.rs @@ -0,0 +1,317 @@ +// 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::time::{Duration, Instant}; + +/// Time statistics of a request in each stage. +#[derive(Default, Clone)] +pub struct TimeGroup { + // TODO add total request time. + dns_start: Option, + dns_end: Option, + dns_duration: Option, + tcp_start: Option, + tcp_end: Option, + tcp_duration: Option, + #[cfg(feature = "http3")] + quic_start: Option, + #[cfg(feature = "http3")] + quic_end: Option, + #[cfg(feature = "http3")] + quic_duration: Option, + #[cfg(feature = "__tls")] + tls_start: Option, + #[cfg(feature = "__tls")] + tls_end: Option, + #[cfg(feature = "__tls")] + tls_duration: Option, + conn_start: Option, + conn_end: Option, + conn_duration: Option, + // start send bytes to peer. + transfer_start: Option, + // received first byte from peer. + transfer_end: Option, + transfer_duration: Option, +} + +impl TimeGroup { + pub(crate) fn set_dns_start(&mut self, start: Instant) { + self.dns_start = Some(start) + } + + pub(crate) fn set_dns_end(&mut self, end: Instant) { + if let Some(start) = self.dns_start { + self.dns_duration = end.checked_duration_since(start); + } + self.dns_end = Some(end) + } + + pub(crate) fn set_tcp_start(&mut self, start: Instant) { + self.tcp_start = Some(start) + } + + pub(crate) fn set_tcp_end(&mut self, end: Instant) { + if let Some(start) = self.tcp_start { + self.tcp_duration = end.checked_duration_since(start); + } + self.tcp_end = Some(end) + } + + #[cfg(feature = "http3")] + pub(crate) fn set_quic_start(&mut self, start: Instant) { + self.quic_start = Some(start) + } + + #[cfg(feature = "http3")] + pub(crate) fn set_quic_end(&mut self, end: Instant) { + if let Some(start) = self.quic_start { + self.quic_duration = end.checked_duration_since(start); + } + self.quic_end = Some(end) + } + + #[cfg(feature = "__tls")] + pub(crate) fn set_tls_start(&mut self, start: Instant) { + self.tls_start = Some(start) + } + + #[cfg(feature = "__tls")] + pub(crate) fn set_tls_end(&mut self, end: Instant) { + if let Some(start) = self.tls_start { + self.tls_duration = end.checked_duration_since(start) + } + self.tls_end = Some(end) + } + + pub(crate) fn set_transfer_start(&mut self, start: Instant) { + self.transfer_start = Some(start) + } + + pub(crate) fn set_transfer_end(&mut self, end: Instant) { + if let Some(start) = self.transfer_start { + self.transfer_duration = end.checked_duration_since(start) + } + self.transfer_end = Some(end) + } + + pub(crate) fn set_connect_start(&mut self, start: Instant) { + self.conn_start = Some(start) + } + + pub(crate) fn set_connect_end(&mut self, end: Instant) { + if let Some(start) = self.conn_start { + self.conn_duration = end.checked_duration_since(start) + } + self.conn_end = Some(end) + } + + #[cfg(feature = "http3")] + pub(crate) fn update_quic_start(&mut self, start: Option) { + self.quic_start = start + } + + #[cfg(feature = "http3")] + pub(crate) fn update_quic_end(&mut self, end: Option) { + self.quic_end = end + } + + #[cfg(feature = "http3")] + pub(crate) fn update_quic_duration(&mut self, duration: Option) { + self.quic_duration = duration + } + + pub(crate) fn update_tcp_start(&mut self, start: Option) { + self.tcp_start = start + } + + pub(crate) fn update_tcp_end(&mut self, end: Option) { + self.tcp_end = end + } + + pub(crate) fn update_tcp_duration(&mut self, duration: Option) { + self.tcp_duration = duration + } + + #[cfg(feature = "__tls")] + pub(crate) fn update_tls_start(&mut self, start: Option) { + self.tls_start = start + } + + #[cfg(feature = "__tls")] + pub(crate) fn update_tls_end(&mut self, end: Option) { + self.tls_end = end + } + + #[cfg(feature = "__tls")] + pub(crate) fn update_tls_duration(&mut self, duration: Option) { + self.tls_duration = duration + } + + pub(crate) fn update_dns_start(&mut self, start: Option) { + self.dns_start = start + } + + pub(crate) fn update_dns_end(&mut self, end: Option) { + self.dns_end = end + } + + pub(crate) fn update_dns_duration(&mut self, duration: Option) { + self.dns_duration = duration + } + + pub(crate) fn update_connection_start(&mut self, start: Option) { + self.conn_start = start + } + + pub(crate) fn update_connection_end(&mut self, end: Option) { + self.conn_end = end + } + + pub(crate) fn update_connection_duration(&mut self, duration: Option) { + self.conn_duration = duration + } + + pub(crate) fn update_transport_conn_time(&mut self, time_group: &TimeGroup) { + self.update_dns_start(time_group.dns_start_time()); + self.update_dns_end(time_group.dns_end_time()); + self.update_dns_duration(time_group.dns_duration()); + + self.update_tcp_start(time_group.tcp_start_time()); + self.update_tcp_end(time_group.tcp_end_time()); + self.update_tcp_duration(time_group.tcp_duration()); + + #[cfg(feature = "http3")] + self.update_quic_start(time_group.quic_start_time()); + #[cfg(feature = "http3")] + self.update_quic_end(time_group.quic_end_time()); + #[cfg(feature = "http3")] + self.update_quic_duration(time_group.quic_duration()); + + self.update_tcp_start(time_group.tcp_start_time()); + self.update_tcp_end(time_group.tcp_end_time()); + self.update_tcp_duration(time_group.tcp_duration()); + + #[cfg(feature = "__tls")] + self.update_tls_start(time_group.tls_start_time()); + #[cfg(feature = "__tls")] + self.update_tls_end(time_group.tls_end_time()); + #[cfg(feature = "__tls")] + self.update_tls_duration(time_group.tls_duration()); + + self.update_connection_start(time_group.connect_start_time()); + self.update_connection_end(time_group.connect_end_time()); + self.update_connection_duration(time_group.connect_duration()); + } + + /// Gets the point in time when the tcp connection starts to be + /// established. + pub fn tcp_start_time(&self) -> Option { + self.tcp_start + } + + /// Gets the point in time when the tcp connection was established. + pub fn tcp_end_time(&self) -> Option { + self.tcp_end + } + + /// Gets the total time taken to establish a tcp connection. + pub fn tcp_duration(&self) -> Option { + self.tcp_duration + } + + /// Gets the point in time when the quic connection starts to be + /// established. + #[cfg(feature = "http3")] + pub fn quic_start_time(&self) -> Option { + self.quic_start + } + + /// Gets the point in time when the quic connection was established. + #[cfg(feature = "http3")] + pub fn quic_end_time(&self) -> Option { + self.quic_end + } + + /// Gets the total time taken to establish a quic connection. + #[cfg(feature = "http3")] + pub fn quic_duration(&self) -> Option { + self.quic_duration + } + + /// Gets the start point in time of the tls handshake. + #[cfg(feature = "__tls")] + pub fn tls_start_time(&self) -> Option { + self.tls_start + } + + /// Gets the point in time when the tls connection was established. + #[cfg(feature = "__tls")] + pub fn tls_end_time(&self) -> Option { + self.tls_end + } + + /// Gets the time taken for the tls connection to be established. + #[cfg(feature = "__tls")] + pub fn tls_duration(&self) -> Option { + self.tls_duration + } + + /// Gets the point in time when the dns query started (not currently + /// recorded). + pub fn dns_start_time(&self) -> Option { + self.dns_start + } + + /// Gets the point in time when the dns query ended (not currently + /// recorded). + pub fn dns_end_time(&self) -> Option { + self.dns_end + } + + /// Gets the time spent on dns queries (not currently recorded). + pub fn dns_duration(&self) -> Option { + self.dns_duration + } + + /// Gets the start point in time of data transmission. + pub fn transfer_start_time(&self) -> Option { + self.transfer_start + } + + /// Gets the point in time the data was received. + pub fn transfer_end_time(&self) -> Option { + self.transfer_end + } + + /// Gets the time it takes from the time the data is sent to the time it is + /// received. + pub fn transfer_duration(&self) -> Option { + self.transfer_duration + } + + /// Gets the point in time to start establishing the request connection. + pub fn connect_start_time(&self) -> Option { + self.conn_start + } + + /// Gets the point in time when the request connection was established. + pub fn connect_end_time(&self) -> Option { + self.conn_end + } + + /// Gets the time it took to establish the requested connection. + pub fn connect_duration(&self) -> Option { + self.conn_duration + } +}