salvo-file-handling
Handle file uploads (single/multiple), downloads, and multipart forms. Use for file management, image uploads, and content delivery.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Handle file uploads (single/multiple), downloads, and multipart forms. Use for file management, image uploads, and content delivery.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
Guide for using RBatis v4 Rust ORM framework. Use when implementing database CRUD operations, writing dynamic SQL with py_sql or html_sql macros, configuring database connections, managing transactions, implementing interceptors, syncing table structures, or integrating RBatis into Genies microservices. Also use when the user asks about RBatis usage patterns, query building, or database operations in Rust.
Genies 前后端接口规范。Use when designing API contracts, defining field naming conventions, date/time formats, response models, pagination, error handling, or ID strategies between Genies backend and frontend (Web/Android/OHOS).
Guide for using flyway-rs database migration framework with RBatis. Use when implementing database schema migrations, managing SQL changelog files, configuring migration runners, handling multi-database migrations, or integrating database versioning into Genies microservices.
Guide for developing Rust microservices using Genies framework following Java DDD layering principles. Use when creating new microservices, designing aggregate roots, implementing domain events, organizing service layers, setting up Flyway migrations, or structuring a DDD-based Genies project.
Genies framework unified skill hub. Use when you need to find the right skill for any Genies framework task, including authentication, authorization, caching, configuration, database, DDD microservices, Dapr messaging, macros, K8s deployment, testing, API conventions, gateway proxy, or Salvo web framework features. Also use when the user asks about Genies framework capabilities, asks which skill to use, or wants a quick overview of available Genies skills.
Guide for using the Genies Rust microservice framework with DDD and Dapr. Use when developing with Genies, creating aggregates, domain events, Dapr subscriptions, Casbin field-level permissions, configuration management, or when the user asks about Genies framework usage patterns.
| name | salvo-file-handling |
| description | Handle file uploads (single/multiple), downloads, and multipart forms. Use for file management, image uploads, and content delivery. |
| version | 0.89.3 |
| tags | ["data","file-upload","multipart","download"] |
This skill helps handle file uploads and downloads in Salvo applications.
[dependencies]
salvo = "0.89.3"
tokio = { version = "1", features = ["fs"] }
use std::path::Path;
use salvo::prelude::*;
#[handler]
async fn index(res: &mut Response) {
res.render(Text::Html(r#"
<!DOCTYPE html>
<html>
<body>
<h1>Upload File</h1>
<form action="/" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" value="Upload" />
</form>
</body>
</html>
"#));
}
#[handler]
async fn upload(req: &mut Request, res: &mut Response) {
let file = req.file("file").await;
if let Some(file) = file {
let dest = format!("temp/{}", file.name().unwrap_or("file"));
println!("Uploading to: {}", dest);
if let Err(e) = std::fs::copy(file.path(), Path::new(&dest)) {
res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
res.render(Text::Plain(format!("Upload failed: {e}")));
} else {
res.render(Text::Plain(format!("File uploaded to {dest}")));
}
} else {
res.status_code(StatusCode::BAD_REQUEST);
res.render(Text::Plain("No file in request"));
}
}
#[tokio::main]
async fn main() {
std::fs::create_dir_all("temp").unwrap();
let router = Router::new().get(index).post(upload);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
use std::path::Path;
use salvo::prelude::*;
#[handler]
async fn upload_files(req: &mut Request, res: &mut Response) {
let files = req.files("files").await;
if let Some(files) = files {
let mut uploaded = Vec::with_capacity(files.len());
for file in files {
let dest = format!("temp/{}", file.name().unwrap_or("file"));
if let Err(e) = std::fs::copy(file.path(), Path::new(&dest)) {
res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
res.render(Text::Plain(format!("Failed to upload: {e}")));
return;
} else {
uploaded.push(dest);
}
}
res.render(Text::Plain(format!(
"Files uploaded:\n\n{}",
uploaded.join("\n")
)));
} else {
res.status_code(StatusCode::BAD_REQUEST);
res.render(Text::Plain("No files in request"));
}
}
#[handler]
async fn upload_image(req: &mut Request, res: &mut Response) {
let file = req.file("image").await;
let Some(file) = file else {
res.status_code(StatusCode::BAD_REQUEST);
res.render("No file provided");
return;
};
// Validate content type
let content_type = file.content_type().unwrap_or_default();
let allowed_types = ["image/jpeg", "image/png", "image/gif", "image/webp"];
if !allowed_types.contains(&content_type) {
res.status_code(StatusCode::BAD_REQUEST);
res.render(format!(
"Invalid file type: {}. Allowed: {}",
content_type,
allowed_types.join(", ")
));
return;
}
// Validate file size (5MB max)
let max_size = 5 * 1024 * 1024;
if file.size() > max_size {
res.status_code(StatusCode::BAD_REQUEST);
res.render(format!(
"File too large: {} bytes. Max: {} bytes",
file.size(),
max_size
));
return;
}
// Generate unique filename
let filename = file.name().unwrap_or("unnamed");
let extension = filename.rsplit('.').next().unwrap_or("");
let unique_name = format!(
"{}_{}.{}",
uuid::Uuid::new_v4(),
chrono::Utc::now().timestamp(),
extension
);
let dest = format!("uploads/{}", unique_name);
if let Err(e) = std::fs::copy(file.path(), &dest) {
res.status_code(StatusCode::INTERNAL_SERVER_ERROR);
res.render(format!("Upload failed: {e}"));
} else {
res.render(Json(serde_json::json!({
"filename": unique_name,
"original_name": filename,
"size": file.size(),
"content_type": content_type
})));
}
}
use salvo::prelude::*;
#[tokio::main]
async fn main() {
let router = Router::new()
.push(
Router::with_path("upload")
.hoop(max_size(10 * 1024 * 1024)) // 10MB limit
.post(upload_handler)
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
use salvo::fs::NamedFile;
use salvo::prelude::*;
#[handler]
async fn download(req: &mut Request, res: &mut Response) {
let filename: String = req.param("filename").unwrap();
let filepath = format!("files/{}", filename);
match NamedFile::builder(&filepath)
.attached_name(&filename)
.send(req.headers(), res)
.await
{
Ok(_) => {}
Err(_) => {
res.status_code(StatusCode::NOT_FOUND);
res.render("File not found");
}
}
}
#[handler]
async fn view_pdf(req: &mut Request, res: &mut Response) {
match NamedFile::builder("documents/report.pdf")
.content_type("application/pdf")
.send(req.headers(), res)
.await
{
Ok(_) => {}
Err(_) => {
res.status_code(StatusCode::NOT_FOUND);
}
}
}
#[handler]
async fn protected_download(
req: &mut Request,
depot: &mut Depot,
res: &mut Response,
) {
let user = depot.get::<User>("user");
if user.is_none() {
res.status_code(StatusCode::UNAUTHORIZED);
res.render("Please login");
return;
}
let filename: String = req.param("filename").unwrap();
// Validate filename to prevent directory traversal
if filename.contains("..") || filename.contains('/') || filename.contains('\\') {
res.status_code(StatusCode::BAD_REQUEST);
res.render("Invalid filename");
return;
}
let filepath = format!("private/{}", filename);
match NamedFile::builder(&filepath)
.attached_name(&filename)
.send(req.headers(), res)
.await
{
Ok(_) => {}
Err(_) => {
res.status_code(StatusCode::NOT_FOUND);
res.render("File not found");
}
}
}
use salvo::prelude::*;
#[handler]
async fn update_profile(req: &mut Request, res: &mut Response) {
// Parse form fields
let name = req.form::<String>("name").await.unwrap_or_default();
let bio = req.form::<String>("bio").await.unwrap_or_default();
// Handle file upload
let avatar_path = if let Some(file) = req.file("avatar").await {
let filename = format!("avatar_{}.jpg", uuid::Uuid::new_v4());
let dest = format!("uploads/avatars/{}", filename);
std::fs::copy(file.path(), &dest).ok();
Some(filename)
} else {
None
};
res.render(Json(serde_json::json!({
"name": name,
"bio": bio,
"avatar": avatar_path
})));
}