Skip to content

Commit 7bae566

Browse files
Make permessage-deflate server window size and memLevel configurable (#16809)
Motivation: PerMessageDeflateServerExtensionHandshaker and DeflateFrameServerExtensionHandshaker hardcode the server-side deflate window size (15) and memory level (8), so applications cannot trade compression ratio for lower per-connection memory (~300KB per connection). Modification: Add constructor overloads accepting serverWindowSize (8-15) and memLevel (1-9) to both handshakers. For permessage-deflate the negotiated window is min(client_offer, serverWindowSize) per RFC 7692 §7.1.2.1. Existing constructors delegate with the former defaults. Result: Fixes #13083 --------- Co-authored-by: Norman Maurer <[email protected]>
1 parent 9e3bcdf commit 7bae566

7 files changed

Lines changed: 363 additions & 16 deletions

File tree

codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/compression/DeflateEncoder.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@
4141
*/
4242
abstract class DeflateEncoder extends WebSocketExtensionEncoder {
4343

44+
static final int DEFAULT_MEM_LEVEL = 8;
45+
4446
private final int compressionLevel;
4547
private final int windowSize;
48+
private final int memLevel;
4649
private final boolean noContext;
4750
private final WebSocketExtensionFilter extensionEncoderFilter;
4851

@@ -57,8 +60,22 @@ abstract class DeflateEncoder extends WebSocketExtensionEncoder {
5760
*/
5861
DeflateEncoder(int compressionLevel, int windowSize, boolean noContext,
5962
WebSocketExtensionFilter extensionEncoderFilter) {
63+
this(compressionLevel, windowSize, DEFAULT_MEM_LEVEL, noContext, extensionEncoderFilter);
64+
}
65+
66+
/**
67+
* Constructor
68+
* @param compressionLevel compression level of the compressor.
69+
* @param windowSize maximum size of the window compressor buffer.
70+
* @param memLevel internal compression state memory level (1..9).
71+
* @param noContext true to disable context takeover.
72+
* @param extensionEncoderFilter extension encoder filter.
73+
*/
74+
DeflateEncoder(int compressionLevel, int windowSize, int memLevel, boolean noContext,
75+
WebSocketExtensionFilter extensionEncoderFilter) {
6076
this.compressionLevel = compressionLevel;
6177
this.windowSize = windowSize;
78+
this.memLevel = memLevel;
6279
this.noContext = noContext;
6380
this.extensionEncoderFilter = checkNotNull(extensionEncoderFilter, "extensionEncoderFilter");
6481
}
@@ -119,7 +136,7 @@ private ByteBuf compressContent(ChannelHandlerContext ctx, WebSocketFrame msg) {
119136
if (encoder == null) {
120137
encoder = EmbeddedChannel.builder()
121138
.handlers(ZlibCodecFactory.newZlibEncoder(
122-
ZlibWrapper.NONE, compressionLevel, windowSize, 8))
139+
ZlibWrapper.NONE, compressionLevel, windowSize, memLevel))
123140
.build();
124141
}
125142

codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/compression/DeflateFrameServerExtensionHandshaker.java

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,18 @@
3333
public final class DeflateFrameServerExtensionHandshaker implements WebSocketServerExtensionHandshaker {
3434

3535
public static final int DEFAULT_COMPRESSION_LEVEL = 6;
36+
public static final int MIN_WINDOW_SIZE = 8;
37+
public static final int MAX_WINDOW_SIZE = 15;
38+
public static final int DEFAULT_MEM_LEVEL = 8;
39+
public static final int MIN_MEM_LEVEL = 1;
40+
public static final int MAX_MEM_LEVEL = 9;
3641

3742
static final String X_WEBKIT_DEFLATE_FRAME_EXTENSION = "x-webkit-deflate-frame";
3843
static final String DEFLATE_FRAME_EXTENSION = "deflate-frame";
3944

4045
private final int compressionLevel;
46+
private final int windowSize;
47+
private final int memLevel;
4148
private final WebSocketExtensionFilterProvider extensionFilterProvider;
4249
private final int maxAllocation;
4350

@@ -74,7 +81,27 @@ public DeflateFrameServerExtensionHandshaker(int compressionLevel) {
7481
* Maximum size of the decompression buffer. Must be &gt;= 0. If zero, maximum size is not limited.
7582
*/
7683
public DeflateFrameServerExtensionHandshaker(int compressionLevel, int maxAllocation) {
77-
this(compressionLevel, WebSocketExtensionFilterProvider.DEFAULT, maxAllocation);
84+
this(compressionLevel, MAX_WINDOW_SIZE, DEFAULT_MEM_LEVEL, WebSocketExtensionFilterProvider.DEFAULT,
85+
maxAllocation);
86+
}
87+
88+
/**
89+
* Constructor with custom configuration including compressor memory limits.
90+
*
91+
* @param compressionLevel
92+
* Compression level between 0 and 9 (default is 6).
93+
* @param windowSize
94+
* zlib window size in bits (8-15) for the server-side deflater. Lower values reduce
95+
* per-connection memory at the cost of compression ratio.
96+
* @param memLevel
97+
* zlib memory level for the server-side deflater (1-9). Lower values reduce per-connection
98+
* memory at the cost of compression ratio.
99+
* @param maxAllocation
100+
* Maximum size of the decompression buffer. Must be &gt;= 0. If zero, maximum size is not limited.
101+
*/
102+
public DeflateFrameServerExtensionHandshaker(int compressionLevel, int windowSize, int memLevel,
103+
int maxAllocation) {
104+
this(compressionLevel, windowSize, memLevel, WebSocketExtensionFilterProvider.DEFAULT, maxAllocation);
78105
}
79106

80107
/**
@@ -103,15 +130,41 @@ public DeflateFrameServerExtensionHandshaker(int compressionLevel,
103130
* provides server extension filters for per frame deflate encoder and decoder.
104131
* @param maxAllocation
105132
* Maximum size of the decompression buffer. Must be &gt;= 0. If zero, maximum size is not limited.
133+
* @deprecated
134+
* Use {@link DeflateFrameServerExtensionHandshaker#DeflateFrameServerExtensionHandshaker(int, int,
135+
* int, WebSocketExtensionFilterProvider, int)}.
106136
*/
137+
@Deprecated
107138
public DeflateFrameServerExtensionHandshaker(int compressionLevel,
108139
WebSocketExtensionFilterProvider extensionFilterProvider,
109140
int maxAllocation) {
141+
this(compressionLevel, MAX_WINDOW_SIZE, DEFAULT_MEM_LEVEL, extensionFilterProvider, maxAllocation);
142+
}
143+
144+
/**
145+
* Constructor with full custom configuration.
146+
*
147+
* @param compressionLevel
148+
* Compression level between 0 and 9 (default is 6).
149+
* @param windowSize
150+
* zlib window size in bits (8-15) for the server-side deflater.
151+
* @param memLevel
152+
* zlib memory level for the server-side deflater (1-9).
153+
* @param extensionFilterProvider
154+
* provides server extension filters for per frame deflate encoder and decoder.
155+
* @param maxAllocation
156+
* Maximum size of the decompression buffer. Must be &gt;= 0. If zero, maximum size is not limited.
157+
*/
158+
public DeflateFrameServerExtensionHandshaker(int compressionLevel, int windowSize, int memLevel,
159+
WebSocketExtensionFilterProvider extensionFilterProvider,
160+
int maxAllocation) {
110161
if (compressionLevel < 0 || compressionLevel > 9) {
111162
throw new IllegalArgumentException(
112163
"compressionLevel: " + compressionLevel + " (expected: 0-9)");
113164
}
114165
this.compressionLevel = compressionLevel;
166+
this.windowSize = checkInRange(windowSize, MIN_WINDOW_SIZE, MAX_WINDOW_SIZE, "windowSize");
167+
this.memLevel = checkInRange(memLevel, MIN_MEM_LEVEL, MAX_MEM_LEVEL, "memLevel");
115168
this.extensionFilterProvider = checkNotNull(extensionFilterProvider, "extensionFilterProvider");
116169
this.maxAllocation = checkPositiveOrZero(maxAllocation, "maxAllocation");
117170
}
@@ -124,8 +177,8 @@ public WebSocketServerExtension handshakeExtension(WebSocketExtensionData extens
124177
}
125178

126179
if (extensionData.parameters().isEmpty()) {
127-
return new DeflateFrameServerExtension(compressionLevel, extensionData.name(), extensionFilterProvider,
128-
maxAllocation);
180+
return new DeflateFrameServerExtension(compressionLevel, windowSize, memLevel, extensionData.name(),
181+
extensionFilterProvider, maxAllocation);
129182
} else {
130183
return null;
131184
}
@@ -135,14 +188,18 @@ private static class DeflateFrameServerExtension implements WebSocketServerExten
135188

136189
private final String extensionName;
137190
private final int compressionLevel;
191+
private final int windowSize;
192+
private final int memLevel;
138193
private final WebSocketExtensionFilterProvider extensionFilterProvider;
139194
private final int maxAllocation;
140195

141-
DeflateFrameServerExtension(int compressionLevel, String extensionName,
196+
DeflateFrameServerExtension(int compressionLevel, int windowSize, int memLevel, String extensionName,
142197
WebSocketExtensionFilterProvider extensionFilterProvider,
143198
int maxAllocation) {
144199
this.extensionName = extensionName;
145200
this.compressionLevel = compressionLevel;
201+
this.windowSize = windowSize;
202+
this.memLevel = memLevel;
146203
this.extensionFilterProvider = extensionFilterProvider;
147204
this.maxAllocation = maxAllocation;
148205
}
@@ -154,7 +211,7 @@ public int rsv() {
154211

155212
@Override
156213
public WebSocketExtensionEncoder newExtensionEncoder() {
157-
return new PerFrameDeflateEncoder(compressionLevel, 15, false,
214+
return new PerFrameDeflateEncoder(compressionLevel, windowSize, memLevel, false,
158215
extensionFilterProvider.encoderFilter());
159216
}
160217

codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/compression/PerFrameDeflateEncoder.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,20 @@ class PerFrameDeflateEncoder extends DeflateEncoder {
5151
super(compressionLevel, windowSize, noContext, extensionEncoderFilter);
5252
}
5353

54+
/**
55+
* Constructor
56+
*
57+
* @param compressionLevel compression level of the compressor.
58+
* @param windowSize maximum size of the window compressor buffer.
59+
* @param memLevel internal compression state memory level (1..9).
60+
* @param noContext true to disable context takeover.
61+
* @param extensionEncoderFilter extension encoder filter for per frame deflate encoder.
62+
*/
63+
PerFrameDeflateEncoder(int compressionLevel, int windowSize, int memLevel, boolean noContext,
64+
WebSocketExtensionFilter extensionEncoderFilter) {
65+
super(compressionLevel, windowSize, memLevel, noContext, extensionEncoderFilter);
66+
}
67+
5468
@Override
5569
public boolean acceptOutboundMessage(Object msg) throws Exception {
5670
if (!super.acceptOutboundMessage(msg)) {

codec-http/src/main/java/io/netty/handler/codec/http/websocketx/extensions/compression/PerMessageDeflateEncoder.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,20 @@ class PerMessageDeflateEncoder extends DeflateEncoder {
5656
super(compressionLevel, windowSize, noContext, extensionEncoderFilter);
5757
}
5858

59+
/**
60+
* Constructor
61+
*
62+
* @param compressionLevel compression level of the compressor.
63+
* @param windowSize maximum size of the window compressor buffer.
64+
* @param memLevel internal compression state memory level (1..9).
65+
* @param noContext true to disable context takeover.
66+
* @param extensionEncoderFilter extension filter for per message deflate encoder.
67+
*/
68+
PerMessageDeflateEncoder(int compressionLevel, int windowSize, int memLevel, boolean noContext,
69+
WebSocketExtensionFilter extensionEncoderFilter) {
70+
super(compressionLevel, windowSize, memLevel, noContext, extensionEncoderFilter);
71+
}
72+
5973
@Override
6074
public boolean acceptOutboundMessage(Object msg) throws Exception {
6175
if (!super.acceptOutboundMessage(msg)) {

0 commit comments

Comments
 (0)