001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.http.client; 018 019import java.io.IOException; 020import java.net.URI; 021import java.util.concurrent.TimeUnit; 022 023import javax.net.ssl.SSLException; 024 025import io.netty.bootstrap.Bootstrap; 026import io.netty.channel.ChannelConfig; 027import io.netty.channel.ChannelInitializer; 028import io.netty.channel.ChannelPipeline; 029import io.netty.channel.EventLoopGroup; 030import io.netty.channel.nio.NioEventLoopGroup; 031import io.netty.channel.socket.SocketChannel; 032import io.netty.channel.socket.SocketChannelConfig; 033import io.netty.channel.socket.nio.NioSocketChannel; 034import io.netty.handler.codec.http.HttpClientCodec; 035import io.netty.handler.codec.http.HttpObjectAggregator; 036import io.netty.handler.ssl.SslContext; 037import io.netty.handler.ssl.SslContextBuilder; 038import io.netty.handler.timeout.ReadTimeoutHandler; 039 040import org.springframework.beans.factory.DisposableBean; 041import org.springframework.beans.factory.InitializingBean; 042import org.springframework.http.HttpMethod; 043import org.springframework.lang.Nullable; 044import org.springframework.util.Assert; 045 046/** 047 * {@link org.springframework.http.client.ClientHttpRequestFactory} implementation 048 * that uses <a href="https://netty.io/">Netty 4</a> to create requests. 049 * 050 * <p>Allows to use a pre-configured {@link EventLoopGroup} instance: useful for 051 * sharing across multiple clients. 052 * 053 * <p>Note that this implementation consistently closes the HTTP connection on each 054 * request. 055 * 056 * @author Arjen Poutsma 057 * @author Rossen Stoyanchev 058 * @author Brian Clozel 059 * @author Mark Paluch 060 * @since 4.1.2 061 * @deprecated as of Spring 5.0, in favor of 062 * {@link org.springframework.http.client.reactive.ReactorClientHttpConnector} 063 */ 064@Deprecated 065public class Netty4ClientHttpRequestFactory implements ClientHttpRequestFactory, 066 AsyncClientHttpRequestFactory, InitializingBean, DisposableBean { 067 068 /** 069 * The default maximum response size. 070 * @see #setMaxResponseSize(int) 071 */ 072 public static final int DEFAULT_MAX_RESPONSE_SIZE = 1024 * 1024 * 10; 073 074 075 private final EventLoopGroup eventLoopGroup; 076 077 private final boolean defaultEventLoopGroup; 078 079 private int maxResponseSize = DEFAULT_MAX_RESPONSE_SIZE; 080 081 @Nullable 082 private SslContext sslContext; 083 084 private int connectTimeout = -1; 085 086 private int readTimeout = -1; 087 088 @Nullable 089 private volatile Bootstrap bootstrap; 090 091 092 /** 093 * Create a new {@code Netty4ClientHttpRequestFactory} with a default 094 * {@link NioEventLoopGroup}. 095 */ 096 public Netty4ClientHttpRequestFactory() { 097 int ioWorkerCount = Runtime.getRuntime().availableProcessors() * 2; 098 this.eventLoopGroup = new NioEventLoopGroup(ioWorkerCount); 099 this.defaultEventLoopGroup = true; 100 } 101 102 /** 103 * Create a new {@code Netty4ClientHttpRequestFactory} with the given 104 * {@link EventLoopGroup}. 105 * <p><b>NOTE:</b> the given group will <strong>not</strong> be 106 * {@linkplain EventLoopGroup#shutdownGracefully() shutdown} by this factory; 107 * doing so becomes the responsibility of the caller. 108 */ 109 public Netty4ClientHttpRequestFactory(EventLoopGroup eventLoopGroup) { 110 Assert.notNull(eventLoopGroup, "EventLoopGroup must not be null"); 111 this.eventLoopGroup = eventLoopGroup; 112 this.defaultEventLoopGroup = false; 113 } 114 115 116 /** 117 * Set the default maximum response size. 118 * <p>By default this is set to {@link #DEFAULT_MAX_RESPONSE_SIZE}. 119 * @since 4.1.5 120 * @see HttpObjectAggregator#HttpObjectAggregator(int) 121 */ 122 public void setMaxResponseSize(int maxResponseSize) { 123 this.maxResponseSize = maxResponseSize; 124 } 125 126 /** 127 * Set the SSL context. When configured it is used to create and insert an 128 * {@link io.netty.handler.ssl.SslHandler} in the channel pipeline. 129 * <p>A default client SslContext is configured if none has been provided. 130 */ 131 public void setSslContext(SslContext sslContext) { 132 this.sslContext = sslContext; 133 } 134 135 /** 136 * Set the underlying connect timeout (in milliseconds). 137 * A timeout value of 0 specifies an infinite timeout. 138 * @see ChannelConfig#setConnectTimeoutMillis(int) 139 */ 140 public void setConnectTimeout(int connectTimeout) { 141 this.connectTimeout = connectTimeout; 142 } 143 144 /** 145 * Set the underlying URLConnection's read timeout (in milliseconds). 146 * A timeout value of 0 specifies an infinite timeout. 147 * @see ReadTimeoutHandler 148 */ 149 public void setReadTimeout(int readTimeout) { 150 this.readTimeout = readTimeout; 151 } 152 153 154 @Override 155 public void afterPropertiesSet() { 156 if (this.sslContext == null) { 157 this.sslContext = getDefaultClientSslContext(); 158 } 159 } 160 161 private SslContext getDefaultClientSslContext() { 162 try { 163 return SslContextBuilder.forClient().build(); 164 } 165 catch (SSLException ex) { 166 throw new IllegalStateException("Could not create default client SslContext", ex); 167 } 168 } 169 170 171 @Override 172 public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { 173 return createRequestInternal(uri, httpMethod); 174 } 175 176 @Override 177 public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) throws IOException { 178 return createRequestInternal(uri, httpMethod); 179 } 180 181 private Netty4ClientHttpRequest createRequestInternal(URI uri, HttpMethod httpMethod) { 182 return new Netty4ClientHttpRequest(getBootstrap(uri), uri, httpMethod); 183 } 184 185 private Bootstrap getBootstrap(URI uri) { 186 boolean isSecure = (uri.getPort() == 443 || "https".equalsIgnoreCase(uri.getScheme())); 187 if (isSecure) { 188 return buildBootstrap(uri, true); 189 } 190 else { 191 Bootstrap bootstrap = this.bootstrap; 192 if (bootstrap == null) { 193 bootstrap = buildBootstrap(uri, false); 194 this.bootstrap = bootstrap; 195 } 196 return bootstrap; 197 } 198 } 199 200 private Bootstrap buildBootstrap(URI uri, boolean isSecure) { 201 Bootstrap bootstrap = new Bootstrap(); 202 bootstrap.group(this.eventLoopGroup).channel(NioSocketChannel.class) 203 .handler(new ChannelInitializer<SocketChannel>() { 204 @Override 205 protected void initChannel(SocketChannel channel) throws Exception { 206 configureChannel(channel.config()); 207 ChannelPipeline pipeline = channel.pipeline(); 208 if (isSecure) { 209 Assert.notNull(sslContext, "sslContext should not be null"); 210 pipeline.addLast(sslContext.newHandler(channel.alloc(), uri.getHost(), uri.getPort())); 211 } 212 pipeline.addLast(new HttpClientCodec()); 213 pipeline.addLast(new HttpObjectAggregator(maxResponseSize)); 214 if (readTimeout > 0) { 215 pipeline.addLast(new ReadTimeoutHandler(readTimeout, 216 TimeUnit.MILLISECONDS)); 217 } 218 } 219 }); 220 return bootstrap; 221 } 222 223 /** 224 * Template method for changing properties on the given {@link SocketChannelConfig}. 225 * <p>The default implementation sets the connect timeout based on the set property. 226 * @param config the channel configuration 227 */ 228 protected void configureChannel(SocketChannelConfig config) { 229 if (this.connectTimeout >= 0) { 230 config.setConnectTimeoutMillis(this.connectTimeout); 231 } 232 } 233 234 235 @Override 236 public void destroy() throws InterruptedException { 237 if (this.defaultEventLoopGroup) { 238 // Clean up the EventLoopGroup if we created it in the constructor 239 this.eventLoopGroup.shutdownGracefully().sync(); 240 } 241 } 242 243}