http_stat/
request.rs

1// Copyright 2025 Tree xie.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// This file implements HTTP request functionality with support for HTTP/1.1, HTTP/2, and HTTP/3
16// It includes features like DNS resolution, TLS handshake, and request/response handling
17
18use super::decompress::decompress;
19use super::error::{Error, Result};
20use super::stats::{HttpStat, ALPN_HTTP1, ALPN_HTTP2, ALPN_HTTP3};
21use super::SkipVerifier;
22use bytes::{Buf, Bytes, BytesMut};
23use chrono::{Local, TimeZone};
24use futures::future;
25use hickory_resolver::config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig};
26use hickory_resolver::name_server::TokioConnectionProvider;
27use hickory_resolver::TokioResolver;
28use http::request::Builder;
29use http::HeaderValue;
30use http::Request;
31use http::Response;
32use http::Uri;
33use http::{HeaderMap, Method};
34use http_body_util::{BodyExt, Full};
35use hyper::body::Incoming;
36use hyper_util::rt::TokioExecutor;
37use hyper_util::rt::TokioIo;
38use std::net::IpAddr;
39use std::net::SocketAddr;
40use std::sync::Arc;
41use std::sync::Once;
42use std::time::Duration;
43use std::time::Instant;
44use tokio::net::TcpStream;
45use tokio::sync::oneshot;
46use tokio::time::timeout;
47use tokio_rustls::client::TlsStream;
48use tokio_rustls::rustls::{ClientConfig, RootCertStore};
49use tokio_rustls::TlsConnector;
50
51// Version information from Cargo.toml
52const VERSION: &str = env!("CARGO_PKG_VERSION");
53
54// Format TLS protocol version for display
55fn format_tls_protocol(protocol: &str) -> String {
56    match protocol {
57        "TLSv1_3" => "tls v1.3".to_string(),
58        "TLSv1_2" => "tls v1.2".to_string(),
59        "TLSv1_1" => "tls v1.1".to_string(),
60        _ => protocol.to_string(),
61    }
62}
63
64// Format timestamp to human-readable string
65fn format_time(timestamp_seconds: i64) -> String {
66    Local
67        .timestamp_nanos(timestamp_seconds * 1_000_000_000)
68        .to_string()
69}
70
71// HttpRequest struct to hold request configuration
72#[derive(Default, Debug, Clone)]
73pub struct HttpRequest {
74    pub uri: Uri,                                // Target URI
75    pub method: Option<Method>,                  // HTTP method (GET, POST, etc.)
76    pub alpn_protocols: Vec<String>,             // Supported ALPN protocols
77    pub resolve: Option<IpAddr>,                 // Custom DNS resolution
78    pub headers: Option<HeaderMap<HeaderValue>>, // Custom HTTP headers
79    pub ip_version: Option<i32>,                 // IP version (4 for IPv4, 6 for IPv6)
80    pub skip_verify: bool,                       // Skip TLS certificate verification
81    pub body: Option<Bytes>,                     // Request body
82    pub silent: bool,                            // Silent mode
83    pub dns_servers: Option<Vec<String>>,        // DNS servers
84    pub dns_timeout: Option<Duration>,           // DNS resolution timeout
85    pub tcp_timeout: Option<Duration>,           // TCP connection timeout
86    pub tls_timeout: Option<Duration>,           // TLS handshake timeout
87    pub request_timeout: Option<Duration>,       // HTTP request timeout
88    pub quic_timeout: Option<Duration>,          // QUIC connection timeout
89}
90
91impl HttpRequest {
92    pub fn get_port(&self) -> u16 {
93        let default_port = if self.uri.scheme() == Some(&http::uri::Scheme::HTTPS) {
94            443
95        } else {
96            80
97        };
98        self.uri.port_u16().unwrap_or(default_port)
99    }
100    // Build HTTP request with proper headers
101    fn builder(&self) -> Builder {
102        let uri = &self.uri;
103        let mut builder = Request::builder()
104            .uri(uri)
105            .method(self.method.clone().unwrap_or(Method::GET));
106        let mut set_host = false;
107        let mut set_user_agent = false;
108
109        // Add custom headers if provided
110        if let Some(headers) = &self.headers {
111            for (key, value) in headers.iter() {
112                builder = builder.header(key, value);
113                match key.to_string().to_lowercase().as_str() {
114                    "host" => set_host = true,
115                    "user-agent" => set_user_agent = true,
116                    _ => {}
117                }
118            }
119        }
120
121        // Set default Host header if not provided
122        if !set_host {
123            if let Some(host) = uri.host() {
124                builder = builder.header("Host", host);
125            }
126        }
127
128        // Set default User-Agent if not provided
129        if !set_user_agent {
130            builder = builder.header("User-Agent", format!("httpstat.rs/{}", VERSION));
131        }
132        builder
133    }
134}
135
136// Convert string URL to HttpRequest
137impl TryFrom<&str> for HttpRequest {
138    type Error = Error;
139
140    fn try_from(url: &str) -> Result<Self> {
141        let value = if url.starts_with("http://") || url.starts_with("https://") {
142            url.to_string()
143        } else {
144            format!("http://{}", url)
145        };
146        let uri = value.parse::<Uri>().map_err(|e| Error::Uri { source: e })?;
147        Ok(Self {
148            uri,
149            alpn_protocols: vec![ALPN_HTTP2.to_string(), ALPN_HTTP1.to_string()],
150            ..Default::default()
151        })
152    }
153}
154
155// Convert HttpRequest to hyper Request
156impl TryFrom<&HttpRequest> for Request<Full<Bytes>> {
157    type Error = Error;
158    fn try_from(req: &HttpRequest) -> Result<Self> {
159        req.builder()
160            .body(Full::new(req.body.clone().unwrap_or_default()))
161            .map_err(|e| Error::Http { source: e })
162    }
163}
164
165// Initialize crypto provider once
166static INIT: Once = Once::new();
167
168fn ensure_crypto_provider() {
169    INIT.call_once(|| {
170        let _ = tokio_rustls::rustls::crypto::ring::default_provider().install_default();
171    });
172}
173
174// Perform DNS resolution
175async fn dns_resolve(req: &HttpRequest, stat: &mut HttpStat) -> Result<(SocketAddr, String)> {
176    let host = req
177        .uri
178        .host()
179        .ok_or(Error::Common {
180            category: "http".to_string(),
181            message: "host is required".to_string(),
182        })?
183        .to_string();
184    let port = req.get_port();
185
186    // Check custom DNS resolutions first
187    if let Some(resolve) = &req.resolve {
188        let addr = SocketAddr::new(*resolve, port);
189        stat.addr = Some(addr.to_string());
190        return Ok((addr, host));
191    }
192
193    // Configure DNS resolver
194    let provider = TokioConnectionProvider::default();
195
196    let mut builder = if let Some(dns_servers) = &req.dns_servers {
197        let servers: Vec<_> = dns_servers
198            .iter()
199            .flat_map(|server| server.parse::<IpAddr>().ok())
200            .collect();
201
202        let mut config = ResolverConfig::new();
203        for server in NameServerConfigGroup::from_ips_clear(&servers, 53, true).into_inner() {
204            config.add_name_server(server);
205        }
206        TokioResolver::builder_with_config(config, provider)
207    } else {
208        TokioResolver::builder(provider).map_err(|e| Error::Resolve { source: e })?
209    };
210
211    if let Some(ip_version) = req.ip_version {
212        match ip_version {
213            4 => builder.options_mut().ip_strategy = LookupIpStrategy::Ipv4Only,
214            6 => builder.options_mut().ip_strategy = LookupIpStrategy::Ipv6Only,
215            _ => {}
216        }
217    }
218
219    // Perform DNS lookup
220    let resolver = builder.build();
221    let dns_start = Instant::now();
222    let addr = timeout(
223        req.dns_timeout.unwrap_or(Duration::from_secs(5)),
224        resolver.lookup_ip(&host),
225    )
226    .await
227    .map_err(|e| Error::Timeout { source: e })?
228    .map_err(|e| Error::Resolve { source: e })?;
229    stat.dns_lookup = Some(dns_start.elapsed());
230    let addr = addr.into_iter().next().ok_or(Error::Common {
231        category: "http".to_string(),
232        message: "dns lookup failed".to_string(),
233    })?;
234    let addr = SocketAddr::new(addr, port);
235    stat.addr = Some(addr.to_string());
236
237    Ok((addr, host))
238}
239
240// Establish TCP connection
241async fn tcp_connect(
242    addr: SocketAddr,
243    tcp_timeout: Option<Duration>,
244    stat: &mut HttpStat,
245) -> Result<TcpStream> {
246    let tcp_start = Instant::now();
247    let tcp_stream = timeout(
248        tcp_timeout.unwrap_or(Duration::from_secs(5)),
249        TcpStream::connect(addr),
250    )
251    .await
252    .map_err(|e| Error::Timeout { source: e })?
253    .map_err(|e| Error::Io { source: e })?;
254    stat.tcp_connect = Some(tcp_start.elapsed());
255    Ok(tcp_stream)
256}
257
258// Perform TLS handshake
259async fn tls_handshake(
260    host: String,
261    tcp_stream: TcpStream,
262    tls_timeout: Option<Duration>,
263    alpn_protocols: Vec<String>,
264    skip_verify: bool,
265    stat: &mut HttpStat,
266) -> Result<(TlsStream<TcpStream>, bool)> {
267    let tls_start = Instant::now();
268    let mut root_store = RootCertStore::empty();
269    let certs = rustls_native_certs::load_native_certs().certs;
270
271    // Add root certificates
272    for cert in certs {
273        root_store
274            .add(cert)
275            .map_err(|e| Error::Rustls { source: e })?;
276    }
277
278    // Configure TLS client
279    let mut config = ClientConfig::builder()
280        .with_root_certificates(root_store)
281        .with_no_client_auth();
282
283    // Skip certificate verification if requested
284    if skip_verify {
285        config
286            .dangerous()
287            .set_certificate_verifier(Arc::new(SkipVerifier));
288    }
289
290    // Set ALPN protocols
291    config.alpn_protocols = alpn_protocols
292        .iter()
293        .map(|s| s.as_bytes().to_vec())
294        .collect();
295
296    let connector = TlsConnector::from(Arc::new(config));
297
298    // Perform TLS handshake
299    let tls_stream = timeout(
300        tls_timeout.unwrap_or(Duration::from_secs(5)),
301        connector.connect(
302            host.clone()
303                .try_into()
304                .map_err(|e| Error::InvalidDnsName { source: e })?,
305            tcp_stream,
306        ),
307    )
308    .await
309    .map_err(|e| Error::Timeout { source: e })?
310    .map_err(|e| Error::Io { source: e })?;
311    stat.tls_handshake = Some(tls_start.elapsed());
312
313    // Get TLS session information
314    let (_, session) = tls_stream.get_ref();
315
316    stat.tls = session
317        .protocol_version()
318        .map(|v| format_tls_protocol(v.as_str().unwrap_or_default()));
319
320    // Extract certificate information
321    if let Some(certs) = session.peer_certificates() {
322        if let Some(cert) = certs.first() {
323            if let Ok((_, cert)) = x509_parser::parse_x509_certificate(cert.as_ref()) {
324                stat.subject = Some(cert.subject().to_string());
325                stat.cert_not_before = Some(format_time(cert.validity().not_before.timestamp()));
326                stat.cert_not_after = Some(format_time(cert.validity().not_after.timestamp()));
327                stat.issuer = Some(cert.issuer().to_string());
328                if let Ok(Some(sans)) = cert.subject_alternative_name() {
329                    let mut domains = Vec::new();
330                    for san in sans.value.general_names.iter() {
331                        if let x509_parser::extensions::GeneralName::DNSName(domain) = san {
332                            domains.push(domain.to_string());
333                        }
334                    }
335                    stat.cert_domains = Some(domains);
336                };
337            }
338        }
339    }
340
341    // Get cipher suite information
342    if let Some(cipher) = session.negotiated_cipher_suite() {
343        let cipher = format!("{:?}", cipher);
344        if let Some((_, cipher)) = cipher.split_once("_") {
345            stat.cert_cipher = Some(cipher.to_string());
346        } else {
347            stat.cert_cipher = Some(cipher);
348        }
349    }
350
351    // Check if HTTP/2 is negotiated
352    let mut is_http2 = false;
353    if let Some(protocol) = session.alpn_protocol() {
354        let alpn = String::from_utf8_lossy(protocol).to_string();
355        is_http2 = alpn == ALPN_HTTP2;
356        stat.alpn = Some(alpn);
357    }
358    Ok((tls_stream, is_http2))
359}
360
361// Send HTTP/1.1 request
362async fn send_http_request(
363    req: Request<Full<Bytes>>,
364    tcp_stream: TcpStream,
365    request_timeout: Option<Duration>,
366    tx: oneshot::Sender<String>,
367    stat: &mut HttpStat,
368) -> Result<Response<Incoming>> {
369    let (mut sender, conn) = timeout(
370        request_timeout.unwrap_or(Duration::from_secs(30)),
371        hyper::client::conn::http1::handshake(TokioIo::new(tcp_stream)),
372    )
373    .await
374    .map_err(|e| Error::Timeout { source: e })?
375    .map_err(|e| Error::Hyper { source: e })?;
376
377    // Spawn connection task
378    tokio::spawn(async move {
379        if let Err(e) = conn.await {
380            let _ = tx.send(e.to_string());
381        }
382    });
383
384    let server_processing_start = Instant::now();
385    let resp = sender
386        .send_request(req)
387        .await
388        .map_err(|e| Error::Hyper { source: e })?;
389    stat.server_processing = Some(server_processing_start.elapsed());
390    Ok(resp)
391}
392
393// Send HTTPS request
394async fn send_https_request(
395    req: Request<Full<Bytes>>,
396    tls_stream: TlsStream<TcpStream>,
397    request_timeout: Option<Duration>,
398    tx: oneshot::Sender<String>,
399    stat: &mut HttpStat,
400) -> Result<Response<Incoming>> {
401    let (mut sender, conn) = timeout(
402        request_timeout.unwrap_or(Duration::from_secs(30)),
403        hyper::client::conn::http1::handshake(TokioIo::new(tls_stream)),
404    )
405    .await
406    .map_err(|e| Error::Timeout { source: e })?
407    .map_err(|e| Error::Hyper { source: e })?;
408
409    // Spawn connection task
410    tokio::spawn(async move {
411        if let Err(e) = conn.await {
412            let _ = tx.send(e.to_string());
413        }
414    });
415
416    let server_processing_start = Instant::now();
417    let resp = sender
418        .send_request(req)
419        .await
420        .map_err(|e| Error::Hyper { source: e })?;
421    stat.server_processing = Some(server_processing_start.elapsed());
422    Ok(resp)
423}
424
425// Send HTTP/2 request
426async fn send_https2_request(
427    req: Request<Full<Bytes>>,
428    tls_stream: TlsStream<TcpStream>,
429    tx: oneshot::Sender<String>,
430    stat: &mut HttpStat,
431) -> Result<Response<Incoming>> {
432    let (mut sender, conn) = timeout(
433        Duration::from_secs(30),
434        hyper::client::conn::http2::handshake(TokioExecutor::new(), TokioIo::new(tls_stream)),
435    )
436    .await
437    .map_err(|e| Error::Timeout { source: e })?
438    .map_err(|e| Error::Hyper { source: e })?;
439
440    // Spawn connection task
441    tokio::spawn(async move {
442        if let Err(e) = conn.await {
443            let _ = tx.send(e.to_string());
444        }
445    });
446
447    let mut req = req;
448    *req.version_mut() = hyper::Version::HTTP_2;
449    // Remove Host header for HTTP/2 as it's replaced by :authority
450    req.headers_mut().remove("Host");
451
452    let server_processing_start = Instant::now();
453    let resp = sender
454        .send_request(req)
455        .await
456        .map_err(|e| Error::Hyper { source: e })?;
457    stat.server_processing = Some(server_processing_start.elapsed());
458    Ok(resp)
459}
460
461// Handle request error and update statistics
462fn finish_with_error(mut stat: HttpStat, error: impl ToString, start: Instant) -> HttpStat {
463    stat.error = Some(error.to_string());
464    stat.total = Some(start.elapsed());
465    stat
466}
467
468// Establish QUIC connection for HTTP/3
469async fn quic_connect(
470    host: String,
471    addr: SocketAddr,
472    skip_verify: bool,
473    stat: &mut HttpStat,
474) -> Result<(quinn::Endpoint, quinn::Connection)> {
475    let quic_start = Instant::now();
476    let mut root_store = RootCertStore::empty();
477    let certs = rustls_native_certs::load_native_certs().certs;
478
479    // Add root certificates
480    for cert in certs {
481        root_store
482            .add(cert)
483            .map_err(|e| Error::Rustls { source: e })?;
484    }
485
486    // Configure QUIC client
487    let mut config = ClientConfig::builder()
488        .with_root_certificates(root_store)
489        .with_no_client_auth();
490    config.enable_early_data = true;
491    config.alpn_protocols = vec![ALPN_HTTP3.as_bytes().to_vec()];
492
493    // Skip certificate verification if requested
494    if skip_verify {
495        config
496            .dangerous()
497            .set_certificate_verifier(Arc::new(SkipVerifier));
498    }
499
500    // Create QUIC endpoint
501    let mut client_endpoint =
502        h3_quinn::quinn::Endpoint::client("[::]:0".parse().map_err(|_| Error::Common {
503            category: "parse".to_string(),
504            message: "failed to parse address".to_string(),
505        })?)
506        .map_err(|e| Error::Io { source: e })?;
507
508    let h3_config =
509        quinn::crypto::rustls::QuicClientConfig::try_from(config).map_err(|e| Error::Common {
510            category: "quic".to_string(),
511            message: e.to_string(),
512        })?;
513
514    let client_config = quinn::ClientConfig::new(Arc::new(h3_config));
515    client_endpoint.set_default_client_config(client_config);
516
517    // Establish QUIC connection
518    let conn = client_endpoint
519        .connect(addr, &host)
520        .map_err(|e| Error::QuicConnect { source: e })?
521        .await
522        .map_err(|e| Error::QuicConnection { source: e })?;
523
524    stat.quic_connect = Some(quic_start.elapsed());
525    Ok((client_endpoint, conn))
526}
527
528// Handle HTTP/3 request
529async fn http3_request(http_req: HttpRequest) -> HttpStat {
530    let start = Instant::now();
531    let mut stat = HttpStat {
532        alpn: Some(ALPN_HTTP3.to_string()),
533        ..Default::default()
534    };
535
536    // DNS resolution
537    let dns_result = dns_resolve(&http_req, &mut stat).await;
538    let (addr, host) = match dns_result {
539        Ok(result) => result,
540        Err(e) => {
541            return finish_with_error(stat, e, start);
542        }
543    };
544
545    // Establish QUIC connection
546    let (client_endpoint, conn) = match timeout(
547        http_req.quic_timeout.unwrap_or(Duration::from_secs(30)),
548        quic_connect(host, addr, http_req.skip_verify, &mut stat),
549    )
550    .await
551    {
552        Ok(Ok(result)) => result,
553        Ok(Err(e)) => {
554            return finish_with_error(stat, e, start);
555        }
556        Err(e) => {
557            return finish_with_error(stat, e, start);
558        }
559    };
560
561    // Set TLS information
562    stat.tls = Some("tls 1.3".to_string()); // QUIC always uses TLS 1.3
563    stat.alpn = Some(ALPN_HTTP3.to_string()); // We always use HTTP/3 for QUIC
564
565    // Extract certificate information
566    if let Some(peer_identity) = conn.peer_identity() {
567        if let Ok(certs) = peer_identity.downcast::<Vec<rustls::pki_types::CertificateDer>>() {
568            if let Some(cert) = certs.first() {
569                if let Ok((_, cert)) = x509_parser::parse_x509_certificate(cert.as_ref()) {
570                    let oid_str = match cert.signature_algorithm.algorithm.to_string().as_str() {
571                        "1.2.840.113549.1.1.11" => "AES_256_GCM_SHA384".to_string(),
572                        "1.2.840.113549.1.1.12" => "AES_128_GCM_SHA256".to_string(),
573                        "1.2.840.113549.1.1.13" => "CHACHA20_POLY1305_SHA256".to_string(),
574                        "1.2.840.10045.4.3.2" => "AES_256_GCM_SHA384".to_string(),
575                        "1.2.840.10045.4.3.3" => "AES_128_GCM_SHA256".to_string(),
576                        "1.2.840.10045.4.3.4" => "CHACHA20_POLY1305_SHA256".to_string(),
577                        "1.3.101.112" => "AES_256_GCM_SHA384".to_string(),
578                        "1.3.101.113" => "AES_128_GCM_SHA256".to_string(),
579                        _ => format!("{:?}", cert.signature_algorithm.algorithm),
580                    };
581                    stat.subject = Some(cert.subject().to_string());
582                    stat.issuer = Some(cert.issuer().to_string());
583                    stat.cert_cipher = Some(oid_str);
584                    stat.cert_not_before =
585                        Some(format_time(cert.validity().not_before.timestamp()));
586                    stat.cert_not_after = Some(format_time(cert.validity().not_after.timestamp()));
587                    if let Ok(Some(sans)) = cert.subject_alternative_name() {
588                        let mut domains = Vec::new();
589                        for san in sans.value.general_names.iter() {
590                            if let x509_parser::extensions::GeneralName::DNSName(domain) = san {
591                                domains.push(domain.to_string());
592                            }
593                        }
594                        stat.cert_domains = Some(domains);
595                    };
596                }
597            }
598        }
599    }
600
601    // Create HTTP/3 connection
602    let quinn_conn = h3_quinn::Connection::new(conn);
603
604    let (mut driver, mut send_request) = match timeout(
605        http_req.request_timeout.unwrap_or(Duration::from_secs(30)),
606        h3::client::new(quinn_conn),
607    )
608    .await
609    {
610        Ok(Ok(result)) => result,
611        Ok(Err(e)) => {
612            return finish_with_error(stat, e, start);
613        }
614        Err(e) => {
615            return finish_with_error(stat, e, start);
616        }
617    };
618
619    // Prepare request
620    let req = match http_req.builder().body(()) {
621        Ok(req) => req,
622        Err(e) => {
623            return finish_with_error(stat, e, start);
624        }
625    };
626    let body = http_req.body.unwrap_or_default();
627
628    // Handle connection driver
629    let drive = async move {
630        Err::<(), h3::error::ConnectionError>(future::poll_fn(|cx| driver.poll_close(cx)).await)
631    };
632
633    // Send request and handle response
634    let request = async move {
635        let mut stream = send_request.send_request(req).await?;
636        stream.send_data(body).await?;
637
638        let mut sub_stat = HttpStat::default();
639
640        // Finish sending
641        stream.finish().await?;
642
643        let server_processing_start = Instant::now();
644
645        let resp = stream.recv_response().await?;
646        sub_stat.server_processing = Some(server_processing_start.elapsed());
647
648        sub_stat.status = Some(resp.status());
649        sub_stat.headers = Some(resp.headers().clone());
650
651        // Receive response body
652        let content_transfer_start = Instant::now();
653        let mut buf = BytesMut::new();
654        while let Some(chunk) = stream.recv_data().await? {
655            buf.extend(chunk.chunk());
656        }
657        sub_stat.content_transfer = Some(content_transfer_start.elapsed());
658        sub_stat.body = Some(Bytes::from(buf));
659        Ok::<HttpStat, h3::error::StreamError>(sub_stat)
660    };
661
662    // Execute request and handle results
663    let (req_res, drive_res) = tokio::join!(request, drive);
664    match req_res {
665        Ok(sub_stat) => {
666            stat.server_processing = sub_stat.server_processing;
667            stat.content_transfer = sub_stat.content_transfer;
668            stat.status = sub_stat.status;
669            stat.headers = sub_stat.headers;
670            stat.body = sub_stat.body;
671        }
672        Err(err) => {
673            if !err.is_h3_no_error() {
674                stat.error = Some(err.to_string());
675            }
676        }
677    }
678    if let Err(err) = drive_res {
679        if !err.is_h3_no_error() {
680            stat.error = Some(err.to_string());
681        }
682    }
683
684    stat.total = Some(start.elapsed());
685    // Close the connection immediately instead of waiting for idle
686    client_endpoint.close(0u32.into(), b"done");
687
688    stat
689}
690
691async fn http1_2_request(http_req: HttpRequest) -> HttpStat {
692    let start = Instant::now();
693    let mut stat = HttpStat::default();
694
695    // DNS resolution
696    let dns_result = dns_resolve(&http_req, &mut stat).await;
697    let (addr, host) = match dns_result {
698        Ok(result) => result,
699        Err(e) => {
700            return finish_with_error(stat, e, start);
701        }
702    };
703
704    let uri = &http_req.uri;
705    let is_https = uri.scheme() == Some(&http::uri::Scheme::HTTPS);
706
707    // Convert request to hyper Request
708    let req: Request<Full<Bytes>> = match (&http_req).try_into() {
709        Ok(req) => req,
710        Err(e) => {
711            return finish_with_error(stat, e, start);
712        }
713    };
714
715    // TCP connection
716    let tcp_stream = match tcp_connect(addr, http_req.tcp_timeout, &mut stat).await {
717        Ok(stream) => stream,
718        Err(e) => {
719            return finish_with_error(stat, e, start);
720        }
721    };
722
723    // Create channel for connection errors
724    let (tx, mut rx) = oneshot::channel();
725
726    // Send request based on protocol
727    let resp = if is_https {
728        // TLS handshake
729        let tls_result = tls_handshake(
730            host.clone(),
731            tcp_stream,
732            http_req.tls_timeout,
733            http_req.alpn_protocols,
734            http_req.skip_verify,
735            &mut stat,
736        )
737        .await;
738        let (tls_stream, is_http2) = match tls_result {
739            Ok(result) => result,
740            Err(e) => {
741                return finish_with_error(stat, e, start);
742            }
743        };
744
745        // Send HTTPS request
746        if is_http2 {
747            match send_https2_request(req, tls_stream, tx, &mut stat).await {
748                Ok(resp) => resp,
749                Err(e) => {
750                    return finish_with_error(stat, e, start);
751                }
752            }
753        } else {
754            match send_https_request(req, tls_stream, http_req.request_timeout, tx, &mut stat).await
755            {
756                Ok(resp) => resp,
757                Err(e) => {
758                    return finish_with_error(stat, e, start);
759                }
760            }
761        }
762    } else {
763        // Send HTTP request
764        match send_http_request(req, tcp_stream, http_req.request_timeout, tx, &mut stat).await {
765            Ok(resp) => resp,
766            Err(e) => {
767                return finish_with_error(stat, e, start);
768            }
769        }
770    };
771
772    // Process response
773    stat.status = Some(resp.status());
774    stat.headers = Some(resp.headers().clone());
775
776    // Read response body
777    let content_transfer_start = Instant::now();
778    let body_result = resp.collect().await;
779    let body = match body_result {
780        Ok(body) => body,
781        Err(e) => {
782            return finish_with_error(stat, format!("Failed to read response body: {}", e), start);
783        }
784    };
785
786    let body_bytes = body.to_bytes();
787    stat.body = Some(body_bytes);
788    stat.content_transfer = Some(content_transfer_start.elapsed());
789
790    // Check for connection errors
791    if let Ok(error) = rx.try_recv() {
792        stat.error = Some(error);
793    }
794
795    stat.total = Some(start.elapsed());
796    stat
797}
798
799/// Performs an HTTP request and returns detailed statistics about the request lifecycle.
800///
801/// This function handles HTTP/1.1, HTTP/2, and HTTP/3 requests with the following features:
802/// - Automatic protocol selection based on ALPN negotiation
803/// - DNS resolution with support for custom IP mappings
804/// - TLS handshake with certificate verification
805/// - Response body handling with optional file output
806/// - Detailed timing statistics for each phase of the request
807///
808/// # Arguments
809///
810/// * `http_req` - An `HttpRequest` struct containing the request configuration including:
811///   - URI and HTTP method
812///   - ALPN protocols to negotiate
813///   - Custom DNS resolutions
814///   - Headers and request body
815///   - TLS verification settings
816///   - Output file path (optional)
817///
818/// # Returns
819///
820/// Returns an `HttpStat` struct containing:
821/// - DNS lookup time
822/// - QUIC connection time
823/// - TCP connection time
824/// - TLS handshake time (for HTTPS)
825/// - Server processing time
826/// - Content transfer time
827/// - Total request time
828/// - Response status and headers
829/// - Response body (if not written to file)
830/// - TLS and certificate information (for HTTPS)
831/// - Any errors that occurred during the request
832///
833/// # Examples
834///
835/// ```rust
836/// let http_req = HttpRequest {
837///     uri: "https://example.com".parse().unwrap(),
838///     ..Default::default()
839/// };
840/// let stats = request(http_req).await;
841/// ```
842pub async fn request(http_req: HttpRequest) -> HttpStat {
843    ensure_crypto_provider();
844    let silent = http_req.silent;
845
846    // Handle HTTP/3 request
847    let mut stat = if http_req.alpn_protocols.contains(&ALPN_HTTP3.to_string()) {
848        http3_request(http_req).await
849    } else {
850        http1_2_request(http_req).await
851    };
852    if let Some(body) = &stat.body {
853        stat.body_size = Some(body.len());
854    }
855    let encoding = if let Some(headers) = &stat.headers {
856        headers
857            .get("content-encoding")
858            .map(|v| v.to_str().unwrap_or_default())
859            .unwrap_or_default()
860    } else {
861        ""
862    };
863
864    if !encoding.is_empty() {
865        if let Some(body) = &stat.body {
866            match decompress(encoding, body) {
867                Ok(data) => {
868                    stat.body = Some(data);
869                }
870                Err(e) => {
871                    stat.error = Some(e.to_string());
872                }
873            }
874        }
875    }
876
877    stat.silent = silent;
878
879    stat
880}