Skip to content

Commit f684e40

Browse files
committed
Make the initial window size for H2 configurable
1 parent 651077d commit f684e40

File tree

9 files changed

+704
-4
lines changed

9 files changed

+704
-4
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"type": "feature",
3+
"category": "Netty NIO HTTP Client",
4+
"description": "`SETTINGS_INITIAL_WINDOW_SIZE` is now configurable on HTTP/2 connections opened by the Netty client using `Http2Configuration#initialWindowSize(Integer)` along with `NettyNioAsyncHttpClient.Builder#http2Configuration(Http2Configuration)`. See https://tools.ietf.org/html/rfc7540#section-6.5.2 for more information."
5+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.http.nio.netty;
17+
18+
import software.amazon.awssdk.annotations.SdkPublicApi;
19+
import software.amazon.awssdk.utils.Validate;
20+
import software.amazon.awssdk.utils.builder.CopyableBuilder;
21+
import software.amazon.awssdk.utils.builder.ToCopyableBuilder;
22+
23+
/**
24+
* Configuration specific to HTTP/2 connections.
25+
*/
26+
@SdkPublicApi
27+
public final class Http2Configuration implements ToCopyableBuilder<Http2Configuration.Builder, Http2Configuration> {
28+
private final Long maxStreams;
29+
private final Integer initialWindowSize;
30+
31+
private Http2Configuration(DefaultBuilder builder) {
32+
this.maxStreams = builder.maxStreams;
33+
this.initialWindowSize = builder.initialWindowSize;
34+
}
35+
36+
/**
37+
* @return The maximum number of streams to be created per HTTP/2 connection.
38+
*/
39+
public Long maxStreams() {
40+
return maxStreams;
41+
}
42+
43+
/**
44+
* @return The initial window size for an HTTP/2 stream.
45+
*/
46+
public Integer initialWindowSize() {
47+
return initialWindowSize;
48+
}
49+
50+
@Override
51+
public Builder toBuilder() {
52+
return new DefaultBuilder(this);
53+
}
54+
55+
@Override
56+
public boolean equals(Object o) {
57+
if (this == o) {
58+
return true;
59+
}
60+
61+
if (o == null || getClass() != o.getClass()) {
62+
return false;
63+
}
64+
65+
Http2Configuration that = (Http2Configuration) o;
66+
67+
if (maxStreams != null ? !maxStreams.equals(that.maxStreams) : that.maxStreams != null) {
68+
return false;
69+
}
70+
71+
return initialWindowSize != null ? initialWindowSize.equals(that.initialWindowSize) : that.initialWindowSize == null;
72+
73+
}
74+
75+
@Override
76+
public int hashCode() {
77+
int result = maxStreams != null ? maxStreams.hashCode() : 0;
78+
result = 31 * result + (initialWindowSize != null ? initialWindowSize.hashCode() : 0);
79+
return result;
80+
}
81+
82+
public static Builder builder() {
83+
return new DefaultBuilder();
84+
}
85+
86+
public interface Builder extends CopyableBuilder<Builder, Http2Configuration> {
87+
88+
/**
89+
* Sets the max number of concurrent streams per connection.
90+
*
91+
* <p>Note that this cannot exceed the value of the MAX_CONCURRENT_STREAMS setting returned by the service. If it
92+
* does the service setting is used instead.</p>
93+
*
94+
* @param maxStreams Max concurrent HTTP/2 streams per connection.
95+
* @return This builder for method chaining.
96+
*/
97+
Builder maxStreams(Long maxStreams);
98+
99+
/**
100+
* Sets initial window size of a stream. This setting is only respected when the HTTP/2 protocol is used.
101+
*
102+
* See <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">https://tools.ietf.org/html/rfc7540#section-6.5.2</a>
103+
* for more information about this parameter.
104+
*
105+
* @param initialWindowSize The initial window size of a stream.
106+
* @return This builder for method chaining.
107+
*/
108+
Builder initialWindowSize(Integer initialWindowSize);
109+
}
110+
111+
private static final class DefaultBuilder implements Builder {
112+
private Long maxStreams;
113+
private Integer initialWindowSize;
114+
115+
private DefaultBuilder() {
116+
}
117+
118+
private DefaultBuilder(Http2Configuration http2Configuration) {
119+
this.maxStreams = http2Configuration.maxStreams;
120+
this.initialWindowSize = http2Configuration.initialWindowSize;
121+
}
122+
123+
@Override
124+
public Builder maxStreams(Long maxStreams) {
125+
this.maxStreams = Validate.isPositiveOrNull(maxStreams, "maxStreams");
126+
return this;
127+
}
128+
129+
public void setMaxStreams(Long maxStreams) {
130+
maxStreams(maxStreams);
131+
}
132+
133+
@Override
134+
public Builder initialWindowSize(Integer initialWindowSize) {
135+
this.initialWindowSize = Validate.isPositiveOrNull(initialWindowSize, "initialWindowSize");
136+
return this;
137+
}
138+
139+
public void setInitialWindowSize(Integer initialWindowSize) {
140+
initialWindowSize(initialWindowSize);
141+
}
142+
143+
@Override
144+
public Http2Configuration build() {
145+
return new Http2Configuration(this);
146+
}
147+
}
148+
}

http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClient.java

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import java.util.concurrent.ExecutionException;
4343
import java.util.concurrent.TimeUnit;
4444
import java.util.concurrent.TimeoutException;
45+
import java.util.function.Consumer;
4546
import org.slf4j.Logger;
4647
import org.slf4j.LoggerFactory;
4748
import software.amazon.awssdk.annotations.SdkPublicApi;
@@ -77,6 +78,7 @@ public final class NettyNioAsyncHttpClient implements SdkAsyncHttpClient {
7778

7879
private static final Logger log = LoggerFactory.getLogger(NettyNioAsyncHttpClient.class);
7980
private static final long MAX_STREAMS_ALLOWED = 4294967295L; // unsigned 32-bit, 2^32 -1
81+
private static final int DEFAULT_INITIAL_WINDOW_SIZE = 1_048_576; // 1MiB
8082

8183
// Override connection idle timeout for Netty http client to reduce the frequency of "server failed to complete the
8284
// response error". see https://github.com/aws/aws-sdk-java-v2/issues/1122
@@ -91,13 +93,19 @@ public final class NettyNioAsyncHttpClient implements SdkAsyncHttpClient {
9193
private NettyNioAsyncHttpClient(DefaultBuilder builder, AttributeMap serviceDefaultsMap) {
9294
this.configuration = new NettyConfiguration(serviceDefaultsMap);
9395
Protocol protocol = serviceDefaultsMap.get(SdkHttpConfigurationOption.PROTOCOL);
94-
long maxStreams = builder.maxHttp2Streams == null ? MAX_STREAMS_ALLOWED : builder.maxHttp2Streams;
9596
this.sdkEventLoopGroup = eventLoopGroup(builder);
97+
98+
Http2Configuration http2Configuration = builder.http2Configuration;
99+
100+
long maxStreams = resolveMaxHttp2Streams(builder.maxHttp2Streams, http2Configuration);
101+
int initialWindowSize = resolveInitialWindowSize(http2Configuration);
102+
96103
this.pools = AwaitCloseChannelPoolMap.builder()
97104
.sdkChannelOptions(builder.sdkChannelOptions)
98105
.configuration(configuration)
99106
.protocol(protocol)
100107
.maxStreams(maxStreams)
108+
.initialWindowSize(initialWindowSize)
101109
.sdkEventLoopGroup(sdkEventLoopGroup)
102110
.sslProvider(resolveSslProvider(builder))
103111
.proxyConfiguration(builder.proxyConfiguration)
@@ -149,6 +157,25 @@ private SslProvider resolveSslProvider(DefaultBuilder builder) {
149157
return SslContext.defaultClientProvider();
150158
}
151159

160+
private long resolveMaxHttp2Streams(Integer topLevelValue, Http2Configuration http2Configuration) {
161+
if (topLevelValue != null) {
162+
return topLevelValue;
163+
}
164+
165+
if (http2Configuration == null || http2Configuration.maxStreams() == null) {
166+
return MAX_STREAMS_ALLOWED;
167+
}
168+
169+
return Math.min(http2Configuration.maxStreams(), MAX_STREAMS_ALLOWED);
170+
}
171+
172+
private int resolveInitialWindowSize(Http2Configuration http2Configuration) {
173+
if (http2Configuration == null || http2Configuration.initialWindowSize() == null) {
174+
return DEFAULT_INITIAL_WINDOW_SIZE;
175+
}
176+
return http2Configuration.initialWindowSize();
177+
}
178+
152179
private SdkEventLoopGroup nonManagedEventLoopGroup(SdkEventLoopGroup eventLoopGroup) {
153180
return SdkEventLoopGroup.create(new NonManagedEventLoopGroup(eventLoopGroup.eventLoopGroup()),
154181
eventLoopGroup.channelFactory());
@@ -343,6 +370,9 @@ public interface Builder extends SdkAsyncHttpClient.Builder<NettyNioAsyncHttpCli
343370
*
344371
* @param maxHttp2Streams Max concurrent HTTP/2 streams per connection.
345372
* @return This builder for method chaining.
373+
*
374+
* @deprecated Use {@link #http2Configuration(Http2Configuration)} along with
375+
* {@link Http2Configuration.Builder#maxStreams(Integer)} instead.
346376
*/
347377
Builder maxHttp2Streams(Integer maxHttp2Streams);
348378

@@ -380,6 +410,28 @@ public interface Builder extends SdkAsyncHttpClient.Builder<NettyNioAsyncHttpCli
380410
* @return The builder for method chaining.
381411
*/
382412
Builder tlsKeyManagersProvider(TlsKeyManagersProvider keyManagersProvider);
413+
414+
/**
415+
* Set the HTTP/2 specific configuration for this client.
416+
* <p>
417+
* <b>Note:</b>If {@link #maxHttp2Streams(Integer)} and {@link Http2Configuration#maxStreams()} are both set,
418+
* the value set using {@link #maxHttp2Streams(Integer)} takes precedence.
419+
*
420+
* @param http2Configuration The HTTP/2 configuration object.
421+
* @return the builder for method chaining.
422+
*/
423+
Builder http2Configuration(Http2Configuration http2Configuration);
424+
425+
/**
426+
* Set the HTTP/2 specific configuration for this client.
427+
* <p>
428+
* <b>Note:</b>If {@link #maxHttp2Streams(Integer)} and {@link Http2Configuration#maxStreams()} are both set,
429+
* the value set using {@link #maxHttp2Streams(Integer)} takes precedence.
430+
*
431+
* @param http2ConfigurationBuilderConsumer The consumer of the HTTP/2 configuration builder object.
432+
* @return the builder for method chaining.
433+
*/
434+
Builder http2Configuration(Consumer<Http2Configuration.Builder> http2ConfigurationBuilderConsumer);
383435
}
384436

385437
/**
@@ -394,6 +446,7 @@ private static final class DefaultBuilder implements Builder {
394446
private SdkEventLoopGroup eventLoopGroup;
395447
private SdkEventLoopGroup.Builder eventLoopGroupBuilder;
396448
private Integer maxHttp2Streams;
449+
private Http2Configuration http2Configuration;
397450
private SslProvider sslProvider;
398451
private ProxyConfiguration proxyConfiguration;
399452

@@ -568,6 +621,23 @@ public Builder tlsKeyManagersProvider(TlsKeyManagersProvider tlsKeyManagersProvi
568621
return this;
569622
}
570623

624+
@Override
625+
public Builder http2Configuration(Http2Configuration http2Configuration) {
626+
this.http2Configuration = http2Configuration;
627+
return this;
628+
}
629+
630+
@Override
631+
public Builder http2Configuration(Consumer<Http2Configuration.Builder> http2ConfigurationBuilderConsumer) {
632+
Http2Configuration.Builder builder = Http2Configuration.builder();
633+
http2ConfigurationBuilderConsumer.accept(builder);
634+
return http2Configuration(builder.build());
635+
}
636+
637+
public void setHttp2Configuration(Http2Configuration http2Configuration) {
638+
http2Configuration(http2Configuration);
639+
}
640+
571641
@Override
572642
public SdkAsyncHttpClient buildWithDefaults(AttributeMap serviceDefaults) {
573643
return new NettyNioAsyncHttpClient(this, standardOptions.build()

http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public void channelCreated(Channel ch) throws Exception {
8181
private final NettyConfiguration configuration;
8282
private final Protocol protocol;
8383
private final long maxStreams;
84+
private final int initialWindowSize;
8485
private final SslProvider sslProvider;
8586
private final ProxyConfiguration proxyConfiguration;
8687

@@ -90,6 +91,7 @@ private AwaitCloseChannelPoolMap(Builder builder) {
9091
this.configuration = builder.configuration;
9192
this.protocol = builder.protocol;
9293
this.maxStreams = builder.maxStreams;
94+
this.initialWindowSize = builder.initialWindowSize;
9395
this.sslProvider = builder.sslProvider;
9496
this.proxyConfiguration = builder.proxyConfiguration;
9597
}
@@ -112,8 +114,13 @@ protected SimpleChannelPoolAwareChannelPool newPool(URI key) {
112114

113115
AtomicReference<ChannelPool> channelPoolRef = new AtomicReference<>();
114116

115-
ChannelPipelineInitializer pipelineInitializer =
116-
new ChannelPipelineInitializer(protocol, sslContext, maxStreams, channelPoolRef, configuration, key);
117+
ChannelPipelineInitializer pipelineInitializer = new ChannelPipelineInitializer(protocol,
118+
sslContext,
119+
maxStreams,
120+
initialWindowSize,
121+
channelPoolRef,
122+
configuration,
123+
key);
117124

118125
BetterSimpleChannelPool tcpChannelPool;
119126
ChannelPool baseChannelPool;
@@ -289,6 +296,7 @@ public static class Builder {
289296
private NettyConfiguration configuration;
290297
private Protocol protocol;
291298
private long maxStreams;
299+
private int initialWindowSize;
292300
private SslProvider sslProvider;
293301
private ProxyConfiguration proxyConfiguration;
294302

@@ -320,6 +328,11 @@ public Builder maxStreams(long maxStreams) {
320328
return this;
321329
}
322330

331+
public Builder initialWindowSize(int initialWindowSize) {
332+
this.initialWindowSize = initialWindowSize;
333+
return this;
334+
}
335+
323336
public Builder sslProvider(SslProvider sslProvider) {
324337
this.sslProvider = sslProvider;
325338
return this;

http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ChannelPipelineInitializer.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,19 +53,22 @@ public final class ChannelPipelineInitializer extends AbstractChannelPoolHandler
5353
private final Protocol protocol;
5454
private final SslContext sslCtx;
5555
private final long clientMaxStreams;
56+
private final int clientInitialWindowSize;
5657
private final AtomicReference<ChannelPool> channelPoolRef;
5758
private final NettyConfiguration configuration;
5859
private final URI poolKey;
5960

6061
public ChannelPipelineInitializer(Protocol protocol,
6162
SslContext sslCtx,
6263
long clientMaxStreams,
64+
int clientInitialWindowSize,
6365
AtomicReference<ChannelPool> channelPoolRef,
6466
NettyConfiguration configuration,
6567
URI poolKey) {
6668
this.protocol = protocol;
6769
this.sslCtx = sslCtx;
6870
this.clientMaxStreams = clientMaxStreams;
71+
this.clientInitialWindowSize = clientInitialWindowSize;
6972
this.channelPoolRef = channelPoolRef;
7073
this.configuration = configuration;
7174
this.poolKey = poolKey;
@@ -125,7 +128,7 @@ private void configureHttp2(Channel ch, ChannelPipeline pipeline) {
125128
Http2FrameCodec codec =
126129
Http2FrameCodecBuilder.forClient()
127130
.headerSensitivityDetector((name, value) -> lowerCase(name.toString()).equals("authorization"))
128-
.initialSettings(Http2Settings.defaultSettings().initialWindowSize(1_048_576))
131+
.initialSettings(Http2Settings.defaultSettings().initialWindowSize(clientInitialWindowSize))
129132
.frameLogger(new Http2FrameLogger(LogLevel.DEBUG))
130133
.build();
131134

0 commit comments

Comments
 (0)