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 { 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 { self.proxy(self.image_url(file), req).await } pub(crate) async fn upload( &self, req: &HttpRequest, body: web::Payload, ) -> Result { 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::().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 { 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()) } }