From f5c4a6a87fc589dec2f23bee0196beb07d427ae7 Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Sat, 10 Dec 2022 11:59:12 +0100 Subject: [PATCH] actix-server: Add static file route with gz support This shows how to serve dynamically computed file paths/types, with gzip transport encoding support. --- actix-server/Cargo.toml | 1 + actix-server/src/main.rs | 57 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/actix-server/Cargo.toml b/actix-server/Cargo.toml index fa315f9..803f53e 100644 --- a/actix-server/Cargo.toml +++ b/actix-server/Cargo.toml @@ -9,3 +9,4 @@ edition = "2021" actix-web = "4" actix-files = "0.6" env_logger = "0.9" +log = "0.4" diff --git a/actix-server/src/main.rs b/actix-server/src/main.rs index ae2b223..c73e7ad 100644 --- a/actix-server/src/main.rs +++ b/actix-server/src/main.rs @@ -1,6 +1,9 @@ -use actix_web::{get, web, App, HttpRequest, HttpServer, Result}; +use std::path::Path; + +use actix_web::{get, web, App, HttpRequest, HttpServer, Responder, Result}; use actix_web::http::header; use actix_web::middleware::Logger; +use actix_files::{Files, NamedFile}; #[get("/hello/{name}")] async fn hello(params: web::Path, req: HttpRequest) -> Result { @@ -12,6 +15,28 @@ async fn hello(params: web::Path, req: HttpRequest) -> Result { } } +#[get("/file/{path:.*}")] +async fn static_file(params: web::Path, req: HttpRequest) -> Result { + let request_path = params.into_inner(); + let disk_path = "../static/".to_string() + &request_path; + + // if the client accepts gzip encoding, try that first + if let Some(accept_encoding) = req.headers().get(header::ACCEPT_ENCODING) { + if accept_encoding.to_str().unwrap().contains("gzip") { + let path_gz = disk_path.clone() + ".gz"; + if Path::new(&path_gz).is_file() { + log::debug!("client accepts gzip encoding, sending pre-compressed file {}", &path_gz); + return Ok(NamedFile::open_async(path_gz).await? + .customize() + .insert_header(header::ContentEncoding::Gzip)); + } + } + } + + // uncompressed file + Ok(NamedFile::open_async(disk_path).await?.customize()) +} + #[actix_web::main] async fn main() -> std::io::Result<()> { env_logger::init_from_env(env_logger::Env::default().default_filter_or("info")); @@ -19,7 +44,8 @@ async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .service(hello) - .service(actix_files::Files::new("/dir", "../static")) + .service(static_file) + .service(Files::new("/dir", "../static")) .wrap(Logger::default()) }) .bind(("127.0.0.1", 3030))? @@ -32,7 +58,7 @@ mod tests { use actix_web::{App, body, test, web}; use actix_web::http::{header, StatusCode}; - use super::{hello}; + use super::{hello, static_file}; #[actix_web::test] async fn test_hello() { @@ -81,4 +107,29 @@ mod tests { let res = test::call_service(&app, req).await; assert_eq!(res.status(), StatusCode::NOT_FOUND); } + + #[actix_web::test] + async fn test_static_file() { + // FIXME: duplicating the .service() call from main() here is super ugly, but it's hard to move that into a fn + let app = test::init_service(App::new().service(static_file)).await; + + // uncompressed + let req = test::TestRequest::get().uri("/file/dir1/optzip.txt").to_request(); + let res = test::call_service(&app, req).await; + assert!(res.status().is_success()); + assert_eq!(body::to_bytes(res.into_body()).await.unwrap(), + web::Bytes::from_static(b"This file is available uncompressed or compressed\n\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n")); + + // gzipped + let req = test::TestRequest::get() + .uri("/file/dir1/optzip.txt") + .insert_header((header::ACCEPT_ENCODING, "deflate, gzip")) + .to_request(); + let res = test::call_service(&app, req).await; + assert!(res.status().is_success()); + let res_bytes = body::to_bytes(res.into_body()).await.unwrap(); + assert_eq!(res_bytes.len(), 63); // file size of ../static/dir1/optzip.txt.gz + assert_eq!(res_bytes[0], 31); + } } -- 2.39.2