use std::collections::HashMap; use std::io::Read; 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 { 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 add_user_agent(&self, req: ureq::Request) -> ureq::Request { req.set( "User-Agent", self.options .user_agent .as_deref() .unwrap_or(DEFAULT_USER_AGENT_STRING), ) } fn call(&self, req: ureq::Request) -> Result>, Error> { let req = self.add_user_agent(req); Self::convert_response(req.call()?) } fn send(&self, req: ureq::Request, body: R) -> Result>, Error> where R: Read, { let req = self.add_user_agent(req); Self::convert_response(req.send(body)?) } fn convert_response(res: ureq::Response) -> Result>, Error> { 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); } } let mut body = Vec::new(); res.into_reader().read_to_end(&mut body)?; builder .body(body) .map_err(|err| format_err!("Failed to convert HTTP response - {err}")) } fn convert_body_to_string(res: Response>) -> Result, Error> { let (parts, body) = res.into_parts(); let body = String::from_utf8(body)?; Ok(Response::from_parts(parts, body)) } } impl HttpClient for Client { fn get( &self, uri: &str, extra_headers: Option<&HashMap>, ) -> Result, 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.call(req).and_then(Self::convert_body_to_string) } fn post( &self, uri: &str, body: Option, content_type: Option<&str>, ) -> Result, Error> where R: Read, { let mut req = self.agent()?.post(uri); if let Some(content_type) = content_type { req = req.set("Content-Type", content_type); } match body { Some(body) => self.send(req, body), None => self.call(req), } .and_then(Self::convert_body_to_string) } fn request(&self, request: http::Request) -> Result, 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.send(req, request.body().as_bytes()) .and_then(Self::convert_body_to_string) } } impl HttpClient> for Client { fn get( &self, uri: &str, extra_headers: Option<&HashMap>, ) -> Result>, 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.call(req) } fn post( &self, uri: &str, body: Option, content_type: Option<&str>, ) -> Result>, Error> where R: Read, { let mut req = self.agent()?.post(uri); if let Some(content_type) = content_type { req = req.set("Content-Type", content_type); } match body { Some(body) => self.send(req, body), None => self.call(req), } } fn request(&self, request: http::Request>) -> Result>, 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.send(req, request.body().as_slice()) } }