use std::future::Future;
pub trait HttpClient: Send + Sync + Sized {
fn send(
&self,
request: HttpRequest,
) -> impl Future<Output = Result<HttpResponse, Box<dyn std::error::Error + Send + Sync>>> + Send;
}
#[derive(Debug, Clone)]
pub struct HttpRequest {
pub method: http::Method,
pub url: String,
pub headers: Vec<(String, String)>,
pub body: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct HttpResponse {
pub status: u16,
pub headers: Vec<(String, String)>,
pub body: Vec<u8>,
}
#[cfg(feature = "reqwest-client")]
mod reqwest_impl {
use super::{Future, HttpClient, HttpRequest, HttpResponse};
#[derive(Debug, Clone)]
pub struct ReqwestClient(pub reqwest::Client);
impl Default for ReqwestClient {
fn default() -> Self {
Self(reqwest::Client::new())
}
}
impl HttpClient for ReqwestClient {
fn send(
&self,
request: HttpRequest,
) -> impl Future<Output = Result<HttpResponse, Box<dyn std::error::Error + Send + Sync>>> + Send
{
let client = self.0.clone();
async move {
let method = reqwest::Method::from_bytes(request.method.as_str().as_bytes())?;
let mut req = client.request(method, &request.url).body(request.body);
for (name, value) in &request.headers {
req = req.header(name, value);
}
let resp = req.send().await?;
let status = resp.status().as_u16();
let headers = resp
.headers()
.iter()
.map(|(k, v)| (k.as_str().to_owned(), v.to_str().unwrap_or("").to_owned()))
.collect();
let body = resp.bytes().await?.to_vec();
Ok(HttpResponse {
status,
headers,
body,
})
}
}
}
}
#[cfg(feature = "reqwest-client")]
pub use reqwest_impl::ReqwestClient;
#[cfg(test)]
mod tests {
use super::*;
struct EchoClient;
impl HttpClient for EchoClient {
async fn send(
&self,
request: HttpRequest,
) -> Result<HttpResponse, Box<dyn std::error::Error + Send + Sync>> {
Ok(HttpResponse {
status: 200,
headers: request.headers,
body: request.body,
})
}
}
#[test]
fn http_request_round_trip_fields() {
let req = HttpRequest {
method: http::Method::POST,
url: "https://example.test/saml/artifact".into(),
headers: vec![("Content-Type".into(), "text/xml".into())],
body: b"<soap/>".to_vec(),
};
assert_eq!(req.method, http::Method::POST);
assert_eq!(req.headers.len(), 1);
assert_eq!(req.body, b"<soap/>");
}
#[tokio::test]
async fn echo_client_runs_in_executor() {
let client = EchoClient;
let req = HttpRequest {
method: http::Method::GET,
url: "x".into(),
headers: vec![],
body: b"hello".to_vec(),
};
let resp = client.send(req).await.expect("echo");
assert_eq!(resp.status, 200);
assert_eq!(resp.body, b"hello");
}
}