Skip to content

Commit adbf992

Browse files
committed
Configure default SSL context in Netty RequestFactory
This commit configures a default SslContext if none has been provided. This also enforces separate Netty bootstrap instances for cleartext and TLS exchanges. Issue: SPR-14744 (cherry picked from 53441f8)
1 parent b3a1e5f commit adbf992

File tree

1 file changed

+57
-27
lines changed

1 file changed

+57
-27
lines changed

spring-web/src/main/java/org/springframework/http/client/Netty4ClientHttpRequestFactory.java

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.IOException;
2020
import java.net.URI;
2121
import java.util.concurrent.TimeUnit;
22+
import javax.net.ssl.SSLException;
2223

2324
import io.netty.bootstrap.Bootstrap;
2425
import io.netty.channel.ChannelConfig;
@@ -32,6 +33,7 @@
3233
import io.netty.handler.codec.http.HttpClientCodec;
3334
import io.netty.handler.codec.http.HttpObjectAggregator;
3435
import io.netty.handler.ssl.SslContext;
36+
import io.netty.handler.ssl.SslContextBuilder;
3537
import io.netty.handler.timeout.ReadTimeoutHandler;
3638

3739
import org.springframework.beans.factory.DisposableBean;
@@ -48,6 +50,7 @@
4850
*
4951
* @author Arjen Poutsma
5052
* @author Rossen Stoyanchev
53+
* @author Brian Clozel
5154
* @since 4.1.2
5255
*/
5356
public class Netty4ClientHttpRequestFactory implements ClientHttpRequestFactory,
@@ -74,6 +77,8 @@ public class Netty4ClientHttpRequestFactory implements ClientHttpRequestFactory,
7477

7578
private volatile Bootstrap bootstrap;
7679

80+
private volatile Bootstrap sslBootstrap;
81+
7782

7883
/**
7984
* Create a new {@code Netty4ClientHttpRequestFactory} with a default
@@ -99,6 +104,15 @@ public Netty4ClientHttpRequestFactory(EventLoopGroup eventLoopGroup) {
99104
}
100105

101106

107+
private SslContext getDefaultClientSslContext() {
108+
try {
109+
return SslContextBuilder.forClient().build();
110+
}
111+
catch (SSLException exc) {
112+
throw new IllegalStateException("Could not create default client SslContext", exc);
113+
}
114+
}
115+
102116
/**
103117
* Set the default maximum response size.
104118
* <p>By default this is set to {@link #DEFAULT_MAX_RESPONSE_SIZE}.
@@ -112,7 +126,7 @@ public void setMaxResponseSize(int maxResponseSize) {
112126
/**
113127
* Set the SSL context. When configured it is used to create and insert an
114128
* {@link io.netty.handler.ssl.SslHandler} in the channel pipeline.
115-
* <p>By default this is not set.
129+
* <p>A default client SslContext is configured if none has been provided.
116130
*/
117131
public void setSslContext(SslContext sslContext) {
118132
this.sslContext = sslContext;
@@ -136,29 +150,44 @@ public void setReadTimeout(int readTimeout) {
136150
this.readTimeout = readTimeout;
137151
}
138152

139-
private Bootstrap getBootstrap() {
140-
if (this.bootstrap == null) {
141-
Bootstrap bootstrap = new Bootstrap();
142-
bootstrap.group(this.eventLoopGroup).channel(NioSocketChannel.class)
143-
.handler(new ChannelInitializer<SocketChannel>() {
144-
@Override
145-
protected void initChannel(SocketChannel channel) throws Exception {
146-
configureChannel(channel.config());
147-
ChannelPipeline pipeline = channel.pipeline();
148-
if (sslContext != null) {
149-
pipeline.addLast(sslContext.newHandler(channel.alloc()));
150-
}
151-
pipeline.addLast(new HttpClientCodec());
152-
pipeline.addLast(new HttpObjectAggregator(maxResponseSize));
153-
if (readTimeout > 0) {
154-
pipeline.addLast(new ReadTimeoutHandler(readTimeout,
155-
TimeUnit.MILLISECONDS));
156-
}
157-
}
158-
});
159-
this.bootstrap = bootstrap;
153+
private Bootstrap getBootstrap(URI uri) {
154+
final boolean isSecure = (uri.getPort() == 443)
155+
|| (uri.getPort() == -1 && "https".equalsIgnoreCase(uri.getScheme()));
156+
if (isSecure) {
157+
if (this.sslBootstrap == null) {
158+
this.sslBootstrap = buildBootstrap(true);
159+
}
160+
return this.sslBootstrap;
161+
}
162+
else {
163+
if (this.bootstrap == null) {
164+
this.bootstrap = buildBootstrap(false);
165+
}
166+
return this.bootstrap;
160167
}
161-
return this.bootstrap;
168+
}
169+
170+
private Bootstrap buildBootstrap(final boolean isSecure) {
171+
Bootstrap bootstrap = new Bootstrap();
172+
bootstrap.group(this.eventLoopGroup).channel(NioSocketChannel.class)
173+
.handler(new ChannelInitializer<SocketChannel>() {
174+
@Override
175+
protected void initChannel(SocketChannel channel) throws Exception {
176+
configureChannel(channel.config());
177+
ChannelPipeline pipeline = channel.pipeline();
178+
if (isSecure) {
179+
Assert.notNull(sslContext, "sslContext should not be null");
180+
pipeline.addLast(sslContext.newHandler(channel.alloc()));
181+
}
182+
pipeline.addLast(new HttpClientCodec());
183+
pipeline.addLast(new HttpObjectAggregator(maxResponseSize));
184+
if (readTimeout > 0) {
185+
pipeline.addLast(new ReadTimeoutHandler(readTimeout,
186+
TimeUnit.MILLISECONDS));
187+
}
188+
}
189+
});
190+
return bootstrap;
162191
}
163192

164193
/**
@@ -173,11 +202,12 @@ protected void configureChannel(SocketChannelConfig config) {
173202
}
174203

175204
@Override
176-
public void afterPropertiesSet() {
177-
getBootstrap();
205+
public void afterPropertiesSet() throws Exception {
206+
if (this.sslContext == null) {
207+
this.sslContext = getDefaultClientSslContext();
208+
}
178209
}
179210

180-
181211
@Override
182212
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
183213
return createRequestInternal(uri, httpMethod);
@@ -189,7 +219,7 @@ public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod)
189219
}
190220

191221
private Netty4ClientHttpRequest createRequestInternal(URI uri, HttpMethod httpMethod) {
192-
return new Netty4ClientHttpRequest(getBootstrap(), uri, httpMethod);
222+
return new Netty4ClientHttpRequest(getBootstrap(uri), uri, httpMethod);
193223
}
194224

195225

0 commit comments

Comments
 (0)