X-Git-Url: https://piware.de/gitweb/?p=learn-rust.git;a=blobdiff_plain;f=actix-server%2Fsrc%2Fmain.rs;h=44ed45cc60bc055feedb1b3d8cb2ddb872bd1171;hp=4a43ef55b36d8b1f2ccf31f84d11979a1efd35fd;hb=121e3e9e83ec2bba3f0;hpb=7580ac5646156d35fd83b65f7d0bd959a896fb6c diff --git a/actix-server/src/main.rs b/actix-server/src/main.rs index 4a43ef5..44ed45c 100644 --- a/actix-server/src/main.rs +++ b/actix-server/src/main.rs @@ -1,9 +1,11 @@ use std::path::Path; -use actix_web::{get, route, web, App, HttpRequest, HttpServer, Responder, Result}; +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; #[route("/hello/{name}", method="GET", method="HEAD")] async fn hello(params: web::Path, req: HttpRequest) -> Result { @@ -37,15 +39,85 @@ async fn static_file(params: web::Path, req: HttpRequest) -> Result std::io::Result<()> { - env_logger::init_from_env(env_logger::Env::default().default_filter_or("info")); +struct WsEcho; - HttpServer::new(|| { +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(|| { + get_app!() .wrap(Logger::default()) }) .bind(("127.0.0.1", 3030))? @@ -57,13 +129,17 @@ async fn main() -> std::io::Result<()> { mod tests { use actix_web::{App, body, test, web}; use actix_web::http::{header, StatusCode}; + use actix_web_actors::ws; + use actix_files::Files; + + use futures_util::sink::SinkExt; + use futures_util::StreamExt; - use super::{hello, static_file}; + use super::{hello, static_file, ws_echo, ws_rev}; #[actix_web::test] async fn test_hello() { - // 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(hello)).await; + let app = test::init_service(get_app!()).await; // no user-agent let req = test::TestRequest::get().uri("/hello/rust").to_request(); @@ -85,8 +161,7 @@ mod tests { #[actix_web::test] async fn test_static_dir() { - // 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(actix_files::Files::new("/dir", "../static"))).await; + 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; @@ -110,8 +185,7 @@ mod tests { #[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; + let app = test::init_service(get_app!()).await; // uncompressed let req = test::TestRequest::get().uri("/file/dir1/optzip.txt").to_request(); @@ -132,4 +206,36 @@ mod tests { 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]))); + } }