quoter/src/handlers/proxy.rs
Untone 0982dff45b
Some checks failed
deploy / deploy (push) Failing after 5s
heic-bypass
2024-11-13 12:03:32 +03:00

258 lines
13 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use actix_web::error::ErrorNotFound;
use actix_web::{error::ErrorInternalServerError, web, HttpRequest, HttpResponse, Result};
use log::{error, warn};
use crate::app_state::AppState;
use crate::handlers::serve_file::serve_file;
use crate::s3_utils::{check_file_exists, load_file_from_s3, upload_to_s3};
use crate::thumbnail::{find_closest_width, parse_file_path, thumbdata_save};
use crate::lookup::{find_file_by_pattern, get_mime_type};
/// Обработчик для скачивания файла и генерации миниатюры, если она недоступна.
pub async fn proxy_handler(
req: HttpRequest,
requested_res: web::Path<String>,
state: web::Data<AppState>,
) -> Result<HttpResponse, actix_web::Error> {
warn!("\t>>>\tGET {} [START]", requested_res);
let normalized_path = if requested_res.ends_with("/webp") {
warn!("Removing /webp suffix from path");
requested_res.replace("/webp", "")
} else {
requested_res.to_string()
};
// парсим GET запрос
let (base_filename, requested_width, extension) = parse_file_path(&normalized_path);
warn!("detected file extension: {}", extension);
warn!("base_filename: {}", base_filename);
warn!("requested width: {}", requested_width);
let ext = extension.as_str().to_lowercase();
warn!("normalized to lowercase: {}", ext);
let filekey = format!("{}.{}", base_filename, &ext);
warn!("filekey: {}", filekey);
let content_type = match get_mime_type(&ext) {
Some(mime) => mime.to_string(),
None => {
let mut redis = state.redis.clone();
match find_file_by_pattern(&mut redis, &base_filename).await {
Ok(Some(found_file)) => {
if let Some(found_ext) = found_file.split('.').last() {
get_mime_type(found_ext)
.unwrap_or("application/octet-stream")
.to_string()
} else {
"application/octet-stream".to_string()
}
}
_ => {
error!("unsupported file format");
return Err(ErrorInternalServerError("unsupported file format"));
}
}
}
};
warn!("content_type: {}", content_type);
let shout_id = match req.query_string().contains("s=") {
true => req.query_string().split("s=").collect::<Vec<&str>>().pop().unwrap_or(""),
false => ""
};
return match state.get_path(&filekey).await {
Ok(Some(stored_path)) => {
warn!("Found stored path in DB: {}", stored_path);
warn!("Checking Storj path - bucket: {}, path: {}", state.bucket, stored_path);
if check_file_exists(&state.storj_client, &state.bucket, &stored_path).await? {
warn!("File exists in Storj: {}", stored_path);
if content_type.starts_with("image") {
warn!("Processing image file with width: {}", requested_width);
if requested_width == 0 {
warn!("Serving original file without resizing");
serve_file(&stored_path, &state, shout_id).await
} else {
let closest: u32 = find_closest_width(requested_width as u32);
warn!("Calculated closest width: {} for requested: {}", closest, requested_width);
let thumb_filename = &format!("{}_{}.{}", base_filename, closest, ext);
warn!("Generated thumbnail filename: {}", thumb_filename);
// Проверяем, существует ли уже миниатюра в Storj
match check_file_exists(&state.storj_client, &state.bucket, thumb_filename).await {
Ok(true) => {
warn!("serve existed thumb file: {}", thumb_filename);
serve_file(thumb_filename, &state, shout_id).await
},
Ok(false) => {
// Миниатюра не существует, возвращаем оригинал и запускаем генерацию миниатюры
let original_file = serve_file(&stored_path, &state, shout_id).await?;
// Запускаем асинхронную задачу для генерации миниатюры
let state_clone = state.clone();
let stored_path_clone = stored_path.clone();
let filekey_clone = filekey.clone();
let content_type_clone = content_type.to_string();
actix_web::rt::spawn(async move {
if let Ok(filedata) = load_file_from_s3(&state_clone.storj_client, &state_clone.bucket, &stored_path_clone).await {
warn!("generate new thumb files: {}", stored_path_clone);
if let Err(e) = thumbdata_save(filedata, &state_clone, &filekey_clone, content_type_clone).await {
error!("Failed to generate thumbnail: {}", e);
}
}
});
Ok(original_file)
}
Err(e) => {
error!("ошибка при проверке существования миниатюры: {}", e);
Err(ErrorInternalServerError("failed to load thumbnail"))
}
}
}
} else {
warn!("File is not an image, proceeding with normal serving");
serve_file(&stored_path, &state, shout_id).await
}
} else {
warn!("Attempting to load from AWS - bucket: {}, path: {}", state.bucket, stored_path);
// Определяем тип медиа из content_type
let media_type = content_type.split("/").next().unwrap_or("image");
// Создаем варианты путей с обоими регистрами расширения
let paths_lower = vec![
stored_path.clone(),
// format!("production/{}", stored_path),
format!("production/{}/{}", media_type, stored_path)
];
// Создаем те же пути, но с оригинальным регистром расширения
let orig_ext = extension.as_str(); // оригинальное расширение
let orig_stored_path = format!("{}.{}", base_filename, orig_ext);
let paths_orig = vec![
orig_stored_path.clone(),
// format!("production/{}", orig_stored_path),
format!("production/{}/{}", media_type, orig_stored_path)
];
// Объединяем все пути для проверки
let all_paths = paths_lower.into_iter().chain(paths_orig.into_iter());
for path in all_paths {
warn!("Trying AWS path: {}", path);
match load_file_from_s3(&state.aws_client, &state.bucket, &path).await {
Ok(filedata) => {
warn!("Successfully loaded file from AWS, size: {} bytes", filedata.len());
warn!("Attempting to upload to Storj with key: {}", filekey);
if let Err(e) = upload_to_s3(
&state.storj_client,
&state.bucket,
&filekey,
filedata.clone(),
&content_type,
).await {
error!("Failed to upload to Storj: {} - Error: {}", filekey, e);
} else {
warn!("Successfully uploaded to Storj: {}", filekey);
}
return Ok(HttpResponse::Ok().content_type(content_type).body(filedata));
}
Err(err) => {
warn!("Failed to load from AWS path {}: {:?}", path, err);
continue;
}
}
}
error!("Failed to load from any AWS path for: {}", stored_path);
Err(ErrorInternalServerError("Failed to load file from AWS"))
}
}
Ok(None) => {
warn!("No stored path found in DB for: {}", filekey);
let ct_parts = content_type.split("/").collect::<Vec<&str>>();
// Создаем два варианта пути - с оригинальным расширением и с нижним регистром
let filepath_lower = format!("production/{}/{}.{}", ct_parts[0], base_filename, ext);
let filepath_orig = format!("production/{}/{}.{}", ct_parts[0], base_filename, extension);
warn!("Looking up files with paths: {} or {} in bucket: {}",
filepath_lower, filepath_orig, state.bucket);
// Проверяем существование файла с обоими вариантами расширения
let exists_in_aws_lower = check_file_exists(&state.aws_client, &state.bucket, &filepath_lower).await?;
let exists_in_aws_orig = check_file_exists(&state.aws_client, &state.bucket, &filepath_orig).await?;
let filepath = if exists_in_aws_orig {
filepath_orig
} else if exists_in_aws_lower {
filepath_lower
} else {
// Если файл не найден ни с одним из расширений, используем нижний регистр по умолчанию
filepath_lower
};
let exists_in_storj = check_file_exists(&state.storj_client, &state.bucket, &filepath).await?;
warn!("Checking existence in Storj: {}", exists_in_storj);
if exists_in_storj {
warn!("file {} exists in storj, try to generate thumbnails", filepath);
match load_file_from_s3(&state.aws_client, &state.bucket, &filepath).await {
Ok(filedata) => {
let _ = thumbdata_save(filedata.clone(), &state, &filekey, content_type.to_string()).await;
}
Err(e) => {
error!("cannot download {} from storj: {}", filekey, e);
return Err(ErrorInternalServerError(e));
}
}
} else {
warn!("file {} does not exist in storj", filepath);
}
let exists_in_aws = check_file_exists(&state.aws_client, &state.bucket, &filepath).await?;
warn!("Checking existence in AWS: {}", exists_in_aws);
if exists_in_aws {
warn!("File found in AWS, attempting to download: {}", filepath);
match load_file_from_s3(&state.aws_client, &state.bucket, &filepath).await {
Ok(filedata) => {
warn!("Successfully downloaded file from AWS, size: {} bytes", filedata.len());
let _ = thumbdata_save(filedata.clone(), &state, &filekey, content_type.to_string())
.await;
if let Err(e) = upload_to_s3(
&state.storj_client,
&state.bucket,
&filekey,
filedata.clone(),
&content_type,
)
.await {
warn!("cannot upload to storj: {}", e);
} else {
warn!("file {} uploaded to storj", filekey);
state.set_path(&filekey, &filepath).await;
}
Ok(HttpResponse::Ok().content_type(content_type).body(filedata))
},
Err(e) => {
error!("Failed to download from AWS: {} - Error: {}", filepath, e);
Err(ErrorInternalServerError(e))
},
}
} else {
error!("File not found in either Storj or AWS: {}", filepath);
Err(ErrorNotFound("file does not exist"))
}
},
Err(e) => {
error!("Database error while getting path: {} - Full error: {:?}", filekey, e);
Err(ErrorInternalServerError(e))
}
}
}