Skip to content
This repository was archived by the owner on May 8, 2026. It is now read-only.

Commit 78b7bea

Browse files
authored
HTTP3: Allow to support non-standard HTTP3 settings (#362)
Motivation: To be able to add extensions that are still in draft we need a way to also allow non-standard HTTP3 settings. This was not possible before. Modifications: - Allow to plugin a NonStandardHttp3SettingsValidator to support non-standard settings - Add unit test Result: Be able to support non-standard HTTP3 settings and so allow to add support for drafts Port of netty/netty#16171
1 parent 21ea64c commit 78b7bea

13 files changed

Lines changed: 171 additions & 43 deletions

src/main/java/io/netty/incubator/codec/http3/DefaultHttp3SettingsFrame.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ public String toString() {
106106
* @return the newly created copy.
107107
*/
108108
public static DefaultHttp3SettingsFrame copyOf(Http3SettingsFrame settingsFrame) {
109-
DefaultHttp3SettingsFrame copy = new DefaultHttp3SettingsFrame();
109+
Http3Settings settingsCopy = new Http3Settings(settingsFrame.settings().nonStandardSettingsValidator);
110+
DefaultHttp3SettingsFrame copy = new DefaultHttp3SettingsFrame(settingsCopy);
110111
if (settingsFrame instanceof DefaultHttp3SettingsFrame) {
111112
copy.settings.putAll(((DefaultHttp3SettingsFrame) settingsFrame).settings);
112113
} else {

src/main/java/io/netty/incubator/codec/http3/Http3ClientConnectionHandler.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,37 @@ public Http3ClientConnectionHandler(@Nullable ChannelHandler inboundControlStrea
5454
@Nullable LongFunction<ChannelHandler> pushStreamHandlerFactory,
5555
@Nullable LongFunction<ChannelHandler> unknownInboundStreamHandlerFactory,
5656
@Nullable Http3SettingsFrame localSettings, boolean disableQpackDynamicTable) {
57+
this(inboundControlStreamHandler, pushStreamHandlerFactory, unknownInboundStreamHandlerFactory, localSettings,
58+
disableQpackDynamicTable, null);
59+
}
60+
61+
/**
62+
* Create a new instance.
63+
*
64+
* @param inboundControlStreamHandler the {@link ChannelHandler} which will be notified about
65+
* {@link Http3RequestStreamFrame}s or {@code null} if the user is not
66+
* interested in these.
67+
* @param pushStreamHandlerFactory the {@link LongFunction} that will provide a custom
68+
* {@link ChannelHandler} for push streams {@code null} if no special
69+
* handling should be done. When present, push ID will be passed as an
70+
* argument to the {@link LongFunction}.
71+
* @param unknownInboundStreamHandlerFactory the {@link LongFunction} that will provide a custom
72+
* {@link ChannelHandler} for unknown inbound stream types or
73+
* {@code null} if no special handling should be done.
74+
* @param localSettings the local {@link Http3SettingsFrame} that should be sent to the
75+
* remote peer or {@code null} if the default settings should be used.
76+
* @param disableQpackDynamicTable If QPACK dynamic table should be disabled.
77+
* @param nonStandardSettingsValidator the {@link Http3Settings.NonStandardHttp3SettingsValidator} to use
78+
* when validating settings that are non-standard.
79+
*/
80+
public Http3ClientConnectionHandler(@Nullable ChannelHandler inboundControlStreamHandler,
81+
@Nullable LongFunction<ChannelHandler> pushStreamHandlerFactory,
82+
@Nullable LongFunction<ChannelHandler> unknownInboundStreamHandlerFactory,
83+
@Nullable Http3SettingsFrame localSettings, boolean disableQpackDynamicTable,
84+
@Nullable Http3Settings.NonStandardHttp3SettingsValidator
85+
nonStandardSettingsValidator) {
5786
super(false, inboundControlStreamHandler, unknownInboundStreamHandlerFactory, localSettings,
58-
disableQpackDynamicTable);
87+
disableQpackDynamicTable, nonStandardSettingsValidator);
5988
this.pushStreamHandlerFactory = pushStreamHandlerFactory;
6089
}
6190

@@ -70,7 +99,7 @@ void initBidirectionalStream(ChannelHandlerContext ctx, QuicStreamChannel channe
7099
void initUnidirectionalStream(ChannelHandlerContext ctx, QuicStreamChannel streamChannel) {
71100
final long maxTableCapacity = maxTableCapacity();
72101
streamChannel.pipeline().addLast(
73-
new Http3UnidirectionalStreamInboundClientHandler(codecFactory,
102+
new Http3UnidirectionalStreamInboundClientHandler(codecFactory, nonStandardSettingsValidator,
74103
localControlStreamHandler, remoteControlStreamHandler,
75104
unknownInboundStreamHandlerFactory, pushStreamHandlerFactory,
76105
() -> new QpackEncoderHandler(maxTableCapacity, qpackDecoder),

src/main/java/io/netty/incubator/codec/http3/Http3ConnectionHandler.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public abstract class Http3ConnectionHandler extends ChannelInboundHandlerAdapte
4242
final Http3ControlStreamOutboundHandler remoteControlStreamHandler;
4343
final QpackDecoder qpackDecoder;
4444
final QpackEncoder qpackEncoder;
45+
final Http3Settings.NonStandardHttp3SettingsValidator nonStandardSettingsValidator;
4546
private boolean controlStreamCreationInProgress;
4647

4748
final long maxTableCapacity;
@@ -61,9 +62,15 @@ public abstract class Http3ConnectionHandler extends ChannelInboundHandlerAdapte
6162
*/
6263
Http3ConnectionHandler(boolean server, @Nullable ChannelHandler inboundControlStreamHandler,
6364
@Nullable LongFunction<ChannelHandler> unknownInboundStreamHandlerFactory,
64-
@Nullable Http3SettingsFrame localSettings, boolean disableQpackDynamicTable) {
65+
@Nullable Http3SettingsFrame localSettings, boolean disableQpackDynamicTable,
66+
@Nullable Http3Settings.NonStandardHttp3SettingsValidator nonStandardSettingsValidator) {
6567
this.unknownInboundStreamHandlerFactory = unknownInboundStreamHandlerFactory;
6668
this.disableQpackDynamicTable = disableQpackDynamicTable;
69+
if (nonStandardSettingsValidator != null) {
70+
this.nonStandardSettingsValidator = nonStandardSettingsValidator;
71+
} else {
72+
this.nonStandardSettingsValidator = (id, value) -> false;
73+
}
6774
if (localSettings == null) {
6875
localSettings = new DefaultHttp3SettingsFrame();
6976
} else {
@@ -81,7 +88,8 @@ public abstract class Http3ConnectionHandler extends ChannelInboundHandlerAdapte
8188
qpackEncoder = new QpackEncoder();
8289
codecFactory = Http3FrameCodec.newFactory(qpackDecoder, maxFieldSectionSize, qpackEncoder);
8390
remoteControlStreamHandler = new Http3ControlStreamOutboundHandler(server, localSettings,
84-
codecFactory.newCodec(Http3FrameTypeValidator.NO_VALIDATION, NO_STATE, NO_STATE));
91+
codecFactory.newCodec(Http3FrameTypeValidator.NO_VALIDATION, NO_STATE, NO_STATE,
92+
this.nonStandardSettingsValidator));
8593
localControlStreamHandler = new Http3ControlStreamInboundHandler(server, inboundControlStreamHandler,
8694
qpackEncoder, remoteControlStreamHandler);
8795
}
@@ -121,7 +129,8 @@ public final boolean isGoAwayReceived() {
121129
*/
122130
public final ChannelHandler newCodec(Http3RequestStreamCodecState encodeState,
123131
Http3RequestStreamCodecState decodeState) {
124-
return codecFactory.newCodec(Http3RequestStreamFrameTypeValidator.INSTANCE, encodeState, decodeState);
132+
return codecFactory.newCodec(
133+
Http3RequestStreamFrameTypeValidator.INSTANCE, encodeState, decodeState, nonStandardSettingsValidator);
125134
}
126135

127136
final ChannelHandler newRequestStreamValidationHandler(

src/main/java/io/netty/incubator/codec/http3/Http3FrameCodec.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ final class Http3FrameCodec extends ByteToMessageDecoder implements ChannelOutbo
6262
private final QpackEncoder qpackEncoder;
6363
private final Http3RequestStreamCodecState encodeState;
6464
private final Http3RequestStreamCodecState decodeState;
65+
private final Http3Settings.NonStandardHttp3SettingsValidator nonStandardSettingsValidator;
6566

6667
private boolean firstFrame = true;
6768
private boolean error;
@@ -77,19 +78,22 @@ static Http3FrameCodecFactory newFactory(QpackDecoder qpackDecoder,
7778
checkNotNull(qpackDecoder, "qpackDecoder");
7879

7980
// QPACK decoder and encoder are shared between streams in a connection.
80-
return (validator, encodeState, decodeState) -> new Http3FrameCodec(validator, qpackDecoder,
81-
maxHeaderListSize, qpackEncoder, encodeState, decodeState);
81+
return (validator, encodeState, decodeState,
82+
nonStandardSettingsValidator) -> new Http3FrameCodec(validator, qpackDecoder,
83+
maxHeaderListSize, qpackEncoder, encodeState, decodeState, nonStandardSettingsValidator);
8284
}
8385

8486
Http3FrameCodec(Http3FrameTypeValidator validator, QpackDecoder qpackDecoder,
8587
long maxHeaderListSize, QpackEncoder qpackEncoder, Http3RequestStreamCodecState encodeState,
86-
Http3RequestStreamCodecState decodeState) {
88+
Http3RequestStreamCodecState decodeState,
89+
Http3Settings.NonStandardHttp3SettingsValidator nonStandardSettingsValidator) {
8790
this.validator = checkNotNull(validator, "validator");
8891
this.qpackDecoder = checkNotNull(qpackDecoder, "qpackDecoder");
8992
this.maxHeaderListSize = checkPositive(maxHeaderListSize, "maxHeaderListSize");
9093
this.qpackEncoder = checkNotNull(qpackEncoder, "qpackEncoder");
9194
this.encodeState = checkNotNull(encodeState, "encodeState");
9295
this.decodeState = checkNotNull(decodeState, "decodeState");
96+
this.nonStandardSettingsValidator = nonStandardSettingsValidator;
9397
}
9498

9599
@Override
@@ -352,7 +356,8 @@ private boolean enforceMaxPayloadLength(
352356

353357
@Nullable
354358
private Http3SettingsFrame decodeSettings(ChannelHandlerContext ctx, ByteBuf in, int payLoadLength) {
355-
Http3SettingsFrame settingsFrame = new DefaultHttp3SettingsFrame();
359+
Http3SettingsFrame settingsFrame = new DefaultHttp3SettingsFrame(
360+
new Http3Settings(nonStandardSettingsValidator));
356361
while (payLoadLength > 0) {
357362
int keyLen = numBytesForVariableLengthInteger(in.getByte(in.readerIndex()));
358363
long key = readVariableLengthInteger(in, keyLen);
@@ -812,6 +817,7 @@ interface Http3FrameCodecFactory {
812817
* @return new codec instance for the passed {@code streamType}.
813818
*/
814819
ChannelHandler newCodec(Http3FrameTypeValidator validator, Http3RequestStreamCodecState encodeState,
815-
Http3RequestStreamCodecState decodeState);
820+
Http3RequestStreamCodecState decodeState,
821+
Http3Settings.NonStandardHttp3SettingsValidator nonStandardSettingsValidator);
816822
}
817823
}

src/main/java/io/netty/incubator/codec/http3/Http3ServerConnectionHandler.java

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,35 @@ public Http3ServerConnectionHandler(ChannelHandler requestStreamHandler,
6262
@Nullable ChannelHandler inboundControlStreamHandler,
6363
@Nullable LongFunction<ChannelHandler> unknownInboundStreamHandlerFactory,
6464
@Nullable Http3SettingsFrame localSettings, boolean disableQpackDynamicTable) {
65+
this(requestStreamHandler, inboundControlStreamHandler, unknownInboundStreamHandlerFactory,
66+
localSettings, disableQpackDynamicTable, null);
67+
}
68+
69+
/**
70+
* Create a new instance.
71+
* @param requestStreamHandler the {@link ChannelHandler} that is used for each new request stream.
72+
* This handler will receive {@link Http3HeadersFrame} and
73+
* {@link Http3DataFrame}s.
74+
* @param inboundControlStreamHandler the {@link ChannelHandler} which will be notified about
75+
* {@link Http3RequestStreamFrame}s or {@code null} if the user is not
76+
* interested in these.
77+
* @param unknownInboundStreamHandlerFactory the {@link LongFunction} that will provide a custom
78+
* {@link ChannelHandler} for unknown inbound stream types or
79+
* {@code null} if no special handling should be done.
80+
* @param localSettings the local {@link Http3SettingsFrame} that should be sent to the
81+
* remote peer or {@code null} if the default settings should be used.
82+
* @param disableQpackDynamicTable If QPACK dynamic table should be disabled.
83+
* @param nonStandardSettingsValidator the {@link Http3Settings.NonStandardHttp3SettingsValidator} to
84+
* use when validating settings that are non-standard.
85+
*/
86+
public Http3ServerConnectionHandler(ChannelHandler requestStreamHandler,
87+
@Nullable ChannelHandler inboundControlStreamHandler,
88+
@Nullable LongFunction<ChannelHandler> unknownInboundStreamHandlerFactory,
89+
@Nullable Http3SettingsFrame localSettings, boolean disableQpackDynamicTable,
90+
@Nullable Http3Settings.NonStandardHttp3SettingsValidator
91+
nonStandardSettingsValidator) {
6592
super(true, inboundControlStreamHandler, unknownInboundStreamHandlerFactory, localSettings,
66-
disableQpackDynamicTable);
93+
disableQpackDynamicTable, nonStandardSettingsValidator);
6794
this.requestStreamHandler = ObjectUtil.checkNotNull(requestStreamHandler, "requestStreamHandler");
6895
}
6996

@@ -84,7 +111,7 @@ void initBidirectionalStream(ChannelHandlerContext ctx, QuicStreamChannel stream
84111
void initUnidirectionalStream(ChannelHandlerContext ctx, QuicStreamChannel streamChannel) {
85112
final long maxTableCapacity = maxTableCapacity();
86113
streamChannel.pipeline().addLast(
87-
new Http3UnidirectionalStreamInboundServerHandler(codecFactory,
114+
new Http3UnidirectionalStreamInboundServerHandler(codecFactory, nonStandardSettingsValidator,
88115
localControlStreamHandler, remoteControlStreamHandler,
89116
unknownInboundStreamHandlerFactory,
90117
() -> new QpackEncoderHandler(maxTableCapacity, qpackDecoder),

src/main/java/io/netty/incubator/codec/http3/Http3Settings.java

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
public final class Http3Settings implements Iterable<Map.Entry<Long, Long>> {
4646

4747
private final LongObjectMap<Long> settings;
48+
final NonStandardHttp3SettingsValidator nonStandardSettingsValidator;
4849

4950
/**
5051
* QPACK maximum table capacity setting identifier (<b>0x1</b>).
@@ -119,26 +120,18 @@ public final class Http3Settings implements Iterable<Map.Entry<Long, Long>> {
119120
* Creates a new instance
120121
*/
121122
public Http3Settings() {
122-
this.settings = new LongObjectHashMap<>(Http3SettingIdentifier.values().length);
123-
}
124-
125-
/**
126-
* Creates a new instance with the specified initial capacity.
127-
*
128-
* @param initialCapacity initial capacity of the underlying map
129-
*/
130-
Http3Settings(int initialCapacity) {
131-
this.settings = new LongObjectHashMap<>(initialCapacity);
123+
this((id, v) -> false);
132124
}
133125

134126
/**
135-
* Creates a new instance with the specified initial capacity and load factor.
127+
* Creates a new instance
136128
*
137-
* @param initialCapacity initial capacity of the underlying map
138-
* @param loadFactor load factor for the underlying map
129+
* @param nonStandardSettingsValidator the {@link NonStandardHttp3SettingsValidator} to use to check if a specific
130+
* setting that is non-standard should be supported or not.
139131
*/
140-
Http3Settings(int initialCapacity, float loadFactor) {
141-
this.settings = new LongObjectHashMap<>(initialCapacity, loadFactor);
132+
public Http3Settings(NonStandardHttp3SettingsValidator nonStandardSettingsValidator) {
133+
this.settings = new LongObjectHashMap<>(Http3SettingIdentifier.values().length);
134+
this.nonStandardSettingsValidator = checkNotNull(nonStandardSettingsValidator, "nonStandardSettingsValidator");
142135
}
143136

144137
/**
@@ -162,14 +155,15 @@ public Long put(long key, Long value) {
162155

163156
Http3SettingIdentifier identifier = Http3SettingIdentifier.fromId(key);
164157

165-
// When Non-Standard/Unknown settings identifier identifier present - Ignore
166158
if (identifier == null) {
167-
return null;
159+
// When Non-Standard/Unknown settings identifier present check if we should ignore it or not.
160+
if (!nonStandardSettingsValidator.validate(key, value)) {
161+
return null;
162+
}
163+
} else {
164+
//Validation
165+
verifyStandardSetting(identifier, value);
168166
}
169-
170-
//Validation
171-
verifyStandardSetting(identifier, value);
172-
173167
return settings.put(key, value);
174168
}
175169

@@ -434,4 +428,20 @@ private static void verifyStandardSetting(Http3SettingIdentifier identifier, Lon
434428
}
435429
}
436430
}
431+
432+
/**
433+
* Allows to handle non-standard settings. By default non-standard settings will be ignore as defined by the
434+
* RFC.
435+
*/
436+
public interface NonStandardHttp3SettingsValidator {
437+
/**
438+
* Validate the setting with the given id and value.
439+
*
440+
* @param id the id of the setting
441+
* @param value the value of the setting
442+
* @return {@code true} if the settings is supported, {@code false} otherwise.
443+
* @throws IllegalArgumentException if the given {@code value} is not supported for the id.
444+
*/
445+
boolean validate(long id, Long value) throws IllegalArgumentException;
446+
}
437447
}

src/main/java/io/netty/incubator/codec/http3/Http3UnidirectionalStreamInboundClientHandler.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ final class Http3UnidirectionalStreamInboundClientHandler extends Http3Unidirect
2929

3030
Http3UnidirectionalStreamInboundClientHandler(
3131
Http3FrameCodecFactory codecFactory,
32+
Http3Settings.NonStandardHttp3SettingsValidator nonStandardSettingsValidator,
3233
Http3ControlStreamInboundHandler localControlStreamHandler,
3334
Http3ControlStreamOutboundHandler remoteControlStreamHandler,
3435
@Nullable LongFunction<ChannelHandler> unknownStreamHandlerFactory,
3536
@Nullable LongFunction<ChannelHandler> pushStreamHandlerFactory,
3637
Supplier<ChannelHandler> qpackEncoderHandlerFactory, Supplier<ChannelHandler> qpackDecoderHandlerFactory) {
37-
super(codecFactory, localControlStreamHandler, remoteControlStreamHandler, unknownStreamHandlerFactory,
38+
super(codecFactory, nonStandardSettingsValidator,
39+
localControlStreamHandler, remoteControlStreamHandler, unknownStreamHandlerFactory,
3840
qpackEncoderHandlerFactory, qpackDecoderHandlerFactory);
3941
this.pushStreamHandlerFactory = pushStreamHandlerFactory == null ? __ -> ReleaseHandler.INSTANCE :
4042
pushStreamHandlerFactory;

0 commit comments

Comments
 (0)