Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 29 additions & 17 deletions src/cmd/serve/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ async fn handle_request(
let proxy_client = state.proxy_client.clone();
let scss_transformer = state.scss_transformer.clone();
let typescript_transformer = state.typescript_transformer.clone();
let encoding = compress::negotiate(
req
.headers()
.get("accept-encoding")
.and_then(|value| value.to_str().ok()),
);

// Basic Auth
if !config.basic_auth.is_empty() {
Expand Down Expand Up @@ -350,11 +356,12 @@ async fn handle_request(

let content_type = format!("text/css; {}", DEFAULT_CHARSET_SUFFIX);

if config.compress {
res = res.header("Content-Encoding", "br");
if config.compress && encoding == Some(compress::Encoding::Brotli) {
return Ok(
res
.header("Content-Type", content_type)
.header("Content-Encoding", "br")
.header("Vary", "Accept-Encoding")
.status(200)
.body_from(compress::brotli(css.as_bytes()))?,
);
Expand Down Expand Up @@ -393,11 +400,12 @@ async fn handle_request(

let content_type = format!("application/javascript; {}", DEFAULT_CHARSET_SUFFIX);

if config.compress {
res = res.header("Content-Encoding", "br");
if config.compress && encoding == Some(compress::Encoding::Brotli) {
return Ok(
res
.header("Content-Type", content_type)
.header("Content-Encoding", "br")
.header("Vary", "Accept-Encoding")
.status(200)
.body_from(compress::brotli(code.as_bytes()))?,
);
Expand Down Expand Up @@ -431,19 +439,18 @@ async fn handle_request(
}

// If a .br or .gz file is found next to the target, serve that file
if !config.compress {
if !config.compress
&& let Some(encoding) = encoding
{
let Some(file_path_str) = file_path.to_str() else {
return Ok(res.status(404).body_from("File not found")?);
};
let brotli_path = PathBuf::from(format!("{}.br", file_path_str));
let gzip_path = PathBuf::from(format!("{}.gz", file_path_str));

if brotli_path.exists() {
file_path = brotli_path;
res = res.header("Content-Encoding", "br");
} else if gzip_path.exists() {
file_path = gzip_path;
res = res.header("Content-Encoding", "gzip");
let path = PathBuf::from(format!("{}.{}", file_path_str, encoding.name()));
if path.exists() {
file_path = path;
res = res
.header("Content-Encoding", encoding.name())
.header("Vary", "Accept-Encoding");
}
}

Expand Down Expand Up @@ -508,9 +515,14 @@ async fn handle_request(
}
}

if config.compress {
res = res.header("Content-Encoding", "br");
Ok(res.status(200).body_from(compress::brotli(&contents))?)
if config.compress && encoding == Some(compress::Encoding::Brotli) {
Ok(
res
.header("Content-Encoding", "br")
.header("Vary", "Accept-Encoding")
.status(200)
.body_from(compress::brotli(&contents))?,
)
} else {
Ok(res.status(200).body_from(contents)?)
}
Expand Down
9 changes: 9 additions & 0 deletions src/platform/compress/brotli.rs
Comment thread
alshdavid marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use std::io::Write;

use brotli as brotli_rs;

pub fn brotli(input: &[u8]) -> Vec<u8> {
let mut writer = brotli_rs::CompressorWriter::new(Vec::new(), 4096, 11, 22);
writer.write_all(input).unwrap();
writer.into_inner()
}
65 changes: 65 additions & 0 deletions src/platform/compress/encoding.rs

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alshdavid-machine read-only. What is the purpose of this file?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/platform/compress/encoding.rs defines the content-encoding negotiation for the server — the logic that decides whether to compress a response with brotli or gzip based on the client's Accept-Encoding header.

  • Encoding enum (Brotli, Gzip) with name() mapping to the wire tokens br / gzip.
  • negotiate(Option<&str>) picks the best supported coding: it takes the header, evaluates each supported encoding's quality via quality(), drops anything with q=0, and returns the highest-preference match (brotli wins ties, since it's listed first in the [Brotli, Gzip] array).
  • quality() parses Accept-Encoding into tokens and params, reads q= (defaulting to 1.0), and handles the * wildcard as a fallback.

Consumed by src/cmd/serve/handler.rs, which calls compress::negotiate(...) and sets Content-Encoding / Vary on responses. Tests live in encoding_test.rs.

Read-only request — no code changed, nothing committed or pushed.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alshdavid-machine

Despite offering the ability to negotiate the use of gzip, it appears only brotli is supported for actual compression.

Is there a mechanism to compress with gzip?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No — nothing produces gzip at request time. The only gzip path is pre-compressed sidecar files.

  • JIT (--compress): src/platform/compress/brotli.rs is the entire compressor, brotli only. Every compression site in src/cmd/serve/handler.rs gates on encoding == Some(compress::Encoding::Brotli) (lines 359, 403, 518). When negotiate returns Gzip, none of those branches match, so the client gets the uncompressed body and no Content-Encoding.
  • Sidecars (--compress off): handler.rs:441-455 looks for a <file>.br / <file>.gz sibling and, if it exists, serves it with Content-Encoding: gzip and Vary: Accept-Encoding. This is the only place Encoding::Gzip is actually honoured — and it is guarded by !config.compress, so it never runs when JIT compression is on.

So negotiation advertises gzip as supported while JIT can't fulfil it. Closing the gap would need a gzip encoder plus a Gzip arm at those three sites; no code changed for this question.

Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/// The content codings the server can produce, in preference order.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Encoding {
Brotli,
Gzip,
}

impl Encoding {
pub fn name(self) -> &'static str {
match self {
Encoding::Brotli => "br",
Encoding::Gzip => "gzip",
}
}
}

/// Pick the most preferred supported coding from an `Accept-Encoding` header.
pub fn negotiate(accept_encoding: Option<&str>) -> Option<Encoding> {
let header = accept_encoding?;

let mut best: Option<(Encoding, f32)> = None;

for encoding in [Encoding::Brotli, Encoding::Gzip] {
let Some(quality) = quality(header, encoding.name()) else {
continue;
};

if quality <= 0.0 {
continue;
}

match best {
Some((_, best_quality)) if best_quality >= quality => {}
_ => best = Some((encoding, quality)),
}
}

best.map(|(encoding, _)| encoding)
}

fn quality(
header: &str,
name: &str,
) -> Option<f32> {
let mut wildcard = None;

for part in header.split(',') {
let mut params = part.split(';');
let token = params.next().unwrap_or_default().trim();

let quality = params
.find_map(|param| param.trim().strip_prefix("q=")?.trim().parse::<f32>().ok())
.unwrap_or(1.0);

if token.eq_ignore_ascii_case(name) {
return Some(quality);
}

if token == "*" {
wildcard = Some(quality);
}
}

wildcard
}
35 changes: 35 additions & 0 deletions src/platform/compress/encoding_test.rs
Comment thread
alshdavid marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use super::Encoding;
use super::negotiate;

fn accepted(header: &str) -> Option<&'static str> {
negotiate(Some(header)).map(Encoding::name)
}

#[test]
fn prefers_brotli_when_accepted() {
assert_eq!(accepted("gzip, deflate, br"), Some("br"));
}

#[test]
fn honours_quality_values() {
assert_eq!(accepted("br;q=0.5, gzip"), Some("gzip"));
}

#[test]
fn falls_back_to_gzip() {
assert_eq!(accepted("gzip"), Some("gzip"));
}

#[test]
fn returns_none_when_nothing_supported() {
assert_eq!(accepted("deflate"), None);
assert_eq!(accepted("br;q=0"), None);
assert_eq!(accepted("identity"), None);
assert_eq!(negotiate(None), None);
}

#[test]
fn honours_wildcard() {
assert_eq!(accepted("*"), Some("br"));
assert_eq!(accepted("br;q=0, *"), Some("gzip"));
}
13 changes: 6 additions & 7 deletions src/platform/compress/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use std::io::Write;
mod brotli;
mod encoding;

use brotli as brotli_rs;
#[cfg(test)]
Comment thread
alshdavid marked this conversation as resolved.
mod encoding_test;

pub fn brotli(input: &[u8]) -> Vec<u8> {
let mut writer = brotli_rs::CompressorWriter::new(Vec::new(), 4096, 11, 22);
writer.write_all(input).unwrap();
writer.into_inner()
}
pub use self::brotli::*;
pub use self::encoding::*;
Loading