Skip to content
This repository was archived by the owner on Feb 24, 2026. It is now read-only.

Brotli little proxy support#75

Merged
k-sever merged 43 commits intoverygoodsecurity:vgs-editionfrom
ozoz03:ozasymenko/ch61834/investigate-brotli-little-proxy-support
May 26, 2020
Merged

Brotli little proxy support#75
k-sever merged 43 commits intoverygoodsecurity:vgs-editionfrom
ozoz03:ozasymenko/ch61834/investigate-brotli-little-proxy-support

Conversation

@ozoz03
Copy link
Copy Markdown

@ozoz03 ozoz03 commented May 12, 2020

@osklyarenko
Copy link
Copy Markdown

[ch61834]

Automatically linked to https://app.clubhouse.io/vgs/story/61834

@ozoz03 ozoz03 changed the title Investigate brotli little proxy support [WIP] Investigate brotli little proxy support May 12, 2020
@ozoz03 ozoz03 force-pushed the ozasymenko/ch61834/investigate-brotli-little-proxy-support branch from a4f92a1 to d2cc6cf Compare May 13, 2020 12:24
if (initialRequest instanceof ReferenceCounted) {
((ReferenceCounted)initialRequest).release();
}
}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@viacheslav-fomin-main i am not sure we need this
could pls take a look?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@ozoz03 I don't know where did this commit come from, looks like you rebased your PR incorrecty.
this was removed in #48, pls revert this change.

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.

yeah, I think this is not needed

InternalLoggerFactory.getInstance(BrotliDecoder.class);

static {
BrotliLoader.isBrotliAvailable();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

what is this for?

return null;
}

ByteArrayOutputStream out;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

can we put it inside of try / finally?


public static byte[] decompress(byte[] compressedArray) throws IOException {
if(compressedArray == null) {
return null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

why not return byte[0]?

try (BrotliInputStream is = new BrotliInputStream(new ByteArrayInputStream(compressedArray))) {
out = new ByteArrayOutputStream();
if (!decompress(out, is)) {
return null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

why not return byte[0]?

return null;
}
} catch (IOException e) {
log.error("Unhandled exception when decompressing brotli", e);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

why is this error? are there log.error in the gzip decoder? try log.warn

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

also this will duplicate log error as the exception is rehtrown

Comment on lines +93 to +110
private static boolean decompress(OutputStream output, BrotliInputStream brotliInputStream) throws IOException {
byte[] decompressBuffer = new byte[BROTLI_MAX_NUMBER_OF_BLOCK_TYPES];
// is the stream ready for us to decompress?
int bytesRead;
try {
bytesRead = brotliInputStream.read(decompressBuffer);
} catch (IOException e) {
// unexpected end of input, not ready to decompress, so just return
return false;
}
// continue reading until we have hit EOF
while (bytesRead > -1) { // -1 means EOF
output.write(decompressBuffer, 0, bytesRead);
Arrays.fill(decompressBuffer, (byte) 0);
bytesRead = brotliInputStream.read(decompressBuffer);
}
return true;
}
Copy link
Copy Markdown

@osklyarenko osklyarenko May 21, 2020

Choose a reason for hiding this comment

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

Suggested change
private static boolean decompress(OutputStream output, BrotliInputStream brotliInputStream) throws IOException {
byte[] decompressBuffer = new byte[BROTLI_MAX_NUMBER_OF_BLOCK_TYPES];
// is the stream ready for us to decompress?
int bytesRead;
try {
bytesRead = brotliInputStream.read(decompressBuffer);
} catch (IOException e) {
// unexpected end of input, not ready to decompress, so just return
return false;
}
// continue reading until we have hit EOF
while (bytesRead > -1) { // -1 means EOF
output.write(decompressBuffer, 0, bytesRead);
Arrays.fill(decompressBuffer, (byte) 0);
bytesRead = brotliInputStream.read(decompressBuffer);
}
return true;
}
private static boolean decompress(OutputStream output, BrotliInputStream brotliInputStream) {
byte[] decompressBuffer = new byte[BROTLI_MAX_NUMBER_OF_BLOCK_TYPES];
// is the stream ready for us to decompress?
int bytesRead;
try {
bytesRead = brotliInputStream.read(decompressBuffer);
// continue reading until we have hit EOF
while (bytesRead > -1) { // -1 means EOF
output.write(decompressBuffer, 0, bytesRead);
Arrays.fill(decompressBuffer, (byte) 0);
bytesRead = brotliInputStream.read(decompressBuffer);
}
return true;
} catch (IOException e) {
log.warn("Could not decompress a brotli stream.", e);
// unexpected end of input, not ready to decompress, so just return
return false;
} catch (RuntimeException e) {
log.warn("Could not decompress a brotli stream.", e);
// unexpected runtime error
return false;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

this impl has an uneven error handling strategy.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

changes the impl so
1 - the error handling strategy is consistent
2 - runtime errors coming from the netty byte buf manipulations are accounted for
3 - removed the declared checked IOException from the signature.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

protected void aggregateContentForFiltering(ChannelPipeline pipeline,
int numberOfBytesToBuffer) {
pipeline.addLast("inflater", new HttpContentDecompressor());
pipeline.addLast("inflater", new BrotliHttpContentDecompressor());
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.

the name was general before and now it is brotli specific, isn't it now handing 2 different compression algorithms?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

can it be GeneralHttpContentDecompressor?

Comment on lines +76 to +91
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
/*
use in.alloc().buffer() instead of Unpooled.buffer() as best practice.
See: https://github.com/netty/netty/wiki/New-and-noteworthy-in-4.0#pooled-buffers
*/
try (ByteBufOutputStream output = new ByteBufOutputStream(in.alloc().buffer())) {
try (BrotliInputStream brotliInputStream = new BrotliInputStream(new ByteBufInputStream(in))) {
if (!decompress(output, brotliInputStream)) return;
} catch (IOException e) {
log.error("Unhandled exception when decompressing brotli", e);
throw e;
}
out.add(output.buffer());
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
/*
use in.alloc().buffer() instead of Unpooled.buffer() as best practice.
See: https://github.com/netty/netty/wiki/New-and-noteworthy-in-4.0#pooled-buffers
*/
try (ByteBufOutputStream output = new ByteBufOutputStream(in.alloc().buffer())) {
try (BrotliInputStream brotliInputStream = new BrotliInputStream(new ByteBufInputStream(in))) {
if (!decompress(output, brotliInputStream)) return;
} catch (IOException e) {
log.error("Unhandled exception when decompressing brotli", e);
throw e;
}
out.add(output.buffer());
}
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
/*
use in.alloc().buffer() instead of Unpooled.buffer() as best practice.
See: https://github.com/netty/netty/wiki/New-and-noteworthy-in-4.0#pooled-buffers
*/
boolean success = true;
ByteBuf outBuffer = null;
try (ByteBufOutputStream output = new ByteBufOutputStream(in.alloc().buffer())) {
try (ByteBufInputStream bbin = new ByteBufInputStream(in)) {
try (BrotliInputStream brotliInputStream = new BrotliInputStream(bbin)) {
success = decompress(output, brotliInputStream);
if (!success) {
return;
}
} catch (IOException e) {
log.error("Unhandled exception when decompressing brotli", e);
throw e;
}
}
outBuffer = output.buffer();
if (outBuffer.isReadable()) {
out.add(outBuffer);
} else {
success = false;
}
} finally {
if (!success) {
if (outBuffer != null) {
outBuffer.release();
}
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

this implementation follows practices established by other implementations of the decoder i found in the netty source files

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

a buffer should be released if the decompression was unsuccessful. otherwise we have a memory leak

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

reading properties of an output buffer should be checked. if they are unfit, the buffer is released.

use in.alloc().buffer() instead of Unpooled.buffer() as best practice.
See: https://github.com/netty/netty/wiki/New-and-noteworthy-in-4.0#pooled-buffers
*/
try (ByteBufOutputStream output = new ByteBufOutputStream(in.alloc().buffer())) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

usually the buffer allocation is done via ctx.

public BrotliDecoder() {
}

public static byte[] decompress(byte[] compressedArray) throws IOException {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

who is the consumer of this public method? so far i see only tests. if so, pls remove it.

Comment on lines +103 to +120
protected void encode(ChannelHandlerContext ctx, ByteBuf uncompressed, ByteBuf out) throws Exception {
ByteBufOutputStream dst = new ByteBufOutputStream(uncompressed.alloc().buffer());
Encoder.Parameters params = new Encoder.Parameters().setQuality(this.compressionQuality);
try {
BrotliOutputStream brotliOutputStream = new BrotliOutputStream(dst, params);
try {
brotliOutputStream.write(ByteBufUtil.getBytes(uncompressed));
} finally {
brotliOutputStream.flush();
brotliOutputStream.close();
out.writeBytes(dst.buffer());
}
} catch (IOException e) {
log.error("Unhandled exception when compressing brotli", e);
throw e;
}

}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
protected void encode(ChannelHandlerContext ctx, ByteBuf uncompressed, ByteBuf out) throws Exception {
ByteBufOutputStream dst = new ByteBufOutputStream(uncompressed.alloc().buffer());
Encoder.Parameters params = new Encoder.Parameters().setQuality(this.compressionQuality);
try {
BrotliOutputStream brotliOutputStream = new BrotliOutputStream(dst, params);
try {
brotliOutputStream.write(ByteBufUtil.getBytes(uncompressed));
} finally {
brotliOutputStream.flush();
brotliOutputStream.close();
out.writeBytes(dst.buffer());
}
} catch (IOException e) {
log.error("Unhandled exception when compressing brotli", e);
throw e;
}
}
protected void encode(ChannelHandlerContext ctx, ByteBuf uncompressed, ByteBuf out) throws Exception {
if (!uncompressed.isReadable() || uncompressed.readableBytes() == 0) {
return;
}
ByteBufOutputStream dst = new ByteBufOutputStream(uncompressed.alloc().buffer());
Encoder.Parameters params = new Encoder.Parameters().setQuality(this.compressionQuality);
try {
BrotliOutputStream brotliOutputStream = new BrotliOutputStream(dst, params);
try {
brotliOutputStream.write(ByteBufUtil.getBytes(uncompressed, uncompressed.readerIndex(), uncompressed.readableBytes(), false));
} finally {
brotliOutputStream.flush();
brotliOutputStream.close();
out.writeBytes(dst.buffer());
}
} catch (IOException e) {
log.error("Unhandled exception when compressing brotli", e);
throw e;
}
}

ByteBuf outBuffer = null;
try (ByteBufOutputStream output = new ByteBufOutputStream(in.alloc().buffer())) {
try (ByteBufInputStream bbin = new ByteBufInputStream(in)) {
try (BrotliInputStream brotliInputStream = new BrotliInputStream(bbin)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You can have multiple statements in a single try()

public static boolean decompress(OutputStream output, BrotliInputStream brotliInputStream) {
byte[] decompressBuffer = new byte[BROTLI_MAX_NUMBER_OF_BLOCK_TYPES];
// is the stream ready for us to decompress?
int bytesRead;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

it seems it can be moved inside try

}
} finally {
if (!success) {
if (outBuffer != null) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

can be collapsed with &&


String contentEncoding = response.headers().get(HttpHeaderNames.CONTENT_ENCODING);
if (contentEncoding != null) {
// Content-Encoding was set, either as something specific or as the IDENTITY encoding
Copy link
Copy Markdown

@nestorsokil nestorsokil May 26, 2020

Choose a reason for hiding this comment

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

should this be 'was not set'?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

it means that it was already somehow encoded

private static final int DEFAULT_COMPRESSION_QUALITY = 4;

/*
If the Brotli encoding is being used to compress streams in real-time,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

comment seems to be identical to the above, can we describe this one properly?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

fixed

@ozoz03 ozoz03 requested a review from nestorsokil May 26, 2020 15:55
@k-sever k-sever merged commit cc2c5e3 into verygoodsecurity:vgs-edition May 26, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants