Skip to content

Commit 676b5eb

Browse files
committed
Add back in support for gzip compression
See #28 for context. This new code will serve up br compressed if supported, but will fall back to gzip compression if not.
1 parent c7c5d84 commit 676b5eb

File tree

4 files changed

+93
-16
lines changed

4 files changed

+93
-16
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ tokio = { version = "1.11", default-features = false, features = ["rt-multi-thre
2323
tower-http = { version = "0.3", features = ["compression-full", "fs", "set-header", "trace"] }
2424
tower = "0.4"
2525
fastrand = "1.5"
26+
flate2 = "1.0"
2627
brotli = { version = "3", default-features = false, features = ["std"]}
2728
rcgen = { version = "0.9", default-features = false }
2829

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ fn main() -> Result<(), anyhow::Error> {
3838

3939
let output = wasm_bindgen::generate(&options, &wasm_file)?;
4040

41-
info!("compressed wasm output is {} large", pretty_size(output.compressed_wasm.len()));
41+
info!("compressed wasm output is {} large", pretty_size(output.br_compressed_wasm.len()));
4242

4343
let rt = tokio::runtime::Runtime::new()?;
4444
rt.block_on(server::run_server(options, output))?;

src/server.rs

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use std::collections::HashMap;
22
use std::net::SocketAddr;
3+
use std::str::from_utf8;
34

45
use axum::headers::HeaderName;
6+
use axum::http::header::ACCEPT_ENCODING;
57
use axum::http::{HeaderMap, HeaderValue, StatusCode, Uri};
68
use axum::response::{Html, IntoResponse, Response};
79
use axum::routing::{get, get_service};
@@ -59,7 +61,8 @@ pub async fn run_server(options: Options, output: WasmBindgenOutput) -> Result<(
5961
}
6062

6163
fn get_router(options: &Options, output: WasmBindgenOutput) -> Router {
62-
let WasmBindgenOutput { js, compressed_wasm, snippets, local_modules } = output;
64+
let WasmBindgenOutput { js, br_compressed_wasm, gzip_compressed_wasm, snippets, local_modules } =
65+
output;
6366

6467
let middleware_stack = ServiceBuilder::new()
6568
.layer(CompressionLayer::new())
@@ -85,8 +88,35 @@ fn get_router(options: &Options, output: WasmBindgenOutput) -> Router {
8588
let serve_dir =
8689
get_service(ServeDir::new(options.directory.clone())).handle_error(internal_server_error);
8790

88-
let serve_wasm = || async move {
89-
([("content-encoding", "br")], WithContentType("application/wasm", compressed_wasm))
91+
let serve_wasm = |headers: HeaderMap| async move {
92+
if let Some(accept_encoding) = headers.get(ACCEPT_ENCODING) {
93+
match from_utf8(accept_encoding.as_bytes()) {
94+
Ok(encodings) => {
95+
let split_encodings: Vec<&str> = encodings.split(",").map(str::trim).collect();
96+
if split_encodings.contains(&"br") {
97+
Ok((
98+
[("content-encoding", "br")],
99+
WithContentType("application/wasm", br_compressed_wasm),
100+
))
101+
} else if split_encodings.contains(&"gzip") {
102+
Ok((
103+
[("content-encoding", "gzip")],
104+
WithContentType("application/wasm", gzip_compressed_wasm),
105+
))
106+
} else {
107+
tracing::warn!("Unsupported encoding in request for wasm.wasm");
108+
Err((
109+
StatusCode::BAD_REQUEST,
110+
format!("Unsupported encoding(s): {:?}", split_encodings),
111+
))
112+
}
113+
}
114+
Err(err) => Err((StatusCode::BAD_REQUEST, err.to_string())),
115+
}
116+
} else {
117+
tracing::error!("Received request missing the accept-encoding header");
118+
Err((StatusCode::BAD_REQUEST, "Missing `accept-encoding` header".to_string()))
119+
}
90120
};
91121

92122
Router::new()
@@ -180,6 +210,7 @@ mod tests {
180210
use axum_test_helper::TestClient;
181211

182212
const FAKE_BR_COMPRESSED_WASM: [u8; 4] = [1, 2, 3, 4];
213+
const FAKE_GZIP_COMPRESSED_WASM: [u8; 4] = [0x1f, 0x8b, 0x08, 0x08];
183214

184215
fn fake_options() -> Options {
185216
Options {
@@ -194,7 +225,8 @@ mod tests {
194225
fn fake_wasm_bindgen_output() -> WasmBindgenOutput {
195226
WasmBindgenOutput {
196227
js: "fake js".to_string(),
197-
compressed_wasm: FAKE_BR_COMPRESSED_WASM.to_vec(),
228+
br_compressed_wasm: FAKE_BR_COMPRESSED_WASM.to_vec(),
229+
gzip_compressed_wasm: FAKE_GZIP_COMPRESSED_WASM.to_vec(),
198230
snippets: HashMap::<String, Vec<String>>::new(),
199231
local_modules: HashMap::<String, String>::new(),
200232
}
@@ -208,10 +240,17 @@ mod tests {
208240
}
209241

210242
#[tokio::test]
211-
async fn test_router() {
243+
async fn test_router_bad_request() {
212244
let client = make_test_client();
213245

214-
// Test with br compression requested
246+
// Test without any supported compression
247+
let res = client.get("/api/wasm.wasm").header("accept-encoding", "deflate").send().await;
248+
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
249+
}
250+
251+
#[tokio::test]
252+
async fn test_router_br() {
253+
let client = make_test_client();
215254
let mut res = client
216255
.get("/api/wasm.wasm")
217256
.header("accept-encoding", "gzip, deflate, br")
@@ -221,4 +260,16 @@ mod tests {
221260
let result = res.chunk().await.unwrap();
222261
assert_eq!(result.to_vec(), FAKE_BR_COMPRESSED_WASM);
223262
}
263+
264+
#[tokio::test]
265+
async fn test_router_gzip() {
266+
let client = make_test_client();
267+
// Test without br compression, defaulting to gzip
268+
let mut res =
269+
client.get("/api/wasm.wasm").header("accept-encoding", "gzip, deflate").send().await;
270+
assert_eq!(res.status(), StatusCode::OK);
271+
let result = res.chunk().await.unwrap();
272+
// This is the gzip 3-byte file header
273+
assert_eq!(result.to_vec(), FAKE_GZIP_COMPRESSED_WASM);
274+
}
224275
}

src/wasm_bindgen.rs

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ use std::collections::HashMap;
55
use std::path::Path;
66
use tracing::debug;
77

8+
const COMPRESSION_LEVEL: u32 = 2;
9+
810
pub struct WasmBindgenOutput {
911
pub js: String,
10-
pub compressed_wasm: Vec<u8>,
12+
pub br_compressed_wasm: Vec<u8>,
13+
pub gzip_compressed_wasm: Vec<u8>,
1114
pub snippets: HashMap<String, Vec<String>>,
1215
pub local_modules: HashMap<String, String>,
1316
}
@@ -35,22 +38,44 @@ pub fn generate(options: &Options, wasm_file: &Path) -> Result<WasmBindgenOutput
3538
let wasm = output.wasm_mut().emit_wasm();
3639
debug!("emitting wasm took {:?}", start.elapsed());
3740

38-
debug!("compressing wasm...");
41+
debug!("br compressing wasm...");
3942
let start = std::time::Instant::now();
40-
let compressed_wasm = compress(&wasm).context("failed to compress wasm file")?;
43+
let br_compressed_wasm = br_compress(&wasm).context("failed to compress wasm file")?;
4144
debug!("compressing took {:?}", start.elapsed());
4245

43-
Ok(WasmBindgenOutput { js, compressed_wasm, snippets, local_modules })
46+
debug!("gzip compressing wasm...");
47+
let start = std::time::Instant::now();
48+
let gzip_compressed_wasm = gzip_compress(&wasm).context("failed to compress wasm file")?;
49+
debug!("compressing took {:?}", start.elapsed());
50+
51+
Ok(WasmBindgenOutput { js, br_compressed_wasm, gzip_compressed_wasm, snippets, local_modules })
4452
}
4553

46-
fn compress(mut bytes: &[u8]) -> Result<Vec<u8>, std::io::Error> {
54+
fn br_compress(mut bytes: &[u8]) -> Result<Vec<u8>, std::io::Error> {
4755
use brotli::enc::{self, BrotliEncoderParams};
4856

4957
let mut output = Vec::new();
50-
enc::BrotliCompress(&mut bytes, &mut output, &BrotliEncoderParams {
51-
quality: 5, // https://github.com/jakobhellermann/wasm-server-runner/pull/22#issuecomment-1235804905
52-
..Default::default()
53-
})?;
58+
enc::BrotliCompress(
59+
&mut bytes,
60+
&mut output,
61+
&BrotliEncoderParams {
62+
quality: 5, // https://github.com/jakobhellermann/wasm-server-runner/pull/22#issuecomment-1235804905
63+
..Default::default()
64+
},
65+
)?;
5466

5567
Ok(output)
5668
}
69+
70+
fn gzip_compress(bytes: &[u8]) -> Result<Vec<u8>, std::io::Error> {
71+
use flate2::write::GzEncoder;
72+
use flate2::Compression;
73+
use std::io::prelude::*;
74+
75+
let mut encoder = GzEncoder::new(Vec::new(), Compression::new(COMPRESSION_LEVEL));
76+
77+
encoder.write_all(bytes)?;
78+
let compressed = encoder.finish()?;
79+
80+
Ok(compressed)
81+
}

0 commit comments

Comments
 (0)