4 routing::{get, get_service},
5 extract::{Path, TypedHeader, ws},
10 async fn hello(Path(name): Path<String>, user_agent: Option<TypedHeader<axum::headers::UserAgent>>) -> impl response::IntoResponse {
11 if let Some(TypedHeader(user_agent)) = user_agent {
12 (StatusCode::OK, format!("Hello {} from {}", name, user_agent))
14 (StatusCode::OK, format!("Hello {}", name))
18 async fn ws_echo(mut socket: ws::WebSocket) {
19 while let Some(msg) = socket.recv().await {
20 if let Ok(msg) = msg {
21 tracing::debug!("websocket got message: {:?}", msg);
23 let reply = match msg {
24 ws::Message::Text(t) => ws::Message::Text(t),
25 ws::Message::Binary(b) => ws::Message::Binary(b),
26 // axum handles Ping/Pong by itself
27 ws::Message::Ping(_) => { continue },
28 ws::Message::Pong(_) => { continue },
29 ws::Message::Close(_) => { break }
32 if socket.send(reply).await
34 tracing::info!("websocket client disconnected");
39 tracing::info!("websocket client disconnected");
47 .route("/hello/:name", get(hello))
49 get_service(tower_http::services::ServeDir::new("../static").precompressed_gzip())
50 .handle_error(|e: io::Error| async move {
51 (StatusCode::INTERNAL_SERVER_ERROR, format!("Unhandled internal error: {}", e))
54 .route("/ws-echo", get(|ws: ws::WebSocketUpgrade| async {ws.on_upgrade(ws_echo)}))
56 tower::ServiceBuilder::new()
58 tower_http::trace::TraceLayer::new_for_http()
59 .make_span_with(tower_http::trace::DefaultMakeSpan::default().include_headers(true)),
66 tracing_subscriber::fmt::init();
68 let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3030));
69 tracing::info!("listening on {}", addr);
70 axum::Server::bind(&addr)
71 .serve(app().into_make_service())
79 http::{Request, StatusCode},
83 use tower::ServiceExt; // for `oneshot`
85 async fn assert_res_ok_body(res: Response, expected_body: &[u8]) {
86 assert_eq!(res.status(), StatusCode::OK);
87 assert_eq!(hyper::body::to_bytes(res.into_body()).await.unwrap(), expected_body);
91 async fn test_hello() {
93 let res = super::app()
94 .oneshot(Request::builder().uri("/hello/rust").body(Body::empty()).unwrap())
97 assert_res_ok_body(res, b"Hello rust").await;
100 let res = super::app()
101 .oneshot(Request::builder()
103 .header("user-agent", "TestBrowser 0.1")
104 .body(Body::empty()).unwrap())
107 assert_res_ok_body(res, b"Hello rust from TestBrowser 0.1").await;
111 async fn test_static_dir() {
112 let res = super::app()
113 .oneshot(Request::builder().uri("/dir/plain.txt").body(Body::empty()).unwrap())
116 assert_res_ok_body(res, b"Hello world! This is uncompressed text.\n").await;
118 // transparent .gz lookup, without gzip transfer encoding
119 let res = super::app()
120 .oneshot(Request::builder()
121 .uri("/dir/dir1/optzip.txt")
122 .header("accept-encoding", "deflate")
123 .body(Body::empty()).unwrap())
126 assert_eq!(res.status(), StatusCode::OK);
127 // that returns the uncompressed file
128 assert_res_ok_body(res, b"This file is available uncompressed or compressed\n\
129 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n").await;
131 // transparent .gz lookup, with gzip transfer encoding
132 let res = super::app()
133 .oneshot(Request::builder()
134 .uri("/dir/dir1/optzip.txt")
135 .header("accept-encoding", "deflate, gzip")
136 .body(Body::empty()).unwrap())
139 assert_eq!(res.status(), StatusCode::OK);
140 let res_bytes: &[u8] = &hyper::body::to_bytes(res.into_body()).await.unwrap();
141 // that returns the compressed file
142 assert_eq!(res_bytes.len(), 63); // file size of ../static/dir1/optzip.txt.gz
143 assert_eq!(res_bytes[0], 31);