// Per-message compression (RFC 1952 gzip over RFC 1951 DEFLATE, and the raw DEFLATE stream
// gRPC calls `deflate`): `gunzip` inflates a gzip member, the client engine transparently
// inflates a `grpc-encoding: gzip` reply whose message has the compression flag set, and
// the negotiation decides which encoding a reply may use. The vectors are real gzip
// members (fixed-Huffman, stored, and one with an FNAME field); the last is delivered as a
// compressed reply the caller receives already decompressed.
//
// moon run src/examples/16-gzip
///|
fn ascii(bs : Bytes) -> String {
let buf = StringBuilder()
for b in bs {
buf.write_char((b.to_int() & 0xFF).unsafe_to_char())
}
buf.to_string()
}
///|
fn run() -> Unit raise {
// Which encoding a reply may go out under: the server's choice, kept only when the
// caller listed it, and identity when it did not.
let accepts = b"identity,gzip,deflate"
println(
"negotiated with \"\{ascii(accepts)}\": gzip -> \{@moonrpc.negotiated(Gzip, accepts[:]).code()}",
)
println(
"negotiated with \"identity\": gzip -> \{@moonrpc.negotiated(Gzip, b"identity"[:]).code()}",
)
// A body survives both encodings, and both shrink a repetitive one.
let body = b"the quick brown fox jumps over the lazy dog. ".repeat(16)
let all : Array[@moonrpc.Encoding] = [Identity, Gzip, Deflate]
for encoding in all {
let encoded = encoding.encode(body[:])
let back = encoding.decode(encoded[:]).unwrap()
println(
"\{encoding.code()}: \{body.length()} -> \{encoded.length()} octets, round trip \{back == body}",
)
}
// Three real gzip members, one per DEFLATE block type / optional field.
let fixed = @base16.decode_lossy(
"1f8b0800cb9b6a6a02ffcb48cdc9c9d75148afca2c5028cf2fca490100eb83468211000000",
)
let stored = @base16.decode_lossy(
"1f8b0800fe9d6a6a04ff011d00e2ff73746f72656420626c6f636b2c206e6f20687566666d616e20686572652b46bf561d000000",
)
let named = @base16.decode_lossy(
"1f8b08080000000002ff68692e74787400cb4bcc4d4d51c84dcd4d4a2d522848acccc94f4c01004e5eca2a14000000",
)
println("gunzip fixed-Huffman: \{ascii(@gzip.gunzip(fixed[:]))}")
println("gunzip stored block: \{ascii(@gzip.gunzip(stored[:]))}")
println("gunzip FNAME member: \{ascii(@gzip.gunzip(named[:]))}")
// The client-side path: a peer replies `grpc-encoding: gzip` with a compression-flagged
// message; the caller gets the plaintext, never the gzip bytes.
let client = @moonrpc.H2Client::new()
let (id, _req) = client.unary("/echo.Echo/Say", b"x")
let _ = client.feed(Settings(params=[], ack=false))
let enc = @hpack.Encoder::new()
let hblock = enc.encode([
{ name: b":status", value: b"200", },
{ name: b"content-type", value: b"application/grpc", },
{ name: b"grpc-encoding", value: b"gzip", },
])
let _ = client.feed(
Headers(
stream_id=id,
fragment=hblock,
end_stream=false,
end_headers=true,
priority=None,
padding=0,
),
)
let _ = client.feed(
Data(
stream_id=id,
data=@moonrpc.encode_message(fixed, compressed=true),
end_stream=false,
padding=0,
),
)
let tblock = enc.encode([{ name: b"grpc-status", value: b"0", }])
let _ = client.feed(
Headers(
stream_id=id,
fragment=tblock,
end_stream=true,
end_headers=true,
priority=None,
padding=0,
),
)
let reply = client.reply(id)
println(
"gzip reply inflated by the client: \{ascii(reply.messages[0])} (grpc-status \{reply.grpc_status})",
)
}
///|
#coverage.skip
fn main {
run() catch {
e => println("error: \{e}")
}
}