静态文件
单个文件服务
当你希望返回一个用户请求的特定文件,比如 PDF 报告、图片、音频文件等,可以使用 NamedFile。它适合处理“指定路径”下的文件访问需求。
可以使用自定义路径模式和 NamedFile 提供静态文件。要匹配路径尾部,我们可以使用 [.*]
正则表达式。
rust
use actix_files::NamedFile;
use actix_web::HttpRequest;
use std::path::PathBuf;
async fn index(req: HttpRequest) -> actix_web::Result<NamedFile> {
let path: PathBuf = req.match_info().query("filename").parse().unwrap();
Ok(NamedFile::open(path)?)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
use actix_web::{web, App, HttpServer};
HttpServer::new(|| App::new().route("/{filename:.*}", web::get().to(index)))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
安全风险
这个设计存在一个重要问题 —— 路径穿越攻击(Path Traversal):如果用户输入的路径是类似 ../../../etc/passwd
,你的服务器可能会返回系统敏感文件。这种攻击的本质是绕过文件根目录,访问主机上不该暴露的资源。
应始终限定用户只能访问你设定的“安全目录”,并对输入路径进行合法性验证,例如:将路径拼接到指定根目录;使用 canonicalize()
方法获得规范路径;检查最终路径是否仍在允许目录下。
rust
use actix_files::NamedFile;
use actix_web::{get, web, App, HttpRequest, HttpServer, Result};
use std::path::{PathBuf, Path};
#[get("/{filename:.*}")]
async fn index(req: HttpRequest) -> Result<NamedFile> {
// 限定根目录
let base_dir = Path::new("./static");
// 提取路径参数
let filename: PathBuf = req.match_info().query("filename").parse().unwrap();
let full_path = base_dir.join(&filename);
// 进行安全性检查,防止路径穿越
let canon = full_path.canonicalize()?;
if !canon.starts_with(base_dir.canonicalize()?) {
return Err(actix_web::error::ErrorForbidden("Access denied"));
}
// 返回文件
Ok(NamedFile::open(canon)?)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(index))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
目录服务
如果你要提供一个完整的静态资源目录,比如一个网页的前端资源(HTML、JS、CSS、图片等),就可以使用 Files
。它会自动映射目录结构和 URL 路径,更安全、推荐用于网站的资源部署。
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", ".").show_files_listing()))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
特性:
- 默认不会显示目录结构(避免暴露内部信息)
- 可以启用
index_file()
显示首页,如 index.html - 可以开启文件列表显示
show_files_listing()
,但建议只用于调试
配置
适用于 NamedFile 和 Files
1️⃣ use_last_modified(true)
是否在响应头中添加 Last-Modified
, 可以让浏览器能缓存文件,减少重复下载,且支持 304 Not Modified 响应,节省流量。
2️⃣ use_etag(true)
是否自动生成 ETag 头(实体标签), 为了更精确的缓存控制(对比 If-None-Match 请求头),浏览器可以根据 ETag 判断是否重新下载资源。
3️⃣ set_content_disposition(...)
是否设置 Content-Disposition
响应头, 作用于控制浏览器行为(是打开还是下载文件)
常用值:
inline
:浏览器直接预览attachment
:强制下载(下载窗口)
rust
use actix_files::NamedFile;
use actix_web::http::header::{ContentDisposition, DispositionType};
use actix_web::{get, App, Error, HttpRequest, HttpServer};
use std::path::PathBuf;
#[get("/{filename:.*}")]
async fn index(req: HttpRequest) -> Result<NamedFile, Error> {
let filename: PathBuf = req.match_info().query("filename").parse().unwrap();
let file = NamedFile::open(filename)?;
Ok(file
.use_etag(true) // 开启 ETag 缓存
.use_last_modified(true) // 开启 Last-Modified 缓存
.set_content_disposition(ContentDisposition {
disposition: DispositionType::Attachment, // 强制下载
parameters: vec![],
}))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(index))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
rust
use actix_files::Files;
use actix_web::{App, HttpServer};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new().service(
Files::new("/static", "./static")
.use_last_modified(true) // 启用 Last-Modified 缓存头
.use_etag(true) // 启用 ETag 缓存
.show_files_listing(), // 展示目录结构(开发调试用)
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}