Skip to content

Commit 4fbbd76

Browse files
petedmarshcodex
andcommitted
netty: Make HPACK dynamic table size configurable
Replace the disable-only Netty client and server options with a byte-size configuration. Keep the default at 4096 bytes, allow zero to disable the dynamic table, and reject negative values. Cap the encoder at the configured size and advertise non-default sizes to the peer. Co-authored-by: Codex <noreply@openai.com>
1 parent 349b4ec commit 4fbbd76

17 files changed

Lines changed: 172 additions & 96 deletions

‎interop-testing/src/test/java/io/grpc/testing/integration/HpackDynamicTableInteropTest.java‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
import org.junit.runner.RunWith;
4545
import org.junit.runners.JUnit4;
4646

47-
/** Interoperability tests for disabling the HPACK dynamic table. */
47+
/** Interoperability tests for configuring the HPACK dynamic table. */
4848
@RunWith(JUnit4.class)
4949
public final class HpackDynamicTableInteropTest {
5050
private static final int CALL_COUNT = 3;
@@ -62,10 +62,10 @@ public final class HpackDynamicTableInteropTest {
6262
private final AtomicInteger requestsWithExpectedMetadata = new AtomicInteger();
6363

6464
@Test
65-
public void defaultOkHttpClient_interoperatesWithDisabledNettyServer() throws Exception {
65+
public void defaultOkHttpClient_interoperatesWithZeroTableNettyServer() throws Exception {
6666
Server server = startServer(
6767
NettyServerBuilder.forPort(0, InsecureServerCredentials.create())
68-
.disableHpackDynamicTable());
68+
.hpackDynamicTableSize(0));
6969
ManagedChannel channel = grpcCleanup.register(
7070
OkHttpChannelBuilder.forAddress("localhost", server.getPort())
7171
.usePlaintext()
@@ -75,13 +75,13 @@ public void defaultOkHttpClient_interoperatesWithDisabledNettyServer() throws Ex
7575
}
7676

7777
@Test
78-
public void disabledNettyClient_interoperatesWithDefaultOkHttpServer() throws Exception {
78+
public void zeroTableNettyClient_interoperatesWithDefaultOkHttpServer() throws Exception {
7979
Server server = startServer(
8080
OkHttpServerBuilder.forPort(0, InsecureServerCredentials.create()));
8181
ManagedChannel channel = grpcCleanup.register(
8282
NettyChannelBuilder.forAddress("localhost", server.getPort())
8383
.usePlaintext()
84-
.disableHpackDynamicTable()
84+
.hpackDynamicTableSize(0)
8585
.build());
8686

8787
makeRepeatedCalls(channel);

‎netty/src/main/java/io/grpc/netty/GrpcHttp2HeadersEncoder.java‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,38 +17,40 @@
1717
package io.grpc.netty;
1818

1919
import io.netty.handler.codec.http2.DefaultHttp2HeadersEncoder;
20+
import io.netty.handler.codec.http2.Http2CodecUtil;
2021
import io.netty.handler.codec.http2.Http2Exception;
2122
import io.netty.handler.codec.http2.Http2HeadersEncoder;
2223

2324
/** HTTP/2 headers encoder with gRPC's HPACK configuration. */
2425
final class GrpcHttp2HeadersEncoder extends DefaultHttp2HeadersEncoder {
26+
static final int DEFAULT_DYNAMIC_TABLE_SIZE = Http2CodecUtil.DEFAULT_HEADER_TABLE_SIZE;
2527
private static final int DEFAULT_DYNAMIC_TABLE_ARRAY_SIZE_HINT = 16;
2628
private static final int MIN_DYNAMIC_TABLE_ARRAY_SIZE_HINT = 2;
2729

28-
private final boolean disableDynamicTable;
30+
private final int configuredMaxDynamicTableSize;
2931

3032
GrpcHttp2HeadersEncoder(
3133
Http2HeadersEncoder.SensitivityDetector sensitivityDetector,
32-
boolean disableDynamicTable) {
34+
int configuredMaxDynamicTableSize) {
3335
super(
3436
sensitivityDetector,
3537
false,
36-
disableDynamicTable
38+
configuredMaxDynamicTableSize == 0
3739
? MIN_DYNAMIC_TABLE_ARRAY_SIZE_HINT : DEFAULT_DYNAMIC_TABLE_ARRAY_SIZE_HINT,
3840
Integer.MAX_VALUE);
39-
this.disableDynamicTable = disableDynamicTable;
40-
if (disableDynamicTable) {
41+
this.configuredMaxDynamicTableSize = configuredMaxDynamicTableSize;
42+
if (configuredMaxDynamicTableSize < DEFAULT_DYNAMIC_TABLE_SIZE) {
4143
try {
42-
super.maxHeaderTableSize(0);
44+
super.maxHeaderTableSize(configuredMaxDynamicTableSize);
4345
} catch (Http2Exception e) {
44-
// Zero is always a valid HPACK dynamic table size.
46+
// Non-negative configured sizes are valid HPACK dynamic table sizes.
4547
throw new AssertionError(e);
4648
}
4749
}
4850
}
4951

5052
@Override
5153
public void maxHeaderTableSize(long max) throws Http2Exception {
52-
super.maxHeaderTableSize(disableDynamicTable ? 0 : max);
54+
super.maxHeaderTableSize(Math.min(configuredMaxDynamicTableSize, max));
5355
}
5456
}

‎netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java‎

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public final class NettyChannelBuilder extends ForwardingChannelBuilder2<NettyCh
110110
private boolean autoFlowControl = DEFAULT_AUTO_FLOW_CONTROL;
111111
private int flowControlWindow = DEFAULT_FLOW_CONTROL_WINDOW;
112112
private final Set<AsciiString> neverIndexedMetadataKeys = new HashSet<>();
113-
private boolean disableHpackDynamicTable;
113+
private int hpackDynamicTableSize = GrpcHttp2HeadersEncoder.DEFAULT_DYNAMIC_TABLE_SIZE;
114114
private int maxHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE;
115115
private int softLimitHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE;
116116
private int maxInboundMessageSize = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
@@ -478,17 +478,17 @@ public NettyChannelBuilder neverIndexMetadataKeys(
478478
}
479479

480480
/**
481-
* Disables use of the HPACK dynamic table for HTTP/2 header compression.
481+
* Sets the maximum HPACK dynamic table size, in bytes, for both directions of a connection.
482+
* The peer may advertise a smaller maximum for headers encoded by this endpoint. A value of
483+
* zero disables the dynamic table while retaining HPACK static-table references and Huffman
484+
* encoding. By default, HTTP/2's standard 4 KiB capacity is used.
482485
*
483-
* <p>HPACK itself remains enabled, as required by HTTP/2. Static table references may still be
484-
* used. Disabling the dynamic table reduces per-connection memory usage, but can increase the
485-
* size of header blocks. The inbound dynamic table is disabled after the peer acknowledges the
486-
* corresponding HTTP/2 setting, and requires a peer that correctly implements that setting. By
487-
* default, the dynamic table is enabled.
486+
* @throws IllegalArgumentException if {@code bytes} is negative
488487
*/
489488
@CanIgnoreReturnValue
490-
public NettyChannelBuilder disableHpackDynamicTable() {
491-
disableHpackDynamicTable = true;
489+
public NettyChannelBuilder hpackDynamicTableSize(int bytes) {
490+
checkArgument(bytes >= 0, "hpackDynamicTableSize must not be negative: %s", bytes);
491+
hpackDynamicTableSize = bytes;
492492
return this;
493493
}
494494

@@ -685,7 +685,7 @@ ClientTransportFactory buildTransportFactory() {
685685
autoFlowControl,
686686
flowControlWindow,
687687
neverIndexedMetadataKeys,
688-
disableHpackDynamicTable,
688+
hpackDynamicTableSize,
689689
maxInboundMessageSize,
690690
maxHeaderListSize,
691691
softLimitHeaderListSize,
@@ -830,7 +830,7 @@ private static final class NettyTransportFactory implements ClientTransportFacto
830830
private final boolean autoFlowControl;
831831
private final int flowControlWindow;
832832
private final Set<AsciiString> neverIndexedMetadataKeys;
833-
private final boolean disableHpackDynamicTable;
833+
private final int hpackDynamicTableSize;
834834
private final int maxMessageSize;
835835
private final int maxHeaderListSize;
836836
private final int softLimitHeaderListSize;
@@ -853,7 +853,7 @@ private static final class NettyTransportFactory implements ClientTransportFacto
853853
boolean autoFlowControl,
854854
int flowControlWindow,
855855
Set<AsciiString> neverIndexedMetadataKeys,
856-
boolean disableHpackDynamicTable,
856+
int hpackDynamicTableSize,
857857
int maxMessageSize,
858858
int maxHeaderListSize,
859859
int softLimitHeaderListSize,
@@ -873,7 +873,7 @@ private static final class NettyTransportFactory implements ClientTransportFacto
873873
this.flowControlWindow = flowControlWindow;
874874
this.neverIndexedMetadataKeys = Collections.unmodifiableSet(
875875
new HashSet<>(checkNotNull(neverIndexedMetadataKeys, "neverIndexedMetadataKeys")));
876-
this.disableHpackDynamicTable = disableHpackDynamicTable;
876+
this.hpackDynamicTableSize = hpackDynamicTableSize;
877877
this.maxMessageSize = maxMessageSize;
878878
this.maxHeaderListSize = maxHeaderListSize;
879879
this.softLimitHeaderListSize = softLimitHeaderListSize;
@@ -924,7 +924,7 @@ public void run() {
924924
autoFlowControl,
925925
flowControlWindow,
926926
neverIndexedMetadataKeys,
927-
disableHpackDynamicTable,
927+
hpackDynamicTableSize,
928928
maxMessageSize,
929929
maxHeaderListSize,
930930
softLimitHeaderListSize,
@@ -965,7 +965,7 @@ public SwapChannelCredentialsResult swapChannelCredentials(ChannelCredentials ch
965965
autoFlowControl,
966966
flowControlWindow,
967967
neverIndexedMetadataKeys,
968-
disableHpackDynamicTable,
968+
hpackDynamicTableSize,
969969
maxMessageSize,
970970
maxHeaderListSize,
971971
softLimitHeaderListSize,

‎netty/src/main/java/io/grpc/netty/NettyClientHandler.java‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ static NettyClientHandler newHandler(
161161
boolean autoFlowControl,
162162
int flowControlWindow,
163163
Set<AsciiString> neverIndexedMetadataKeys,
164-
boolean disableHpackDynamicTable,
164+
int hpackDynamicTableSize,
165165
int maxHeaderListSize,
166166
int softLimitHeaderListSize,
167167
Supplier<Stopwatch> stopwatchFactory,
@@ -176,7 +176,7 @@ static NettyClientHandler newHandler(
176176
Http2HeadersDecoder headersDecoder = new GrpcHttp2ClientHeadersDecoder(maxHeaderListSize);
177177
Http2FrameReader frameReader = new DefaultHttp2FrameReader(headersDecoder);
178178
Http2HeadersEncoder encoder = new GrpcHttp2HeadersEncoder(
179-
sensitivityDetector(neverIndexedMetadataKeys), disableHpackDynamicTable);
179+
sensitivityDetector(neverIndexedMetadataKeys), hpackDynamicTableSize);
180180
Http2FrameWriter frameWriter = new DefaultHttp2FrameWriter(encoder);
181181
Http2Connection connection = new DefaultHttp2Connection(false);
182182
UniformStreamByteDistributor dist = new UniformStreamByteDistributor(connection);
@@ -193,7 +193,7 @@ static NettyClientHandler newHandler(
193193
keepAliveManager,
194194
autoFlowControl,
195195
flowControlWindow,
196-
disableHpackDynamicTable,
196+
hpackDynamicTableSize,
197197
maxHeaderListSize,
198198
softLimitHeaderListSize,
199199
stopwatchFactory,
@@ -215,7 +215,7 @@ static NettyClientHandler newHandler(
215215
KeepAliveManager keepAliveManager,
216216
boolean autoFlowControl,
217217
int flowControlWindow,
218-
boolean disableHpackDynamicTable,
218+
int hpackDynamicTableSize,
219219
int maxHeaderListSize,
220220
int softLimitHeaderListSize,
221221
Supplier<Stopwatch> stopwatchFactory,
@@ -263,8 +263,8 @@ static NettyClientHandler newHandler(
263263
settings.initialWindowSize(flowControlWindow);
264264
settings.maxConcurrentStreams(0);
265265
settings.maxHeaderListSize(maxHeaderListSize);
266-
if (disableHpackDynamicTable) {
267-
settings.headerTableSize(0);
266+
if (hpackDynamicTableSize != GrpcHttp2HeadersEncoder.DEFAULT_DYNAMIC_TABLE_SIZE) {
267+
settings.headerTableSize(hpackDynamicTableSize);
268268
}
269269

270270
return new NettyClientHandler(

‎netty/src/main/java/io/grpc/netty/NettyClientTransport.java‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ class NettyClientTransport implements ConnectionClientTransport,
8787
private final boolean autoFlowControl;
8888
private final int flowControlWindow;
8989
private final Set<AsciiString> neverIndexedMetadataKeys;
90-
private final boolean disableHpackDynamicTable;
90+
private final int hpackDynamicTableSize;
9191
private final int maxMessageSize;
9292
private final int maxHeaderListSize;
9393
private final int softLimitHeaderListSize;
@@ -124,7 +124,7 @@ class NettyClientTransport implements ConnectionClientTransport,
124124
boolean autoFlowControl,
125125
int flowControlWindow,
126126
Set<AsciiString> neverIndexedMetadataKeys,
127-
boolean disableHpackDynamicTable,
127+
int hpackDynamicTableSize,
128128
int maxMessageSize,
129129
int maxHeaderListSize,
130130
int softLimitHeaderListSize,
@@ -152,7 +152,7 @@ class NettyClientTransport implements ConnectionClientTransport,
152152
this.flowControlWindow = flowControlWindow;
153153
this.neverIndexedMetadataKeys =
154154
Preconditions.checkNotNull(neverIndexedMetadataKeys, "neverIndexedMetadataKeys");
155-
this.disableHpackDynamicTable = disableHpackDynamicTable;
155+
this.hpackDynamicTableSize = hpackDynamicTableSize;
156156
this.maxMessageSize = maxMessageSize;
157157
this.maxHeaderListSize = maxHeaderListSize;
158158
this.softLimitHeaderListSize = softLimitHeaderListSize;
@@ -256,7 +256,7 @@ public Runnable start(Listener transportListener) {
256256
autoFlowControl,
257257
flowControlWindow,
258258
neverIndexedMetadataKeys,
259-
disableHpackDynamicTable,
259+
hpackDynamicTableSize,
260260
maxHeaderListSize,
261261
softLimitHeaderListSize,
262262
GrpcUtil.STOPWATCH_SUPPLIER,

‎netty/src/main/java/io/grpc/netty/NettyServer.java‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ class NettyServer implements InternalServer, InternalWithLogId {
9595
private final boolean autoFlowControl;
9696
private final int flowControlWindow;
9797
private final Set<AsciiString> neverIndexedMetadataKeys;
98-
private final boolean disableHpackDynamicTable;
98+
private final int hpackDynamicTableSize;
9999
private final int maxMessageSize;
100100
private final int maxHeaderListSize;
101101
private final int softLimitHeaderListSize;
@@ -135,7 +135,7 @@ class NettyServer implements InternalServer, InternalWithLogId {
135135
boolean autoFlowControl,
136136
int flowControlWindow,
137137
Set<AsciiString> neverIndexedMetadataKeys,
138-
boolean disableHpackDynamicTable,
138+
int hpackDynamicTableSize,
139139
int maxMessageSize,
140140
int maxHeaderListSize,
141141
int softLimitHeaderListSize,
@@ -169,7 +169,7 @@ class NettyServer implements InternalServer, InternalWithLogId {
169169
this.flowControlWindow = flowControlWindow;
170170
this.neverIndexedMetadataKeys = Collections.unmodifiableSet(
171171
new HashSet<>(checkNotNull(neverIndexedMetadataKeys, "neverIndexedMetadataKeys")));
172-
this.disableHpackDynamicTable = disableHpackDynamicTable;
172+
this.hpackDynamicTableSize = hpackDynamicTableSize;
173173
this.maxMessageSize = maxMessageSize;
174174
this.maxHeaderListSize = maxHeaderListSize;
175175
this.softLimitHeaderListSize = softLimitHeaderListSize;
@@ -276,7 +276,7 @@ public void initChannel(Channel ch) {
276276
autoFlowControl,
277277
flowControlWindow,
278278
neverIndexedMetadataKeys,
279-
disableHpackDynamicTable,
279+
hpackDynamicTableSize,
280280
maxMessageSize,
281281
maxHeaderListSize,
282282
softLimitHeaderListSize,

‎netty/src/main/java/io/grpc/netty/NettyServerBuilder.java‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public final class NettyServerBuilder extends ForwardingServerBuilder<NettyServe
111111
private boolean autoFlowControl = true;
112112
private int flowControlWindow = DEFAULT_FLOW_CONTROL_WINDOW;
113113
private final Set<AsciiString> neverIndexedMetadataKeys = new HashSet<>();
114-
private boolean disableHpackDynamicTable;
114+
private int hpackDynamicTableSize = GrpcHttp2HeadersEncoder.DEFAULT_DYNAMIC_TABLE_SIZE;
115115
private int maxMessageSize = DEFAULT_MAX_MESSAGE_SIZE;
116116
private int maxHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE;
117117
private int softLimitHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE;
@@ -485,17 +485,17 @@ public NettyServerBuilder neverIndexMetadataKeys(
485485
}
486486

487487
/**
488-
* Disables use of the HPACK dynamic table for HTTP/2 header compression.
488+
* Sets the maximum HPACK dynamic table size, in bytes, for both directions of a connection.
489+
* The peer may advertise a smaller maximum for headers encoded by this endpoint. A value of
490+
* zero disables the dynamic table while retaining HPACK static-table references and Huffman
491+
* encoding. By default, HTTP/2's standard 4 KiB capacity is used.
489492
*
490-
* <p>HPACK itself remains enabled, as required by HTTP/2. Static table references may still be
491-
* used. Disabling the dynamic table reduces per-connection memory usage, but can increase the
492-
* size of header blocks. The inbound dynamic table is disabled after the peer acknowledges the
493-
* corresponding HTTP/2 setting, and requires a peer that correctly implements that setting. By
494-
* default, the dynamic table is enabled.
493+
* @throws IllegalArgumentException if {@code bytes} is negative
495494
*/
496495
@CanIgnoreReturnValue
497-
public NettyServerBuilder disableHpackDynamicTable() {
498-
disableHpackDynamicTable = true;
496+
public NettyServerBuilder hpackDynamicTableSize(int bytes) {
497+
checkArgument(bytes >= 0, "hpackDynamicTableSize must not be negative: %s", bytes);
498+
hpackDynamicTableSize = bytes;
499499
return this;
500500
}
501501

@@ -789,7 +789,7 @@ NettyServer buildTransportServers(
789789
autoFlowControl,
790790
flowControlWindow,
791791
neverIndexedMetadataKeys,
792-
disableHpackDynamicTable,
792+
hpackDynamicTableSize,
793793
maxMessageSize,
794794
maxHeaderListSize,
795795
softLimitHeaderListSize,

‎netty/src/main/java/io/grpc/netty/NettyServerHandler.java‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ static NettyServerHandler newHandler(
165165
boolean autoFlowControl,
166166
int flowControlWindow,
167167
Set<AsciiString> neverIndexedMetadataKeys,
168-
boolean disableHpackDynamicTable,
168+
int hpackDynamicTableSize,
169169
int maxHeaderListSize,
170170
int softLimitHeaderListSize,
171171
int maxMessageSize,
@@ -188,7 +188,7 @@ static NettyServerHandler newHandler(
188188
new DefaultHttp2FrameReader(headersDecoder), frameLogger);
189189
Http2HeadersEncoder encoder = new GrpcHttp2HeadersEncoder(
190190
NettyClientHandler.sensitivityDetector(neverIndexedMetadataKeys),
191-
disableHpackDynamicTable);
191+
hpackDynamicTableSize);
192192
Http2FrameWriter frameWriter =
193193
new Http2OutboundFrameLogger(new DefaultHttp2FrameWriter(encoder), frameLogger);
194194
return newHandler(
@@ -201,7 +201,7 @@ static NettyServerHandler newHandler(
201201
maxStreams,
202202
autoFlowControl,
203203
flowControlWindow,
204-
disableHpackDynamicTable,
204+
hpackDynamicTableSize,
205205
maxHeaderListSize,
206206
softLimitHeaderListSize,
207207
maxMessageSize,
@@ -229,7 +229,7 @@ static NettyServerHandler newHandler(
229229
int maxStreams,
230230
boolean autoFlowControl,
231231
int flowControlWindow,
232-
boolean disableHpackDynamicTable,
232+
int hpackDynamicTableSize,
233233
int maxHeaderListSize,
234234
int softLimitHeaderListSize,
235235
int maxMessageSize,
@@ -287,8 +287,8 @@ static NettyServerHandler newHandler(
287287
settings.initialWindowSize(flowControlWindow);
288288
settings.maxConcurrentStreams(maxStreams);
289289
settings.maxHeaderListSize(maxHeaderListSize);
290-
if (disableHpackDynamicTable) {
291-
settings.headerTableSize(0);
290+
if (hpackDynamicTableSize != GrpcHttp2HeadersEncoder.DEFAULT_DYNAMIC_TABLE_SIZE) {
291+
settings.headerTableSize(hpackDynamicTableSize);
292292
}
293293

294294
return new NettyServerHandler(

0 commit comments

Comments
 (0)