Skip to content
This repository has been archived by the owner on Nov 21, 2023. It is now read-only.

Commit

Permalink
Add back in support for gzip compression
Browse files Browse the repository at this point in the history
See jakobhellermann#28 for context.

This new code will serve up br compressed if supported, but will
fall back to gzip compression if not.
  • Loading branch information
heisencoder committed Nov 23, 2022
1 parent 2d089c3 commit 650e3ba
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 19 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ tokio = { version = "1.11", default-features = false, features = ["rt-multi-thre
tower-http = { version = "0.3", features = ["compression-full", "fs", "set-header", "trace"] }
tower = "0.4"
fastrand = "1.5"
flate2 = "1.0"
brotli = { version = "3", default-features = false, features = ["std"]}
rcgen = { version = "0.9", default-features = false }

Expand Down
64 changes: 49 additions & 15 deletions src/server.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::collections::HashMap;
use std::net::SocketAddr;
use std::str::from_utf8;

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

fn get_router(options: &Options, output: WasmBindgenOutput) -> Router {
let WasmBindgenOutput { js, compressed_wasm, snippets, local_modules } = output;
let WasmBindgenOutput { js, compressed_wasm, gzip_compressed_wasm, snippets, local_modules } =
output;

let middleware_stack = ServiceBuilder::new()
.layer(CompressionLayer::new())
Expand Down Expand Up @@ -87,7 +90,37 @@ fn get_router(options: &Options, output: WasmBindgenOutput) -> Router {

let serve_wasm = |headers: HeaderMap| async move {
println!(" request headers: {:?}", headers);
([("content-encoding", "br")], WithContentType("application/wasm", compressed_wasm))
if let Some(accept_encoding) = headers.get(ACCEPT_ENCODING) {
match from_utf8(accept_encoding.as_bytes()) {
Ok(encodings) => {
let split_encodings: Vec<&str> = encodings.split(",").map(str::trim).collect();
println!("split_encodings: {:?}", split_encodings);
if split_encodings.contains(&"br") {
println!("serving br compressed wasm");
Ok((
[("content-encoding", "br")],
WithContentType("application/wasm", compressed_wasm),
))
} else if split_encodings.contains(&"gzip") {
println!("serving gzip compressed wasm");
Ok((
[("content-encoding", "gzip")],
WithContentType("application/wasm", gzip_compressed_wasm),
))
} else {
println!("No support for requested encoding!");
Err((
StatusCode::BAD_REQUEST,
format!("Unsupported encoding(s): {:?}", split_encodings),
))
}
}
Err(err) => Err((StatusCode::BAD_REQUEST, err.to_string())),
}
} else {
tracing::error!("Received request missing the accept-encoding header");
Err((StatusCode::BAD_REQUEST, "Missing `accept-encoding` header".to_string()))
}
};

Router::new()
Expand Down Expand Up @@ -217,7 +250,13 @@ mod tests {
println!("{:?}", &router);
let client = TestClient::new(router);

// Test with br compression requested
// Test without any supported compression
let mut res =
client.get("/api/wasm.wasm").header("accept-encoding", "deflate").send().await;
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
let result = res.chunk().await.unwrap();
println!("Bad Request result: {:?}", result);

let mut res = client
.get("/api/wasm.wasm")
.header("accept-encoding", "gzip, deflate, br")
Expand All @@ -227,17 +266,12 @@ mod tests {
let result = res.chunk().await.unwrap();
assert_ne!(result[0..3], Bytes::from(vec![0x1f, 0x8b, 0x08]));

// Test without br compression
// let mut res = client
// .get("/api/wasm.wasm")
// .header("accept-encoding", "gzip, deflate")
// .send()
// .await;
// assert_eq!(res.status(), StatusCode::OK);
// let result = res.chunk().await.unwrap();
// // This is the gzip 3-byte file header
// assert_eq!(result[0..3], Bytes::from(vec![0x1f, 0x8b, 0x08]));

//tokio_test::block_on(server).unwrap();
// Test without br compression, defaulting to gzip
let mut res =
client.get("/api/wasm.wasm").header("accept-encoding", "gzip, deflate").send().await;
assert_eq!(res.status(), StatusCode::OK);
let result = res.chunk().await.unwrap();
// This is the gzip 3-byte file header
assert_eq!(result[0..3], Bytes::from(vec![0x1f, 0x8b, 0x08]));
}
}
29 changes: 25 additions & 4 deletions src/wasm_bindgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ use std::collections::HashMap;
use std::path::Path;
use tracing::debug;

const COMPRESSION_LEVEL: u32 = 2;

pub struct WasmBindgenOutput {
pub js: String,
pub compressed_wasm: Vec<u8>,
pub gzip_compressed_wasm: Vec<u8>,
pub snippets: HashMap<String, Vec<String>>,
pub local_modules: HashMap<String, String>,
}
Expand Down Expand Up @@ -35,15 +38,20 @@ pub fn generate(options: &Options, wasm_file: &Path) -> Result<WasmBindgenOutput
let wasm = output.wasm_mut().emit_wasm();
debug!("emitting wasm took {:?}", start.elapsed());

debug!("compressing wasm...");
debug!("br compressing wasm...");
let start = std::time::Instant::now();
let compressed_wasm = compress(&wasm).context("failed to compress wasm file")?;
let compressed_wasm = br_compress(&wasm).context("failed to compress wasm file")?;
debug!("compressing took {:?}", start.elapsed());

Ok(WasmBindgenOutput { js, compressed_wasm, snippets, local_modules })
debug!("gzip compressing wasm...");
let start = std::time::Instant::now();
let gzip_compressed_wasm = gzip_compress(&wasm).context("failed to compress wasm file")?;
debug!("compressing took {:?}", start.elapsed());

Ok(WasmBindgenOutput { js, compressed_wasm, gzip_compressed_wasm, snippets, local_modules })
}

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

let mut output = Vec::new();
Expand All @@ -54,3 +62,16 @@ fn compress(mut bytes: &[u8]) -> Result<Vec<u8>, std::io::Error> {

Ok(output)
}

fn gzip_compress(bytes: &[u8]) -> Result<Vec<u8>, std::io::Error> {
use flate2::write::GzEncoder;
use flate2::Compression;
use std::io::prelude::*;

let mut encoder = GzEncoder::new(Vec::new(), Compression::new(COMPRESSION_LEVEL));

encoder.write_all(bytes)?;
let compressed = encoder.finish()?;

Ok(compressed)
}

0 comments on commit 650e3ba

Please sign in to comment.