diff --git a/spring-web/src/main/java/org/springframework/http/client/Netty4ClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/Netty4ClientHttpRequest.java index a8f3b4f6c79e..e7693d832e03 100644 --- a/spring-web/src/main/java/org/springframework/http/client/Netty4ClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/Netty4ClientHttpRequest.java @@ -60,6 +60,7 @@ class Netty4ClientHttpRequest extends AbstractAsyncClientHttpRequest implements private final ByteBufOutputStream body; + Netty4ClientHttpRequest(Bootstrap bootstrap, URI uri, HttpMethod method, int maxRequestSize) { this.bootstrap = bootstrap; this.uri = uri; @@ -67,6 +68,7 @@ class Netty4ClientHttpRequest extends AbstractAsyncClientHttpRequest implements this.body = new ByteBufOutputStream(Unpooled.buffer(maxRequestSize)); } + @Override public HttpMethod getMethod() { return this.method; @@ -83,8 +85,7 @@ protected OutputStream getBodyInternal(HttpHeaders headers) throws IOException { } @Override - protected ListenableFuture executeInternal(final HttpHeaders headers) - throws IOException { + protected ListenableFuture executeInternal(final HttpHeaders headers) throws IOException { final SettableListenableFuture responseFuture = new SettableListenableFuture(); @@ -93,42 +94,19 @@ protected ListenableFuture executeInternal(final HttpHeaders public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { Channel channel = future.channel(); - channel.pipeline() - .addLast(new SimpleChannelInboundHandler() { - - @Override - protected void channelRead0( - ChannelHandlerContext ctx, - FullHttpResponse msg) throws Exception { - responseFuture - .set(new Netty4ClientHttpResponse(ctx, - msg)); - } - - @Override - public void exceptionCaught( - ChannelHandlerContext ctx, - Throwable cause) throws Exception { - responseFuture.setException(cause); - } - }); - - FullHttpRequest nettyRequest = - createFullHttpRequest(headers); - + channel.pipeline().addLast(new RequestExecuteHandler(responseFuture)); + FullHttpRequest nettyRequest = createFullHttpRequest(headers); channel.writeAndFlush(nettyRequest); } else { responseFuture.setException(future.cause()); } - } }; - bootstrap.connect(uri.getHost(), getPort(uri)).addListener(connectionListener); + this.bootstrap.connect(this.uri.getHost(), getPort(this.uri)).addListener(connectionListener); return responseFuture; - } @Override @@ -142,7 +120,8 @@ public ClientHttpResponse execute() throws IOException { catch (ExecutionException ex) { if (ex.getCause() instanceof IOException) { throw (IOException) ex.getCause(); - } else { + } + else { throw new IOException(ex.getMessage(), ex); } } @@ -163,17 +142,13 @@ else if ("https".equalsIgnoreCase(uri.getScheme())) { private FullHttpRequest createFullHttpRequest(HttpHeaders headers) { io.netty.handler.codec.http.HttpMethod nettyMethod = - io.netty.handler.codec.http.HttpMethod.valueOf(method.name()); + io.netty.handler.codec.http.HttpMethod.valueOf(this.method.name()); FullHttpRequest nettyRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, - nettyMethod, this.uri.getRawPath(), - this.body.buffer()); + nettyMethod, this.uri.getRawPath(), this.body.buffer()); - nettyRequest.headers() - .set(io.netty.handler.codec.http.HttpHeaders.Names.HOST, uri.getHost()); - nettyRequest.headers() - .set(io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION, - io.netty.handler.codec.http.HttpHeaders.Values.CLOSE); + nettyRequest.headers().set(HttpHeaders.HOST, uri.getHost()); + nettyRequest.headers().set(HttpHeaders.CONNECTION, io.netty.handler.codec.http.HttpHeaders.Values.CLOSE); for (Map.Entry> entry : headers.entrySet()) { nettyRequest.headers().add(entry.getKey(), entry.getValue()); @@ -183,4 +158,26 @@ private FullHttpRequest createFullHttpRequest(HttpHeaders headers) { } + /** + * A SimpleChannelInboundHandler to update the given SettableListenableFuture. + */ + private static class RequestExecuteHandler extends SimpleChannelInboundHandler { + + private final SettableListenableFuture responseFuture; + + public RequestExecuteHandler(SettableListenableFuture responseFuture) { + this.responseFuture = responseFuture; + } + + @Override + protected void channelRead0(ChannelHandlerContext context, FullHttpResponse response) throws Exception { + this.responseFuture.set(new Netty4ClientHttpResponse(context, response)); + } + + @Override + public void exceptionCaught(ChannelHandlerContext context, Throwable cause) throws Exception { + this.responseFuture.setException(cause); + } + } + } diff --git a/spring-web/src/main/java/org/springframework/http/client/Netty4ClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/Netty4ClientHttpRequestFactory.java index 75000b6513a0..eb6ca0ac289b 100644 --- a/spring-web/src/main/java/org/springframework/http/client/Netty4ClientHttpRequestFactory.java +++ b/spring-web/src/main/java/org/springframework/http/client/Netty4ClientHttpRequestFactory.java @@ -45,9 +45,8 @@ * @author Arjen Poutsma * @since 4.2 */ -public class Netty4ClientHttpRequestFactory - implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory, - InitializingBean, DisposableBean { +public class Netty4ClientHttpRequestFactory implements ClientHttpRequestFactory, + AsyncClientHttpRequestFactory, InitializingBean, DisposableBean { /** * The default maximum request size. @@ -55,6 +54,7 @@ public class Netty4ClientHttpRequestFactory */ public static final int DEFAULT_MAX_REQUEST_SIZE = 1024 * 1024 * 10; + private final EventLoopGroup eventLoopGroup; private final boolean defaultEventLoopGroup; @@ -65,18 +65,19 @@ public class Netty4ClientHttpRequestFactory private Bootstrap bootstrap; + /** - * Creates a new {@code Netty4ClientHttpRequestFactory} with a default + * Create a new {@code Netty4ClientHttpRequestFactory} with a default * {@link NioEventLoopGroup}. */ public Netty4ClientHttpRequestFactory() { int ioWorkerCount = Runtime.getRuntime().availableProcessors() * 2; - eventLoopGroup = new NioEventLoopGroup(ioWorkerCount); - defaultEventLoopGroup = true; + this.eventLoopGroup = new NioEventLoopGroup(ioWorkerCount); + this.defaultEventLoopGroup = true; } /** - * Creates a new {@code Netty4ClientHttpRequestFactory} with the given + * Create a new {@code Netty4ClientHttpRequestFactory} with the given * {@link EventLoopGroup}. * *

NOTE: the given group will not be @@ -89,9 +90,10 @@ public Netty4ClientHttpRequestFactory(EventLoopGroup eventLoopGroup) { this.defaultEventLoopGroup = false; } + /** - * Sets the default maximum request size. The default is - * {@link #DEFAULT_MAX_REQUEST_SIZE}. + * Set the default maximum request size. + *

By default this is set to {@link #DEFAULT_MAX_REQUEST_SIZE}. * @see HttpObjectAggregator#HttpObjectAggregator(int) */ public void setMaxRequestSize(int maxRequestSize) { @@ -99,7 +101,9 @@ public void setMaxRequestSize(int maxRequestSize) { } /** - * Sets the SSL context. + * Set the SSL context. When configured it is used to create and insert an + * {@link io.netty.handler.ssl.SslHandler} in the channel pipeline. + *

By default this is not set. */ public void setSslContext(SslContext sslContext) { this.sslContext = sslContext; @@ -108,14 +112,14 @@ public void setSslContext(SslContext sslContext) { private Bootstrap getBootstrap() { if (this.bootstrap == null) { Bootstrap bootstrap = new Bootstrap(); - bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class) + bootstrap.group(this.eventLoopGroup).channel(NioSocketChannel.class) .handler(new ChannelInitializer() { @Override - protected void initChannel(SocketChannel ch) throws Exception { - ChannelPipeline pipeline = ch.pipeline(); + protected void initChannel(SocketChannel channel) throws Exception { + ChannelPipeline pipeline = channel.pipeline(); if (sslContext != null) { - pipeline.addLast(sslContext.newHandler(ch.alloc())); + pipeline.addLast(sslContext.newHandler(channel.alloc())); } pipeline.addLast(new HttpClientCodec()); pipeline.addLast(new HttpObjectAggregator(maxRequestSize)); @@ -131,29 +135,26 @@ public void afterPropertiesSet() throws Exception { getBootstrap(); } - private Netty4ClientHttpRequest createRequestInternal(URI uri, HttpMethod httpMethod) { - return new Netty4ClientHttpRequest(getBootstrap(), uri, httpMethod, maxRequestSize); - } - @Override - public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) - throws IOException { + public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { return createRequestInternal(uri, httpMethod); } @Override - public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) - throws IOException { + public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) throws IOException { return createRequestInternal(uri, httpMethod); } + private Netty4ClientHttpRequest createRequestInternal(URI uri, HttpMethod httpMethod) { + return new Netty4ClientHttpRequest(getBootstrap(), uri, httpMethod, this.maxRequestSize); + } + @Override public void destroy() throws InterruptedException { - if (defaultEventLoopGroup) { + if (this.defaultEventLoopGroup) { // clean up the EventLoopGroup if we created it in the constructor - eventLoopGroup.shutdownGracefully().sync(); + this.eventLoopGroup.shutdownGracefully().sync(); } } - } diff --git a/spring-web/src/main/java/org/springframework/http/client/Netty4ClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/Netty4ClientHttpResponse.java index 7e2ae0ae6fb2..2da9256abb54 100644 --- a/spring-web/src/main/java/org/springframework/http/client/Netty4ClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/Netty4ClientHttpResponse.java @@ -45,8 +45,7 @@ class Netty4ClientHttpResponse extends AbstractClientHttpResponse { private HttpHeaders headers; - Netty4ClientHttpResponse(ChannelHandlerContext context, - FullHttpResponse nettyResponse) { + Netty4ClientHttpResponse(ChannelHandlerContext context, FullHttpResponse nettyResponse) { Assert.notNull(context, "'context' must not be null"); Assert.notNull(nettyResponse, "'nettyResponse' must not be null"); this.context = context; @@ -55,6 +54,7 @@ class Netty4ClientHttpResponse extends AbstractClientHttpResponse { this.nettyResponse.retain(); } + @Override public int getRawStatusCode() throws IOException { return this.nettyResponse.getStatus().code(); diff --git a/spring-web/src/test/java/org/springframework/http/client/AbstractAsyncHttpRequestFactoryTestCase.java b/spring-web/src/test/java/org/springframework/http/client/AbstractAsyncHttpRequestFactoryTestCase.java index aa06557b5cea..49fb91856def 100644 --- a/spring-web/src/test/java/org/springframework/http/client/AbstractAsyncHttpRequestFactoryTestCase.java +++ b/spring-web/src/test/java/org/springframework/http/client/AbstractAsyncHttpRequestFactoryTestCase.java @@ -16,6 +16,10 @@ package org.springframework.http.client; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.io.IOException; import java.net.URI; import java.util.Arrays; @@ -23,10 +27,8 @@ import java.util.concurrent.Future; import org.junit.After; -import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; - import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.http.HttpMethod; @@ -44,16 +46,16 @@ public abstract class AbstractAsyncHttpRequestFactoryTestCase extends AbstractJe @Before public final void createFactory() throws Exception { - factory = createRequestFactory(); - if (factory instanceof InitializingBean) { - ((InitializingBean) factory).afterPropertiesSet(); + this.factory = createRequestFactory(); + if (this.factory instanceof InitializingBean) { + ((InitializingBean) this.factory).afterPropertiesSet(); } } @After public final void destroyFactory() throws Exception { - if (factory instanceof DisposableBean) { - ((DisposableBean) factory).destroy(); + if (this.factory instanceof DisposableBean) { + ((DisposableBean) this.factory).destroy(); } } @@ -63,14 +65,15 @@ public final void destroyFactory() throws Exception { @Test public void status() throws Exception { URI uri = new URI(baseUrl + "/status/notfound"); - AsyncClientHttpRequest request = factory.createAsyncRequest(uri, HttpMethod.GET); + AsyncClientHttpRequest request = this.factory.createAsyncRequest(uri, HttpMethod.GET); assertEquals("Invalid HTTP method", HttpMethod.GET, request.getMethod()); assertEquals("Invalid HTTP URI", uri, request.getURI()); Future futureResponse = request.executeAsync(); ClientHttpResponse response = futureResponse.get(); try { assertEquals("Invalid status code", HttpStatus.NOT_FOUND, response.getStatusCode()); - } finally { + } + finally { response.close(); } } @@ -78,7 +81,7 @@ public void status() throws Exception { @Test public void statusCallback() throws Exception { URI uri = new URI(baseUrl + "/status/notfound"); - AsyncClientHttpRequest request = factory.createAsyncRequest(uri, HttpMethod.GET); + AsyncClientHttpRequest request = this.factory.createAsyncRequest(uri, HttpMethod.GET); assertEquals("Invalid HTTP method", HttpMethod.GET, request.getMethod()); assertEquals("Invalid HTTP URI", uri, request.getURI()); ListenableFuture listenableFuture = request.executeAsync(); @@ -108,7 +111,7 @@ public void onFailure(Throwable ex) { @Test public void echo() throws Exception { - AsyncClientHttpRequest request = factory.createAsyncRequest(new URI(baseUrl + "/echo"), HttpMethod.PUT); + AsyncClientHttpRequest request = this.factory.createAsyncRequest(new URI(baseUrl + "/echo"), HttpMethod.PUT); assertEquals("Invalid HTTP method", HttpMethod.PUT, request.getMethod()); String headerName = "MyHeader"; String headerValue1 = "value1"; @@ -143,7 +146,7 @@ public void echo() throws Exception { @Test public void multipleWrites() throws Exception { - AsyncClientHttpRequest request = factory.createAsyncRequest(new URI(baseUrl + "/echo"), HttpMethod.POST); + AsyncClientHttpRequest request = this.factory.createAsyncRequest(new URI(baseUrl + "/echo"), HttpMethod.POST); final byte[] body = "Hello World".getBytes("UTF-8"); if (request instanceof StreamingHttpOutputMessage) { @@ -170,7 +173,7 @@ public void multipleWrites() throws Exception { @Test public void headersAfterExecute() throws Exception { - AsyncClientHttpRequest request = factory.createAsyncRequest(new URI(baseUrl + "/echo"), HttpMethod.POST); + AsyncClientHttpRequest request = this.factory.createAsyncRequest(new URI(baseUrl + "/echo"), HttpMethod.POST); request.getHeaders().add("MyHeader", "value"); byte[] body = "Hello World".getBytes("UTF-8"); FileCopyUtils.copy(body, request.getBody()); @@ -202,7 +205,7 @@ public void httpMethods() throws Exception { protected void assertHttpMethod(String path, HttpMethod method) throws Exception { ClientHttpResponse response = null; try { - AsyncClientHttpRequest request = factory.createAsyncRequest(new URI(baseUrl + "/methods/" + path), method); + AsyncClientHttpRequest request = this.factory.createAsyncRequest(new URI(baseUrl + "/methods/" + path), method); Future futureResponse = request.executeAsync(); response = futureResponse.get(); assertEquals("Invalid response status", HttpStatus.OK, response.getStatusCode()); diff --git a/spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTestCase.java b/spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTestCase.java index 5ebb3f8bb0ec..8d82074d0bec 100644 --- a/spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTestCase.java +++ b/spring-web/src/test/java/org/springframework/http/client/AbstractHttpRequestFactoryTestCase.java @@ -69,7 +69,8 @@ public void status() throws Exception { ClientHttpResponse response = request.execute(); try { assertEquals("Invalid status code", HttpStatus.NOT_FOUND, response.getStatusCode()); - } finally { + } + finally { response.close(); } } diff --git a/spring-web/src/test/java/org/springframework/http/client/Netty4AsyncClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/Netty4AsyncClientHttpRequestFactoryTests.java index 363356b1684e..3ca6055ac8e0 100644 --- a/spring-web/src/test/java/org/springframework/http/client/Netty4AsyncClientHttpRequestFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/Netty4AsyncClientHttpRequestFactoryTests.java @@ -27,11 +27,11 @@ /** * @author Arjen Poutsma */ -public class Netty4AsyncClientHttpRequestFactoryTests - extends AbstractAsyncHttpRequestFactoryTestCase { +public class Netty4AsyncClientHttpRequestFactoryTests extends AbstractAsyncHttpRequestFactoryTestCase { private static EventLoopGroup eventLoopGroup; + @BeforeClass public static void createEventLoopGroup() { eventLoopGroup = new NioEventLoopGroup(); diff --git a/spring-web/src/test/java/org/springframework/http/client/Netty4ClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/Netty4ClientHttpRequestFactoryTests.java index 61aaf0493ae0..802d5f9fadfe 100644 --- a/spring-web/src/test/java/org/springframework/http/client/Netty4ClientHttpRequestFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/Netty4ClientHttpRequestFactoryTests.java @@ -27,11 +27,11 @@ /** * @author Arjen Poutsma */ -public class Netty4ClientHttpRequestFactoryTests - extends AbstractHttpRequestFactoryTestCase { +public class Netty4ClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryTestCase { private static EventLoopGroup eventLoopGroup; + @BeforeClass public static void createEventLoopGroup() { eventLoopGroup = new NioEventLoopGroup();