3 use actix::{Actor, ActorContext, StreamHandler};
4 use actix_web::{get, route, web, App, Error, HttpRequest, HttpResponse, HttpServer, Responder, Result};
5 use actix_web::http::header;
6 use actix_web::middleware::Logger;
7 use actix_files::{Files, NamedFile};
8 use actix_web_actors::ws;
10 #[route("/hello/{name}", method="GET", method="HEAD")]
11 async fn hello(params: web::Path<String>, req: HttpRequest) -> Result<String> {
12 let name = params.into_inner();
14 match req.headers().get(header::USER_AGENT) {
15 Some(agent) => Ok(format!("Hello {} from {}!", name, agent.to_str().unwrap())),
16 None => Ok(format!("Hello {}!", name))
20 #[get("/file/{path:.*}")]
21 async fn static_file(params: web::Path<String>, req: HttpRequest) -> Result<impl Responder> {
22 let request_path = params.into_inner();
23 let disk_path = "../static/".to_string() + &request_path;
25 // if the client accepts gzip encoding, try that first
26 if let Some(accept_encoding) = req.headers().get(header::ACCEPT_ENCODING) {
27 if accept_encoding.to_str().unwrap().contains("gzip") {
28 let path_gz = disk_path.clone() + ".gz";
29 if Path::new(&path_gz).is_file() {
30 log::debug!("client accepts gzip encoding, sending pre-compressed file {}", &path_gz);
31 return Ok(NamedFile::open_async(path_gz).await?
33 .insert_header(header::ContentEncoding::Gzip));
39 Ok(NamedFile::open_async(disk_path).await?.customize())
44 impl Actor for WsEcho {
45 type Context = ws::WebsocketContext<Self>;
48 impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WsEcho {
49 fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
50 log::info!("WsEcho got message {:?}", msg);
52 Ok(ws::Message::Ping(msg)) => ctx.pong(&msg),
53 Ok(ws::Message::Text(text)) => ctx.text(text),
54 Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
55 Ok(ws::Message::Close(reason)) => {
65 async fn ws_echo(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
66 ws::start(WsEcho {}, &req, stream)
71 impl Actor for WsRev {
72 type Context = ws::WebsocketContext<Self>;
75 impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WsRev {
76 fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
77 log::info!("WsRev got message {:?}", msg);
79 Ok(ws::Message::Ping(msg)) => ctx.pong(&msg),
80 Ok(ws::Message::Text(text)) => {
81 let rev = text.chars().rev().collect::<String>();
84 Ok(ws::Message::Binary(bin)) => {
85 let mut rev = bin.to_vec();
89 Ok(ws::Message::Close(reason)) => {
99 async fn ws_rev(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
100 ws::start(WsRev {}, &req, stream)
104 async fn main() -> std::io::Result<()> {
105 env_logger::init_from_env(env_logger::Env::default().default_filter_or("info"));
110 .service(static_file)
111 .service(Files::new("/dir", "../static"))
114 .wrap(Logger::default())
116 .bind(("127.0.0.1", 3030))?
123 use actix_web::{App, body, test, web};
124 use actix_web::http::{header, StatusCode};
125 use actix_web_actors::ws;
127 use futures_util::sink::SinkExt;
128 use futures_util::StreamExt;
130 use super::{hello, static_file, ws_echo, ws_rev};
133 async fn test_hello() {
134 // FIXME: duplicating the .service() call from main() here is super ugly, but it's hard to move that into a fn
135 let app = test::init_service(App::new().service(hello)).await;
138 let req = test::TestRequest::get().uri("/hello/rust").to_request();
139 let res = test::call_service(&app, req).await;
140 assert!(res.status().is_success());
141 assert_eq!(body::to_bytes(res.into_body()).await.unwrap(),
142 web::Bytes::from_static(b"Hello rust!"));
145 let req = test::TestRequest::get()
147 .insert_header((header::USER_AGENT, "TestBrowser 0.1"))
149 let res = test::call_service(&app, req).await;
150 assert!(res.status().is_success());
151 assert_eq!(body::to_bytes(res.into_body()).await.unwrap(),
152 web::Bytes::from_static(b"Hello rust from TestBrowser 0.1!"));
156 async fn test_static_dir() {
157 // FIXME: duplicating the .service() call from main() here is super ugly, but it's hard to move that into a fn
158 let app = test::init_service(App::new().service(actix_files::Files::new("/dir", "../static"))).await;
160 let req = test::TestRequest::get().uri("/dir/plain.txt").to_request();
161 let res = test::call_service(&app, req).await;
162 assert!(res.status().is_success());
163 assert_eq!(body::to_bytes(res.into_body()).await.unwrap(),
164 web::Bytes::from_static(b"Hello world! This is uncompressed text.\n"));
167 let req = test::TestRequest::get().uri("/dir/dir1/optzip.txt").to_request();
168 let res = test::call_service(&app, req).await;
169 assert!(res.status().is_success());
170 assert_eq!(body::to_bytes(res.into_body()).await.unwrap(),
171 web::Bytes::from_static(b"This file is available uncompressed or compressed\n\
172 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"));
174 // does not support transparent decompression
175 let req = test::TestRequest::get().uri("/dir/onlycompressed.txt").to_request();
176 let res = test::call_service(&app, req).await;
177 assert_eq!(res.status(), StatusCode::NOT_FOUND);
181 async fn test_static_file() {
182 // FIXME: duplicating the .service() call from main() here is super ugly, but it's hard to move that into a fn
183 let app = test::init_service(App::new().service(static_file)).await;
186 let req = test::TestRequest::get().uri("/file/dir1/optzip.txt").to_request();
187 let res = test::call_service(&app, req).await;
188 assert!(res.status().is_success());
189 assert_eq!(body::to_bytes(res.into_body()).await.unwrap(),
190 web::Bytes::from_static(b"This file is available uncompressed or compressed\n\
191 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n"));
194 let req = test::TestRequest::get()
195 .uri("/file/dir1/optzip.txt")
196 .insert_header((header::ACCEPT_ENCODING, "deflate, gzip"))
198 let res = test::call_service(&app, req).await;
199 assert!(res.status().is_success());
200 let res_bytes = body::to_bytes(res.into_body()).await.unwrap();
201 assert_eq!(res_bytes.len(), 63); // file size of ../static/dir1/optzip.txt.gz
202 assert_eq!(res_bytes[0], 31);
206 async fn test_ws_echo() {
207 // FIXME: duplicating the .service() call from main() here is super ugly, but it's hard to move that into a fn
208 let mut srv = actix_test::start(|| App::new().service(ws_echo));
209 let mut client = srv.ws_at("/ws-echo").await.unwrap();
212 client.send(ws::Message::Text("hello".into())).await.unwrap();
213 let received = client.next().await.unwrap().unwrap();
214 assert_eq!(received, ws::Frame::Text("hello".into()));
217 client.send(ws::Message::Binary(web::Bytes::from_static(&[42, 99]))).await.unwrap();
218 let received = client.next().await.unwrap().unwrap();
219 assert_eq!(received, ws::Frame::Binary(web::Bytes::from_static(&[42, 99])));
223 async fn test_ws_rev() {
224 // FIXME: duplicating the .service() call from main() here is super ugly, but it's hard to move that into a fn
225 let mut srv = actix_test::start(|| App::new().service(ws_rev));
226 let mut client = srv.ws_at("/ws-rev").await.unwrap();
229 client.send(ws::Message::Text("hello".into())).await.unwrap();
230 let received = client.next().await.unwrap().unwrap();
231 assert_eq!(received, ws::Frame::Text("olleh".into()));
234 client.send(ws::Message::Binary(web::Bytes::from_static(&[42, 99]))).await.unwrap();
235 let received = client.next().await.unwrap().unwrap();
236 assert_eq!(received, ws::Frame::Binary(web::Bytes::from_static(&[99, 42])));