]> piware.de Git - learn-rust.git/commitdiff
actix-server: Add static file route with gz support
authorMartin Pitt <martin@piware.de>
Sat, 10 Dec 2022 10:59:12 +0000 (11:59 +0100)
committerMartin Pitt <martin@piware.de>
Sat, 10 Dec 2022 10:59:12 +0000 (11:59 +0100)
This shows how to serve dynamically computed file paths/types, with gzip
transport encoding support.

actix-server/Cargo.toml
actix-server/src/main.rs

index fa315f9210f5f77676e42fb59146baf82b4aa763..803f53e15613db130f966ad9ef1185b0e232d735 100644 (file)
@@ -9,3 +9,4 @@ edition = "2021"
 actix-web = "4"
 actix-files = "0.6"
 env_logger = "0.9"
+log = "0.4"
index ae2b22315a4b3be0537a30983e835eaecf2148f1..c73e7add5ee333cfecb7b72a2ac64e8595940b65 100644 (file)
@@ -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<String>, req: HttpRequest) -> Result<String> {
@@ -12,6 +15,28 @@ async fn hello(params: web::Path<String>, req: HttpRequest) -> Result<String> {
     }
 }
 
+#[get("/file/{path:.*}")]
+async fn static_file(params: web::Path<String>, req: HttpRequest) -> Result<impl Responder> {
+    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);
+    }
 }