Skip to content

Commit 2d089c3

Browse files
committed
Extract get_router and add simple test.
This PR extracts the logic from `run_server` that creates a new Router into a helper function named `get_router` to make it easier to test without needing to start the full server. This also adds a simple test that verifies current behavior for returning a br compressed wasm.wasm file. This change is in preparation for adding support dynamically change the available compression.
1 parent 51e4728 commit 2d089c3

File tree

2 files changed

+112
-32
lines changed

2 files changed

+112
-32
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,7 @@ tower = "0.4"
2525
fastrand = "1.5"
2626
brotli = { version = "3", default-features = false, features = ["std"]}
2727
rcgen = { version = "0.9", default-features = false }
28+
29+
[dev-dependencies]
30+
tokio-test = "0.4"
31+
axum-test-helper = "0.1"

src/server.rs

Lines changed: 108 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::collections::HashMap;
22
use std::net::SocketAddr;
33

44
use axum::headers::HeaderName;
5-
use axum::http::{HeaderValue, StatusCode, Uri};
5+
use axum::http::{HeaderMap, HeaderValue, StatusCode, Uri};
66
use axum::response::{Html, IntoResponse, Response};
77
use axum::routing::{get, get_service};
88
use axum::Router;
@@ -29,6 +29,36 @@ pub struct Options {
2929
}
3030

3131
pub async fn run_server(options: Options, output: WasmBindgenOutput) -> Result<()> {
32+
let app = get_router(&options, output);
33+
let mut address_string = options.address;
34+
if !address_string.contains(":") {
35+
address_string +=
36+
&(":".to_owned() + &pick_port::pick_free_port(1334, 10).unwrap_or(1334).to_string());
37+
}
38+
let addr: SocketAddr = address_string.parse().expect("Couldn't parse address");
39+
40+
if options.https {
41+
let certificate = rcgen::generate_simple_self_signed([String::from("localhost")])?;
42+
let config = RustlsConfig::from_der(
43+
vec![certificate.serialize_der()?],
44+
certificate.serialize_private_key_der(),
45+
)
46+
.await?;
47+
48+
tracing::info!("starting webserver at https://{}", addr);
49+
axum_server_dual_protocol::bind_dual_protocol(addr, config)
50+
.set_upgrade(true)
51+
.serve(app.into_make_service())
52+
.await?;
53+
} else {
54+
tracing::info!("starting webserver at http://{}", addr);
55+
axum_server::bind(addr).serve(app.into_make_service()).await?;
56+
}
57+
58+
Ok(())
59+
}
60+
61+
fn get_router(options: &Options, output: WasmBindgenOutput) -> Router {
3262
let WasmBindgenOutput { js, compressed_wasm, snippets, local_modules } = output;
3363

3464
let middleware_stack = ServiceBuilder::new()
@@ -53,13 +83,14 @@ pub async fn run_server(options: Options, output: WasmBindgenOutput) -> Result<(
5383
let html = html.replace("{{ TITLE }}", &options.title);
5484

5585
let serve_dir =
56-
get_service(ServeDir::new(options.directory)).handle_error(internal_server_error);
86+
get_service(ServeDir::new(options.directory.clone())).handle_error(internal_server_error);
5787

58-
let serve_wasm = || async move {
88+
let serve_wasm = |headers: HeaderMap| async move {
89+
println!(" request headers: {:?}", headers);
5990
([("content-encoding", "br")], WithContentType("application/wasm", compressed_wasm))
6091
};
6192

62-
let app = Router::new()
93+
Router::new()
6394
.route("/", get(move || async { Html(html) }))
6495
.route("/api/wasm.js", get(|| async { WithContentType("application/javascript", js) }))
6596
.route("/api/wasm.wasm", get(serve_wasm))
@@ -77,34 +108,7 @@ pub async fn run_server(options: Options, output: WasmBindgenOutput) -> Result<(
77108
}),
78109
)
79110
.fallback(serve_dir)
80-
.layer(middleware_stack);
81-
82-
let mut address_string = options.address;
83-
if !address_string.contains(":") {
84-
address_string +=
85-
&(":".to_owned() + &pick_port::pick_free_port(1334, 10).unwrap_or(1334).to_string());
86-
}
87-
let addr: SocketAddr = address_string.parse().expect("Couldn't parse address");
88-
89-
if options.https {
90-
let certificate = rcgen::generate_simple_self_signed([String::from("localhost")])?;
91-
let config = RustlsConfig::from_der(
92-
vec![certificate.serialize_der()?],
93-
certificate.serialize_private_key_der(),
94-
)
95-
.await?;
96-
97-
tracing::info!("starting webserver at https://{}", addr);
98-
axum_server_dual_protocol::bind_dual_protocol(addr, config)
99-
.set_upgrade(true)
100-
.serve(app.into_make_service())
101-
.await?;
102-
} else {
103-
tracing::info!("starting webserver at http://{}", addr);
104-
axum_server::bind(addr).serve(app.into_make_service()).await?;
105-
}
106-
107-
Ok(())
111+
.layer(middleware_stack)
108112
}
109113

110114
fn get_snippet_source(
@@ -165,3 +169,75 @@ mod pick_port {
165169
.or_else(ask_free_tcp_port)
166170
}
167171
}
172+
173+
#[cfg(test)]
174+
mod tests {
175+
use crate::server::get_router;
176+
use crate::wasm_bindgen;
177+
use crate::Options;
178+
use axum::body::Bytes;
179+
use axum::http::StatusCode;
180+
use axum_test_helper::TestClient;
181+
use std::path::Path;
182+
183+
/// Headers for requests from 127.0.0.1 and local IP:
184+
///
185+
/// In this request, br is missing from the "accept-encoding" header
186+
///
187+
/// request headers: {"host": "192.168.68.107:1334", "connection": "keep-alive",
188+
/// "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36",
189+
/// "accept": "*/*", "referer": "http://192.168.68.107:1334/",
190+
/// "accept-encoding": "gzip, deflate", "accept-language": "en-US,en;q=0.9"}
191+
192+
/// request headers: {"host": "127.0.0.1:1334", "connection": "keep-alive",
193+
/// "sec-ch-ua": "\"Google Chrome\";v=\"107\", \"Chromium\";v=\"107\", \"Not=A?Brand\";v=\"24\"",
194+
/// "sec-ch-ua-mobile": "?0",
195+
/// "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36",
196+
/// "sec-ch-ua-platform": "\"Linux\"", "accept": "*/*", "sec-fetch-site": "same-origin",
197+
/// "sec-fetch-mode": "cors", "sec-fetch-dest": "empty", "referer": "http://127.0.0.1:1334/",
198+
/// "accept-encoding": "gzip, deflate, br", "accept-language": "en-US,en;q=0.9"}
199+
200+
/// To run this test, it is necessary to first create `example-project.wasm` by these commands:
201+
/// cd example-project
202+
/// cargo build
203+
#[tokio::test]
204+
async fn test_router() {
205+
let options = Options {
206+
title: "title".to_string(),
207+
address: "127.0.0.1:0".to_string(),
208+
directory: ".".to_string(),
209+
https: false,
210+
no_module: false,
211+
};
212+
213+
let wasm_file =
214+
Path::new("example-project/target/wasm32-unknown-unknown/debug/example-project.wasm");
215+
let output = wasm_bindgen::generate(&options, wasm_file).unwrap();
216+
let router = get_router(&options, output);
217+
println!("{:?}", &router);
218+
let client = TestClient::new(router);
219+
220+
// Test with br compression requested
221+
let mut res = client
222+
.get("/api/wasm.wasm")
223+
.header("accept-encoding", "gzip, deflate, br")
224+
.send()
225+
.await;
226+
assert_eq!(res.status(), StatusCode::OK);
227+
let result = res.chunk().await.unwrap();
228+
assert_ne!(result[0..3], Bytes::from(vec![0x1f, 0x8b, 0x08]));
229+
230+
// Test without br compression
231+
// let mut res = client
232+
// .get("/api/wasm.wasm")
233+
// .header("accept-encoding", "gzip, deflate")
234+
// .send()
235+
// .await;
236+
// assert_eq!(res.status(), StatusCode::OK);
237+
// let result = res.chunk().await.unwrap();
238+
// // This is the gzip 3-byte file header
239+
// assert_eq!(result[0..3], Bytes::from(vec![0x1f, 0x8b, 0x08]));
240+
241+
//tokio_test::block_on(server).unwrap();
242+
}
243+
}

0 commit comments

Comments
 (0)