org.glassfish.jersey.apache.connector.ApacheConnector Maven / Gradle / Ivy
/*
* Copyright (c) 2010, 2019 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.jersey.apache.connector;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.ClientRequest;
import org.glassfish.jersey.client.ClientResponse;
import org.glassfish.jersey.client.RequestEntityProcessing;
import org.glassfish.jersey.client.spi.AsyncConnectorCallback;
import org.glassfish.jersey.client.spi.Connector;
import org.glassfish.jersey.internal.util.PropertiesHelper;
import org.glassfish.jersey.message.internal.HeaderUtils;
import org.glassfish.jersey.message.internal.OutboundMessageContext;
import org.glassfish.jersey.message.internal.ReaderWriter;
import org.glassfish.jersey.message.internal.Statuses;
import org.apache.http.ConnectionReuseStrategy;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.CookieStore;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.ManagedHttpClientConnection;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.entity.ContentLengthStrategy;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.DefaultManagedHttpClientConnection;
import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.io.ChunkedOutputStream;
import org.apache.http.io.SessionOutputBuffer;
import org.apache.http.util.TextUtils;
import org.apache.http.util.VersionInfo;
/**
* A {@link Connector} that utilizes the Apache HTTP Client to send and receive
* HTTP request and responses.
*
* The following properties are only supported at construction of this class:
*
* - {@link ApacheClientProperties#CONNECTION_MANAGER}
* - {@link ApacheClientProperties#REQUEST_CONFIG}
* - {@link ApacheClientProperties#CREDENTIALS_PROVIDER}
* - {@link ApacheClientProperties#DISABLE_COOKIES}
* - {@link ApacheClientProperties#KEEPALIVE_STRATEGY}
* - {@link org.glassfish.jersey.client.ClientProperties#PROXY_URI}
* - {@link org.glassfish.jersey.client.ClientProperties#PROXY_USERNAME}
* - {@link org.glassfish.jersey.client.ClientProperties#PROXY_PASSWORD}
* - {@link org.glassfish.jersey.client.ClientProperties#REQUEST_ENTITY_PROCESSING} - default value is {@link org.glassfish.jersey.client.RequestEntityProcessing#CHUNKED}
* - {@link ApacheClientProperties#PREEMPTIVE_BASIC_AUTHENTICATION}
* - {@link ApacheClientProperties#RETRY_HANDLER}
* - {@link ApacheClientProperties#REUSE_STRATEGY}
*
*
* This connector uses {@link RequestEntityProcessing#CHUNKED chunked encoding} as a default setting. This can
* be overridden by the {@link ClientProperties#REQUEST_ENTITY_PROCESSING}. By default the
* {@link ClientProperties#CHUNKED_ENCODING_SIZE} property is only supported by using default connection manager. If custom
* connection manager needs to be used then chunked encoding size can be set by providing a custom
* {@link org.apache.http.HttpClientConnection} (via custom {@link org.apache.http.impl.conn.ManagedHttpClientConnectionFactory})
* and overriding {@code createOutputStream} method.
*
*
* Using of authorization is dependent on the chunk encoding setting. If the entity
* buffering is enabled, the entity is buffered and authorization can be performed
* automatically in response to a 401 by sending the request again. When entity buffering
* is disabled (chunked encoding is used) then the property
* {@link org.glassfish.jersey.apache.connector.ApacheClientProperties#PREEMPTIVE_BASIC_AUTHENTICATION} must
* be set to {@code true}.
*
*
* Registration of {@link ApacheHttpClientBuilderConfigurator} instance on the
* {@link javax.ws.rs.client.Client#register(Object) Client} is supported. A configuration provided by
* {@link ApacheHttpClientBuilderConfigurator} will override the {@link org.apache.http.impl.client.HttpClientBuilder}
* configuration set by using the properties.
*
*
* If a {@link org.glassfish.jersey.client.ClientResponse} is obtained and an
* entity is not read from the response then
* {@link org.glassfish.jersey.client.ClientResponse#close()} MUST be called
* after processing the response to release connection-based resources.
*
*
* Client operations are thread safe, the HTTP connection may
* be shared between different threads.
*
*
* If a response entity is obtained that is an instance of {@link Closeable}
* then the instance MUST be closed after processing the entity to release
* connection-based resources.
*
*
* The following methods are currently supported: HEAD, GET, POST, PUT, DELETE, OPTIONS, PATCH and TRACE.
*
*
* @author [email protected]
* @author Paul Sandoz
* @author Pavel Bucek
* @author Arul Dhesiaseelan (aruld at acm.org)
* @see ApacheClientProperties#CONNECTION_MANAGER
*/
@SuppressWarnings("deprecation")
class ApacheConnector implements Connector {
private static final Logger LOGGER = Logger.getLogger(ApacheConnector.class.getName());
private static final VersionInfo vi;
private static final String release;
static {
vi = VersionInfo.loadVersionInfo("org.apache.http.client", HttpClientBuilder.class.getClassLoader());
release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE;
}
private final CloseableHttpClient client;
private final CookieStore cookieStore;
private final boolean preemptiveBasicAuth;
private final RequestConfig requestConfig;
/**
* Create the new Apache HTTP Client connector.
*
* @param client JAX-RS client instance for which the connector is being created.
* @param config client configuration.
*/
ApacheConnector(final Client client, final Configuration config) {
final Object connectionManager = config.getProperties().get(ApacheClientProperties.CONNECTION_MANAGER);
if (connectionManager != null) {
if (!(connectionManager instanceof HttpClientConnectionManager)) {
LOGGER.log(
Level.WARNING,
LocalizationMessages.IGNORING_VALUE_OF_PROPERTY(
ApacheClientProperties.CONNECTION_MANAGER,
connectionManager.getClass().getName(),
HttpClientConnectionManager.class.getName())
);
}
}
Object keepAliveStrategy = config.getProperties().get(ApacheClientProperties.KEEPALIVE_STRATEGY);
if (keepAliveStrategy != null) {
if (!(keepAliveStrategy instanceof ConnectionKeepAliveStrategy)) {
LOGGER.log(
Level.WARNING,
LocalizationMessages.IGNORING_VALUE_OF_PROPERTY(
ApacheClientProperties.KEEPALIVE_STRATEGY,
keepAliveStrategy.getClass().getName(),
ConnectionKeepAliveStrategy.class.getName())
);
keepAliveStrategy = null;
}
}
Object reuseStrategy = config.getProperties().get(ApacheClientProperties.REUSE_STRATEGY);
if (reuseStrategy != null) {
if (!(reuseStrategy instanceof ConnectionReuseStrategy)) {
LOGGER.log(
Level.WARNING,
LocalizationMessages.IGNORING_VALUE_OF_PROPERTY(
ApacheClientProperties.REUSE_STRATEGY,
reuseStrategy.getClass().getName(),
ConnectionReuseStrategy.class.getName())
);
reuseStrategy = null;
}
}
Object reqConfig = config.getProperties().get(ApacheClientProperties.REQUEST_CONFIG);
if (reqConfig != null) {
if (!(reqConfig instanceof RequestConfig)) {
LOGGER.log(
Level.WARNING,
LocalizationMessages.IGNORING_VALUE_OF_PROPERTY(
ApacheClientProperties.REQUEST_CONFIG,
reqConfig.getClass().getName(),
RequestConfig.class.getName())
);
reqConfig = null;
}
}
final SSLContext sslContext = client.getSslContext();
final HttpClientBuilder clientBuilder = HttpClientBuilder.create();
clientBuilder.setConnectionManager(getConnectionManager(client, config, sslContext));
clientBuilder.setConnectionManagerShared(
PropertiesHelper.getValue(config.getProperties(), ApacheClientProperties.CONNECTION_MANAGER_SHARED, false, null));
clientBuilder.setSSLContext(sslContext);
if (keepAliveStrategy != null) {
clientBuilder.setKeepAliveStrategy((ConnectionKeepAliveStrategy) keepAliveStrategy);
}
if (reuseStrategy != null) {
clientBuilder.setConnectionReuseStrategy((ConnectionReuseStrategy) reuseStrategy);
}
final RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
final Object credentialsProvider = config.getProperty(ApacheClientProperties.CREDENTIALS_PROVIDER);
if (credentialsProvider != null && (credentialsProvider instanceof CredentialsProvider)) {
clientBuilder.setDefaultCredentialsProvider((CredentialsProvider) credentialsProvider);
}
final Object retryHandler = config.getProperties().get(ApacheClientProperties.RETRY_HANDLER);
if (retryHandler != null && (retryHandler instanceof HttpRequestRetryHandler)) {
clientBuilder.setRetryHandler((HttpRequestRetryHandler) retryHandler);
}
final Object proxyUri;
proxyUri = config.getProperty(ClientProperties.PROXY_URI);
if (proxyUri != null) {
final URI u = getProxyUri(proxyUri);
final HttpHost proxy = new HttpHost(u.getHost(), u.getPort(), u.getScheme());
final String userName;
userName = ClientProperties.getValue(config.getProperties(), ClientProperties.PROXY_USERNAME, String.class);
if (userName != null) {
final String password;
password = ClientProperties.getValue(config.getProperties(), ClientProperties.PROXY_PASSWORD, String.class);
if (password != null) {
final CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
new AuthScope(u.getHost(), u.getPort()),
new UsernamePasswordCredentials(userName, password)
);
clientBuilder.setDefaultCredentialsProvider(credsProvider);
}
}
clientBuilder.setProxy(proxy);
}
final Boolean preemptiveBasicAuthProperty = (Boolean) config.getProperties()
.get(ApacheClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION);
this.preemptiveBasicAuth = (preemptiveBasicAuthProperty != null) ? preemptiveBasicAuthProperty : false;
final boolean ignoreCookies = PropertiesHelper.isProperty(config.getProperties(), ApacheClientProperties.DISABLE_COOKIES);
if (reqConfig != null) {
final RequestConfig.Builder reqConfigBuilder = RequestConfig.copy((RequestConfig) reqConfig);
if (ignoreCookies) {
reqConfigBuilder.setCookieSpec(CookieSpecs.IGNORE_COOKIES);
}
requestConfig = reqConfigBuilder.build();
} else {
if (ignoreCookies) {
requestConfigBuilder.setCookieSpec(CookieSpecs.IGNORE_COOKIES);
}
requestConfig = requestConfigBuilder.build();
}
if (requestConfig.getCookieSpec() == null || !requestConfig.getCookieSpec().equals(CookieSpecs.IGNORE_COOKIES)) {
this.cookieStore = new BasicCookieStore();
clientBuilder.setDefaultCookieStore(cookieStore);
} else {
this.cookieStore = null;
}
clientBuilder.setDefaultRequestConfig(requestConfig);
LinkedList