Skip to content

Commit

Permalink
Supports gzip, deflate, zstd compression level setting
Browse files Browse the repository at this point in the history
- gzip: only the range 0 to 9 is allowed
- deflate: only the range 0 to 9 is allowed
- zstd: only the range -7 to 22 is allowed

brotli and snappy compression are supported by default.

fix: reactor#3244
  • Loading branch information
raccoonback committed Dec 29, 2024
1 parent 94a3d1d commit a060024
Show file tree
Hide file tree
Showing 12 changed files with 343 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
final class Http2StreamBridgeServerHandler extends ChannelDuplexHandler {

final BiPredicate<HttpServerRequest, HttpServerResponse> compress;
final int compressionLevel;
final HttpCompressionSettingsSpec compressionSettings;
final ServerCookieDecoder cookieDecoder;
final ServerCookieEncoder cookieEncoder;
final HttpServerFormDecoderProvider formDecoderProvider;
Expand All @@ -85,7 +85,7 @@ final class Http2StreamBridgeServerHandler extends ChannelDuplexHandler {

Http2StreamBridgeServerHandler(
@Nullable BiPredicate<HttpServerRequest, HttpServerResponse> compress,
int compressionLevel,
HttpCompressionSettingsSpec compressionSettings,
ServerCookieDecoder decoder,
ServerCookieEncoder encoder,
HttpServerFormDecoderProvider formDecoderProvider,
Expand All @@ -96,7 +96,7 @@ final class Http2StreamBridgeServerHandler extends ChannelDuplexHandler {
@Nullable Duration readTimeout,
@Nullable Duration requestTimeout) {
this.compress = compress;
this.compressionLevel = compressionLevel;
this.compressionSettings = compressionSettings;
this.cookieDecoder = decoder;
this.cookieEncoder = encoder;
this.formDecoderProvider = formDecoderProvider;
Expand Down Expand Up @@ -143,7 +143,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) {
listener,
request,
compress,
compressionLevel,
compressionSettings,
connectionInfo,
cookieDecoder,
cookieEncoder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ final class Http3Codec extends ChannelInitializer<QuicStreamChannel> {
final Function<String, String> methodTagValue;
final ChannelMetricsRecorder metricsRecorder;
final int minCompressionSize;
final int compressionLevel;
final HttpCompressionSettingsSpec compressionSettings;
final ChannelOperations.OnSetup opsFactory;
final Duration readTimeout;
final Duration requestTimeout;
Expand All @@ -84,7 +84,7 @@ final class Http3Codec extends ChannelInitializer<QuicStreamChannel> {
@Nullable Function<String, String> methodTagValue,
@Nullable ChannelMetricsRecorder metricsRecorder,
int minCompressionSize,
int compressionLevel,
HttpCompressionSettingsSpec compressionSettings,
ChannelOperations.OnSetup opsFactory,
@Nullable Duration readTimeout,
@Nullable Duration requestTimeout,
Expand All @@ -103,7 +103,7 @@ final class Http3Codec extends ChannelInitializer<QuicStreamChannel> {
this.methodTagValue = methodTagValue;
this.metricsRecorder = metricsRecorder;
this.minCompressionSize = minCompressionSize;
this.compressionLevel = compressionLevel;
this.compressionSettings = compressionSettings;
this.opsFactory = opsFactory;
this.readTimeout = readTimeout;
this.requestTimeout = requestTimeout;
Expand All @@ -121,13 +121,13 @@ protected void initChannel(QuicStreamChannel channel) {

p.addLast(NettyPipeline.H3ToHttp11Codec, new Http3FrameToHttpObjectCodec(true, validate))
.addLast(NettyPipeline.HttpTrafficHandler,
new Http3StreamBridgeServerHandler(compressPredicate, compressionLevel, cookieDecoder, cookieEncoder, formDecoderProvider,
new Http3StreamBridgeServerHandler(compressPredicate, compressionSettings, cookieDecoder, cookieEncoder, formDecoderProvider,
forwardedHeaderHandler, httpMessageLogFactory, listener, mapHandle, readTimeout, requestTimeout));

boolean alwaysCompress = compressPredicate == null && minCompressionSize == 0;

if (alwaysCompress) {
p.addLast(NettyPipeline.CompressionHandler, SimpleCompressionHandler.create(compressionLevel));
p.addLast(NettyPipeline.CompressionHandler, SimpleCompressionHandler.create(compressionSettings));
}

ChannelOperations.addReactiveBridge(channel, opsFactory, listener);
Expand Down Expand Up @@ -169,15 +169,15 @@ static ChannelHandler newHttp3ServerConnectionHandler(
@Nullable Function<String, String> methodTagValue,
@Nullable ChannelMetricsRecorder metricsRecorder,
int minCompressionSize,
int compressionLevel,
HttpCompressionSettingsSpec compressionSettings,
ChannelOperations.OnSetup opsFactory,
@Nullable Duration readTimeout,
@Nullable Duration requestTimeout,
@Nullable Function<String, String> uriTagValue,
boolean validate) {
return new Http3ServerConnectionHandler(
new Http3Codec(accessLogEnabled, accessLog, compressPredicate, decoder, encoder, formDecoderProvider, forwardedHeaderHandler,
httpMessageLogFactory, listener, mapHandle, methodTagValue, metricsRecorder, minCompressionSize, compressionLevel,
httpMessageLogFactory, listener, mapHandle, methodTagValue, metricsRecorder, minCompressionSize, compressionSettings,
opsFactory, readTimeout, requestTimeout, uriTagValue, validate));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ final class Http3ServerOperations extends HttpServerOperations {
ConnectionObserver listener,
HttpRequest nettyRequest,
@Nullable BiPredicate<HttpServerRequest, HttpServerResponse> compressionPredicate,
int compressionLevel,
HttpCompressionSettingsSpec compressionSettings,
ConnectionInfo connectionInfo,
ServerCookieDecoder decoder,
ServerCookieEncoder encoder,
Expand All @@ -55,7 +55,7 @@ final class Http3ServerOperations extends HttpServerOperations {
@Nullable Duration requestTimeout,
boolean secured,
ZonedDateTime timestamp) {
super(c, listener, nettyRequest, compressionPredicate, compressionLevel, connectionInfo, decoder, encoder, formDecoderProvider,
super(c, listener, nettyRequest, compressionPredicate, compressionSettings, connectionInfo, decoder, encoder, formDecoderProvider,
httpMessageLogFactory, isHttp2, mapHandle, readTimeout, requestTimeout, secured, timestamp, true);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@

final class Http3StreamBridgeServerHandler extends ChannelDuplexHandler {
final BiPredicate<HttpServerRequest, HttpServerResponse> compress;
final int compressionLevel;
final HttpCompressionSettingsSpec compressionSettings;
final ServerCookieDecoder cookieDecoder;
final ServerCookieEncoder cookieEncoder;
final HttpServerFormDecoderProvider formDecoderProvider;
Expand All @@ -75,7 +75,7 @@ final class Http3StreamBridgeServerHandler extends ChannelDuplexHandler {

Http3StreamBridgeServerHandler(
@Nullable BiPredicate<HttpServerRequest, HttpServerResponse> compress,
int compressionLevel,
HttpCompressionSettingsSpec compressionSettings,
ServerCookieDecoder decoder,
ServerCookieEncoder encoder,
HttpServerFormDecoderProvider formDecoderProvider,
Expand All @@ -86,7 +86,7 @@ final class Http3StreamBridgeServerHandler extends ChannelDuplexHandler {
@Nullable Duration readTimeout,
@Nullable Duration requestTimeout) {
this.compress = compress;
this.compressionLevel = compressionLevel;
this.compressionSettings = compressionSettings;
this.cookieDecoder = decoder;
this.cookieEncoder = encoder;
this.formDecoderProvider = formDecoderProvider;
Expand Down Expand Up @@ -134,7 +134,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) {
listener,
request,
compress,
compressionLevel,
compressionSettings,
connectionInfo,
cookieDecoder,
cookieEncoder,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
* Copyright (c) 2020-2023 VMware, Inc. or its affiliates, All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package reactor.netty.http.server;

import java.util.ArrayList;
import java.util.List;

import io.netty.handler.codec.compression.Brotli;
import io.netty.handler.codec.compression.BrotliOptions;
import io.netty.handler.codec.compression.CompressionOptions;
import io.netty.handler.codec.compression.DeflateOptions;
import io.netty.handler.codec.compression.GzipOptions;
import io.netty.handler.codec.compression.SnappyOptions;
import io.netty.handler.codec.compression.StandardCompressionOptions;
import io.netty.handler.codec.compression.Zstd;
import io.netty.handler.codec.compression.ZstdOptions;
import io.netty.util.internal.ObjectUtil;

/**
* HTTP Compression configuration builder for the {@link SimpleCompressionHandler}.
*
* @author raccoonback
*/
public final class HttpCompressionSettingsSpec {

private final GzipOptions gzipOptions;
private final DeflateOptions deflateOptions;
private final SnappyOptions snappyOptions;
private BrotliOptions brotliOptions;
private ZstdOptions zstdOptions;

private HttpCompressionSettingsSpec() {
gzipOptions = StandardCompressionOptions.gzip();
deflateOptions = StandardCompressionOptions.deflate();
snappyOptions = StandardCompressionOptions.snappy();

if (Brotli.isAvailable()) {
brotliOptions = StandardCompressionOptions.brotli();
}

if (Zstd.isAvailable()) {
zstdOptions = StandardCompressionOptions.zstd();
}
}

private HttpCompressionSettingsSpec(Build build) {
gzipOptions = build.gzipOptions;
deflateOptions = build.gzipOptions;
snappyOptions = StandardCompressionOptions.snappy();

if (Brotli.isAvailable()) {
brotliOptions = StandardCompressionOptions.brotli();
}

if (Zstd.isAvailable() && build.zstdOptions != null) {
zstdOptions = build.zstdOptions;
}
}

/**
* Creates a builder for {@link HttpCompressionSettingsSpec}.
*
* @return a new {@link HttpCompressionSettingsSpec.Builder}
*/
public static Builder builder() {
return new Build();
}

static HttpCompressionSettingsSpec provideDefault() {
return new HttpCompressionSettingsSpec();
}

CompressionOptions[] adaptToOptions() {
List<CompressionOptions> options = new ArrayList<>();
options.add(this.gzipOptions);
options.add(this.deflateOptions);
options.add(this.snappyOptions);

if (brotliOptions != null) {
options.add(this.brotliOptions);
}

if (zstdOptions != null) {
options.add(this.zstdOptions);
}

return options.toArray(new CompressionOptions[0]);
}

public interface Builder {

/**
* Build a new {@link HttpCompressionSettingsSpec}.
*
* @return a new {@link HttpCompressionSettingsSpec}
*/
HttpCompressionSettingsSpec build();

/**
* Sets the gzip compression level.
*
* @return a new {@link HttpCompressionSettingsSpec.Builder}
*/
Builder gzip(int compressionLevel);

/**
* Sets the deflate compression level.
*
* @return a new {@link HttpCompressionSettingsSpec.Builder}
*/
Builder deflate(int compressionLevel);

/**
* Sets the zstd compression level.
*
* @return a new {@link HttpCompressionSettingsSpec.Builder}
*/
Builder zstd(int compressionLevel);
}

private static final class Build implements Builder {

GzipOptions gzipOptions = StandardCompressionOptions.gzip();
DeflateOptions deflateOptions = StandardCompressionOptions.deflate();
ZstdOptions zstdOptions;

private static final int DEFLATE_DEFAULT_WINDOW_BITS = 15;
private static final int DEFLATE_DEFAULT_MEMORY_LEVEL = 8;
private static final int ZSTD_DEFAULT_COMPRESSION_LEVEL = 3;
private static final int ZSTD_DEFAULT_BLOCK_SIZE = 65536;
private static final int ZSTD_MAX_BLOCK_SIZE = 1 << ZSTD_DEFAULT_COMPRESSION_LEVEL + 7 + 15;

@Override
public HttpCompressionSettingsSpec build() {
return new HttpCompressionSettingsSpec(this);
}

@Override
public Builder gzip(int compressionLevel) {
ObjectUtil.checkInRange(compressionLevel, 0, 9, "compressionLevel");

gzipOptions = StandardCompressionOptions.gzip(compressionLevel, DEFLATE_DEFAULT_WINDOW_BITS, DEFLATE_DEFAULT_MEMORY_LEVEL);
return this;
}

@Override
public Builder deflate(int compressionLevel) {
ObjectUtil.checkInRange(compressionLevel, 0, 9, "compressionLevel");

this.deflateOptions = StandardCompressionOptions.deflate(compressionLevel, DEFLATE_DEFAULT_WINDOW_BITS, DEFLATE_DEFAULT_MEMORY_LEVEL);
return this;
}

@Override
public Builder zstd(int compressionLevel) {
if (!Zstd.isAvailable()) {
throw new IllegalStateException("Unable to set compression level on zstd.");
}
ObjectUtil.checkInRange(compressionLevel, -7, 22, "compressionLevel");

this.zstdOptions = StandardCompressionOptions.zstd(compressionLevel, ZSTD_DEFAULT_BLOCK_SIZE, ZSTD_MAX_BLOCK_SIZE);
return this;
}
}
}
Loading

0 comments on commit a060024

Please sign in to comment.