2023-11-13 23:07:05 +00:00
|
|
|
use std::{sync::Arc, time::Duration};
|
2022-05-27 23:27:44 +00:00
|
|
|
|
2023-11-13 23:07:05 +00:00
|
|
|
use crate::{
|
|
|
|
pict::{Details, Extension, Images, Upload, Uploads},
|
|
|
|
Error,
|
2020-12-08 21:59:55 +00:00
|
|
|
};
|
2023-11-13 23:07:05 +00:00
|
|
|
use actix_web::{body::BodyStream, http::StatusCode, web, HttpRequest, HttpResponse};
|
2021-03-10 02:24:04 +00:00
|
|
|
use awc::Client;
|
2020-12-08 21:59:55 +00:00
|
|
|
use url::Url;
|
|
|
|
|
|
|
|
pub(crate) static VALID_SIZES: &[u16] = &[80, 160, 320, 640, 1080, 2160];
|
|
|
|
|
|
|
|
pub(crate) struct Connection {
|
|
|
|
upstream: Url,
|
|
|
|
client: Client,
|
|
|
|
}
|
|
|
|
|
2021-09-19 19:32:04 +00:00
|
|
|
impl std::fmt::Debug for Connection {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
f.debug_struct("Connection")
|
2022-03-23 15:32:58 +00:00
|
|
|
.field("upstream", &self.upstream.as_str())
|
2021-09-19 19:32:04 +00:00
|
|
|
.field("client", &"Client")
|
|
|
|
.finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-08 21:59:55 +00:00
|
|
|
impl Connection {
|
|
|
|
pub(crate) fn new(upstream: Url, client: Client) -> Self {
|
|
|
|
Connection { upstream, client }
|
|
|
|
}
|
|
|
|
|
2022-05-27 23:27:44 +00:00
|
|
|
#[tracing::instrument(skip_all)]
|
2023-11-13 23:07:05 +00:00
|
|
|
pub(crate) async fn claim(&self, upload: Upload) -> Result<Images, Error> {
|
2022-05-27 23:27:44 +00:00
|
|
|
let mut attempts = 0;
|
|
|
|
const CLAIM_ATTEMPT_LIMIT: usize = 10;
|
|
|
|
loop {
|
|
|
|
match self.client.get(self.claim_url(&upload)).send().await {
|
|
|
|
Ok(mut res) => {
|
|
|
|
match res.status() {
|
|
|
|
StatusCode::OK => {
|
2023-11-13 23:07:05 +00:00
|
|
|
return res
|
|
|
|
.json::<Images>()
|
|
|
|
.await
|
|
|
|
.map_err(|_| UploadError::Json.into());
|
2022-05-27 23:27:44 +00:00
|
|
|
}
|
|
|
|
StatusCode::NO_CONTENT => {
|
|
|
|
// continue
|
|
|
|
}
|
|
|
|
_ => {
|
2023-11-13 23:07:05 +00:00
|
|
|
let images =
|
|
|
|
res.json::<Images>().await.map_err(|_| UploadError::Json)?;
|
|
|
|
|
|
|
|
let code = images.code().unwrap_or("uknown-error").to_string();
|
|
|
|
let msg = images.msg().to_string();
|
|
|
|
|
|
|
|
return Err(UploadError::UploadFailure(code, msg).into());
|
2022-05-27 23:27:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(_) => {
|
|
|
|
attempts += 1;
|
|
|
|
|
|
|
|
if attempts > CLAIM_ATTEMPT_LIMIT {
|
2023-11-13 23:07:05 +00:00
|
|
|
return Err(UploadError::Status.into());
|
2022-05-27 23:27:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
|
|
|
// continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-13 23:07:05 +00:00
|
|
|
#[tracing::instrument(skip_all)]
|
2020-12-08 21:59:55 +00:00
|
|
|
pub(crate) async fn thumbnail(
|
|
|
|
&self,
|
|
|
|
size: u16,
|
|
|
|
file: &str,
|
|
|
|
extension: Extension,
|
|
|
|
req: &HttpRequest,
|
2023-11-13 23:07:05 +00:00
|
|
|
) -> Result<HttpResponse, Error> {
|
2020-12-08 21:59:55 +00:00
|
|
|
if !VALID_SIZES.contains(&size) {
|
2023-11-13 23:07:05 +00:00
|
|
|
return Err(UploadError::Size(size).into());
|
2020-12-08 21:59:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
self.proxy(self.thumbnail_url(size, file, extension), req)
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
|
2023-11-13 23:07:05 +00:00
|
|
|
#[tracing::instrument(skip_all)]
|
|
|
|
pub(crate) async fn image(&self, file: &str, req: &HttpRequest) -> Result<HttpResponse, Error> {
|
2020-12-08 21:59:55 +00:00
|
|
|
self.proxy(self.image_url(file), req).await
|
|
|
|
}
|
|
|
|
|
2023-11-13 23:07:05 +00:00
|
|
|
#[tracing::instrument(skip_all)]
|
|
|
|
pub(crate) async fn details(&self, file: &str) -> Result<Details, Error> {
|
2023-11-13 21:38:00 +00:00
|
|
|
let mut response = self
|
|
|
|
.client
|
|
|
|
.get(self.details_url(file))
|
|
|
|
.send()
|
|
|
|
.await
|
2023-11-13 23:07:05 +00:00
|
|
|
.map_err(|e| UploadError::Request(Arc::from(e.to_string())))?;
|
2023-11-13 21:38:00 +00:00
|
|
|
|
|
|
|
if !response.status().is_success() {
|
2023-11-13 23:07:05 +00:00
|
|
|
return Err(UploadError::Status.into());
|
2023-11-13 21:38:00 +00:00
|
|
|
}
|
|
|
|
|
2023-11-13 23:07:05 +00:00
|
|
|
response.json().await.map_err(|_| UploadError::Json.into())
|
2023-11-13 21:38:00 +00:00
|
|
|
}
|
|
|
|
|
2023-11-13 23:07:05 +00:00
|
|
|
#[tracing::instrument(skip_all)]
|
2020-12-08 21:59:55 +00:00
|
|
|
pub(crate) async fn upload(
|
|
|
|
&self,
|
|
|
|
req: &HttpRequest,
|
|
|
|
body: web::Payload,
|
2023-11-13 23:07:05 +00:00
|
|
|
) -> Result<Uploads, Error> {
|
2020-12-08 21:59:55 +00:00
|
|
|
let client_request = self.client.request_from(self.upload_url(), req.head());
|
|
|
|
|
2021-06-19 20:26:16 +00:00
|
|
|
let mut client_request = if let Some(addr) = req.head().peer_addr {
|
2021-02-10 21:20:36 +00:00
|
|
|
client_request.append_header(("X-Forwarded-For", addr.to_string()))
|
2020-12-08 21:59:55 +00:00
|
|
|
} else {
|
|
|
|
client_request
|
|
|
|
};
|
|
|
|
|
2021-06-19 20:26:16 +00:00
|
|
|
client_request.headers_mut().remove("Accept-Encoding");
|
|
|
|
|
2020-12-08 21:59:55 +00:00
|
|
|
let mut res = client_request
|
|
|
|
.send_stream(body)
|
|
|
|
.await
|
2023-11-13 23:07:05 +00:00
|
|
|
.map_err(|e| UploadError::Request(Arc::from(e.to_string())))?;
|
2020-12-08 21:59:55 +00:00
|
|
|
|
2022-05-27 23:27:44 +00:00
|
|
|
let uploads = res.json::<Uploads>().await.map_err(|_| UploadError::Json)?;
|
2020-12-08 21:59:55 +00:00
|
|
|
|
2022-05-27 23:27:44 +00:00
|
|
|
Ok(uploads)
|
2020-12-08 21:59:55 +00:00
|
|
|
}
|
|
|
|
|
2023-11-13 23:07:05 +00:00
|
|
|
#[tracing::instrument(skip_all)]
|
|
|
|
pub(crate) async fn delete(&self, file: &str, token: &str) -> Result<(), Error> {
|
2020-12-08 21:59:55 +00:00
|
|
|
let res = self
|
|
|
|
.client
|
|
|
|
.delete(self.delete_url(file, token))
|
|
|
|
.send()
|
|
|
|
.await
|
2023-11-13 23:07:05 +00:00
|
|
|
.map_err(|e| UploadError::Request(Arc::from(e.to_string())))?;
|
2020-12-08 21:59:55 +00:00
|
|
|
|
|
|
|
if !res.status().is_success() {
|
2023-11-13 23:07:05 +00:00
|
|
|
return Err(UploadError::Status.into());
|
2020-12-08 21:59:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-05-27 23:27:44 +00:00
|
|
|
fn claim_url(&self, upload: &Upload) -> String {
|
|
|
|
let mut url = self.upstream.clone();
|
|
|
|
url.set_path("/image/backgrounded/claim");
|
|
|
|
url.set_query(Some(&format!("upload_id={}", upload.id())));
|
|
|
|
|
|
|
|
url.to_string()
|
|
|
|
}
|
|
|
|
|
2020-12-08 21:59:55 +00:00
|
|
|
fn upload_url(&self) -> String {
|
|
|
|
let mut url = self.upstream.clone();
|
2022-05-27 23:27:44 +00:00
|
|
|
url.set_path("/image/backgrounded");
|
2020-12-08 21:59:55 +00:00
|
|
|
|
|
|
|
url.to_string()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn thumbnail_url(&self, size: u16, file: &str, extension: Extension) -> String {
|
|
|
|
let mut url = self.upstream.clone();
|
2023-01-29 19:40:21 +00:00
|
|
|
url.set_path(&format!("/image/process.{extension}"));
|
|
|
|
url.set_query(Some(&format!("src={file}&resize={size}")));
|
2020-12-08 21:59:55 +00:00
|
|
|
|
|
|
|
url.to_string()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn image_url(&self, file: &str) -> String {
|
|
|
|
let mut url = self.upstream.clone();
|
2023-01-29 19:40:21 +00:00
|
|
|
url.set_path(&format!("/image/original/{file}"));
|
2020-12-08 21:59:55 +00:00
|
|
|
|
|
|
|
url.to_string()
|
|
|
|
}
|
|
|
|
|
2023-11-13 21:38:00 +00:00
|
|
|
fn details_url(&self, file: &str) -> String {
|
|
|
|
let mut url = self.upstream.clone();
|
|
|
|
url.set_path(&format!("/image/details/original/{file}"));
|
|
|
|
|
|
|
|
url.to_string()
|
|
|
|
}
|
|
|
|
|
2020-12-08 21:59:55 +00:00
|
|
|
fn delete_url(&self, file: &str, token: &str) -> String {
|
|
|
|
let mut url = self.upstream.clone();
|
2023-01-29 19:40:21 +00:00
|
|
|
url.set_path(&format!("/image/delete/{token}/{file}"));
|
2020-12-08 21:59:55 +00:00
|
|
|
|
|
|
|
url.to_string()
|
|
|
|
}
|
|
|
|
|
2023-11-13 23:07:05 +00:00
|
|
|
#[tracing::instrument(skip_all)]
|
|
|
|
async fn proxy(&self, url: String, req: &HttpRequest) -> Result<HttpResponse, Error> {
|
2020-12-08 21:59:55 +00:00
|
|
|
let client_request = self.client.request_from(url, req.head());
|
|
|
|
let client_request = if let Some(addr) = req.head().peer_addr {
|
2021-02-10 21:20:36 +00:00
|
|
|
client_request.append_header(("X-Forwarded-For", addr.to_string()))
|
2020-12-08 21:59:55 +00:00
|
|
|
} else {
|
|
|
|
client_request
|
|
|
|
};
|
|
|
|
|
2021-06-19 20:26:16 +00:00
|
|
|
let res = client_request
|
|
|
|
.no_decompress()
|
|
|
|
.send()
|
|
|
|
.await
|
2023-11-13 23:07:05 +00:00
|
|
|
.map_err(|e| UploadError::Request(Arc::from(e.to_string())))?;
|
2020-12-08 21:59:55 +00:00
|
|
|
|
|
|
|
let mut client_res = HttpResponse::build(res.status());
|
|
|
|
|
|
|
|
for (name, value) in res.headers().iter().filter(|(h, _)| *h != "connection") {
|
2021-02-10 21:20:36 +00:00
|
|
|
client_res.append_header((name.clone(), value.clone()));
|
2020-12-08 21:59:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(client_res.body(BodyStream::new(res)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, thiserror::Error)]
|
|
|
|
pub(crate) enum UploadError {
|
2023-11-13 23:07:05 +00:00
|
|
|
#[error("There was an error making the upstream request: {0}")]
|
|
|
|
Request(Arc<str>),
|
2020-12-08 21:59:55 +00:00
|
|
|
|
2023-11-13 23:07:05 +00:00
|
|
|
#[error("There was an error parsing the upstream response")]
|
2020-12-08 21:59:55 +00:00
|
|
|
Json,
|
|
|
|
|
|
|
|
#[error("Request returned bad HTTP status")]
|
|
|
|
Status,
|
|
|
|
|
2023-11-13 23:07:05 +00:00
|
|
|
#[error("Request failed with {0}: {1}")]
|
|
|
|
UploadFailure(String, String),
|
2020-12-08 21:59:55 +00:00
|
|
|
|
2023-11-13 23:07:05 +00:00
|
|
|
#[error("Requested size {0} is invalid")]
|
|
|
|
Size(u16),
|
2020-12-08 21:59:55 +00:00
|
|
|
}
|