Skip to content

Commit

Permalink
feat: upload br encoding to asset canister (#3791)
Browse files Browse the repository at this point in the history
  • Loading branch information
sesi200 authored Jun 19, 2024
1 parent 192744f commit 5ee2a1c
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 8 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ Adds support for the `log_visibility` canister setting, which configures which u
Valid options are `controllers` and `public`. The setting can be used with the `--log-visibility` flag in `dfx canister create`
and `dfx canister update-settings`, or in `dfx.json` under `canisters[].initialization_values.log_visibility`.

## Asset canister synchronization

### feat: support `brotli` encoding

Asset synchronization now not only supports `identity` and `gzip`, but also `brotli` encoding.
The default encodings are still
- `identity` and `gzip` for MIME types `.txt`, `.html` and `.js`
- `identity` for anything else

## Dependencies

### Frontend canister
Expand Down
37 changes: 37 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions e2e/tests-dfx/assetscanister.bash
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,41 @@ check_permission_failure() {
diff encoded-compressed-2 src/e2e_project_frontend/assets/notreally.js
}

@test "can use brotli compression" {
install_asset assetscanister
for i in $(seq 1 400); do
echo "some easily duplicate text $i" >>src/e2e_project_frontend/assets/notreally.js
done

echo '[{
"match": "*.js",
"encodings": ["identity", "br"]
}
]' > src/e2e_project_frontend/assets/.ic-assets.json5

dfx_start
assert_command dfx deploy
dfx canister call --query e2e_project_frontend list '(record{})'

ID=$(dfx canister id e2e_project_frontend)
PORT=$(get_webserver_port)

assert_command curl -v --output not-compressed http://localhost:"$PORT"/notreally.js?canisterId="$ID"
assert_not_match "content-encoding:"
diff not-compressed src/e2e_project_frontend/assets/notreally.js

assert_command curl -v --output encoded-compressed-1.br -H "Accept-Encoding: br" http://localhost:"$PORT"/notreally.js?canisterId="$ID"
assert_match "content-encoding: br"
brotli --decompress encoded-compressed-1.br
diff encoded-compressed-1 src/e2e_project_frontend/assets/notreally.js

# should split up accept-encoding lines with more than one encoding
assert_command curl -v --output encoded-compressed-2.br -H "Accept-Encoding: br, deflate, gzip" http://localhost:"$PORT"/notreally.js?canisterId="$ID"
assert_match "content-encoding: br"
brotli --decompress encoded-compressed-2.br
diff encoded-compressed-2 src/e2e_project_frontend/assets/notreally.js
}

@test "leaves in place files that were already installed" {
install_asset assetscanister
dd if=/dev/urandom of=src/e2e_project_frontend/assets/asset1.bin bs=400000 count=1
Expand Down
1 change: 1 addition & 0 deletions src/canisters/frontend/ic-asset/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ keywords = ["internet-computer", "assets", "icp", "dfinity"]

[dependencies]
backoff.workspace = true
brotli = "6.0.0"
candid = { workspace = true }
derivative = "2.2.0"
dfx-core = { path = "../../../dfx-core" }
Expand Down
15 changes: 15 additions & 0 deletions src/canisters/frontend/ic-asset/src/asset/content.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::asset::content_encoder::ContentEncoder;
use brotli::CompressorWriter;
use dfx_core::error::fs::FsError;
use flate2::write::GzEncoder;
use flate2::Compression;
Expand Down Expand Up @@ -28,6 +29,7 @@ impl Content {
pub fn encode(&self, encoder: &ContentEncoder) -> Result<Content, std::io::Error> {
match encoder {
ContentEncoder::Gzip => self.to_gzip(),
ContentEncoder::Brotli => self.to_brotli(),
ContentEncoder::Identity => Ok(self.clone()),
}
}
Expand All @@ -42,6 +44,19 @@ impl Content {
})
}

pub fn to_brotli(&self) -> Result<Content, std::io::Error> {
let mut compressed_data = Vec::new();
{
let mut compressor = CompressorWriter::new(&mut compressed_data, 4096, 11, 22);
compressor.write_all(&self.data)?;
compressor.flush()?;
}
Ok(Content {
data: compressed_data,
media_type: self.media_type.clone(),
})
}

pub fn sha256(&self) -> Vec<u8> {
Sha256::digest(&self.data).to_vec()
}
Expand Down
3 changes: 3 additions & 0 deletions src/canisters/frontend/ic-asset/src/asset/content_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ use serde::{Deserialize, Serialize};
#[serde(rename_all = "lowercase")]
pub enum ContentEncoder {
Gzip,
#[serde(alias = "br")]
Brotli,
Identity,
}

impl std::fmt::Display for ContentEncoder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self {
ContentEncoder::Gzip => f.write_str("gzip"),
ContentEncoder::Brotli => f.write_str("br"),
ContentEncoder::Identity => f.write_str("identity"),
}
}
Expand Down
17 changes: 9 additions & 8 deletions src/canisters/frontend/ic-asset/src/evidence/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::asset::content::Content;
use crate::asset::content_encoder::ContentEncoder::Gzip;
use crate::asset::content_encoder::ContentEncoder::{Brotli, Gzip};
use crate::batch_upload::operations::assemble_batch_operations;
use crate::batch_upload::operations::AssetDeletionReason::Obsolete;
use crate::batch_upload::plumbing::{make_project_assets, ProjectAsset};
Expand Down Expand Up @@ -123,14 +123,15 @@ fn hash_set_asset_content(

let content = {
let identity = Content::load(&ad.source).map_err(LoadContentFailed)?;
if args.content_encoding == "identity" {
identity
} else if args.content_encoding == "gzip" {
identity
match args.content_encoding.as_str() {
"identity" => identity,
"br" | "brotli" => identity
.encode(&Brotli)
.map_err(|e| EncodeContentFailed(ad.key.clone(), Brotli, e))?,
"gzip" => identity
.encode(&Gzip)
.map_err(|e| EncodeContentFailed(ad.key.clone(), Gzip, e))?
} else {
unreachable!("unhandled content encoder");
.map_err(|e| EncodeContentFailed(ad.key.clone(), Gzip, e))?,
_ => unreachable!("unhandled content encoder"),
}
};
hasher.update(&content.data);
Expand Down

0 comments on commit 5ee2a1c

Please sign in to comment.