New upload api
This commit is contained in:
Generated
+39
-43
@@ -69,7 +69,6 @@ dependencies = [
|
|||||||
"matchit",
|
"matchit",
|
||||||
"memchr",
|
"memchr",
|
||||||
"mime",
|
"mime",
|
||||||
"multer",
|
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
@@ -287,15 +286,6 @@ version = "1.17.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
|
checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding_rs"
|
|
||||||
version = "0.8.35"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@@ -348,6 +338,23 @@ version = "0.3.32"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-sink"
|
||||||
|
version = "0.3.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-task"
|
name = "futures-task"
|
||||||
version = "0.3.32"
|
version = "0.3.32"
|
||||||
@@ -361,6 +368,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-macro",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"slab",
|
"slab",
|
||||||
@@ -542,8 +550,12 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"criterion",
|
"criterion",
|
||||||
|
"futures-util",
|
||||||
|
"http-body-util",
|
||||||
|
"parking_lot",
|
||||||
"rand",
|
"rand",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -603,23 +615,6 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "multer"
|
|
||||||
version = "3.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"encoding_rs",
|
|
||||||
"futures-util",
|
|
||||||
"http",
|
|
||||||
"httparse",
|
|
||||||
"memchr",
|
|
||||||
"mime",
|
|
||||||
"spin",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -750,9 +745,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.10.1"
|
version = "0.10.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
|
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chacha20",
|
"chacha20",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
@@ -960,12 +955,6 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "spin"
|
|
||||||
version = "0.9.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.117"
|
version = "2.0.117"
|
||||||
@@ -995,9 +984,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.52.3"
|
version = "1.53.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
|
checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -1021,6 +1010,19 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-util"
|
||||||
|
version = "0.7.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower"
|
name = "tower"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
@@ -1098,12 +1100,6 @@ version = "0.2.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "version_check"
|
|
||||||
version = "0.9.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "walkdir"
|
name = "walkdir"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
|||||||
+7
-3
@@ -4,10 +4,14 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { version = "0.8.9", features = ["multipart", ] }
|
axum = { version = "0.8.9" }
|
||||||
tokio = { version = "1.52.3", features = ["full"] }
|
tokio = { version = "1.53.1", features = ["full"] }
|
||||||
rand = "0.10.1"
|
tokio-util = { version = "0.7.19", features = ["io"] }
|
||||||
|
rand = "0.10.2"
|
||||||
tower-http = {version = "0.7.0", features = ["cors", "trace"]}
|
tower-http = {version = "0.7.0", features = ["cors", "trace"]}
|
||||||
|
parking_lot = "0.12.5"
|
||||||
|
http-body-util = "0.1.3"
|
||||||
|
futures-util = "0.3.32"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "0.8.2", features = ["html_reports"] }
|
criterion = { version = "0.8.2", features = ["html_reports"] }
|
||||||
|
|||||||
+49
-39
@@ -1,15 +1,19 @@
|
|||||||
use axum::extract::{DefaultBodyLimit, Path};
|
use axum::body::Body;
|
||||||
use axum::http::StatusCode;
|
use axum::extract::{DefaultBodyLimit, Path, State};
|
||||||
use axum::http::Method;
|
use axum::http::Method;
|
||||||
|
use axum::http::StatusCode;
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
use axum::routing::post;
|
use axum::routing::post;
|
||||||
use axum::{extract::Multipart, routing::get, Router};
|
use axum::{routing::get, Router};
|
||||||
|
use futures_util::TryStreamExt;
|
||||||
|
use http_body_util::BodyExt;
|
||||||
|
use parking_lot::RwLock;
|
||||||
use rand::distr::{Alphanumeric, SampleString};
|
use rand::distr::{Alphanumeric, SampleString};
|
||||||
use rand::rng;
|
use rand::rng;
|
||||||
use std::os::unix::prelude::MetadataExt;
|
use std::os::unix::prelude::MetadataExt;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tokio::fs::File;
|
use std::sync::Arc;
|
||||||
|
use tokio_util::io::StreamReader;
|
||||||
use tower_http::cors::{Any, CorsLayer};
|
use tower_http::cors::{Any, CorsLayer};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@@ -17,58 +21,66 @@ async fn main() {
|
|||||||
ensure_work_dir();
|
ensure_work_dir();
|
||||||
let cors = CorsLayer::new()
|
let cors = CorsLayer::new()
|
||||||
.allow_origin(Any)
|
.allow_origin(Any)
|
||||||
.allow_methods(vec![Method::GET, Method::POST]).allow_headers(Any);
|
.allow_methods(vec![Method::GET, Method::POST])
|
||||||
|
.allow_headers(Any);
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/generate", get(generate_id))
|
.route("/generate", get(generate_id))
|
||||||
.route("/upload/{id}", post(upload_file))
|
.route("/upload/{id}", post(upload_file))
|
||||||
.route("/download/{id}", get(download_file))
|
.route("/download/{id}", get(download_file))
|
||||||
.route("/{id}/version", get(version))
|
.route("/{id}/version", get(version))
|
||||||
.layer(DefaultBodyLimit::max(50 * 1024 * 1024))
|
.layer(DefaultBodyLimit::max(5 * 1024 * 1024))
|
||||||
.layer(cors);
|
.layer(cors)
|
||||||
|
.with_state(Arc::new(RwLock::new(ApiState {
|
||||||
|
work_directory: get_dir(),
|
||||||
|
temp_directory: PathBuf::from("/tmp"),
|
||||||
|
})));
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:8089").await.unwrap();
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:8089").await.unwrap();
|
||||||
axum::serve(listener, app).await.unwrap();
|
axum::serve(listener, app).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ApiState {
|
||||||
|
work_directory: PathBuf,
|
||||||
|
temp_directory: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
async fn generate_id() -> String {
|
async fn generate_id() -> String {
|
||||||
Alphanumeric.sample_string(&mut rng(), 24).to_uppercase()
|
Alphanumeric.sample_string(&mut rng(), 24).to_uppercase()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn upload_file(
|
async fn upload_file(
|
||||||
|
State(state): State<Arc<RwLock<ApiState>>>,
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
mut multipart: Multipart,
|
body: Body,
|
||||||
) -> Result<impl IntoResponse, StatusCode> {
|
) -> Result<impl IntoResponse, StatusCode> {
|
||||||
if !validate_id(id.as_str()) {
|
if !validate_id(id.as_str()) {
|
||||||
return Err(StatusCode::FORBIDDEN);
|
return Err(StatusCode::FORBIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
let file = multipart.next_field().await;
|
let temp_path;
|
||||||
|
let store_path;
|
||||||
if let Ok(Some(file)) = file {
|
{
|
||||||
let data = file
|
let state = state.read();
|
||||||
.bytes()
|
temp_path = state.temp_directory.join(id.as_str());
|
||||||
.await
|
store_path = state.work_directory.join(id.as_str());
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
|
||||||
|
|
||||||
let mut path = get_dir();
|
|
||||||
path.push(id.clone());
|
|
||||||
if !path.exists() {
|
|
||||||
File::create(path.clone())
|
|
||||||
.await
|
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
tokio::fs::write(path.clone(), data)
|
|
||||||
.await
|
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
|
||||||
|
|
||||||
let file = File::open(path).await.unwrap();
|
|
||||||
Ok(file.metadata().await.unwrap().mtime().to_string())
|
|
||||||
} else {
|
|
||||||
Err(StatusCode::BAD_REQUEST)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut temp_file = tokio::fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create_new(true)
|
||||||
|
.open(&temp_path)
|
||||||
|
.await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
let stream = TryStreamExt::map_err(body.into_data_stream(), std::io::Error::other);
|
||||||
|
let mut reader = StreamReader::new(stream);
|
||||||
|
tokio::io::copy(&mut reader, &mut temp_file).await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
tokio::fs::rename(temp_path, store_path).await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn download_file(Path(id): Path<String>) -> Result<Vec<u8>, StatusCode> {
|
async fn download_file(Path(id): Path<String>) -> Result<Vec<u8>, StatusCode> {
|
||||||
@@ -91,7 +103,8 @@ async fn version(Path(id): Path<String>) -> Result<impl IntoResponse, StatusCode
|
|||||||
}
|
}
|
||||||
|
|
||||||
let dir = get_dir().join(id);
|
let dir = get_dir().join(id);
|
||||||
let meta = tokio::fs::metadata(dir).await
|
let meta = tokio::fs::metadata(dir)
|
||||||
|
.await
|
||||||
.map_err(|_| StatusCode::BAD_REQUEST)?;
|
.map_err(|_| StatusCode::BAD_REQUEST)?;
|
||||||
|
|
||||||
Ok(meta.mtime().to_string())
|
Ok(meta.mtime().to_string())
|
||||||
@@ -111,8 +124,5 @@ fn get_dir() -> PathBuf {
|
|||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn validate_id(id: &str) -> bool {
|
fn validate_id(id: &str) -> bool {
|
||||||
id.len() == 24
|
id.len() == 24 && id.bytes().all(|b| matches!(b, b'A'..=b'Z' | b'0'..=b'9'))
|
||||||
&& id
|
|
||||||
.bytes()
|
|
||||||
.all(|b| matches!(b, b'A'..=b'Z' | b'0'..=b'9'))
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user