refactor(compression): share the Brotli backend with curl

Replace brotli2 with brotlic so stream codecs and curl use the same
brotlic-sys 0.2.2 dependency. Remove the separate brotli-sys dependency
and preserve compression quality 5 and streaming finish handling.

Validation: cargo fmt --all;
cargo clippy --workspace --all-targets --all-features -- -D warnings.
Tests were skipped as requested.
This commit is contained in:
ldm0
2026-09-09 04:01:49 +08:00
committed by Donough Liu
parent 9e668c97e1
commit 583fde024f
3 changed files with 33 additions and 56 deletions
Generated
+5 -16
View File
@@ -382,23 +382,12 @@ dependencies = [
]
[[package]]
name = "brotli-sys"
version = "0.3.2"
name = "brotlic"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd"
checksum = "f552f56f302af0006c32b50bfa2bdb4696fd6ba33c3ab9f6225fefdb1efdc680"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "brotli2"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e"
dependencies = [
"brotli-sys",
"libc",
"brotlic-sys",
]
[[package]]
@@ -2877,7 +2866,7 @@ dependencies = [
"anyhow",
"app_units",
"base64 0.22.1",
"brotli2",
"brotlic",
"crossbeam-channel",
"cssparser",
"curl",
+1 -1
View File
@@ -18,7 +18,7 @@ wpt-extensions = []
anyhow = "1.0.100"
app_units = "0.7.8"
base64 = "0.22"
brotli2 = "0.3.2"
brotlic = "0.8.2"
crossbeam-channel = "0.5.15"
cssparser = "0.37"
data-url = "0.3.2"
@@ -1,11 +1,9 @@
//! Streaming codecs, independent of V8 and the Streams lifecycle.
//! One decoder accepts exactly one stream/member, including any checksum.
use brotli2::{
CompressParams,
raw::{
CoStatus, Compress as BrotliCompress, CompressOp, DeStatus, Decompress as BrotliDecompress,
},
use brotlic::{
BrotliDecoder, BrotliEncoder, BrotliEncoderOptions, Quality, decode::DecoderInfo,
encode::BrotliOperation,
};
use flate2::{Compress, Compression, Decompress, FlushCompress, FlushDecompress, Status};
@@ -30,8 +28,8 @@ impl Format {
}
enum Engine {
BrotliCompress(BrotliCompress),
BrotliDecompress(BrotliDecompress),
BrotliCompress(BrotliEncoder),
BrotliDecompress(BrotliDecoder),
Compress(Compress),
Decompress(Decompress),
}
@@ -44,12 +42,14 @@ pub(super) struct Codec {
impl Codec {
pub(super) fn new(format: Format, decompress: bool) -> Self {
let engine = match (format, decompress) {
(Format::Brotli, true) => Engine::BrotliDecompress(BrotliDecompress::new()),
(Format::Brotli, true) => Engine::BrotliDecompress(BrotliDecoder::new()),
(Format::Brotli, false) => {
let mut encoder = BrotliCompress::new();
// Moderate quality for interactive streaming; retain the
// standard window size and generic compression mode.
encoder.set_params(CompressParams::new().quality(5));
let encoder = BrotliEncoderOptions::new()
.quality(Quality::new(5).expect("Brotli quality 5 must be valid"))
.build()
.expect("fixed Brotli encoder parameters must be valid");
Engine::BrotliCompress(encoder)
}
(Format::Gzip, true) => Engine::Decompress(Decompress::new_gzip(15)),
@@ -92,40 +92,28 @@ impl Codec {
let mut output = vec![0; 16 * 1024];
let (status, consumed, produced) = match &mut self.engine {
Engine::BrotliCompress(engine) => {
let (before_in, before_out) = (input.len(), output.len());
let mut remaining_in = input;
let mut remaining_out = output.as_mut_slice();
let status = engine
.compress(
if finish {
CompressOp::Finish
} else {
CompressOp::Process
},
&mut remaining_in,
&mut remaining_out,
)
// Process completion is not the end of the stream.
.map(|status| finish && status == CoStatus::Finished)
.map_err(|_| "Compression failed");
let operation = if finish {
BrotliOperation::Finish
} else {
BrotliOperation::Process
};
let Ok(result) = engine.compress(input, &mut output, operation) else {
return (chunks, Err("Compression failed"));
};
(
status,
before_in - remaining_in.len(),
before_out - remaining_out.len(),
Ok(engine.is_finished()),
result.bytes_read,
result.bytes_written,
)
}
Engine::BrotliDecompress(engine) => {
let (before_in, before_out) = (input.len(), output.len());
let mut remaining_in = input;
let mut remaining_out = output.as_mut_slice();
let status = engine
.decompress(&mut remaining_in, &mut remaining_out)
.map(|status| status == DeStatus::Finished)
.map_err(|_| "The compressed data was not valid");
let Ok(result) = engine.decompress(input, &mut output) else {
return (chunks, Err("The compressed data was not valid"));
};
(
status,
before_in - remaining_in.len(),
before_out - remaining_out.len(),
Ok(result.info == DecoderInfo::Finished),
result.bytes_read,
result.bytes_written,
)
}
Engine::Compress(engine) => {