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