Skip to content

Commit a8aafb2

Browse files
committed
Increase feature coverage
1 parent 32ac414 commit a8aafb2

File tree

16 files changed

+740
-13
lines changed

16 files changed

+740
-13
lines changed

client/src/main/java/org/asynchttpclient/AsyncHttpClientConfig.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,20 @@ public interface AsyncHttpClientConfig {
282282
*/
283283
boolean isHttp2Enabled();
284284

285+
int getHttp2InitialWindowSize();
286+
287+
int getHttp2MaxFrameSize();
288+
289+
int getHttp2HeaderTableSize();
290+
291+
int getHttp2MaxHeaderListSize();
292+
293+
int getHttp2MaxConcurrentStreams();
294+
295+
Duration getHttp2PingInterval();
296+
297+
boolean isHttp2CleartextEnabled();
298+
285299
/**
286300
* @return the size of the SSL session cache, 0 means using the default value
287301
*/

client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClientConfig.java

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@
101101
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultUserAgent;
102102
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultValidateResponseHeaders;
103103
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultWebSocketMaxBufferSize;
104+
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultHttp2CleartextEnabled;
105+
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultHttp2HeaderTableSize;
106+
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultHttp2InitialWindowSize;
107+
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultHttp2MaxConcurrentStreams;
108+
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultHttp2MaxFrameSize;
109+
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultHttp2MaxHeaderListSize;
110+
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultHttp2PingInterval;
104111
import static org.asynchttpclient.config.AsyncHttpClientConfigDefaults.defaultWebSocketMaxFrameSize;
105112

106113
/**
@@ -167,6 +174,13 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {
167174
private final @Nullable SslContext sslContext;
168175
private final @Nullable SslEngineFactory sslEngineFactory;
169176
private final boolean http2Enabled;
177+
private final int http2InitialWindowSize;
178+
private final int http2MaxFrameSize;
179+
private final int http2HeaderTableSize;
180+
private final int http2MaxHeaderListSize;
181+
private final int http2MaxConcurrentStreams;
182+
private final Duration http2PingInterval;
183+
private final boolean http2CleartextEnabled;
170184

171185
// filters
172186
private final List<RequestFilter> requestFilters;
@@ -255,6 +269,13 @@ private DefaultAsyncHttpClientConfig(// http
255269
@Nullable SslContext sslContext,
256270
@Nullable SslEngineFactory sslEngineFactory,
257271
boolean http2Enabled,
272+
int http2InitialWindowSize,
273+
int http2MaxFrameSize,
274+
int http2HeaderTableSize,
275+
int http2MaxHeaderListSize,
276+
int http2MaxConcurrentStreams,
277+
Duration http2PingInterval,
278+
boolean http2CleartextEnabled,
258279

259280
// filters
260281
List<RequestFilter> requestFilters,
@@ -351,6 +372,13 @@ private DefaultAsyncHttpClientConfig(// http
351372
this.sslContext = sslContext;
352373
this.sslEngineFactory = sslEngineFactory;
353374
this.http2Enabled = http2Enabled;
375+
this.http2InitialWindowSize = http2InitialWindowSize;
376+
this.http2MaxFrameSize = http2MaxFrameSize;
377+
this.http2HeaderTableSize = http2HeaderTableSize;
378+
this.http2MaxHeaderListSize = http2MaxHeaderListSize;
379+
this.http2MaxConcurrentStreams = http2MaxConcurrentStreams;
380+
this.http2PingInterval = http2PingInterval;
381+
this.http2CleartextEnabled = http2CleartextEnabled;
354382

355383
// filters
356384
this.requestFilters = requestFilters;
@@ -616,6 +644,41 @@ public boolean isHttp2Enabled() {
616644
return http2Enabled;
617645
}
618646

647+
@Override
648+
public int getHttp2InitialWindowSize() {
649+
return http2InitialWindowSize;
650+
}
651+
652+
@Override
653+
public int getHttp2MaxFrameSize() {
654+
return http2MaxFrameSize;
655+
}
656+
657+
@Override
658+
public int getHttp2HeaderTableSize() {
659+
return http2HeaderTableSize;
660+
}
661+
662+
@Override
663+
public int getHttp2MaxHeaderListSize() {
664+
return http2MaxHeaderListSize;
665+
}
666+
667+
@Override
668+
public int getHttp2MaxConcurrentStreams() {
669+
return http2MaxConcurrentStreams;
670+
}
671+
672+
@Override
673+
public Duration getHttp2PingInterval() {
674+
return http2PingInterval;
675+
}
676+
677+
@Override
678+
public boolean isHttp2CleartextEnabled() {
679+
return http2CleartextEnabled;
680+
}
681+
619682
@Override
620683
public int getSslSessionCacheSize() {
621684
return sslSessionCacheSize;
@@ -856,6 +919,13 @@ public static class Builder {
856919
private @Nullable SslContext sslContext;
857920
private @Nullable SslEngineFactory sslEngineFactory;
858921
private boolean http2Enabled = false;
922+
private int http2InitialWindowSize = defaultHttp2InitialWindowSize();
923+
private int http2MaxFrameSize = defaultHttp2MaxFrameSize();
924+
private int http2HeaderTableSize = defaultHttp2HeaderTableSize();
925+
private int http2MaxHeaderListSize = defaultHttp2MaxHeaderListSize();
926+
private int http2MaxConcurrentStreams = defaultHttp2MaxConcurrentStreams();
927+
private Duration http2PingInterval = defaultHttp2PingInterval();
928+
private boolean http2CleartextEnabled = defaultHttp2CleartextEnabled();
859929

860930
// cookie store
861931
private CookieStore cookieStore = new ThreadSafeCookieStore();
@@ -949,6 +1019,13 @@ public Builder(AsyncHttpClientConfig config) {
9491019
sslContext = config.getSslContext();
9501020
sslEngineFactory = config.getSslEngineFactory();
9511021
http2Enabled = config.isHttp2Enabled();
1022+
http2InitialWindowSize = config.getHttp2InitialWindowSize();
1023+
http2MaxFrameSize = config.getHttp2MaxFrameSize();
1024+
http2HeaderTableSize = config.getHttp2HeaderTableSize();
1025+
http2MaxHeaderListSize = config.getHttp2MaxHeaderListSize();
1026+
http2MaxConcurrentStreams = config.getHttp2MaxConcurrentStreams();
1027+
http2PingInterval = config.getHttp2PingInterval();
1028+
http2CleartextEnabled = config.isHttp2CleartextEnabled();
9521029

9531030
// filters
9541031
requestFilters.addAll(config.getRequestFilters());
@@ -1269,6 +1346,41 @@ public Builder setHttp2Enabled(boolean http2Enabled) {
12691346
return this;
12701347
}
12711348

1349+
public Builder setHttp2InitialWindowSize(int http2InitialWindowSize) {
1350+
this.http2InitialWindowSize = http2InitialWindowSize;
1351+
return this;
1352+
}
1353+
1354+
public Builder setHttp2MaxFrameSize(int http2MaxFrameSize) {
1355+
this.http2MaxFrameSize = http2MaxFrameSize;
1356+
return this;
1357+
}
1358+
1359+
public Builder setHttp2HeaderTableSize(int http2HeaderTableSize) {
1360+
this.http2HeaderTableSize = http2HeaderTableSize;
1361+
return this;
1362+
}
1363+
1364+
public Builder setHttp2MaxHeaderListSize(int http2MaxHeaderListSize) {
1365+
this.http2MaxHeaderListSize = http2MaxHeaderListSize;
1366+
return this;
1367+
}
1368+
1369+
public Builder setHttp2MaxConcurrentStreams(int http2MaxConcurrentStreams) {
1370+
this.http2MaxConcurrentStreams = http2MaxConcurrentStreams;
1371+
return this;
1372+
}
1373+
1374+
public Builder setHttp2PingInterval(Duration http2PingInterval) {
1375+
this.http2PingInterval = http2PingInterval;
1376+
return this;
1377+
}
1378+
1379+
public Builder setHttp2CleartextEnabled(boolean http2CleartextEnabled) {
1380+
this.http2CleartextEnabled = http2CleartextEnabled;
1381+
return this;
1382+
}
1383+
12721384
// filters
12731385
public Builder addRequestFilter(RequestFilter requestFilter) {
12741386
requestFilters.add(requestFilter);
@@ -1502,6 +1614,13 @@ public DefaultAsyncHttpClientConfig build() {
15021614
sslContext,
15031615
sslEngineFactory,
15041616
http2Enabled,
1617+
http2InitialWindowSize,
1618+
http2MaxFrameSize,
1619+
http2HeaderTableSize,
1620+
http2MaxHeaderListSize,
1621+
http2MaxConcurrentStreams,
1622+
http2PingInterval,
1623+
http2CleartextEnabled,
15051624
requestFilters.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(requestFilters),
15061625
responseFilters.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(responseFilters),
15071626
ioExceptionFilters.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(ioExceptionFilters),

client/src/main/java/org/asynchttpclient/config/AsyncHttpClientConfigDefaults.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ public final class AsyncHttpClientConfigDefaults {
8383
public static final String HASHED_WHEEL_TIMER_TICK_DURATION = "hashedWheelTimerTickDuration";
8484
public static final String HASHED_WHEEL_TIMER_SIZE = "hashedWheelTimerSize";
8585
public static final String EXPIRED_COOKIE_EVICTION_DELAY = "expiredCookieEvictionDelay";
86+
public static final String HTTP2_INITIAL_WINDOW_SIZE_CONFIG = "http2InitialWindowSize";
87+
public static final String HTTP2_MAX_FRAME_SIZE_CONFIG = "http2MaxFrameSize";
88+
public static final String HTTP2_HEADER_TABLE_SIZE_CONFIG = "http2HeaderTableSize";
89+
public static final String HTTP2_MAX_HEADER_LIST_SIZE_CONFIG = "http2MaxHeaderListSize";
90+
public static final String HTTP2_MAX_CONCURRENT_STREAMS_CONFIG = "http2MaxConcurrentStreams";
91+
public static final String HTTP2_PING_INTERVAL_CONFIG = "http2PingInterval";
92+
public static final String HTTP2_CLEARTEXT_ENABLED_CONFIG = "http2CleartextEnabled";
8693

8794
public static final String AHC_VERSION;
8895

@@ -332,4 +339,32 @@ public static int defaultHashedWheelTimerSize() {
332339
public static int defaultExpiredCookieEvictionDelay() {
333340
return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + EXPIRED_COOKIE_EVICTION_DELAY);
334341
}
342+
343+
public static int defaultHttp2InitialWindowSize() {
344+
return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + HTTP2_INITIAL_WINDOW_SIZE_CONFIG);
345+
}
346+
347+
public static int defaultHttp2MaxFrameSize() {
348+
return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + HTTP2_MAX_FRAME_SIZE_CONFIG);
349+
}
350+
351+
public static int defaultHttp2HeaderTableSize() {
352+
return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + HTTP2_HEADER_TABLE_SIZE_CONFIG);
353+
}
354+
355+
public static int defaultHttp2MaxHeaderListSize() {
356+
return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + HTTP2_MAX_HEADER_LIST_SIZE_CONFIG);
357+
}
358+
359+
public static int defaultHttp2MaxConcurrentStreams() {
360+
return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + HTTP2_MAX_CONCURRENT_STREAMS_CONFIG);
361+
}
362+
363+
public static Duration defaultHttp2PingInterval() {
364+
return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getDuration(ASYNC_CLIENT_CONFIG_ROOT + HTTP2_PING_INTERVAL_CONFIG);
365+
}
366+
367+
public static boolean defaultHttp2CleartextEnabled() {
368+
return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getBoolean(ASYNC_CLIENT_CONFIG_ROOT + HTTP2_CLEARTEXT_ENABLED_CONFIG);
369+
}
335370
}

client/src/main/java/org/asynchttpclient/netty/channel/ChannelManager.java

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import io.netty.channel.Channel;
2121
import io.netty.channel.ChannelFactory;
2222
import io.netty.channel.ChannelHandler;
23+
import io.netty.channel.ChannelInboundHandlerAdapter;
2324
import io.netty.channel.ChannelInitializer;
2425
import io.netty.channel.ChannelOption;
2526
import io.netty.channel.ChannelPipeline;
@@ -34,13 +35,17 @@
3435
import io.netty.handler.codec.http.websocketx.WebSocket08FrameEncoder;
3536
import io.netty.handler.codec.http.websocketx.WebSocketFrameAggregator;
3637
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler;
38+
import io.netty.handler.codec.http2.DefaultHttp2ResetFrame;
39+
import io.netty.handler.codec.http2.Http2Error;
3740
import io.netty.handler.codec.http2.Http2FrameCodec;
3841
import io.netty.handler.codec.http2.Http2FrameCodecBuilder;
3942
import io.netty.handler.codec.http2.Http2MultiplexHandler;
4043
import io.netty.handler.codec.http2.Http2Settings;
44+
import io.netty.handler.codec.http2.Http2SettingsFrame;
4145
import io.netty.handler.codec.http2.Http2StreamChannel;
4246
import io.netty.handler.logging.LogLevel;
4347
import io.netty.handler.logging.LoggingHandler;
48+
import io.netty.handler.timeout.IdleStateHandler;
4449
import io.netty.handler.proxy.ProxyHandler;
4550
import io.netty.handler.proxy.Socks4ProxyHandler;
4651
import io.netty.handler.proxy.Socks5ProxyHandler;
@@ -67,6 +72,7 @@
6772
import org.asynchttpclient.netty.OnLastHttpContentCallback;
6873
import org.asynchttpclient.netty.handler.AsyncHttpClientHandler;
6974
import org.asynchttpclient.netty.handler.Http2Handler;
75+
import org.asynchttpclient.netty.handler.Http2PingHandler;
7076
import org.asynchttpclient.netty.handler.HttpHandler;
7177
import org.asynchttpclient.netty.handler.WebSocketHandler;
7278
import org.asynchttpclient.netty.request.NettyRequestSender;
@@ -603,21 +609,62 @@ public void upgradePipelineToHttp2(ChannelPipeline pipeline) {
603609
}
604610

605611
// Add HTTP/2 frame codec (handles connection preface, SETTINGS, PING, flow control, etc.)
612+
Http2Settings settings = new Http2Settings()
613+
.initialWindowSize(config.getHttp2InitialWindowSize())
614+
.maxFrameSize(config.getHttp2MaxFrameSize())
615+
.headerTableSize(config.getHttp2HeaderTableSize())
616+
.maxHeaderListSize(config.getHttp2MaxHeaderListSize());
617+
606618
Http2FrameCodec frameCodec = Http2FrameCodecBuilder.forClient()
607-
.initialSettings(Http2Settings.defaultSettings())
619+
.initialSettings(settings)
608620
.build();
609621

610622
// Http2MultiplexHandler creates a child channel per HTTP/2 stream.
611-
// Server-push streams are silently ignored (no-op initializer) since AHC is client-only.
623+
// Server-push streams are rejected with RST_STREAM(REFUSED_STREAM).
612624
Http2MultiplexHandler multiplexHandler = new Http2MultiplexHandler(new ChannelInitializer<Channel>() {
613625
@Override
614626
protected void initChannel(Channel ch) {
615-
// Server push not supported — ignore inbound pushed streams
627+
// Reject server push by sending RST_STREAM(REFUSED_STREAM)
628+
ch.writeAndFlush(new DefaultHttp2ResetFrame(Http2Error.REFUSED_STREAM))
629+
.addListener(f -> ch.close());
616630
}
617631
});
618632

619633
pipeline.addLast(HTTP2_FRAME_CODEC, frameCodec);
620634
pipeline.addLast(HTTP2_MULTIPLEX, multiplexHandler);
635+
636+
// Attach HTTP/2 connection state for MAX_CONCURRENT_STREAMS tracking and GOAWAY draining
637+
Http2ConnectionState state = new Http2ConnectionState();
638+
int configMaxStreams = config.getHttp2MaxConcurrentStreams();
639+
if (configMaxStreams > 0) {
640+
state.updateMaxConcurrentStreams(configMaxStreams);
641+
}
642+
pipeline.channel().attr(Http2ConnectionState.HTTP2_STATE_KEY).set(state);
643+
644+
// Install SETTINGS listener to update MAX_CONCURRENT_STREAMS from server
645+
pipeline.addLast("http2-settings-listener", new ChannelInboundHandlerAdapter() {
646+
@Override
647+
public void channelRead(io.netty.channel.ChannelHandlerContext ctx, Object msg) throws Exception {
648+
if (msg instanceof Http2SettingsFrame) {
649+
Http2SettingsFrame settingsFrame = (Http2SettingsFrame) msg;
650+
Long maxStreams = settingsFrame.settings().maxConcurrentStreams();
651+
if (maxStreams != null) {
652+
Http2ConnectionState connState = ctx.channel().attr(Http2ConnectionState.HTTP2_STATE_KEY).get();
653+
if (connState != null) {
654+
connState.updateMaxConcurrentStreams(maxStreams.intValue());
655+
}
656+
}
657+
}
658+
ctx.fireChannelRead(msg);
659+
}
660+
});
661+
662+
// Install PING handler for keepalive if configured
663+
long pingIntervalMs = config.getHttp2PingInterval().toMillis();
664+
if (pingIntervalMs > 0) {
665+
pipeline.addLast("http2-idle-state", new IdleStateHandler(0, 0, pingIntervalMs, java.util.concurrent.TimeUnit.MILLISECONDS));
666+
pipeline.addLast("http2-ping", new Http2PingHandler());
667+
}
621668
}
622669

623670
public void upgradePipelineForWebSockets(ChannelPipeline pipeline) {
@@ -681,4 +728,8 @@ public ClientStats getClientStats() {
681728
public boolean isOpen() {
682729
return channelPool.isOpen();
683730
}
731+
732+
public boolean isHttp2CleartextEnabled() {
733+
return config.isHttp2Enabled() && config.isHttp2CleartextEnabled();
734+
}
684735
}

0 commit comments

Comments
 (0)