http: client_trait: make request body generic

like the response body, instead of hard-coding Read.
This commit is contained in:
Fabian Grünbichler 2022-08-05 09:42:23 +02:00
parent 891dcfda2f
commit 08a6d56eae
4 changed files with 38 additions and 65 deletions

View File

@ -1,7 +1,6 @@
use anyhow::{bail, format_err, Error}; use anyhow::{bail, format_err, Error};
use std::collections::HashMap; use std::collections::HashMap;
use std::io::Read;
#[cfg(all(feature = "client-trait", feature = "proxmox-async"))] #[cfg(all(feature = "client-trait", feature = "proxmox-async"))]
use std::str::FromStr; use std::str::FromStr;
@ -82,23 +81,13 @@ impl Client {
self.client.request(request).map_err(Error::from).await self.client.request(request).map_err(Error::from).await
} }
pub async fn post<R>( pub async fn post(
&self, &self,
uri: &str, uri: &str,
body: Option<R>, body: Option<Body>,
content_type: Option<&str>, content_type: Option<&str>,
extra_headers: Option<&HashMap<String, String>>, extra_headers: Option<&HashMap<String, String>>,
) -> Result<Response<Body>, Error> ) -> Result<Response<Body>, Error> {
where
R: Read,
{
let body = if let Some(mut body) = body {
let mut body_vec = Vec::new();
body.read_to_end(&mut body_vec)?;
Body::from(body_vec)
} else {
Body::empty()
};
let content_type = content_type.unwrap_or("application/json"); let content_type = content_type.unwrap_or("application/json");
let mut request = Request::builder() let mut request = Request::builder()
@ -112,7 +101,7 @@ impl Client {
} }
} }
let request = request.body(body)?; let request = request.body(body.unwrap_or_default())?;
self.request(request).await self.request(request).await
} }
@ -173,7 +162,7 @@ impl Default for Client {
} }
#[cfg(all(feature = "client-trait", feature = "proxmox-async"))] #[cfg(all(feature = "client-trait", feature = "proxmox-async"))]
impl crate::HttpClient<Body> for Client { impl crate::HttpClient<Body, Body> for Client {
fn get( fn get(
&self, &self,
uri: &str, uri: &str,
@ -194,16 +183,13 @@ impl crate::HttpClient<Body> for Client {
proxmox_async::runtime::block_on(self.request(req)) proxmox_async::runtime::block_on(self.request(req))
} }
fn post<R>( fn post(
&self, &self,
uri: &str, uri: &str,
body: Option<R>, body: Option<Body>,
content_type: Option<&str>, content_type: Option<&str>,
extra_headers: Option<&HashMap<String, String>>, extra_headers: Option<&HashMap<String, String>>,
) -> Result<Response<Body>, Error> ) -> Result<Response<Body>, Error> {
where
R: Read,
{
proxmox_async::runtime::block_on(self.post(uri, body, content_type, extra_headers)) proxmox_async::runtime::block_on(self.post(uri, body, content_type, extra_headers))
} }
@ -213,7 +199,7 @@ impl crate::HttpClient<Body> for Client {
} }
#[cfg(all(feature = "client-trait", feature = "proxmox-async"))] #[cfg(all(feature = "client-trait", feature = "proxmox-async"))]
impl crate::HttpClient<String> for Client { impl crate::HttpClient<String, String> for Client {
fn get( fn get(
&self, &self,
uri: &str, uri: &str,
@ -236,17 +222,15 @@ impl crate::HttpClient<String> for Client {
}) })
} }
fn post<R>( fn post(
&self, &self,
uri: &str, uri: &str,
body: Option<R>, body: Option<String>,
content_type: Option<&str>, content_type: Option<&str>,
extra_headers: Option<&HashMap<String, String>>, extra_headers: Option<&HashMap<String, String>>,
) -> Result<Response<String>, Error> ) -> Result<Response<String>, Error> {
where
R: Read,
{
proxmox_async::runtime::block_on(async move { proxmox_async::runtime::block_on(async move {
let body = body.map(|s| Body::from(s.into_bytes()));
Self::convert_body_to_string(self.post(uri, body, content_type, extra_headers).await) Self::convert_body_to_string(self.post(uri, body, content_type, extra_headers).await)
.await .await
}) })

View File

@ -106,7 +106,7 @@ impl Client {
} }
} }
impl HttpClient<String> for Client { impl HttpClient<String, String> for Client {
fn get( fn get(
&self, &self,
uri: &str, uri: &str,
@ -118,21 +118,18 @@ impl HttpClient<String> for Client {
self.call(req).and_then(Self::convert_response_to_string) self.call(req).and_then(Self::convert_response_to_string)
} }
fn post<R>( fn post(
&self, &self,
uri: &str, uri: &str,
body: Option<R>, body: Option<String>,
content_type: Option<&str>, content_type: Option<&str>,
extra_headers: Option<&HashMap<String, String>>, extra_headers: Option<&HashMap<String, String>>,
) -> Result<Response<String>, Error> ) -> Result<Response<String>, Error> {
where
R: Read,
{
let req = self.agent()?.post(uri); let req = self.agent()?.post(uri);
let req = Self::add_headers(req, content_type, extra_headers); let req = Self::add_headers(req, content_type, extra_headers);
match body { match body {
Some(body) => self.send(req, body), Some(body) => self.send(req, body.as_bytes()),
None => self.call(req), None => self.call(req),
} }
.and_then(Self::convert_response_to_string) .and_then(Self::convert_response_to_string)
@ -157,7 +154,7 @@ impl HttpClient<String> for Client {
} }
} }
impl HttpClient<Vec<u8>> for Client { impl HttpClient<&[u8], Vec<u8>> for Client {
fn get( fn get(
&self, &self,
uri: &str, uri: &str,
@ -169,16 +166,13 @@ impl HttpClient<Vec<u8>> for Client {
self.call(req).and_then(Self::convert_response_to_vec) self.call(req).and_then(Self::convert_response_to_vec)
} }
fn post<R>( fn post(
&self, &self,
uri: &str, uri: &str,
body: Option<R>, body: Option<&[u8]>,
content_type: Option<&str>, content_type: Option<&str>,
extra_headers: Option<&HashMap<String, String>>, extra_headers: Option<&HashMap<String, String>>,
) -> Result<Response<Vec<u8>>, Error> ) -> Result<Response<Vec<u8>>, Error> {
where
R: Read,
{
let req = self.agent()?.post(uri); let req = self.agent()?.post(uri);
let req = Self::add_headers(req, content_type, extra_headers); let req = Self::add_headers(req, content_type, extra_headers);
@ -189,7 +183,7 @@ impl HttpClient<Vec<u8>> for Client {
.and_then(Self::convert_response_to_vec) .and_then(Self::convert_response_to_vec)
} }
fn request(&self, request: http::Request<Vec<u8>>) -> Result<Response<Vec<u8>>, Error> { fn request(&self, request: http::Request<&[u8]>) -> Result<Response<Vec<u8>>, Error> {
let mut req = self let mut req = self
.agent()? .agent()?
.request(request.method().as_str(), &request.uri().to_string()); .request(request.method().as_str(), &request.uri().to_string());
@ -203,12 +197,12 @@ impl HttpClient<Vec<u8>> for Client {
} }
} }
self.send(req, request.body().as_slice()) self.send(req, *request.body())
.and_then(Self::convert_response_to_vec) .and_then(Self::convert_response_to_vec)
} }
} }
impl HttpClient<Box<dyn Read>> for Client { impl HttpClient<Box<dyn Read>, Box<dyn Read>> for Client {
fn get( fn get(
&self, &self,
uri: &str, uri: &str,
@ -220,16 +214,13 @@ impl HttpClient<Box<dyn Read>> for Client {
self.call(req).and_then(Self::convert_response_to_reader) self.call(req).and_then(Self::convert_response_to_reader)
} }
fn post<R>( fn post(
&self, &self,
uri: &str, uri: &str,
body: Option<R>, body: Option<Box<dyn Read>>,
content_type: Option<&str>, content_type: Option<&str>,
extra_headers: Option<&HashMap<String, String>>, extra_headers: Option<&HashMap<String, String>>,
) -> Result<Response<Box<dyn Read>>, Error> ) -> Result<Response<Box<dyn Read>>, Error> {
where
R: Read,
{
let req = self.agent()?.post(uri); let req = self.agent()?.post(uri);
let req = Self::add_headers(req, content_type, extra_headers); let req = Self::add_headers(req, content_type, extra_headers);

View File

@ -1,24 +1,22 @@
use std::{collections::HashMap, io::Read}; use std::collections::HashMap;
use anyhow::Error; use anyhow::Error;
use http::{Request, Response}; use http::{Request, Response};
pub trait HttpClient<T> { pub trait HttpClient<RequestBody, ResponseBody> {
fn get( fn get(
&self, &self,
uri: &str, uri: &str,
extra_headers: Option<&HashMap<String, String>>, extra_headers: Option<&HashMap<String, String>>,
) -> Result<Response<T>, Error>; ) -> Result<Response<ResponseBody>, Error>;
fn post<R>( fn post(
&self, &self,
uri: &str, uri: &str,
body: Option<R>, body: Option<RequestBody>,
content_type: Option<&str>, content_type: Option<&str>,
extra_headers: Option<&HashMap<String, String>>, extra_headers: Option<&HashMap<String, String>>,
) -> Result<Response<T>, Error> ) -> Result<Response<ResponseBody>, Error>;
where
R: Read;
fn request(&self, request: Request<T>) -> Result<Response<T>, Error>; fn request(&self, request: Request<RequestBody>) -> Result<Response<ResponseBody>, Error>;
} }

View File

@ -18,7 +18,7 @@ lazy_static! {
const SHOP_URI: &str = "https://shop.proxmox.com/modules/servers/licensing/verify.php"; const SHOP_URI: &str = "https://shop.proxmox.com/modules/servers/licensing/verify.php";
/// (Re)-register a subscription key with the WHMCS server. /// (Re)-register a subscription key with the WHMCS server.
fn register_subscription<C: HttpClient<String>>( fn register_subscription<C: HttpClient<String, String>>(
key: &str, key: &str,
server_id: &str, server_id: &str,
checktime: i64, checktime: i64,
@ -39,7 +39,7 @@ fn register_subscription<C: HttpClient<String>>(
let query = json_object_to_query(params)?; let query = json_object_to_query(params)?;
let response = client.post( let response = client.post(
SHOP_URI, SHOP_URI,
Some(&mut query.as_bytes()), Some(query),
Some("application/x-www-form-urlencoded"), Some("application/x-www-form-urlencoded"),
None, None,
)?; )?;
@ -164,7 +164,7 @@ fn test_parse_register_response() -> Result<(), Error> {
/// Queries the WHMCS server to register/update the subscription key information, parsing the /// Queries the WHMCS server to register/update the subscription key information, parsing the
/// response into a [SubscriptionInfo]. /// response into a [SubscriptionInfo].
pub fn check_subscription<C: HttpClient<String>>( pub fn check_subscription<C: HttpClient<String, String>>(
key: String, key: String,
server_id: String, server_id: String,
product_url: String, product_url: String,