静态文件 #

静态文件概述 #

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