Search code examples
rustaws-lambdarust-axum

How to compress HTML content with Axum / Tower?


I am trying to create a very simple API with Axum 0.7 and lambda_http 0.9

[package]
name = "api"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = "0.7"
chrono = { version = "0.4" }
cookie = { version = "0.18" }
lambda_http = { version = "0.9" }
serde = "1.0"
serde_json = "1.0"
askama = { version = "0.12.1" }
tokio = { version = "1.35", features = ["macros"] }
tower-http = { version = "0.5", features = ["compression-full", "cors"] }
tracing = { version = "0.1", features = ["log"] }
tracing-subscriber = { version = "0.3", default-features = false, features = [
    "fmt",
] }
use axum::{
    http::{
        header::{ACCEPT, ACCEPT_ENCODING, AUTHORIZATION},
        Method,
    },
    routing::{get, post},
    Router,
};
use lambda_http::{run, Error};
use tower_http::{compression::CompressionLayer, cors::CorsLayer};


pub async fn get_index(request: Request) -> (StatusCode, Html<String>) {
    info!("-> {}", request.uri());
    info!("<- get_index");
    (StatusCode::OK, Html("<h1>/</h1>".to_string()))
}


#[tokio::main]
async fn main() -> Result<(), Error> {
    let comression_layer: CompressionLayer = CompressionLayer::new()
        .br(true)
        .deflate(true)
        .gzip(true)
        .zstd(true);

    let app: Router = Router::new()
        .route("/", get(get_index))
        .layer(comression_layer)
        .fallback(not_found);


When I test the service locally (using cargo lambda) I get the following:

❯ curl -H "Accept-Encoding: gzip, deflate, br" -i http://127.0.0.1:9000/
HTTP/1.1 200 OK
content-type: text/html; charset=utf-8
content-length: 10
access-control-allow-credentials: true
vary: origin
vary: access-control-request-method
vary: access-control-request-headers
date: Sun, 24 Dec 2023 21:17:29 GMT

<h1>/</h1>⏎

Am I missing something trivial here or the API should return compressed content?


Solution

  • I looked at the docs for CompressionLayer -> CompressionLayer::compress_when -> Compression::compress_when -> DefaultPredicate:

    This will compress responses unless:

    • ...
    • The response is less than 32 bytes.

    Yours appears to be 11 bytes. You can override this to test if it's working by using a closure or function as the predicate, which should look something like this:

    let comression_layer: CompressionLayer = CompressionLayer::new()
        .br(true)
        .deflate(true)
        .gzip(true)
        .zstd(true)
        .compress_when(|_, _, _, _| true);
    

    Or you can just send a larger response.


    On the HTTP side, the server is allowed to use the identity encoding (no compression) as long as it hasn't been explicitly forbidden. So you could try this curl command to require compression:

    curl -H "Accept-Encoding: gzip, deflate, br, identity;q=0" -i http://127.0.0.1:9000/