Browse Source

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
pull/1198/head
Brian Clozel 9 years ago
parent
commit
53441f8962
  1. 84
      spring-web/src/main/java/org/springframework/http/client/Netty4ClientHttpRequestFactory.java

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

@ -19,6 +19,7 @@ package org.springframework.http.client;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLException;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelConfig; import io.netty.channel.ChannelConfig;
@ -32,6 +33,7 @@ import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpClientCodec; import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.ReadTimeoutHandler;
import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.DisposableBean;
@ -48,6 +50,7 @@ import org.springframework.util.Assert;
* *
* @author Arjen Poutsma * @author Arjen Poutsma
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @author Brian Clozel
* @since 4.1.2 * @since 4.1.2
*/ */
public class Netty4ClientHttpRequestFactory implements ClientHttpRequestFactory, public class Netty4ClientHttpRequestFactory implements ClientHttpRequestFactory,
@ -74,6 +77,8 @@ public class Netty4ClientHttpRequestFactory implements ClientHttpRequestFactory,
private volatile Bootstrap bootstrap; private volatile Bootstrap bootstrap;
private volatile Bootstrap sslBootstrap;
/** /**
* Create a new {@code Netty4ClientHttpRequestFactory} with a default * Create a new {@code Netty4ClientHttpRequestFactory} with a default
@ -99,6 +104,15 @@ public class Netty4ClientHttpRequestFactory implements ClientHttpRequestFactory,
} }
private SslContext getDefaultClientSslContext() {
try {
return SslContextBuilder.forClient().build();
}
catch (SSLException exc) {
throw new IllegalStateException("Could not create default client SslContext", exc);
}
}
/** /**
* Set the default maximum response size. * Set the default maximum response size.
* <p>By default this is set to {@link #DEFAULT_MAX_RESPONSE_SIZE}. * <p>By default this is set to {@link #DEFAULT_MAX_RESPONSE_SIZE}.
@ -112,7 +126,7 @@ public class Netty4ClientHttpRequestFactory implements ClientHttpRequestFactory,
/** /**
* Set the SSL context. When configured it is used to create and insert an * Set the SSL context. When configured it is used to create and insert an
* {@link io.netty.handler.ssl.SslHandler} in the channel pipeline. * {@link io.netty.handler.ssl.SslHandler} in the channel pipeline.
* <p>By default this is not set. * <p>A default client SslContext is configured if none has been provided.
*/ */
public void setSslContext(SslContext sslContext) { public void setSslContext(SslContext sslContext) {
this.sslContext = sslContext; this.sslContext = sslContext;
@ -136,29 +150,44 @@ public class Netty4ClientHttpRequestFactory implements ClientHttpRequestFactory,
this.readTimeout = readTimeout; this.readTimeout = readTimeout;
} }
private Bootstrap getBootstrap() { private Bootstrap getBootstrap(URI uri) {
if (this.bootstrap == null) { boolean isSecure = (uri.getPort() == 443)
Bootstrap bootstrap = new Bootstrap(); || (uri.getPort() == -1 && "https".equalsIgnoreCase(uri.getScheme()));
bootstrap.group(this.eventLoopGroup).channel(NioSocketChannel.class) if (isSecure) {
.handler(new ChannelInitializer<SocketChannel>() { if (this.sslBootstrap == null) {
@Override this.sslBootstrap = buildBootstrap(true);
protected void initChannel(SocketChannel channel) throws Exception { }
configureChannel(channel.config()); return this.sslBootstrap;
ChannelPipeline pipeline = channel.pipeline(); }
if (sslContext != null) { else {
pipeline.addLast(sslContext.newHandler(channel.alloc())); if (this.bootstrap == null) {
} this.bootstrap = buildBootstrap(false);
pipeline.addLast(new HttpClientCodec()); }
pipeline.addLast(new HttpObjectAggregator(maxResponseSize)); return this.bootstrap;
if (readTimeout > 0) {
pipeline.addLast(new ReadTimeoutHandler(readTimeout,
TimeUnit.MILLISECONDS));
}
}
});
this.bootstrap = bootstrap;
} }
return this.bootstrap; }
private Bootstrap buildBootstrap(boolean isSecure) {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(this.eventLoopGroup).channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
configureChannel(channel.config());
ChannelPipeline pipeline = channel.pipeline();
if (isSecure) {
Assert.notNull(sslContext, "sslContext should not be null");
pipeline.addLast(sslContext.newHandler(channel.alloc()));
}
pipeline.addLast(new HttpClientCodec());
pipeline.addLast(new HttpObjectAggregator(maxResponseSize));
if (readTimeout > 0) {
pipeline.addLast(new ReadTimeoutHandler(readTimeout,
TimeUnit.MILLISECONDS));
}
}
});
return bootstrap;
} }
/** /**
@ -173,11 +202,12 @@ public class Netty4ClientHttpRequestFactory implements ClientHttpRequestFactory,
} }
@Override @Override
public void afterPropertiesSet() { public void afterPropertiesSet() throws Exception {
getBootstrap(); if (this.sslContext == null) {
this.sslContext = getDefaultClientSslContext();
}
} }
@Override @Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
return createRequestInternal(uri, httpMethod); return createRequestInternal(uri, httpMethod);
@ -189,7 +219,7 @@ public class Netty4ClientHttpRequestFactory implements ClientHttpRequestFactory,
} }
private Netty4ClientHttpRequest createRequestInternal(URI uri, HttpMethod httpMethod) { private Netty4ClientHttpRequest createRequestInternal(URI uri, HttpMethod httpMethod) {
return new Netty4ClientHttpRequest(getBootstrap(), uri, httpMethod); return new Netty4ClientHttpRequest(getBootstrap(uri), uri, httpMethod);
} }

Loading…
Cancel
Save