From 705802e58435d91b32f01c56209534be3ba02fed Mon Sep 17 00:00:00 2001 From: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Date: Wed, 8 Mar 2023 12:48:21 -0700 Subject: [PATCH] gzip and brotli (#2186) --- backend/Cargo.lock | 51 ++++++++++++++++++++++ backend/Cargo.toml | 5 +++ backend/src/net/static_server.rs | 72 +++++++++++++++++++++++++------- 3 files changed, 113 insertions(+), 15 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 74b525f7d..c6fc37f7c 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -60,6 +60,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -130,6 +145,20 @@ dependencies = [ "futures-core", ] +[[package]] +name = "async-compression" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a" +dependencies = [ + "brotli", + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-stream" version = "0.3.3" @@ -417,6 +446,27 @@ dependencies = [ "serde_with 1.14.0", ] +[[package]] +name = "brotli" +version = "3.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bstr" version = "0.2.17" @@ -1266,6 +1316,7 @@ name = "embassy-os" version = "0.3.4" dependencies = [ "aes", + "async-compression", "async-stream", "async-trait", "avahi-sys", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 0962bbefe..1b45c8efa 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -48,6 +48,11 @@ unstable = ["patch-db/unstable"] [dependencies] aes = { version = "0.7.5", features = ["ctr"] } +async-compression = { version = "0.3.15", features = [ + "gzip", + "brotli", + "tokio", +] } async-stream = "0.3.3" async-trait = "0.1.56" avahi-sys = { git = "https://github.com/Start9Labs/avahi-sys", version = "0.10.0", branch = "feature/dynamic-linking", features = [ diff --git a/backend/src/net/static_server.rs b/backend/src/net/static_server.rs index c9a95763c..23b9055bd 100644 --- a/backend/src/net/static_server.rs +++ b/backend/src/net/static_server.rs @@ -3,14 +3,18 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::UNIX_EPOCH; +use async_compression::tokio::bufread::BrotliEncoder; +use async_compression::tokio::bufread::GzipEncoder; use color_eyre::eyre::eyre; use digest::Digest; use futures::FutureExt; +use http::header::ACCEPT_ENCODING; use http::response::Builder; use hyper::{Body, Method, Request, Response, StatusCode}; use rpc_toolkit::rpc_handler; use tokio::fs::File; -use tokio_util::codec::{BytesCodec, FramedRead}; +use tokio::io::BufReader; +use tokio_util::io::ReaderStream; use crate::context::{DiagnosticContext, InstallContext, RpcContext, SetupContext}; use crate::core::rpc_continuations::RequestGuid; @@ -225,11 +229,20 @@ async fn alt_ui(req: Request, ui_mode: UiMode) -> Result, E }; let (request_parts, _body) = req.into_parts(); + let accept_encoding = request_parts + .headers + .get_all(ACCEPT_ENCODING) + .into_iter() + .filter_map(|h| h.to_str().ok()) + .flat_map(|s| s.split(",")) + .filter_map(|s| s.split(";").next()) + .map(|s| s.trim()) + .collect::>(); match request_parts.uri.path() { "/" => { let full_path = PathBuf::from(selected_root_dir).join("index.html"); - file_send(full_path).await + file_send(full_path, &accept_encoding).await } _ => { match ( @@ -249,12 +262,12 @@ async fn alt_ui(req: Request, ui_mode: UiMode) -> Result, E .unwrap_or(request_parts.uri.path()); let full_path = PathBuf::from(selected_root_dir).join(uri_path); - file_send(full_path).await + file_send(full_path, &accept_encoding).await } (Method::GET, Some((dir, file))) => { let full_path = PathBuf::from(selected_root_dir).join(dir).join(file); - file_send(full_path).await + file_send(full_path, &accept_encoding).await } _ => Ok(not_found()), @@ -267,11 +280,20 @@ async fn main_embassy_ui(req: Request, ctx: RpcContext) -> Result>(); match request_parts.uri.path() { "/" => { let full_path = PathBuf::from(selected_root_dir).join("index.html"); - file_send(full_path).await + file_send(full_path, &accept_encoding).await } _ => { let valid_session = HasValidSession::from_request_parts(&request_parts, &ctx).await; @@ -290,11 +312,19 @@ async fn main_embassy_ui(req: Request, ctx: RpcContext) -> Result { let sub_path = Path::new(path); if let Ok(rest) = sub_path.strip_prefix("package-data") { - file_send(ctx.datadir.join(PKG_PUBLIC_DIR).join(rest)).await + file_send( + ctx.datadir.join(PKG_PUBLIC_DIR).join(rest), + &accept_encoding, + ) + .await } else if let Ok(rest) = sub_path.strip_prefix("eos") { match rest.to_str() { Some("local.crt") => { - file_send(crate::net::ssl::ROOT_CA_STATIC_PATH).await + file_send( + crate::net::ssl::ROOT_CA_STATIC_PATH, + &accept_encoding, + ) + .await } None => Ok(bad_request()), _ => Ok(not_found()), @@ -304,7 +334,11 @@ async fn main_embassy_ui(req: Request, ctx: RpcContext) -> Result { - file_send(PathBuf::from(crate::net::ssl::ROOT_CA_STATIC_PATH)).await + file_send( + PathBuf::from(crate::net::ssl::ROOT_CA_STATIC_PATH), + &accept_encoding, + ) + .await } (Method::GET, None) => { @@ -315,12 +349,12 @@ async fn main_embassy_ui(req: Request, ctx: RpcContext) -> Result { let full_path = PathBuf::from(selected_root_dir).join(dir).join(file); - file_send(full_path).await + file_send(full_path, &accept_encoding).await } _ => Ok(not_found()), @@ -350,12 +384,12 @@ async fn main_embassy_ui(req: Request, ctx: RpcContext) -> Result { let full_path = PathBuf::from(selected_root_dir).join(dir).join(file); - file_send(full_path).await + file_send(full_path, &accept_encoding).await } _ => Ok(not_found()), @@ -397,7 +431,10 @@ fn bad_request() -> Response { .unwrap() } -async fn file_send(path: impl AsRef) -> Result, Error> { +async fn file_send( + path: impl AsRef, + accept_encoding: &[&str], +) -> Result, Error> { // Serve a file by asynchronously reading it by chunks using tokio-util crate. let path = path.as_ref(); @@ -414,8 +451,13 @@ async fn file_send(path: impl AsRef) -> Result, Error> { builder = with_e_tag(path, &metadata, builder)?; builder = with_content_type(path, builder); builder = with_content_length(&metadata, builder); - let stream = FramedRead::new(file, BytesCodec::new()); - let body = Body::wrap_stream(stream); + let body = if accept_encoding.contains(&"br") { + Body::wrap_stream(ReaderStream::new(BrotliEncoder::new(BufReader::new(file)))) + } else if accept_encoding.contains(&"gzip") { + Body::wrap_stream(ReaderStream::new(GzipEncoder::new(BufReader::new(file)))) + } else { + Body::wrap_stream(ReaderStream::new(file)) + }; return builder.body(body).with_kind(ErrorKind::Network); } tracing::debug!("File not found: {:?}", path);