]> piware.de Git - learn-rust.git/blob - actix-server/src/main.rs
actix-server: Add static file route with gz support
[learn-rust.git] / actix-server / src / main.rs
1 use std::path::Path;
2
3 use actix_web::{get, web, App, HttpRequest, HttpServer, Responder, Result};
4 use actix_web::http::header;
5 use actix_web::middleware::Logger;
6 use actix_files::{Files, NamedFile};
7
8 #[get("/hello/{name}")]
9 async fn hello(params: web::Path<String>, req: HttpRequest) -> Result<String> {
10     let name = params.into_inner();
11
12     match req.headers().get(header::USER_AGENT) {
13         Some(agent) => Ok(format!("Hello {} from {}!", name, agent.to_str().unwrap())),
14         None => Ok(format!("Hello {}!", name))
15     }
16 }
17
18 #[get("/file/{path:.*}")]
19 async fn static_file(params: web::Path<String>, req: HttpRequest) -> Result<impl Responder> {
20     let request_path = params.into_inner();
21     let disk_path = "../static/".to_string() + &request_path;
22
23     // if the client accepts gzip encoding, try that first
24     if let Some(accept_encoding) = req.headers().get(header::ACCEPT_ENCODING) {
25         if accept_encoding.to_str().unwrap().contains("gzip") {
26             let path_gz = disk_path.clone() + ".gz";
27             if Path::new(&path_gz).is_file() {
28                 log::debug!("client accepts gzip encoding, sending pre-compressed file {}", &path_gz);
29                 return Ok(NamedFile::open_async(path_gz).await?
30                           .customize()
31                           .insert_header(header::ContentEncoding::Gzip));
32             }
33         }
34     }
35
36     // uncompressed file
37     Ok(NamedFile::open_async(disk_path).await?.customize())
38 }
39
40 #[actix_web::main]
41 async fn main() -> std::io::Result<()> {
42     env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
43
44     HttpServer::new(|| {
45         App::new()
46             .service(hello)
47             .service(static_file)
48             .service(Files::new("/dir", "../static"))
49             .wrap(Logger::default())
50     })
51         .bind(("127.0.0.1", 3030))?
52         .run()
53         .await
54 }
55
56 #[cfg(test)]
57 mod tests {
58     use actix_web::{App, body, test, web};
59     use actix_web::http::{header, StatusCode};
60
61     use super::{hello, static_file};
62
63     #[actix_web::test]
64     async fn test_hello() {
65         // FIXME: duplicating the .service() call from main() here is super ugly, but it's hard to move that into a fn
66         let app = test::init_service(App::new().service(hello)).await;
67
68         // no user-agent
69         let req = test::TestRequest::get().uri("/hello/rust").to_request();
70         let res = test::call_service(&app, req).await;
71         assert!(res.status().is_success());
72         assert_eq!(body::to_bytes(res.into_body()).await.unwrap(),
73                    web::Bytes::from_static(b"Hello rust!"));
74
75         // with user-agent
76         let req = test::TestRequest::get()
77             .uri("/hello/rust")
78             .insert_header((header::USER_AGENT, "TestBrowser 0.1"))
79             .to_request();
80         let res = test::call_service(&app, req).await;
81         assert!(res.status().is_success());
82         assert_eq!(body::to_bytes(res.into_body()).await.unwrap(),
83                    web::Bytes::from_static(b"Hello rust from TestBrowser 0.1!"));
84     }
85
86     #[actix_web::test]
87     async fn test_static_dir() {
88         // FIXME: duplicating the .service() call from main() here is super ugly, but it's hard to move that into a fn
89         let app = test::init_service(App::new().service(actix_files::Files::new("/dir", "../static"))).await;
90
91         let req = test::TestRequest::get().uri("/dir/plain.txt").to_request();
92         let res = test::call_service(&app, req).await;
93         assert!(res.status().is_success());
94         assert_eq!(body::to_bytes(res.into_body()).await.unwrap(),
95                    web::Bytes::from_static(b"Hello world! This is uncompressed text.\n"));
96
97         // subdir
98         let req = test::TestRequest::get().uri("/dir/dir1/optzip.txt").to_request();
99         let res = test::call_service(&app, req).await;
100         assert!(res.status().is_success());
101         assert_eq!(body::to_bytes(res.into_body()).await.unwrap(),
102                    web::Bytes::from_static(b"This file is available uncompressed or compressed\n\
103                                              AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"));
104
105         // does not support transparent decompression
106         let req = test::TestRequest::get().uri("/dir/onlycompressed.txt").to_request();
107         let res = test::call_service(&app, req).await;
108         assert_eq!(res.status(), StatusCode::NOT_FOUND);
109     }
110
111     #[actix_web::test]
112     async fn test_static_file() {
113         // FIXME: duplicating the .service() call from main() here is super ugly, but it's hard to move that into a fn
114         let app = test::init_service(App::new().service(static_file)).await;
115
116         // uncompressed
117         let req = test::TestRequest::get().uri("/file/dir1/optzip.txt").to_request();
118         let res = test::call_service(&app, req).await;
119         assert!(res.status().is_success());
120         assert_eq!(body::to_bytes(res.into_body()).await.unwrap(),
121                    web::Bytes::from_static(b"This file is available uncompressed or compressed\n\
122                                              AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"));
123
124         // gzipped
125         let req = test::TestRequest::get()
126             .uri("/file/dir1/optzip.txt")
127             .insert_header((header::ACCEPT_ENCODING, "deflate, gzip"))
128             .to_request();
129         let res = test::call_service(&app, req).await;
130         assert!(res.status().is_success());
131         let res_bytes = body::to_bytes(res.into_body()).await.unwrap();
132         assert_eq!(res_bytes.len(), 63); // file size of ../static/dir1/optzip.txt.gz
133         assert_eq!(res_bytes[0], 31);
134     }
135 }