Compare commits
4 commits
Author | SHA1 | Date | |
---|---|---|---|
|
3d00a5c888 | ||
|
2f5d8764b6 | ||
|
d1bee14213 | ||
|
319a407ae3 |
11 changed files with 539 additions and 447 deletions
875
Cargo.lock
generated
875
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -7,21 +7,21 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
futures = "0.3"
|
||||
tokio = { version = "1.35", features = ["full", "tracing"] }
|
||||
tokio = { version = "1.38", features = ["full", "tracing"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
tokio-stream = "0.1"
|
||||
tokio-util = "0.7"
|
||||
axum = { version = "0.7" }
|
||||
reqwest = { version = "0.11", features = ["stream"] }
|
||||
reqwest = { version = "0.12", features = ["stream"] }
|
||||
anyhow = "1.0"
|
||||
sentry = "0.32"
|
||||
sentry = "0.34"
|
||||
dotenvy = "0.15"
|
||||
axum-macros = "0.4"
|
||||
magic-crypt = "3.1"
|
||||
async-trait = "0.1"
|
||||
serde = "1.0"
|
||||
serde_yaml = "0.9"
|
||||
base64 = "0.21"
|
||||
base64 = "0.22"
|
||||
regex = "1.10"
|
||||
serde_regex = "1.1"
|
||||
|
|
19
Dockerfile
Normal file
19
Dockerfile
Normal file
|
@ -0,0 +1,19 @@
|
|||
FROM rust:1.79 AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY src/ src/
|
||||
COPY Cargo.lock .
|
||||
COPY Cargo.toml .
|
||||
|
||||
RUN cargo build --release
|
||||
|
||||
|
||||
FROM rust:1.79 AS runner
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/target/release/imgproxy-rs .
|
||||
|
||||
RUN chmod +x imgproxy-rs
|
||||
|
||||
CMD [ "./imgproxy-rs" ]
|
|
@ -1,13 +1,13 @@
|
|||
use std::io::Write;
|
||||
|
||||
use base64::engine::general_purpose;
|
||||
use magic_crypt::{new_magic_crypt, MagicCryptTrait};
|
||||
use magic_crypt::{MagicCrypt256, MagicCryptTrait};
|
||||
|
||||
pub fn decryt<I>(value: I, secret_key: String) -> anyhow::Result<String>
|
||||
where
|
||||
I: AsRef<str>,
|
||||
{
|
||||
let mc = new_magic_crypt!(&secret_key, 256);
|
||||
let mc = MagicCrypt256::new(&secret_key, None::<&str>);
|
||||
Ok(mc.decrypt_base64_to_string(value)?)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use axum::{routing::get, Router};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -32,9 +31,9 @@ async fn main() -> anyhow::Result<()> {
|
|||
None => get(routes::handle_unsecure),
|
||||
},
|
||||
)
|
||||
.with_state(Arc::new(Mutex::new(app_state)));
|
||||
.with_state(Arc::new(app_state));
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3100").await?;
|
||||
axum::serve(listener, app).await?;
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -6,6 +6,7 @@ use axum::{
|
|||
response::IntoResponse,
|
||||
};
|
||||
use futures::TryStreamExt;
|
||||
use reqwest::header;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_stream::Stream;
|
||||
use tokio_util::bytes::Bytes;
|
||||
|
@ -14,41 +15,36 @@ use crate::{crypt, state::AppState};
|
|||
|
||||
pub async fn handle_secure(
|
||||
Path(data): Path<String>,
|
||||
state: State<Arc<Mutex<AppState>>>,
|
||||
state: State<Arc<AppState>>,
|
||||
) -> Result<impl IntoResponse, MyAnyhow> {
|
||||
let app_state = state.lock().await;
|
||||
let decrypt = crypt::decryt(data, app_state.secret_key.clone().unwrap())?;
|
||||
std::mem::drop(app_state);
|
||||
let decrypt = crypt::decryt(data, state.secret_key.clone().unwrap())?;
|
||||
|
||||
let bytes = handle(decrypt, state).await?;
|
||||
Ok(axum::body::Body::from_stream(bytes))
|
||||
Ok(([(header::CACHE_CONTROL, "public, max-age=31919000")], axum::body::Body::from_stream(bytes)))
|
||||
}
|
||||
|
||||
pub async fn handle_unsecure(
|
||||
Path(src): Path<String>,
|
||||
state: State<Arc<Mutex<AppState>>>,
|
||||
state: State<Arc<AppState>>,
|
||||
) -> Result<impl IntoResponse, MyAnyhow> {
|
||||
let bytes = handle(src, state).await?;
|
||||
|
||||
Ok(axum::body::Body::from_stream(bytes))
|
||||
Ok(([(header::CACHE_CONTROL, "public, max-age=31919000")], axum::body::Body::from_stream(bytes)))
|
||||
}
|
||||
|
||||
async fn handle(
|
||||
src: String,
|
||||
state: State<Arc<Mutex<AppState>>>,
|
||||
state: State<Arc<AppState>>,
|
||||
) -> anyhow::Result<impl Stream<Item = Result<Bytes, anyhow::Error>> + Send> {
|
||||
let mut app_state = state.lock().await;
|
||||
let mut storage_pool = state.storage_pool.lock().await;
|
||||
|
||||
match app_state.storage_pool.retrieve(src.clone()).await {
|
||||
match storage_pool.retrieve(src.clone()).await {
|
||||
Some(stream) => Ok(stream),
|
||||
None => {
|
||||
let save_stream = fetch(src.clone()).await?;
|
||||
app_state
|
||||
.storage_pool
|
||||
.save(src.clone(), save_stream)
|
||||
.await?;
|
||||
storage_pool.save(src.clone(), save_stream).await?;
|
||||
|
||||
Ok(app_state.storage_pool.retrieve(src).await.unwrap())
|
||||
Ok(storage_pool.retrieve(src).await.unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::storages::StoragePool;
|
||||
|
||||
pub struct AppState {
|
||||
pub secret_key: Option<String>,
|
||||
pub storage_pool: StoragePool,
|
||||
pub storage_pool: Mutex<StoragePool>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new(storage_pool: StoragePool, secret_key: Option<String>) -> Self {
|
||||
Self {
|
||||
secret_key,
|
||||
storage_pool,
|
||||
storage_pool: Mutex::new(storage_pool),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,10 @@ impl Storage for DiskStorage {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn tick(&mut self) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn eligible(&self, _src: String) -> bool {
|
||||
true
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use std::{collections::HashMap, pin::Pin};
|
||||
|
||||
use anyhow::Ok;
|
||||
use async_trait::async_trait;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use std::{collections::HashMap, pin::Pin};
|
||||
use tokio::time::Instant;
|
||||
use tokio_stream::{Stream, StreamExt};
|
||||
use tokio_util::bytes::Bytes;
|
||||
|
||||
|
@ -9,9 +10,14 @@ use crate::config::MemoryStorageConfig;
|
|||
|
||||
use super::Storage;
|
||||
|
||||
struct MemoryStorageItem {
|
||||
data: Vec<u8>,
|
||||
added_at: Instant,
|
||||
}
|
||||
|
||||
pub struct MemoryStorage {
|
||||
config: MemoryStorageConfig,
|
||||
items: HashMap<String, Vec<u8>>,
|
||||
items: HashMap<String, MemoryStorageItem>,
|
||||
}
|
||||
|
||||
impl MemoryStorage {
|
||||
|
@ -33,6 +39,14 @@ impl Storage for MemoryStorage {
|
|||
true
|
||||
}
|
||||
|
||||
async fn tick(&mut self) -> anyhow::Result<()> {
|
||||
let ttl = self.config.ttl;
|
||||
self.items
|
||||
.retain(|_, elt| elt.added_at.elapsed().as_secs() < ttl as u64);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete(&mut self, key: String) -> anyhow::Result<()> {
|
||||
self.items
|
||||
.remove(&key)
|
||||
|
@ -46,7 +60,7 @@ impl Storage for MemoryStorage {
|
|||
) -> Option<Pin<Box<dyn Stream<Item = Result<Bytes, anyhow::Error>> + Send>>> {
|
||||
match self.items.get(&key) {
|
||||
Some(item) => {
|
||||
let bytes = tokio_util::bytes::Bytes::from(item.clone());
|
||||
let bytes = tokio_util::bytes::Bytes::from(item.data.clone());
|
||||
let stream = futures::stream::iter([Ok(bytes)]);
|
||||
|
||||
Some(Box::pin(stream))
|
||||
|
@ -61,13 +75,22 @@ impl Storage for MemoryStorage {
|
|||
mut stream: Pin<Box<dyn Stream<Item = Result<Bytes, anyhow::Error>> + Send>>,
|
||||
) -> anyhow::Result<()> {
|
||||
if !self.items.contains_key(&key) {
|
||||
self.items.insert(key.clone(), Vec::new());
|
||||
self.items.insert(
|
||||
key.clone(),
|
||||
MemoryStorageItem {
|
||||
data: Vec::new(),
|
||||
added_at: Instant::now(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
println!("save {}", key);
|
||||
|
||||
while let Some(chunk) = stream.next().await {
|
||||
self.items
|
||||
.get_mut(&key)
|
||||
.unwrap()
|
||||
.data
|
||||
.append(&mut chunk?.to_vec());
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,10 @@ impl Storage for MixedStorage {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn tick(&mut self) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn eligible(&self, _src: String) -> bool {
|
||||
true
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ pub trait Storage {
|
|||
async fn init(&mut self) -> anyhow::Result<()>;
|
||||
async fn eligible(&self, src: String) -> bool;
|
||||
async fn delete(&mut self, key: String) -> anyhow::Result<()>;
|
||||
async fn tick(&mut self) -> anyhow::Result<()>;
|
||||
async fn retrieve(
|
||||
&self,
|
||||
key: String,
|
||||
|
@ -44,6 +45,7 @@ impl StoragePool {
|
|||
|
||||
pub async fn init(&mut self) -> anyhow::Result<()> {
|
||||
for item in &mut self.storages {
|
||||
println!("init storage");
|
||||
item.init().await?;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue