pict-rs-aggregator/src/connection.rs
2020-12-08 16:03:18 -06:00

173 lines
4.6 KiB
Rust

use crate::pict::{Extension, Images};
use actix_web::{
body::BodyStream, client::Client, http::StatusCode, web, HttpRequest, HttpResponse,
ResponseError,
};
use url::Url;
pub(crate) static VALID_SIZES: &[u16] = &[80, 160, 320, 640, 1080, 2160];
pub(crate) struct Connection {
upstream: Url,
client: Client,
}
impl Connection {
pub(crate) fn new(upstream: Url, client: Client) -> Self {
Connection { upstream, client }
}
pub(crate) async fn thumbnail(
&self,
size: u16,
file: &str,
extension: Extension,
req: &HttpRequest,
) -> Result<HttpResponse, actix_web::Error> {
if !VALID_SIZES.contains(&size) {
return Err(SizeError(size).into());
}
self.proxy(self.thumbnail_url(size, file, extension), req)
.await
}
pub(crate) async fn image(
&self,
file: &str,
req: &HttpRequest,
) -> Result<HttpResponse, actix_web::Error> {
self.proxy(self.image_url(file), req).await
}
pub(crate) async fn upload(
&self,
req: &HttpRequest,
body: web::Payload,
) -> Result<Images, UploadError> {
let client_request = self.client.request_from(self.upload_url(), req.head());
let client_request = if let Some(addr) = req.head().peer_addr {
client_request.header("X-Forwareded-For", addr.to_string())
} else {
client_request
};
let mut res = client_request
.send_stream(body)
.await
.map_err(|_| UploadError::Request)?;
let images = res.json::<Images>().await.map_err(|_| UploadError::Json)?;
Ok(images)
}
pub(crate) async fn delete(&self, file: &str, token: &str) -> Result<(), UploadError> {
let res = self
.client
.delete(self.delete_url(file, token))
.send()
.await
.map_err(|_| UploadError::Request)?;
if !res.status().is_success() {
return Err(UploadError::Status);
}
Ok(())
}
fn upload_url(&self) -> String {
let mut url = self.upstream.clone();
url.set_path("/image");
url.to_string()
}
fn thumbnail_url(&self, size: u16, file: &str, extension: Extension) -> String {
let mut url = self.upstream.clone();
url.set_path(&format!("/image/process.{}", extension));
url.set_query(Some(&format!("src={}&thumbnail={}", file, size)));
url.to_string()
}
fn image_url(&self, file: &str) -> String {
let mut url = self.upstream.clone();
url.set_path(&format!("/image/original/{}", file,));
url.to_string()
}
fn delete_url(&self, file: &str, token: &str) -> String {
let mut url = self.upstream.clone();
url.set_path(&format!("/image/delete/{}/{}", token, file));
url.to_string()
}
async fn proxy(
&self,
url: String,
req: &HttpRequest,
) -> Result<HttpResponse, actix_web::Error> {
let client_request = self.client.request_from(url, req.head());
let client_request = if let Some(addr) = req.head().peer_addr {
client_request.header("X-Forwarded-For", addr.to_string())
} else {
client_request
};
let res = client_request.no_decompress().send().await?;
let mut client_res = HttpResponse::build(res.status());
for (name, value) in res.headers().iter().filter(|(h, _)| *h != "connection") {
client_res.header(name.clone(), value.clone());
}
Ok(client_res.body(BodyStream::new(res)))
}
}
#[derive(Clone, Debug, thiserror::Error)]
pub(crate) enum UploadError {
#[error("There was an error uploading the image")]
Request,
#[error("There was an error parsing the image response")]
Json,
#[error("Request returned bad HTTP status")]
Status,
}
impl ResponseError for UploadError {
fn status_code(&self) -> StatusCode {
StatusCode::INTERNAL_SERVER_ERROR
}
fn error_response(&self) -> HttpResponse {
HttpResponse::build(self.status_code())
.content_type(mime::TEXT_PLAIN.essence_str())
.body(self.to_string())
}
}
#[derive(Clone, Debug, thiserror::Error)]
#[error("The requested size is invalid, {0}")]
struct SizeError(u16);
impl ResponseError for SizeError {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
}
fn error_response(&self) -> HttpResponse {
HttpResponse::build(self.status_code())
.content_type(mime::TEXT_PLAIN.essence_str())
.body(self.to_string())
}
}