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)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id F0425709F7 for ; Fri, 14 May 2021 15:45:11 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id ECC2B17B4E for ; Fri, 14 May 2021 15:45:11 +0200 (CEST) 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)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id 2C42B17AF4 for ; Fri, 14 May 2021 15:45:09 +0200 (CEST) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 0588946552 for ; Fri, 14 May 2021 15:45:09 +0200 (CEST) From: =?UTF-8?q?Fabian=20Gr=C3=BCnbichler?= To: pbs-devel@lists.proxmox.com Date: Fri, 14 May 2021 15:44:46 +0200 Message-Id: <20210514134457.1447930-11-f.gruenbichler@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20210514134457.1447930-1-f.gruenbichler@proxmox.com> References: <20210514134457.1447930-1-f.gruenbichler@proxmox.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.014 Adjusted score from AWL reputation of From: address 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. [client.rs, simple.rs] Subject: [pbs-devel] [PATCH proxmox 10/13] http: takeover simple HTTP client from proxmox_backup 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: Fri, 14 May 2021 13:45:12 -0000 adapted to use already moved helpers/code. Signed-off-by: Fabian Grünbichler --- proxmox-http/src/http/client.rs | 4 + proxmox-http/src/http/client/simple.rs | 157 +++++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 proxmox-http/src/http/client/simple.rs diff --git a/proxmox-http/src/http/client.rs b/proxmox-http/src/http/client.rs index 21a65e3..c55cbd4 100644 --- a/proxmox-http/src/http/client.rs +++ b/proxmox-http/src/http/client.rs @@ -1,3 +1,7 @@ mod connector; pub use connector::HttpsConnector; + +mod simple; +pub use simple::SimpleHttp; +pub use simple::SimpleHttpOptions; diff --git a/proxmox-http/src/http/client/simple.rs b/proxmox-http/src/http/client/simple.rs new file mode 100644 index 0000000..110fa55 --- /dev/null +++ b/proxmox-http/src/http/client/simple.rs @@ -0,0 +1,157 @@ +use anyhow::{Error, format_err, bail}; +use std::collections::HashMap; + +use hyper::Body; +use hyper::client::{Client, HttpConnector}; +use http::{Request, Response, HeaderValue}; +use openssl::ssl::{SslConnector, SslMethod}; +use futures::*; + +use crate::http::{ + ProxyConfig, + client::HttpsConnector, +}; + +/// Options for a SimpleHttp client. +#[derive(Default)] +pub struct SimpleHttpOptions { + /// Proxy configuration + pub proxy_config: Option, + /// `User-Agent` header value, defaults to `proxmox-simple-http-client/0.1` + pub user_agent: Option, + /// TCP keepalive time, defaults to 7200 + pub tcp_keepalive: Option, +} + +impl SimpleHttpOptions { + fn get_proxy_authorization(&self) -> Option { + if let Some(ref proxy_config) = self.proxy_config { + if !proxy_config.force_connect { + return proxy_config.authorization.clone(); + } + } + + None + } +} + +/// Asyncrounous HTTP client implementation +pub struct SimpleHttp { + client: Client, + options: SimpleHttpOptions, +} + +impl SimpleHttp { + pub const DEFAULT_USER_AGENT_STRING: &'static str = "proxmox-simple-http-client/0.1"; + + pub fn new() -> Self { + Self::with_options(SimpleHttpOptions::default()) + } + + pub fn with_options(options: SimpleHttpOptions) -> Self { + let ssl_connector = SslConnector::builder(SslMethod::tls()).unwrap().build(); + Self::with_ssl_connector(ssl_connector, options) + } + + pub fn with_ssl_connector(ssl_connector: SslConnector, options: SimpleHttpOptions) -> Self { + let connector = HttpConnector::new(); + let mut https = HttpsConnector::with_connector(connector, ssl_connector, options.tcp_keepalive.unwrap_or(7200)); + if let Some(ref proxy_config) = options.proxy_config { + https.set_proxy(proxy_config.clone()); + } + let client = Client::builder().build(https); + Self { client, options } + } + + pub fn set_user_agent(&mut self, user_agent: &str) -> Result<(), Error> { + self.options.user_agent = Some(user_agent.to_owned()); + Ok(()) + } + + fn add_proxy_headers(&self, request: &mut Request) -> Result<(), Error> { + if request.uri().scheme() != Some(&http::uri::Scheme::HTTPS) { + if let Some(ref authorization) = self.options.get_proxy_authorization() { + request + .headers_mut() + .insert( + http::header::PROXY_AUTHORIZATION, + HeaderValue::from_str(authorization)?, + ); + } + } + Ok(()) + } + + pub async fn request(&self, mut request: Request) -> Result, Error> { + let user_agent = if let Some(ref user_agent) = self.options.user_agent { + HeaderValue::from_str(&user_agent)? + } else { + HeaderValue::from_str(Self::DEFAULT_USER_AGENT_STRING)? + }; + + request.headers_mut().insert(hyper::header::USER_AGENT, user_agent); + + self.add_proxy_headers(&mut request)?; + + self.client.request(request) + .map_err(Error::from) + .await + } + + pub async fn post( + &mut self, + uri: &str, + body: Option, + content_type: Option<&str>, + ) -> Result, Error> { + + let body = if let Some(body) = body { + Body::from(body) + } else { + Body::empty() + }; + let content_type = content_type.unwrap_or("application/json"); + + let request = Request::builder() + .method("POST") + .uri(uri) + .header(hyper::header::CONTENT_TYPE, content_type) + .body(body)?; + + self.request(request).await + } + + pub async fn get_string( + &mut self, + uri: &str, + extra_headers: Option<&HashMap>, + ) -> Result { + + let mut request = Request::builder() + .method("GET") + .uri(uri); + + if let Some(hs) = extra_headers { + for (h, v) in hs.iter() { + request = request.header(h, v); + } + } + + let request = request.body(Body::empty())?; + + let res = self.request(request).await?; + + let status = res.status(); + if !status.is_success() { + bail!("Got bad status '{}' from server", status) + } + + Self::response_body_string(res).await + } + + pub async fn response_body_string(res: Response) -> Result { + let buf = hyper::body::to_bytes(res).await?; + String::from_utf8(buf.to_vec()) + .map_err(|err| format_err!("Error converting HTTP result data: {}", err)) + } +} -- 2.20.1