]> git.proxmox.com Git - proxmox.git/commitdiff
http: add ureq-based sync client
authorFabian Grünbichler <f.gruenbichler@proxmox.com>
Thu, 4 Aug 2022 08:01:13 +0000 (10:01 +0200)
committerFabian Grünbichler <f.gruenbichler@proxmox.com>
Wed, 7 Sep 2022 07:17:45 +0000 (09:17 +0200)
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com>
proxmox-http/Cargo.toml
proxmox-http/src/client/mod.rs
proxmox-http/src/client/sync.rs [new file with mode: 0644]
proxmox-http/src/lib.rs
proxmox-http/src/proxy_config.rs

index 541c5dd22cf645929713f23bcc98594acf99e8a8..bfc86c74c412836d55b4295caf0667ce42928008 100644 (file)
@@ -21,6 +21,7 @@ openssl =  { version = "0.10", optional = true }
 serde_json = { version = "1.0", optional = true }
 tokio = { version = "1.0", features = [], optional = true }
 tokio-openssl = { version = "0.6.1", optional = true }
+ureq = { version = "2.4", features = ["native-certs"], optional = true }
 url = { version = "2", optional = true }
 
 proxmox-async = { path = "../proxmox-async", optional = true, version = "0.4.1" }
@@ -32,6 +33,7 @@ proxmox-lang = { path = "../proxmox-lang", optional = true, version = "1.1" }
 default = []
 
 client = [ "dep:futures", "http-helpers", "dep:hyper", "hyper?/full", "dep:openssl", "dep:tokio", "tokio?/io-util", "dep:tokio-openssl" ]
+client-sync = [ "client-trait", "http-helpers", "dep:ureq" ]
 client-trait = [ "dep:http" ]
 http-helpers = [ "dep:base64", "dep:http", "dep:proxmox-sys", "dep:serde_json", "dep:url" ]
 websocket = [
index f56a67788f53afb00a26a55da24e05101a386226..5bbd4d354fe8c6dd88d5361bc368cb3c9c06d05a 100644 (file)
@@ -1,17 +1,36 @@
-//! Simple TLS capable HTTP client implementation.
+//! Simple TLS capable HTTP client implementations.
 //!
-//! Contains a lightweight wrapper around `hyper` with support for TLS connections.
+//! Feature `client` contains a lightweight wrapper around `hyper` with support for TLS connections
+//! in [`SimpleHttp`](crate::client::SimpleHttp).
+//!
+//! Feature `client-sync` contains a lightweight wrapper around `ureq` in
+//! [`sync::Client`](crate::client::sync::Client).
+//!
+//! Both clients implement [`HttpClient`](crate::HttpClient) if the feature `client-trait` is enabled.
 
+#[cfg(feature = "client")]
 mod rate_limiter;
+#[cfg(feature = "client")]
 pub use rate_limiter::{RateLimit, RateLimiter, RateLimiterVec, ShareableRateLimit};
 
+#[cfg(feature = "client")]
 mod rate_limited_stream;
+#[cfg(feature = "client")]
 pub use rate_limited_stream::RateLimitedStream;
 
+#[cfg(feature = "client")]
 mod connector;
+#[cfg(feature = "client")]
 pub use connector::HttpsConnector;
 
+#[cfg(feature = "client")]
 mod simple;
+#[cfg(feature = "client")]
 pub use simple::SimpleHttp;
 
+#[cfg(feature = "client")]
 pub mod tls;
+
+#[cfg(feature = "client-sync")]
+/// Blocking HTTP client
+pub mod sync;
diff --git a/proxmox-http/src/client/sync.rs b/proxmox-http/src/client/sync.rs
new file mode 100644 (file)
index 0000000..b8d86f2
--- /dev/null
@@ -0,0 +1,108 @@
+use std::collections::HashMap;
+
+use anyhow::{format_err, Error};
+use http::Response;
+
+use crate::HttpClient;
+use crate::HttpOptions;
+
+pub const DEFAULT_USER_AGENT_STRING: &str = "proxmox-sync-http-client/0.1";
+
+#[derive(Default)]
+/// Blocking HTTP client for usage with [`HttpClient`].
+pub struct Client {
+    options: HttpOptions,
+}
+
+impl Client {
+    pub fn new(options: HttpOptions) -> Self {
+        Self { options }
+    }
+
+    fn agent(&self) -> Result<ureq::Agent, Error> {
+        let mut builder = ureq::AgentBuilder::new();
+        if let Some(proxy_config) = &self.options.proxy_config {
+            builder = builder.proxy(ureq::Proxy::new(proxy_config.to_proxy_string()?)?);
+        }
+
+        Ok(builder.build())
+    }
+
+    fn exec_request(
+        &self,
+        req: ureq::Request,
+        body: Option<&str>,
+    ) -> Result<Response<String>, Error> {
+        let req = req.set(
+            "User-Agent",
+            self.options
+                .user_agent
+                .as_deref()
+                .unwrap_or(DEFAULT_USER_AGENT_STRING),
+        );
+
+        let res = match body {
+            Some(body) => req.send_string(body),
+            None => req.call(),
+        }?;
+
+        let mut builder = http::response::Builder::new()
+            .status(http::status::StatusCode::from_u16(res.status())?);
+
+        for header in res.headers_names() {
+            if let Some(value) = res.header(&header) {
+                builder = builder.header(header, value);
+            }
+        }
+        builder
+            .body(res.into_string()?)
+            .map_err(|err| format_err!("Failed to convert HTTP response - {err}"))
+    }
+}
+
+impl HttpClient<String> for Client {
+    fn get(
+        &self,
+        uri: &str,
+        extra_headers: Option<&HashMap<String, String>>,
+    ) -> Result<Response<String>, Error> {
+        let mut req = self.agent()?.get(uri);
+
+        if let Some(extra_headers) = extra_headers {
+            for (header, value) in extra_headers {
+                req = req.set(header, value);
+            }
+        }
+
+        self.exec_request(req, None)
+    }
+
+    fn post(
+        &self,
+        uri: &str,
+        body: Option<&str>,
+        content_type: Option<&str>,
+    ) -> Result<Response<String>, Error> {
+        let mut req = self.agent()?.post(uri);
+        if let Some(content_type) = content_type {
+            req = req.set("Content-Type", content_type);
+        }
+
+        self.exec_request(req, body)
+    }
+
+    fn request(&self, request: http::Request<String>) -> Result<Response<String>, Error> {
+        let mut req = self
+            .agent()?
+            .request(request.method().as_str(), &request.uri().to_string());
+        let orig_headers = request.headers();
+
+        for header in orig_headers.keys() {
+            for value in orig_headers.get_all(header) {
+                req = req.set(header.as_str(), value.to_str()?);
+            }
+        }
+
+        self.exec_request(req, Some(request.body().as_str()))
+    }
+}
index f32cfec0eaf15a975159ec24f7785261792efaed..40efcd1d425b5933ebf6cfbbad0ee42093b297b7 100644 (file)
@@ -16,7 +16,7 @@ mod http_options;
 #[cfg(feature = "http-helpers")]
 pub use http_options::HttpOptions;
 
-#[cfg(feature = "client")]
+#[cfg(any(feature = "client", feature = "client-sync"))]
 pub mod client;
 
 #[cfg(feature = "client-trait")]
index 0881c098bfbf07d19c7ab3123a61526e824f4508..f874ce13db9fd874eeafc396a3d16d0c36117df0 100644 (file)
@@ -1,6 +1,6 @@
 //! HTTP proxy configuration.
 //!
-//! This can be used with the [`SimpleHttp`](crate::client::SimpleHttp).
+//! This can be used with the async [`SimpleHttp`](crate::client::SimpleHttp) or sync [`Client`](crate::client::sync::Client).
 
 use anyhow::{bail, format_err, Error};