From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 83FFABBBAF for ; Tue, 26 Mar 2024 16:28:21 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 65ED616AD7 for ; Tue, 26 Mar 2024 16:28:21 +0100 (CET) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS for ; Tue, 26 Mar 2024 16:28:19 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 8034742271 for ; Tue, 26 Mar 2024 16:28:19 +0100 (CET) From: Maximiliano Sandoval To: pbs-devel@lists.proxmox.com Date: Tue, 26 Mar 2024 16:28:17 +0100 Message-Id: <20240326152818.639452-1-m.sandoval@proxmox.com> X-Mailer: git-send-email 2.39.2 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.013 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [simple.rs, httpbin.org] Subject: [pbs-devel] [PATCH proxmox 1/2] http: teach the Client how to speak gzip X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 26 Mar 2024 15:28:21 -0000 Proxmox VE already supports using gzip to encode and decode the body of http requests. We teach the proxmox-http how to do the same. This can be also tested against http://eu.httpbin.org/#/Response_formats/get_gzip via ```rust async fn test_client() { use crate::client_trait::HttpClient; let client = Client::new(); let response = client.get_string("http://eu.httpbin.org/gzip", None).await.unwrap(); assert_eq!(response, ".."); } ``` Suggested-by: Lukas Wagner Signed-off-by: Maximiliano Sandoval --- proxmox-http/Cargo.toml | 5 +++ proxmox-http/src/client/simple.rs | 62 +++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/proxmox-http/Cargo.toml b/proxmox-http/Cargo.toml index 11c6f0b0..9ec32e7f 100644 --- a/proxmox-http/Cargo.toml +++ b/proxmox-http/Cargo.toml @@ -21,12 +21,16 @@ tokio = { workspace = true, features = [], optional = true } tokio-openssl = { workspace = true, optional = true } ureq = { version = "2.4", features = ["native-certs"], optional = true } url = { workspace = true, optional = true } +flate2 = { workspace = true, optional = true } proxmox-async = { workspace = true, optional = true } proxmox-sys = { workspace = true, optional = true } proxmox-io = { workspace = true, optional = true } proxmox-lang = { workspace = true, optional = true } +[dev-dependencies] +tokio = { workspace = true, features = [ "macros" ] } + [features] default = [] @@ -39,6 +43,7 @@ rate-limited-stream = [ "tokio?/time", ] client = [ + "dep:flate2", "dep:futures", "dep:hyper", "dep:openssl", diff --git a/proxmox-http/src/client/simple.rs b/proxmox-http/src/client/simple.rs index e9910802..b33154be 100644 --- a/proxmox-http/src/client/simple.rs +++ b/proxmox-http/src/client/simple.rs @@ -1,9 +1,11 @@ use anyhow::{bail, format_err, Error}; use std::collections::HashMap; - +use std::io::Read; #[cfg(all(feature = "client-trait", feature = "proxmox-async"))] use std::str::FromStr; +use flate2::read::GzDecoder; + use futures::*; #[cfg(all(feature = "client-trait", feature = "proxmox-async"))] use http::header::HeaderName; @@ -72,6 +74,10 @@ impl Client { HeaderValue::from_str(Self::DEFAULT_USER_AGENT_STRING)? }; + request.headers_mut().insert( + hyper::header::ACCEPT_ENCODING, + HeaderValue::from_static("gzip"), + ); request .headers_mut() .insert(hyper::header::USER_AGENT, user_agent); @@ -142,11 +148,22 @@ impl Client { ) -> Result, Error> { match response { Ok(res) => { - let (parts, body) = res.into_parts(); + let (mut parts, body) = res.into_parts(); + let is_gzip_encoded = parts + .headers + .remove(&hyper::header::CONTENT_ENCODING) + .is_some_and(|h| h == "gzip"); let buf = hyper::body::to_bytes(body).await?; - let new_body = String::from_utf8(buf.to_vec()) - .map_err(|err| format_err!("Error converting HTTP result data: {}", err))?; + let new_body = if is_gzip_encoded { + let mut gz = GzDecoder::new(&buf[..]); + let mut s = String::new(); + gz.read_to_string(&mut s)?; + s + } else { + String::from_utf8(buf.to_vec()) + .map_err(|err| format_err!("Error converting HTTP result data: {}", err))? + }; Ok(Response::from_parts(parts, new_body)) } @@ -245,3 +262,40 @@ impl crate::HttpClient for Client { }) } } + +#[cfg(test)] +mod test { + use super::*; + + const BODY: &str = "hello world"; + + #[tokio::test] + async fn test_parse_response_plain_text() { + let body = Body::from(BODY); + let response = Response::new(body); + assert_eq!(Client::response_body_string(response).await.unwrap(), BODY); + } + + #[tokio::test] + async fn test_parse_response_gzip() { + let encoded = encode_gzip(BODY.as_bytes()).unwrap(); + let body = Body::from(encoded); + + let response = Response::builder() + .header(hyper::header::CONTENT_ENCODING, "gzip") + .body(body) + .unwrap(); + assert_eq!(Client::response_body_string(response).await.unwrap(), BODY); + } + + fn encode_gzip(bytes: &[u8]) -> Result, std::io::Error> { + use flate2::write::GzEncoder; + use flate2::Compression; + use std::io::Write; + + let mut e = GzEncoder::new(Vec::new(), Compression::default()); + e.write_all(bytes).unwrap(); + + e.finish() + } +} -- 2.39.2