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.Closeable; 020import java.io.IOException; 021import java.net.URI; 022 023import org.apache.http.client.HttpClient; 024import org.apache.http.client.config.RequestConfig; 025import org.apache.http.client.methods.Configurable; 026import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; 027import org.apache.http.client.methods.HttpGet; 028import org.apache.http.client.methods.HttpHead; 029import org.apache.http.client.methods.HttpOptions; 030import org.apache.http.client.methods.HttpPatch; 031import org.apache.http.client.methods.HttpPost; 032import org.apache.http.client.methods.HttpPut; 033import org.apache.http.client.methods.HttpTrace; 034import org.apache.http.client.methods.HttpUriRequest; 035import org.apache.http.client.protocol.HttpClientContext; 036import org.apache.http.impl.client.HttpClients; 037import org.apache.http.protocol.HttpContext; 038 039import org.springframework.beans.factory.DisposableBean; 040import org.springframework.http.HttpMethod; 041import org.springframework.util.Assert; 042import org.springframework.util.ClassUtils; 043 044/** 045 * {@link org.springframework.http.client.ClientHttpRequestFactory} implementation that 046 * uses <a href="https://hc.apache.org/httpcomponents-client-ga/">Apache HttpComponents 047 * HttpClient</a> to create requests. 048 * 049 * <p>Allows to use a pre-configured {@link HttpClient} instance - 050 * potentially with authentication, HTTP connection pooling, etc. 051 * 052 * <p><b>NOTE:</b> Requires Apache HttpComponents 4.3 or higher, as of Spring 4.0. 053 * 054 * @author Oleg Kalnichevski 055 * @author Arjen Poutsma 056 * @author Stephane Nicoll 057 * @author Juergen Hoeller 058 * @since 3.1 059 */ 060public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequestFactory, DisposableBean { 061 062 private static Class<?> abstractHttpClientClass; 063 064 static { 065 try { 066 // Looking for AbstractHttpClient class (deprecated as of HttpComponents 4.3) 067 abstractHttpClientClass = ClassUtils.forName("org.apache.http.impl.client.AbstractHttpClient", 068 HttpComponentsClientHttpRequestFactory.class.getClassLoader()); 069 } 070 catch (ClassNotFoundException ex) { 071 // Probably removed from HttpComponents in the meantime... 072 } 073 } 074 075 076 private HttpClient httpClient; 077 078 private RequestConfig requestConfig; 079 080 private boolean bufferRequestBody = true; 081 082 083 /** 084 * Create a new instance of the {@code HttpComponentsClientHttpRequestFactory} 085 * with a default {@link HttpClient}. 086 */ 087 public HttpComponentsClientHttpRequestFactory() { 088 this.httpClient = HttpClients.createSystem(); 089 } 090 091 /** 092 * Create a new instance of the {@code HttpComponentsClientHttpRequestFactory} 093 * with the given {@link HttpClient} instance. 094 * @param httpClient the HttpClient instance to use for this request factory 095 */ 096 public HttpComponentsClientHttpRequestFactory(HttpClient httpClient) { 097 setHttpClient(httpClient); 098 } 099 100 101 /** 102 * Set the {@code HttpClient} used for 103 * {@linkplain #createRequest(URI, HttpMethod) synchronous execution}. 104 */ 105 public void setHttpClient(HttpClient httpClient) { 106 Assert.notNull(httpClient, "HttpClient must not be null"); 107 this.httpClient = httpClient; 108 } 109 110 /** 111 * Return the {@code HttpClient} used for 112 * {@linkplain #createRequest(URI, HttpMethod) synchronous execution}. 113 */ 114 public HttpClient getHttpClient() { 115 return this.httpClient; 116 } 117 118 /** 119 * Set the connection timeout for the underlying HttpClient. 120 * A timeout value of 0 specifies an infinite timeout. 121 * <p>Additional properties can be configured by specifying a 122 * {@link RequestConfig} instance on a custom {@link HttpClient}. 123 * @param timeout the timeout value in milliseconds 124 * @see RequestConfig#getConnectTimeout() 125 */ 126 public void setConnectTimeout(int timeout) { 127 Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value"); 128 this.requestConfig = requestConfigBuilder().setConnectTimeout(timeout).build(); 129 setLegacyConnectionTimeout(getHttpClient(), timeout); 130 } 131 132 /** 133 * Apply the specified connection timeout to deprecated {@link HttpClient} 134 * implementations. 135 * <p>As of HttpClient 4.3, default parameters have to be exposed through a 136 * {@link RequestConfig} instance instead of setting the parameters on the 137 * client. Unfortunately, this behavior is not backward-compatible and older 138 * {@link HttpClient} implementations will ignore the {@link RequestConfig} 139 * object set in the context. 140 * <p>If the specified client is an older implementation, we set the custom 141 * connection timeout through the deprecated API. Otherwise, we just return 142 * as it is set through {@link RequestConfig} with newer clients. 143 * @param client the client to configure 144 * @param timeout the custom connection timeout 145 */ 146 @SuppressWarnings("deprecation") 147 private void setLegacyConnectionTimeout(HttpClient client, int timeout) { 148 if (abstractHttpClientClass != null && abstractHttpClientClass.isInstance(client)) { 149 client.getParams().setIntParameter(org.apache.http.params.CoreConnectionPNames.CONNECTION_TIMEOUT, timeout); 150 } 151 } 152 153 /** 154 * Set the timeout in milliseconds used when requesting a connection from the connection 155 * manager using the underlying HttpClient. 156 * A timeout value of 0 specifies an infinite timeout. 157 * <p>Additional properties can be configured by specifying a 158 * {@link RequestConfig} instance on a custom {@link HttpClient}. 159 * @param connectionRequestTimeout the timeout value to request a connection in milliseconds 160 * @see RequestConfig#getConnectionRequestTimeout() 161 */ 162 public void setConnectionRequestTimeout(int connectionRequestTimeout) { 163 this.requestConfig = requestConfigBuilder().setConnectionRequestTimeout(connectionRequestTimeout).build(); 164 } 165 166 /** 167 * Set the socket read timeout for the underlying HttpClient. 168 * A timeout value of 0 specifies an infinite timeout. 169 * <p>Additional properties can be configured by specifying a 170 * {@link RequestConfig} instance on a custom {@link HttpClient}. 171 * @param timeout the timeout value in milliseconds 172 * @see RequestConfig#getSocketTimeout() 173 */ 174 public void setReadTimeout(int timeout) { 175 Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value"); 176 this.requestConfig = requestConfigBuilder().setSocketTimeout(timeout).build(); 177 setLegacySocketTimeout(getHttpClient(), timeout); 178 } 179 180 /** 181 * Apply the specified socket timeout to deprecated {@link HttpClient} 182 * implementations. See {@link #setLegacyConnectionTimeout}. 183 * @param client the client to configure 184 * @param timeout the custom socket timeout 185 * @see #setLegacyConnectionTimeout 186 */ 187 @SuppressWarnings("deprecation") 188 private void setLegacySocketTimeout(HttpClient client, int timeout) { 189 if (abstractHttpClientClass != null && abstractHttpClientClass.isInstance(client)) { 190 client.getParams().setIntParameter(org.apache.http.params.CoreConnectionPNames.SO_TIMEOUT, timeout); 191 } 192 } 193 194 /** 195 * Indicates whether this request factory should buffer the request body internally. 196 * <p>Default is {@code true}. When sending large amounts of data via POST or PUT, it is 197 * recommended to change this property to {@code false}, so as not to run out of memory. 198 * @since 4.0 199 */ 200 public void setBufferRequestBody(boolean bufferRequestBody) { 201 this.bufferRequestBody = bufferRequestBody; 202 } 203 204 205 @Override 206 public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { 207 HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri); 208 postProcessHttpRequest(httpRequest); 209 HttpContext context = createHttpContext(httpMethod, uri); 210 if (context == null) { 211 context = HttpClientContext.create(); 212 } 213 214 // Request configuration not set in the context 215 if (context.getAttribute(HttpClientContext.REQUEST_CONFIG) == null) { 216 // Use request configuration given by the user, when available 217 RequestConfig config = null; 218 if (httpRequest instanceof Configurable) { 219 config = ((Configurable) httpRequest).getConfig(); 220 } 221 if (config == null) { 222 config = createRequestConfig(getHttpClient()); 223 } 224 if (config != null) { 225 context.setAttribute(HttpClientContext.REQUEST_CONFIG, config); 226 } 227 } 228 229 if (this.bufferRequestBody) { 230 return new HttpComponentsClientHttpRequest(getHttpClient(), httpRequest, context); 231 } 232 else { 233 return new HttpComponentsStreamingClientHttpRequest(getHttpClient(), httpRequest, context); 234 } 235 } 236 237 238 /** 239 * Return a builder for modifying the factory-level {@link RequestConfig}. 240 * @since 4.2 241 */ 242 private RequestConfig.Builder requestConfigBuilder() { 243 return (this.requestConfig != null ? RequestConfig.copy(this.requestConfig) : RequestConfig.custom()); 244 } 245 246 /** 247 * Create a default {@link RequestConfig} to use with the given client. 248 * Can return {@code null} to indicate that no custom request config should 249 * be set and the defaults of the {@link HttpClient} should be used. 250 * <p>The default implementation tries to merge the defaults of the client 251 * with the local customizations of this factory instance, if any. 252 * @param client the {@link HttpClient} (or {@code HttpAsyncClient}) to check 253 * @return the actual RequestConfig to use (may be {@code null}) 254 * @since 4.2 255 * @see #mergeRequestConfig(RequestConfig) 256 */ 257 protected RequestConfig createRequestConfig(Object client) { 258 if (client instanceof Configurable) { 259 RequestConfig clientRequestConfig = ((Configurable) client).getConfig(); 260 return mergeRequestConfig(clientRequestConfig); 261 } 262 return this.requestConfig; 263 } 264 265 /** 266 * Merge the given {@link HttpClient}-level {@link RequestConfig} with 267 * the factory-level {@link RequestConfig}, if necessary. 268 * @param clientConfig the config held by the current 269 * @return the merged request config 270 * @since 4.2 271 */ 272 protected RequestConfig mergeRequestConfig(RequestConfig clientConfig) { 273 if (this.requestConfig == null) { // nothing to merge 274 return clientConfig; 275 } 276 277 RequestConfig.Builder builder = RequestConfig.copy(clientConfig); 278 int connectTimeout = this.requestConfig.getConnectTimeout(); 279 if (connectTimeout >= 0) { 280 builder.setConnectTimeout(connectTimeout); 281 } 282 int connectionRequestTimeout = this.requestConfig.getConnectionRequestTimeout(); 283 if (connectionRequestTimeout >= 0) { 284 builder.setConnectionRequestTimeout(connectionRequestTimeout); 285 } 286 int socketTimeout = this.requestConfig.getSocketTimeout(); 287 if (socketTimeout >= 0) { 288 builder.setSocketTimeout(socketTimeout); 289 } 290 return builder.build(); 291 } 292 293 /** 294 * Create a Commons HttpMethodBase object for the given HTTP method and URI specification. 295 * @param httpMethod the HTTP method 296 * @param uri the URI 297 * @return the Commons HttpMethodBase object 298 */ 299 protected HttpUriRequest createHttpUriRequest(HttpMethod httpMethod, URI uri) { 300 switch (httpMethod) { 301 case GET: 302 return new HttpGet(uri); 303 case HEAD: 304 return new HttpHead(uri); 305 case POST: 306 return new HttpPost(uri); 307 case PUT: 308 return new HttpPut(uri); 309 case PATCH: 310 return new HttpPatch(uri); 311 case DELETE: 312 return new HttpDelete(uri); 313 case OPTIONS: 314 return new HttpOptions(uri); 315 case TRACE: 316 return new HttpTrace(uri); 317 default: 318 throw new IllegalArgumentException("Invalid HTTP method: " + httpMethod); 319 } 320 } 321 322 /** 323 * Template method that allows for manipulating the {@link HttpUriRequest} before it is 324 * returned as part of a {@link HttpComponentsClientHttpRequest}. 325 * <p>The default implementation is empty. 326 * @param request the request to process 327 */ 328 protected void postProcessHttpRequest(HttpUriRequest request) { 329 } 330 331 /** 332 * Template methods that creates a {@link HttpContext} for the given HTTP method and URI. 333 * <p>The default implementation returns {@code null}. 334 * @param httpMethod the HTTP method 335 * @param uri the URI 336 * @return the http context 337 */ 338 protected HttpContext createHttpContext(HttpMethod httpMethod, URI uri) { 339 return null; 340 } 341 342 343 /** 344 * Shutdown hook that closes the underlying 345 * {@link org.apache.http.conn.HttpClientConnectionManager ClientConnectionManager}'s 346 * connection pool, if any. 347 */ 348 @Override 349 public void destroy() throws Exception { 350 HttpClient httpClient = getHttpClient(); 351 if (httpClient instanceof Closeable) { 352 ((Closeable) httpClient).close(); 353 } 354 } 355 356 357 /** 358 * An alternative to {@link org.apache.http.client.methods.HttpDelete} that 359 * extends {@link org.apache.http.client.methods.HttpEntityEnclosingRequestBase} 360 * rather than {@link org.apache.http.client.methods.HttpRequestBase} and 361 * hence allows HTTP delete with a request body. For use with the RestTemplate 362 * exchange methods which allow the combination of HTTP DELETE with an entity. 363 * @since 4.1.2 364 */ 365 private static class HttpDelete extends HttpEntityEnclosingRequestBase { 366 367 public HttpDelete(URI uri) { 368 super(); 369 setURI(uri); 370 } 371 372 @Override 373 public String getMethod() { 374 return "DELETE"; 375 } 376 } 377 378}