X-Git-Url: https://piware.de/gitweb/?p=learn-rust.git;a=blobdiff_plain;f=actix-server%2Fsrc%2Fmain.rs;h=44ed45cc60bc055feedb1b3d8cb2ddb872bd1171;hp=946eb36fc0c466fdd424d1c1ff9dba23676c8229;hb=121e3e9e83ec2bba3f0;hpb=577248e40bb772480fb036fe2deafc1ac2a11354 diff --git a/actix-server/src/main.rs b/actix-server/src/main.rs index 946eb36..44ed45c 100644 --- a/actix-server/src/main.rs +++ b/actix-server/src/main.rs @@ -1,8 +1,13 @@ -use actix_web::{get, web, App, HttpRequest, HttpServer, Result}; +use std::path::Path; + +use actix::{Actor, ActorContext, StreamHandler}; +use actix_web::{get, route, web, App, Error, HttpRequest, HttpResponse, HttpServer, Responder, Result}; use actix_web::http::header; use actix_web::middleware::Logger; +use actix_files::{Files, NamedFile}; +use actix_web_actors::ws; -#[get("/hello/{name}")] +#[route("/hello/{name}", method="GET", method="HEAD")] async fn hello(params: web::Path, req: HttpRequest) -> Result { let name = params.into_inner(); @@ -12,13 +17,107 @@ 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()) +} + +struct WsEcho; + +impl Actor for WsEcho { + type Context = ws::WebsocketContext; +} + +impl StreamHandler> for WsEcho { + fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { + log::info!("WsEcho got message {:?}", msg); + match msg { + Ok(ws::Message::Ping(msg)) => ctx.pong(&msg), + Ok(ws::Message::Text(text)) => ctx.text(text), + Ok(ws::Message::Binary(bin)) => ctx.binary(bin), + Ok(ws::Message::Close(reason)) => { + ctx.close(reason); + ctx.stop(); + }, + _ => ctx.stop(), + } + } +} + +#[get("/ws-echo")] +async fn ws_echo(req: HttpRequest, stream: web::Payload) -> Result { + ws::start(WsEcho {}, &req, stream) +} + +struct WsRev; + +impl Actor for WsRev { + type Context = ws::WebsocketContext; +} + +impl StreamHandler> for WsRev { + fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { + log::info!("WsRev got message {:?}", msg); + match msg { + Ok(ws::Message::Ping(msg)) => ctx.pong(&msg), + Ok(ws::Message::Text(text)) => { + let rev = text.chars().rev().collect::(); + ctx.text(rev); + }, + Ok(ws::Message::Binary(bin)) => { + let mut rev = bin.to_vec(); + rev.reverse(); + ctx.binary(rev); + } + Ok(ws::Message::Close(reason)) => { + ctx.close(reason); + ctx.stop(); + }, + _ => ctx.stop(), + } + } +} + +#[get("/ws-rev")] +async fn ws_rev(req: HttpRequest, stream: web::Payload) -> Result { + ws::start(WsRev {}, &req, stream) +} + +// App is a template soup, too hard as a proper function +macro_rules! get_app { + () => { + App::new() + .service(hello) + .service(static_file) + .service(Files::new("/dir", "../static")) + .service(ws_echo) + .service(ws_rev) + } +} + #[actix_web::main] async fn main() -> std::io::Result<()> { env_logger::init_from_env(env_logger::Env::default().default_filter_or("info")); HttpServer::new(|| { - App::new() - .service(hello) + get_app!() .wrap(Logger::default()) }) .bind(("127.0.0.1", 3030))? @@ -29,13 +128,18 @@ async fn main() -> std::io::Result<()> { #[cfg(test)] mod tests { use actix_web::{App, body, test, web}; - use actix_web::http::header; + use actix_web::http::{header, StatusCode}; + use actix_web_actors::ws; + use actix_files::Files; - use super::{hello}; + use futures_util::sink::SinkExt; + use futures_util::StreamExt; + + use super::{hello, static_file, ws_echo, ws_rev}; #[actix_web::test] async fn test_hello() { - let app = test::init_service(App::new().service(hello)).await; + let app = test::init_service(get_app!()).await; // no user-agent let req = test::TestRequest::get().uri("/hello/rust").to_request(); @@ -54,4 +158,84 @@ mod tests { assert_eq!(body::to_bytes(res.into_body()).await.unwrap(), web::Bytes::from_static(b"Hello rust from TestBrowser 0.1!")); } + + #[actix_web::test] + async fn test_static_dir() { + let app = test::init_service(get_app!()).await; + + let req = test::TestRequest::get().uri("/dir/plain.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"Hello world! This is uncompressed text.\n")); + + // subdir + let req = test::TestRequest::get().uri("/dir/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")); + + // does not support transparent decompression + let req = test::TestRequest::get().uri("/dir/onlycompressed.txt").to_request(); + let res = test::call_service(&app, req).await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + } + + #[actix_web::test] + async fn test_static_file() { + let app = test::init_service(get_app!()).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); + } + + #[actix_web::test] + async fn test_ws_echo() { + let mut srv = actix_test::start(|| get_app!()); + let mut client = srv.ws_at("/ws-echo").await.unwrap(); + + // text echo + client.send(ws::Message::Text("hello".into())).await.unwrap(); + let received = client.next().await.unwrap().unwrap(); + assert_eq!(received, ws::Frame::Text("hello".into())); + + // binary echo + client.send(ws::Message::Binary(web::Bytes::from_static(&[42, 99]))).await.unwrap(); + let received = client.next().await.unwrap().unwrap(); + assert_eq!(received, ws::Frame::Binary(web::Bytes::from_static(&[42, 99]))); + } + + #[actix_web::test] + async fn test_ws_rev() { + let mut srv = actix_test::start(|| get_app!()); + let mut client = srv.ws_at("/ws-rev").await.unwrap(); + + // text reversed + client.send(ws::Message::Text("hello".into())).await.unwrap(); + let received = client.next().await.unwrap().unwrap(); + assert_eq!(received, ws::Frame::Text("olleh".into())); + + // binary reversed + client.send(ws::Message::Binary(web::Bytes::from_static(&[42, 99]))).await.unwrap(); + let received = client.next().await.unwrap().unwrap(); + assert_eq!(received, ws::Frame::Binary(web::Bytes::from_static(&[99, 42]))); + } }