静态文件 #
静态文件概述 #
Actix Web 通过 actix-files crate 提供静态文件服务功能。
text
┌─────────────────────────────────────────────────────────────┐
│ 静态文件服务 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 请求: GET /static/css/style.css │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ actix-files │ │
│ │ 1. 解析请求路径 │ │
│ │ 2. 检查文件是否存在 │ │
│ │ 3. 读取文件内容 │ │
│ │ 4. 设置 Content-Type │ │
│ │ 5. 返回响应 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 响应: Content-Type: text/css │
│ Body: 文件内容 │
│ │
└─────────────────────────────────────────────────────────────┘
添加依赖 #
toml
[dependencies]
actix-web = "4"
actix-files = "0.6"
基本静态文件服务 #
服务静态目录 #
rust
use actix_files as fs;
use actix_web::{App, HttpServer};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(fs::Files::new("/static", "./static").show_files_listing())
})
.bind("127.0.0.1:8080")?
.run()
.await
}
服务单个文件 #
rust
use actix_files::NamedFile;
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
async fn index() -> impl Responder {
NamedFile::open("static/index.html").unwrap()
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/", web::get().to(index))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
配置选项 #
禁用目录列表 #
rust
App::new()
.service(
fs::Files::new("/static", "./static")
.show_files_listing()
)
默认文件 #
rust
App::new()
.service(
fs::Files::new("/", "./static")
.index_file("index.html")
)
重定向到斜杠 #
rust
App::new()
.service(
fs::Files::new("/static", "./static")
.redirect_to_slash_directory()
)
自定义处理器 #
rust
use actix_web::dev::{ServiceRequest, ServiceResponse};
App::new()
.service(
fs::Files::new("/static", "./static")
.default_handler(|req: ServiceRequest| async {
Ok(ServiceResponse::new(
req.into_parts().0,
HttpResponse::NotFound().body("File not found"),
))
})
)
文件下载 #
下载响应头 #
rust
use actix_files::NamedFile;
use actix_web::http::header::{ContentDisposition, DispositionParam, DispositionType};
async fn download() -> impl Responder {
NamedFile::open("files/document.pdf")
.unwrap()
.set_content_disposition(ContentDisposition {
disposition: DispositionType::Attachment,
parameters: vec![DispositionParam::Filename("document.pdf".to_string())],
})
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/download", web::get().to(download))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
动态文件下载 #
rust
use actix_web::{web, HttpResponse, Responder};
use actix_files::NamedFile;
async fn download_file(path: web::Path<String>) -> impl Responder {
let filename = path.into_inner();
let filepath = format!("uploads/{}", filename);
match NamedFile::open(&filepath) {
Ok(file) => {
file.set_content_disposition(ContentDisposition {
disposition: DispositionType::Attachment,
parameters: vec![DispositionParam::Filename(filename)],
})
.into_response(&actix_web::HttpRequest::default())
}
Err(_) => HttpResponse::NotFound().body("File not found"),
}
}
缓存配置 #
ETag 支持 #
rust
App::new()
.service(
fs::Files::new("/static", "./static")
.use_etag(true)
)
Last-Modified 支持 #
rust
App::new()
.service(
fs::Files::new("/static", "./static")
.use_last_modified(true)
)
自定义缓存头 #
rust
use actix_web::http::header;
App::new()
.service(
fs::Files::new("/static", "./static")
.prefer_utf8(true)
.use_etag(true)
.use_last_modified(true)
)
嵌套路由 #
静态文件与 API 混合 #
rust
use actix_files as fs;
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
async fn api_health() -> impl Responder {
HttpResponse::Ok().json(serde_json::json!({ "status": "ok" }))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(
web::scope("/api")
.route("/health", web::get().to(api_health))
)
.service(
fs::Files::new("/static", "./static")
.show_files_listing()
)
.service(
fs::Files::new("/", "./public")
.index_file("index.html")
)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
SPA 应用支持 #
rust
use actix_files as fs;
use actix_web::{web, App, HttpServer, HttpResponse, Responder};
async fn spa_fallback() -> impl Responder {
actix_files::NamedFile::open("public/index.html").unwrap()
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(
fs::Files::new("/assets", "./public/assets")
)
.default_service(
web::route().to(spa_fallback)
)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
项目结构 #
text
project/
├── Cargo.toml
├── src/
│ └── main.rs
├── static/
│ ├── css/
│ │ └── style.css
│ ├── js/
│ │ └── app.js
│ └── images/
│ └── logo.png
└── public/
└── index.html
完整示例 #
rust
use actix_files as fs;
use actix_files::NamedFile;
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use actix_web::http::header::{ContentDisposition, DispositionParam, DispositionType};
async fn index() -> impl Responder {
NamedFile::open("public/index.html").unwrap()
}
async fn download(path: web::Path<String>) -> impl Responder {
let filename = path.into_inner();
let filepath = format!("uploads/{}", filename);
match NamedFile::open(&filepath) {
Ok(file) => {
file.set_content_disposition(ContentDisposition {
disposition: DispositionType::Attachment,
parameters: vec![DispositionParam::Filename(filename)],
})
.into_response(&actix_web::HttpRequest::default())
}
Err(_) => HttpResponse::NotFound().json(serde_json::json!({
"error": "File not found"
})),
}
}
async fn spa_fallback() -> impl Responder {
NamedFile::open("public/index.html").unwrap()
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
println!("Server running at http://127.0.0.1:8080");
HttpServer::new(|| {
App::new()
.route("/", web::get().to(index))
.service(
fs::Files::new("/static", "./static")
.show_files_listing()
.use_etag(true)
.use_last_modified(true)
)
.route("/download/{filename:.*}", web::get().to(download))
.default_service(web::route().to(spa_fallback))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
下一步 #
现在你已经掌握了静态文件服务,继续学习 中间件概念,深入了解中间件系统!
最后更新:2026-03-29