Minify production HTML, CSS, and JavaScript in the container build. Negotiate Brotli or gzip at the origin and keep anonymous identity probes out of the browser error console.
- commit
- a8d0f6f4c8a95d437ec47a032a99dda1aa9802ee
- parent
- 0625da737cf0b3c4b964be16b323e12ce091aca2
- author
- Dami <dami.etoile@gmail.com>
| M | Cargo.lock | 108 | ++++++++++++++++++++++++++++++ |
| M | Dockerfile | 32 | ++++++++- |
| M | crates/ | 8 | ++- |
| M | crates/ | 9 | ++- |
| M | crates/ | 4 | + |
@@ -2,6 +2,12 @@
22 # It is not intended for manual editing.33 version = 444 5+[[package]]6+name = "adler2"7+version = "2.0.1"8+source = "registry+https://github.com/rust-lang/crates.io-index"9+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"10+511 [[package]]612 name = "aead"713 version = "0.5.2"@@ -46,6 +52,21 @@ dependencies = [
4652 "memchr",4753 ]4854 55+[[package]]56+name = "alloc-no-stdlib"57+version = "2.0.4"58+source = "registry+https://github.com/rust-lang/crates.io-index"59+checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"60+61+[[package]]62+name = "alloc-stdlib"63+version = "0.2.4"64+source = "registry+https://github.com/rust-lang/crates.io-index"65+checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195"66+dependencies = [67+ "alloc-no-stdlib",68+]69+4970 [[package]]5071 name = "allocator-api2"5172 version = "0.2.21"@@ -138,6 +159,18 @@ version = "0.7.8"
138159 source = "registry+https://github.com/rust-lang/crates.io-index"139160 checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"140161 162+[[package]]163+name = "async-compression"164+version = "0.4.43"165+source = "registry+https://github.com/rust-lang/crates.io-index"166+checksum = "3976abdc8fe7d1133d43d304afd42abdf5bc3e1319d263d223bde07b5efc4be8"167+dependencies = [168+ "compression-codecs",169+ "compression-core",170+ "pin-project-lite",171+ "tokio",172+]173+141174 [[package]]142175 name = "async-trait"143176 version = "0.1.92"@@ -324,6 +357,27 @@ dependencies = [
324357 "objc2",325358 ]326359 360+[[package]]361+name = "brotli"362+version = "8.0.4"363+source = "registry+https://github.com/rust-lang/crates.io-index"364+checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3"365+dependencies = [366+ "alloc-no-stdlib",367+ "alloc-stdlib",368+ "brotli-decompressor",369+]370+371+[[package]]372+name = "brotli-decompressor"373+version = "5.0.3"374+source = "registry+https://github.com/rust-lang/crates.io-index"375+checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583"376+dependencies = [377+ "alloc-no-stdlib",378+ "alloc-stdlib",379+]380+327381 [[package]]328382 name = "bumpalo"329383 version = "3.20.3"@@ -468,6 +522,24 @@ dependencies = [
468522 "memchr",469523 ]470524 525+[[package]]526+name = "compression-codecs"527+version = "0.4.38"528+source = "registry+https://github.com/rust-lang/crates.io-index"529+checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf"530+dependencies = [531+ "brotli",532+ "compression-core",533+ "flate2",534+ "memchr",535+]536+537+[[package]]538+name = "compression-core"539+version = "0.4.32"540+source = "registry+https://github.com/rust-lang/crates.io-index"541+checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789"542+471543 [[package]]472544 name = "const-oid"473545 version = "0.9.6"@@ -549,6 +621,15 @@ dependencies = [
549621 "libc",550622 ]551623 624+[[package]]625+name = "crc32fast"626+version = "1.5.0"627+source = "registry+https://github.com/rust-lang/crates.io-index"628+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"629+dependencies = [630+ "cfg-if",631+]632+552633 [[package]]553634 name = "critical-section"554635 version = "1.2.0"@@ -1037,6 +1118,16 @@ version = "0.1.10"
10371118 source = "registry+https://github.com/rust-lang/crates.io-index"10381119 checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de"10391120 1121+[[package]]1122+name = "flate2"1123+version = "1.1.9"1124+source = "registry+https://github.com/rust-lang/crates.io-index"1125+checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"1126+dependencies = [1127+ "crc32fast",1128+ "miniz_oxide",1129+]1130+10401131 [[package]]10411132 name = "fnv"10421133 version = "1.0.7"@@ -2112,6 +2203,16 @@ dependencies = [
21122203 "unicase",21132204 ]21142205 2206+[[package]]2207+name = "miniz_oxide"2208+version = "0.8.9"2209+source = "registry+https://github.com/rust-lang/crates.io-index"2210+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"2211+dependencies = [2212+ "adler2",2213+ "simd-adler32",2214+]2215+21152216 [[package]]21162217 name = "mio"21172218 version = "1.2.2"@@ -3457,6 +3558,12 @@ dependencies = [
34573558 "rand_core 0.10.1",34583559 ]34593560 3561+[[package]]3562+name = "simd-adler32"3563+version = "0.3.10"3564+source = "registry+https://github.com/rust-lang/crates.io-index"3565+checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"3566+34603567 [[package]]34613568 name = "simd_cesu8"34623569 version = "1.2.0"@@ -3911,6 +4018,7 @@ version = "0.6.11"
39114018 source = "registry+https://github.com/rust-lang/crates.io-index"39124019 checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"39134020 dependencies = [4021+ "async-compression",39144022 "bitflags",39154023 "bytes",39164024 "futures-core",@@ -1,5 +1,33 @@
1+# Keep the checked-in web source readable and only minify the production copy.2+FROM node:24-bookworm-slim AS web3+4+WORKDIR /src5+COPY web ./web6+7+# Versions are exact so rebuilding a commit cannot silently change its assets.8+RUN npm install --global esbuild@0.25.12 html-minifier-terser@7.2.0 \9+ && mkdir /out \10+ && cp -R web/. /out/ \11+ && esbuild web/app.js \12+ --format=esm \13+ --legal-comments=none \14+ --minify \15+ --outfile=/out/app.js \16+ --target=es2022 \17+ && esbuild web/site.css \18+ --legal-comments=none \19+ --minify \20+ --outfile=/out/site.css \21+ && html-minifier-terser web/index.html \22+ --collapse-whitespace \23+ --remove-comments \24+ --remove-redundant-attributes \25+ --remove-script-type-attributes \26+ --remove-style-link-type-attributes \27+ --use-short-doctype \28+ --output /out/index.html29+130 # The control plane, built from source in one go.2-#331 FROM rust:1.96-bookworm AS build432 533 WORKDIR /src@@ -15,7 +43,7 @@ RUN apt-get update \
1543 && rm -rf /var/lib/apt/lists/*1644 1745 COPY --from=build /src/target/release/devsite-server /usr/local/bin/devsite-server18-COPY --from=build /src/web /app/web46+COPY --from=web /out /app/web1947 2048 # Defaults for running in a container. Everything that differs per deployment —2149 # the public origin above all — is set in fly.toml or as a secret.@@ -13,7 +13,13 @@ devsite-proto.workspace = true
1313 anyhow.workspace = true1414 axum = { version = "0.8", features = ["macros"] }1515 tokio = { version = "1", features = ["rt-multi-thread", "macros", "signal"] }16-tower-http = { version = "0.6", features = ["fs", "trace", "set-header"] }16+tower-http = { version = "0.6", features = [17+ "compression-br",18+ "compression-gzip",19+ "fs",20+ "set-header",21+ "trace",22+] }1723 rusqlite = { version = "0.37", features = ["bundled"] }1824 serde.workspace = true1925 serde_json = "1"@@ -468,8 +468,10 @@ async fn sign_out(State(state): State<Shared>, headers: HeaderMap) -> ApiResult<
468468 Ok((StatusCode::NO_CONTENT, [(header::SET_COOKIE, cookie)]).into_response())469469 }470470 471-async fn me(State(state): State<Shared>, headers: HeaderMap) -> ApiResult<impl IntoResponse> {472- let id = require_account(&state, &headers)?;471+async fn me(State(state): State<Shared>, headers: HeaderMap) -> ApiResult<Response> {472+ let Some(id) = current_account(&state, &headers)? else {473+ return Ok(StatusCode::NO_CONTENT.into_response());474+ };473475 let db = state.db.lock().unwrap();474476 let account = db475477 .account_by_id(id)@@ -478,7 +480,8 @@ async fn me(State(state): State<Shared>, headers: HeaderMap) -> ApiResult<impl I
478480 Ok(Json(serde_json::json!({479481 "account_id": account.id.to_string(),480482 "handle": account.handle,481- })))483+ }))484+ .into_response())482485 }483486 484487 async fn claim_handle(@@ -17,6 +17,7 @@ use anyhow::{Context, Result};
1717 use axum::http::{header, HeaderValue};1818 use axum::response::{Html, IntoResponse, Response};1919 use axum::routing::get;20+use tower_http::compression::CompressionLayer;2021 use tower_http::services::{ServeDir, ServeFile};2122 use tower_http::set_header::SetResponseHeaderLayer;2223 use tower_http::trace::TraceLayer;@@ -131,6 +132,9 @@ async fn main() -> Result<()> {
131132 .route("/@{handle}", get(serve_index(index.clone())))132133 .route("/auth/callback", get(serve_index(index.clone())))133134 .fallback_service(static_assets(&config.web_root, &index))135+ // Fly also compresses at its edge today, but the origin should have the same136+ // contract when it is run directly or moved behind a different proxy.137+ .layer(CompressionLayer::new().br(true).gzip(true))134138 .layer(TraceLayer::new_for_http());135139 136140 let listener = tokio::net::TcpListener::bind(&config.bind)