use crate::error::Error;
use async_trait::async_trait;
use awc::Client;
use bytes::Bytes;
use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt};
use http::{
header::{HeaderName, HeaderValue},
uri::Scheme,
StatusCode, Uri,
};
use ipfs_api_prelude::{ApiRequest, Backend, BoxStream, TryFromUri};
use multipart::client::multipart;
use std::time::Duration;
const ACTIX_REQUEST_TIMEOUT: Duration = Duration::from_secs(90);
pub struct ActixBackend {
base: Uri,
client: Client,
credentials: Option<(String, String)>,
}
impl Default for ActixBackend {
fn default() -> Self {
Self::from_ipfs_config()
.unwrap_or_else(|| Self::from_host_and_port(Scheme::HTTP, "localhost", 5001).unwrap())
}
}
impl TryFromUri for ActixBackend {
fn build_with_base_uri(base: Uri) -> Self {
let client = Client::default();
ActixBackend {
base,
client,
credentials: None,
}
}
}
impl ActixBackend {
pub fn with_credentials<U, P>(self, username: U, password: P) -> Self
where
U: Into<String>,
P: Into<String>,
{
Self {
base: self.base,
client: self.client,
credentials: Some((username.into(), password.into())),
}
}
}
#[async_trait(?Send)]
impl Backend for ActixBackend {
type HttpRequest = awc::SendClientRequest;
type HttpResponse = awc::ClientResponse<actix_http::encoding::Decoder<actix_http::Payload>>;
type Error = Error;
fn with_credentials<U, P>(self, username: U, password: P) -> Self
where
U: Into<String>,
P: Into<String>,
{
(self as ActixBackend).with_credentials(username, password)
}
fn build_base_request<Req>(
&self,
req: Req,
form: Option<multipart::Form<'static>>,
) -> Result<Self::HttpRequest, Error>
where
Req: ApiRequest,
{
let url = req.absolute_url(&self.base)?;
let req = self.client.request(Req::METHOD, url);
let req = if let Some((username, password)) = &self.credentials {
req.basic_auth(username, password)
} else {
req
};
let req = if let Some(form) = form {
req.content_type(form.content_type())
.send_body(multipart::Body::from(form))
} else {
req.timeout(ACTIX_REQUEST_TIMEOUT).send()
};
Ok(req)
}
fn get_header(res: &Self::HttpResponse, key: HeaderName) -> Option<&HeaderValue> {
res.headers().get(key)
}
async fn request_raw<Req>(
&self,
req: Req,
form: Option<multipart::Form<'static>>,
) -> Result<(StatusCode, Bytes), Self::Error>
where
Req: ApiRequest,
{
let req = self.build_base_request(req, form)?;
let mut res = req.await?;
let status = res.status();
let body = res.body().await?;
Ok((status, body))
}
fn response_to_byte_stream(res: Self::HttpResponse) -> BoxStream<Bytes, Self::Error> {
let stream = res.err_into();
Box::new(stream)
}
fn request_stream<Res, F>(
&self,
req: Self::HttpRequest,
process: F,
) -> BoxStream<Res, Self::Error>
where
F: 'static + Send + Fn(Self::HttpResponse) -> BoxStream<Res, Self::Error>,
{
let stream = req
.err_into()
.map_ok(move |mut res| {
match res.status() {
StatusCode::OK => process(res).right_stream(),
_ => res
.body()
.map(|maybe_body| match maybe_body {
Ok(body) => Err(Self::process_error_from_body(body)),
Err(e) => Err(e.into()),
})
.into_stream()
.left_stream(),
}
})
.try_flatten_stream();
Box::new(stream)
}
}