Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #8769 - Introduce new Compression Handler with support for gzip, brotli, and zstandard #12075

Draft
wants to merge 76 commits into
base: jetty-12.1.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
af99052
WIP DynamicCompressionHandler
joakime Jun 19, 2024
500a140
Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/12.1.x/dy…
joakime Jul 1, 2024
1eb801d
Renaming to compression
joakime Jul 1, 2024
8d7a084
Initial implementation of common handler and gzip impl
joakime Jul 1, 2024
b0fd2d4
Basic initial testing
joakime Jul 1, 2024
b8edbb5
Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/12.1.x/dy…
joakime Jul 2, 2024
dcf6a77
Working basic gzip tests
joakime Jul 2, 2024
4c2e83a
WIP - working on brotli support now
joakime Jul 3, 2024
f88692d
Fixed GzipEncoder with tests
joakime Jul 12, 2024
553aa6c
More GzipDecoder work
joakime Jul 17, 2024
4d10863
Fix basic DynamicCompressionHandlerTest
joakime Jul 17, 2024
3851aa7
Updates to Gzip support
joakime Jul 17, 2024
642a804
Initial pass on BrotliDecoder and tests
joakime Jul 17, 2024
70426b6
Rename zstd to zstandard
joakime Jul 17, 2024
3b86443
WIP on brotli encoding
joakime Jul 22, 2024
c652844
Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/12.1.x/dy…
joakime Jul 22, 2024
f358fdd
Adding some TODOs
joakime Jul 23, 2024
40bf77f
Reworked naming of methods based on review from gregw
joakime Jul 23, 2024
4eea022
Brotli basics are now happy
joakime Jul 24, 2024
2dc73ac
Functional ZstandardDecoder
joakime Aug 5, 2024
cba11ad
Functional ZstandardEncoder
joakime Aug 6, 2024
f650fa2
Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/12.1.x/dy…
joakime Aug 7, 2024
28945df
Fixing compile issue
joakime Aug 7, 2024
4c43193
code-examples is JDK 21 due to virtual threads examples.
joakime Aug 7, 2024
460e780
revert changes to code-examples pom
joakime Aug 8, 2024
d73dc71
WIP in consistent Decoder behavior
joakime Aug 13, 2024
2abd7eb
WIP in RetainableByteBuffer pool hell
joakime Aug 15, 2024
9c8b477
WIP Implementations via Content.Source and Content.Sink
joakime Aug 15, 2024
251d577
WIP New DecoderSource and EncoderSink base classes
joakime Aug 16, 2024
2d764a8
WIP Remove old Decoder/Encoder interfaces
joakime Aug 16, 2024
784bb2e
Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/12.1.x/dy…
joakime Aug 16, 2024
de90708
Revert delete of OneHandler
joakime Aug 16, 2024
2092f0d
Fix javadoc failure
joakime Aug 16, 2024
2cf9990
Using IteratingNestedCallback in encoders now
joakime Aug 20, 2024
c27c8d5
Fixing GzipEncoderSink handling of no-write/empty scenarios
joakime Aug 20, 2024
4b2bc8a
logging fixes
joakime Aug 20, 2024
21ef5ee
Improving DynamicCompressionHandlerTest
joakime Aug 20, 2024
1f7afcf
More TODOs in CompressionConfig
joakime Aug 20, 2024
02bdfff
Using encoderSink properly, and setting Content-Encoding response header
joakime Aug 20, 2024
32af4b9
Idea for ZstandardCompression configuration.
joakime Aug 20, 2024
96aa234
Disable testTwoSmallBlocks
joakime Aug 20, 2024
3bf5979
Applying change to vary header
joakime Aug 21, 2024
55fa414
Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/12.1.x/dy…
joakime Aug 21, 2024
820564a
Fixing build
joakime Aug 21, 2024
5ba1c5d
Work around Brotli4j EncoderJNI.Operation permission issue.
joakime Aug 21, 2024
00b2992
Using released Brotli4j 1.17.0
joakime Aug 21, 2024
1a1102e
Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/12.1.x/dy…
joakime Aug 21, 2024
97545a8
Adding config for encoders and decoders
joakime Aug 21, 2024
7146b2e
Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/12.1.x/dy…
joakime Aug 22, 2024
5189660
Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/12.1.x/dy…
joakime Aug 26, 2024
c5fc49c
Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/12.1.x/dy…
joakime Aug 26, 2024
49e6ee0
WIP - initial pass at modules for compression handler.
joakime Aug 27, 2024
4d4d22a
WIP - rename to CompressionHandler
joakime Aug 27, 2024
dc9193a
Adding license sections to modules for brotli and zstandard
joakime Aug 27, 2024
20c1647
WIP - preparing jetty-home for jetty-compression
joakime Aug 27, 2024
650ab47
More testing of CompressionHandler
joakime Aug 27, 2024
1076746
Using typical Content.Sink.write() behavior with Strings
joakime Sep 11, 2024
bf05bd4
Introducing EncoderSink.ensureByteBuffer to allow encoders to modify …
joakime Sep 11, 2024
73268c4
Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/12.1.x/dy…
joakime Sep 11, 2024
ceab749
Fixing testcase teardown assumption
joakime Sep 11, 2024
2d13ad6
Better zstandard ensureDirect(ByteBuffer)
joakime Sep 11, 2024
3b64b39
Fix GzipEncoderSink bug with large single buffer encodes.
joakime Sep 11, 2024
c8ff0fb
Cleanup code
joakime Sep 12, 2024
4bc4400
WIP: introducing CompressionConfig.Builder
joakime Sep 12, 2024
381d030
Fixing checkstyle
joakime Sep 16, 2024
c92e9b1
Adding compressPath.exclude tests
joakime Sep 19, 2024
742910a
Rework compressPath tests
joakime Sep 23, 2024
cee6f6c
new mimeTypes config tests
joakime Sep 23, 2024
85320ea
Improve mimeTypes config tests
joakime Sep 23, 2024
78d6540
Fixing comment on testMimeTypesConfig
joakime Sep 23, 2024
bc6748d
new compressEncodings config tests
joakime Sep 23, 2024
f50efbe
WIP: method config testing
joakime Sep 23, 2024
ba3e84a
Fixing DecompressionRequest
joakime Sep 25, 2024
38d501a
Splitting mimeTypes and httpMethod into compress/decompress configs
joakime Sep 25, 2024
b46d075
Adding CompressionConfig.Builder.from(MimeTypes)
joakime Sep 26, 2024
1575b0a
Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/12.1.x/dy…
joakime Sep 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions jetty-core/jetty-compression/jetty-compression-brotli/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.jetty.compression</groupId>
<artifactId>jetty-compression</artifactId>
<version>12.1.0-SNAPSHOT</version>
</parent>
<artifactId>jetty-compression-brotli</artifactId>
<packaging>jar</packaging>
<name>Core :: Compression :: Brotli Support</name>
<description>Jetty Core Compression :: Brotli Support</description>

<properties>
<bundle-symbolic-name>${project.groupId}.brotli</bundle-symbolic-name>
</properties>

<dependencies>
<dependency>
<groupId>com.aayushatharva.brotli4j</groupId>
<artifactId>brotli4j</artifactId>
<version>1.16.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.compression</groupId>
<artifactId>jetty-compression-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-slf4j-impl</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.compression.brotli;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteOrder;
import java.util.List;

import com.aayushatharva.brotli4j.Brotli4jLoader;
import com.aayushatharva.brotli4j.decoder.BrotliInputStream;
import com.aayushatharva.brotli4j.encoder.BrotliOutputStream;
import org.eclipse.jetty.compression.Compression;
import org.eclipse.jetty.compression.DecoderSource;
import org.eclipse.jetty.compression.EncoderSink;
import org.eclipse.jetty.http.CompressedContentFormat;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.RetainableByteBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Brotli Compression.
*
* @see <a href="https://datatracker.ietf.org/doc/html/rfc7932">RFC7932: Brotli Compressed Data Format</a>
*/
public class BrotliCompression extends Compression
{
private static final Logger LOG = LoggerFactory.getLogger(BrotliCompression.class);
public static final List<String> EXTENSIONS = List.of("br");

static
{
Brotli4jLoader.ensureAvailability();
}

private static final CompressedContentFormat BR = new CompressedContentFormat("br", ".br");
private static final String ENCODING_NAME = "br";
private static final HttpField X_CONTENT_ENCODING = new PreEncodedHttpField("X-Content-Encoding", ENCODING_NAME);
private static final HttpField CONTENT_ENCODING = new PreEncodedHttpField(HttpHeader.CONTENT_ENCODING, ENCODING_NAME);
private static final int DEFAULT_MIN_BROTLI_SIZE = 48;

private int minCompressSize = DEFAULT_MIN_BROTLI_SIZE;

public BrotliCompression()
{
super(ENCODING_NAME);
}

public com.aayushatharva.brotli4j.encoder.Encoder.Parameters getEncoderParams()
{
com.aayushatharva.brotli4j.encoder.Encoder.Parameters params = new com.aayushatharva.brotli4j.encoder.Encoder.Parameters();
params.setQuality(5); // TODO: make configurable
params.setMode(com.aayushatharva.brotli4j.encoder.Encoder.Mode.GENERIC);
return params;
}

public int getMinCompressSize()
{
return minCompressSize;
}

public void setMinCompressSize(int minCompressSize)
{
this.minCompressSize = Math.max(minCompressSize, DEFAULT_MIN_BROTLI_SIZE);
}

@Override
public boolean acceptsCompression(HttpFields headers, long contentLength)
{
if (contentLength >= 0 && contentLength < minCompressSize)
{
if (LOG.isDebugEnabled())
LOG.debug("{} excluded minCompressSize {}", this, headers);
return false;
}

// check the accept encoding header
if (!headers.contains(HttpHeader.ACCEPT_ENCODING, getEncodingName()))
{
if (LOG.isDebugEnabled())
LOG.debug("{} excluded not {} acceptable {}", this, getEncodingName(), headers);
return false;
}

return true;
}

@Override
public String getName()
{
return "brotli";
}

@Override
public List<String> getFileExtensionNames()
{
return EXTENSIONS;
}

@Override
public HttpField getXContentEncodingField()
{
return X_CONTENT_ENCODING;
}

@Override
public HttpField getContentEncodingField()
{
return CONTENT_ENCODING;
}

@Override
public RetainableByteBuffer acquireByteBuffer()
{
return acquireByteBuffer(getBufferSize());
}

@Override
public RetainableByteBuffer acquireByteBuffer(int length)
{
// Zero-capacity buffers aren't released, they MUST NOT come from the pool.
if (length == 0)
return RetainableByteBuffer.EMPTY;

// Can Brotli4J use direct byte buffers?
RetainableByteBuffer buffer = getByteBufferPool().acquire(length, false);
buffer.getByteBuffer().order(getByteOrder());
return buffer;
}

private ByteOrder getByteOrder()
{
// Per https://datatracker.ietf.org/doc/html/rfc7932#section-1.5
// Brotli is LITTLE_ENDIAN
return ByteOrder.LITTLE_ENDIAN;
}

@Override
public OutputStream newDecoderOutputStream(OutputStream out) throws IOException
{
return new BrotliOutputStream(out);
}

@Override
public DecoderSource newDecoderSource(Content.Source source)
{
return new BrotliDecoderSource(this, source);
}

@Override
public InputStream newEncoderInputStream(InputStream in) throws IOException
{
return new BrotliInputStream(in);
}

@Override
public EncoderSink newEncoderSink(Content.Sink sink)
{
return new BrotliEncoderSink(this, sink);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.compression.brotli;

import java.io.IOException;
import java.nio.ByteBuffer;

import com.aayushatharva.brotli4j.decoder.DecoderJNI;
import org.eclipse.jetty.compression.DecoderSource;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.util.BufferUtil;

public class BrotliDecoderSource extends DecoderSource
{
private static final ByteBuffer EMPTY_BUFFER = BufferUtil.EMPTY_BUFFER;
private final BrotliCompression compression;
private final DecoderJNI.Wrapper decoder;

public BrotliDecoderSource(BrotliCompression compression, Content.Source source)
{
super(source);
this.compression = compression;
try
{
this.decoder = new DecoderJNI.Wrapper(compression.getBufferSize());
}
catch (IOException e)
{
throw new RuntimeException("Unable to initialize Brotli Decoder", e);
}
}

@Override
protected void release()
{
decoder.destroy();
}

@Override
protected Content.Chunk nextChunk(Content.Chunk readChunk) throws IOException
{
ByteBuffer compressed = readChunk.getByteBuffer();
if (readChunk.isLast() && !readChunk.hasRemaining())
return Content.Chunk.EOF;

boolean last = readChunk.isLast();

while (true)
{
switch (decoder.getStatus())
{
case DONE ->
{
return last ? Content.Chunk.EOF : Content.Chunk.EMPTY;
}
case OK ->
{
decoder.push(0);
}
case NEEDS_MORE_INPUT ->
{
ByteBuffer input = decoder.getInputBuffer();
BufferUtil.clearToFill(input);
int len = BufferUtil.put(compressed, input);
decoder.push(len);

if (len == 0)
{
// rely on status.OK to go to EOF.
return Content.Chunk.EMPTY;
}
}
case NEEDS_MORE_OUTPUT ->
{
ByteBuffer output = decoder.pull();
// rely on status.OK to go to EOF
return Content.Chunk.from(output, false);
}
default ->
{
throw new IOException("Decoder failure: Corrupted input buffer");
}
}
}
}
}
Loading
Loading