Brotli little proxy support#75
Conversation
|
[ch61834] Automatically linked to https://app.clubhouse.io/vgs/story/61834 |
a4f92a1 to
d2cc6cf
Compare
| if (initialRequest instanceof ReferenceCounted) { | ||
| ((ReferenceCounted)initialRequest).release(); | ||
| } | ||
| } |
There was a problem hiding this comment.
@viacheslav-fomin-main i am not sure we need this
could pls take a look?
There was a problem hiding this comment.
yeah, I think this is not needed
…rotliDecoder.java for readability
… an embedded channel
…ssed file should be the array size length
| InternalLoggerFactory.getInstance(BrotliDecoder.class); | ||
|
|
||
| static { | ||
| BrotliLoader.isBrotliAvailable(); |
| return null; | ||
| } | ||
|
|
||
| ByteArrayOutputStream out; |
There was a problem hiding this comment.
can we put it inside of try / finally?
|
|
||
| public static byte[] decompress(byte[] compressedArray) throws IOException { | ||
| if(compressedArray == null) { | ||
| return null; |
| try (BrotliInputStream is = new BrotliInputStream(new ByteArrayInputStream(compressedArray))) { | ||
| out = new ByteArrayOutputStream(); | ||
| if (!decompress(out, is)) { | ||
| return null; |
| return null; | ||
| } | ||
| } catch (IOException e) { | ||
| log.error("Unhandled exception when decompressing brotli", e); |
There was a problem hiding this comment.
why is this error? are there log.error in the gzip decoder? try log.warn
There was a problem hiding this comment.
also this will duplicate log error as the exception is rehtrown
| 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; | ||
| } |
There was a problem hiding this comment.
| 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; | |
| } | |
| } |
There was a problem hiding this comment.
this impl has an uneven error handling strategy.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
| protected void aggregateContentForFiltering(ChannelPipeline pipeline, | ||
| int numberOfBytesToBuffer) { | ||
| pipeline.addLast("inflater", new HttpContentDecompressor()); | ||
| pipeline.addLast("inflater", new BrotliHttpContentDecompressor()); |
There was a problem hiding this comment.
the name was general before and now it is brotli specific, isn't it now handing 2 different compression algorithms?
There was a problem hiding this comment.
can it be GeneralHttpContentDecompressor?
| @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()); | ||
| } | ||
| } |
There was a problem hiding this comment.
| @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(); | |
| } | |
| } | |
| } | |
| } |
There was a problem hiding this comment.
this implementation follows practices established by other implementations of the decoder i found in the netty source files
There was a problem hiding this comment.
a buffer should be released if the decompression was unsuccessful. otherwise we have a memory leak
There was a problem hiding this comment.
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())) { |
There was a problem hiding this comment.
usually the buffer allocation is done via ctx.
| public BrotliDecoder() { | ||
| } | ||
|
|
||
| public static byte[] decompress(byte[] compressedArray) throws IOException { |
There was a problem hiding this comment.
who is the consumer of this public method? so far i see only tests. if so, pls remove it.
| 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; | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
| 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)) { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
it seems it can be moved inside try
| } | ||
| } finally { | ||
| if (!success) { | ||
| if (outBuffer != null) { |
|
|
||
| String contentEncoding = response.headers().get(HttpHeaderNames.CONTENT_ENCODING); | ||
| if (contentEncoding != null) { | ||
| // Content-Encoding was set, either as something specific or as the IDENTITY encoding |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
comment seems to be identical to the above, can we describe this one properly?
Fixes https://app.clubhouse.io/vgs/story/61834/investigate-brotli-little-proxy-support
Uses #68