1use 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
51const VERSION: &str = env!("CARGO_PKG_VERSION");
53
54fn 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
64fn format_time(timestamp_seconds: i64) -> String {
66 Local
67 .timestamp_nanos(timestamp_seconds * 1_000_000_000)
68 .to_string()
69}
70
71#[derive(Default, Debug, Clone)]
73pub struct HttpRequest {
74 pub uri: Uri, pub method: Option<Method>, pub alpn_protocols: Vec<String>, pub resolve: Option<IpAddr>, pub headers: Option<HeaderMap<HeaderValue>>, pub ip_version: Option<i32>, pub skip_verify: bool, pub body: Option<Bytes>, pub silent: bool, pub dns_servers: Option<Vec<String>>, pub dns_timeout: Option<Duration>, pub tcp_timeout: Option<Duration>, pub tls_timeout: Option<Duration>, pub request_timeout: Option<Duration>, pub quic_timeout: Option<Duration>, }
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 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 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 if !set_host {
123 if let Some(host) = uri.host() {
124 builder = builder.header("Host", host);
125 }
126 }
127
128 if !set_user_agent {
130 builder = builder.header("User-Agent", format!("httpstat.rs/{}", VERSION));
131 }
132 builder
133 }
134}
135
136impl 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
155impl 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
165static 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
174async 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 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 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 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
240async 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
258async 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 for cert in certs {
273 root_store
274 .add(cert)
275 .map_err(|e| Error::Rustls { source: e })?;
276 }
277
278 let mut config = ClientConfig::builder()
280 .with_root_certificates(root_store)
281 .with_no_client_auth();
282
283 if skip_verify {
285 config
286 .dangerous()
287 .set_certificate_verifier(Arc::new(SkipVerifier));
288 }
289
290 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 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 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 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 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 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
361async 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 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
393async 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 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
425async 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 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 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
461fn 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
468async 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 for cert in certs {
481 root_store
482 .add(cert)
483 .map_err(|e| Error::Rustls { source: e })?;
484 }
485
486 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 if skip_verify {
495 config
496 .dangerous()
497 .set_certificate_verifier(Arc::new(SkipVerifier));
498 }
499
500 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 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
528async 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 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 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 stat.tls = Some("tls 1.3".to_string()); stat.alpn = Some(ALPN_HTTP3.to_string()); 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 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 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 let drive = async move {
630 Err::<(), h3::error::ConnectionError>(future::poll_fn(|cx| driver.poll_close(cx)).await)
631 };
632
633 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 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 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 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 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 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 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 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 let (tx, mut rx) = oneshot::channel();
725
726 let resp = if is_https {
728 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 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 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 stat.status = Some(resp.status());
774 stat.headers = Some(resp.headers().clone());
775
776 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 if let Ok(error) = rx.try_recv() {
792 stat.error = Some(error);
793 }
794
795 stat.total = Some(start.elapsed());
796 stat
797}
798
799pub async fn request(http_req: HttpRequest) -> HttpStat {
843 ensure_crypto_provider();
844 let silent = http_req.silent;
845
846 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}