org.glassfish.jersey.client.HttpUrlConnectorProvider Maven / Gradle / Ivy
Show all versions of jaxrs-ri Show documentation
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2013-2014 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.jersey.client;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import java.util.logging.Logger;
import javax.ws.rs.client.Client;
import javax.ws.rs.core.Configuration;
import org.glassfish.jersey.client.internal.LocalizationMessages;
import org.glassfish.jersey.client.spi.Connector;
import org.glassfish.jersey.client.spi.ConnectorProvider;
/**
* Default Jersey client {@link org.glassfish.jersey.client.spi.Connector connector} provider
* that provides connector instances which delegate HTTP requests to {@link java.net.HttpURLConnection}
* for processing.
*
* The provided connector instances override default behaviour of the property
* {@link ClientProperties#REQUEST_ENTITY_PROCESSING} and use {@link RequestEntityProcessing#BUFFERED}
* request entity processing by default.
*
*
* Due to a bug in the chunked transport coding support of {@code HttpURLConnection} that causes
* requests to fail unpredictably, this connector provider allows to configure the provided connector
* instances to use {@code HttpURLConnection}'s fixed-length streaming mode as a workaround. This
* workaround can be enabled via {@link #useFixedLengthStreaming()} method or via
* {@link #USE_FIXED_LENGTH_STREAMING} Jersey client configuration property.
*
*
* @author Marek Potociar (marek.potociar at oracle.com)
*/
public class HttpUrlConnectorProvider implements ConnectorProvider {
/**
* If {@code true}, the {@link HttpUrlConnector} (if used) will assume the content length
* from the value of {@value javax.ws.rs.core.HttpHeaders#CONTENT_LENGTH} request
* header (if present).
*
* When this property is enabled and the request has a valid non-zero content length
* value specified in its {@value javax.ws.rs.core.HttpHeaders#CONTENT_LENGTH} request
* header, that this value will be used as an input to the
* {@link java.net.HttpURLConnection#setFixedLengthStreamingMode(int)} method call
* invoked on the underlying {@link java.net.HttpURLConnection connection}.
* This will also suppress the entity buffering in the @{code HttpURLConnection},
* which is undesirable in certain scenarios, e.g. when streaming large entities.
*
*
* Note that the content length value defined in the request header must exactly match
* the real size of the entity. If the {@link javax.ws.rs.core.HttpHeaders#CONTENT_LENGTH} header
* is explicitly specified in a request, this property will be ignored and the
* request entity will be still buffered by the underlying @{code HttpURLConnection} infrastructure.
*
*
* This property also overrides the behaviour enabled by the
* {@link org.glassfish.jersey.client.ClientProperties#CHUNKED_ENCODING_SIZE} property.
* Chunked encoding will only be used, if the size is not specified in the header of the request.
*
*
* Note that this property only applies to client run-times that are configured to use the default
* {@link HttpUrlConnector} as the client connector. The property is ignored by other connectors.
*
*
* The default value is {@code false}.
*
*
* The name of the configuration property is {@value}.
*
*
* @since 2.5
*/
public static final String USE_FIXED_LENGTH_STREAMING =
"jersey.config.client.httpUrlConnector.useFixedLengthStreaming";
/**
* A value of {@code true} declares that the client will try to set
* unsupported HTTP method to {@link java.net.HttpURLConnection} via
* reflection.
*
* NOTE: Enabling this property may cause security related warnings/errors
* and it may break when other JDK implementation is used. Use only
* when you know what you are doing.
*
* The value MUST be an instance of {@link java.lang.Boolean}.
* The default value is {@code false}.
* The name of the configuration property is {@value}.
*/
public static final String SET_METHOD_WORKAROUND =
"jersey.config.client.httpUrlConnection.setMethodWorkaround";
/**
* Default chunk size in HTTP chunk-encoded messages.
*/
private static final int DEFAULT_HTTP_CHUNK_SIZE = 4096;
/**
* Default connection factory to be used.
*/
private static final ConnectionFactory DEFAULT_CONNECTION_FACTORY = new DefaultConnectionFactory();
private static final Logger LOGGER = Logger.getLogger(HttpUrlConnectorProvider.class.getName());
private ConnectionFactory connectionFactory;
private int chunkSize;
private boolean useFixedLengthStreaming;
private boolean useSetMethodWorkaround;
/**
* Create new {@link java.net.HttpURLConnection}-based Jersey client connector provider.
*/
public HttpUrlConnectorProvider() {
this.connectionFactory = DEFAULT_CONNECTION_FACTORY;
this.chunkSize = DEFAULT_HTTP_CHUNK_SIZE;
this.useFixedLengthStreaming = false;
this.useSetMethodWorkaround = false;
}
/**
* Set a custom {@link java.net.HttpURLConnection} factory.
*
* @param connectionFactory custom HTTP URL connection factory. Must not be {@code null}.
* @return updated connector provider instance.
* @throws java.lang.NullPointerException in case the supplied connectionFactory is {@code null}.
*/
public HttpUrlConnectorProvider connectionFactory(ConnectionFactory connectionFactory) {
if (connectionFactory == null) {
throw new NullPointerException(LocalizationMessages.NULL_INPUT_PARAMETER("connectionFactory"));
}
this.connectionFactory = connectionFactory;
return this;
}
/**
* Set chunk size for requests transferred using a
* HTTP chunked transfer coding.
*
* If no value is set, the default chunk size of {@value #DEFAULT_HTTP_CHUNK_SIZE} bytes will be used.
*
* Note that this programmatically set value can be overridden by
* setting the {@link org.glassfish.jersey.client.ClientProperties#CHUNKED_ENCODING_SIZE} property
* specified in the Jersey client instance configuration.
*
*
* @param chunkSize chunked transfer coding chunk size to be used.
* @return updated connector provider instance.
* @throws java.lang.IllegalArgumentException in case the specified chunk size is negative.
*/
public HttpUrlConnectorProvider chunkSize(int chunkSize) {
if (chunkSize < 0) {
throw new IllegalArgumentException(LocalizationMessages.NEGATIVE_INPUT_PARAMETER("chunkSize"));
}
this.chunkSize = chunkSize;
return this;
}
/**
* Instruct the provided connectors to use the {@link java.net.HttpURLConnection#setFixedLengthStreamingMode(int)
* fixed-length streaming mode} on the underlying HTTP URL connection instance when sending requests.
* See {@link #USE_FIXED_LENGTH_STREAMING} property documentation for more details.
*
* Note that this programmatically set value can be overridden by
* setting the {@code USE_FIXED_LENGTH_STREAMING} property specified in the Jersey client instance configuration.
*
*
* @return updated connector provider instance.
*/
public HttpUrlConnectorProvider useFixedLengthStreaming() {
this.useFixedLengthStreaming = true;
return this;
}
/**
* Instruct the provided connectors to use reflection when setting the
* HTTP method value.See {@link #SET_METHOD_WORKAROUND} property documentation for more details.
*
* Note that this programmatically set value can be overridden by
* setting the {@code SET_METHOD_WORKAROUND} property specified in the Jersey client instance configuration
* or in the request properties.
*
*
* @return updated connector provider instance.
*/
public HttpUrlConnectorProvider useSetMethodWorkaround() {
this.useSetMethodWorkaround = true;
return this;
}
@Override
public Connector getConnector(Client client, Configuration config) {
final Map properties = config.getProperties();
int computedChunkSize = ClientProperties.getValue(properties,
ClientProperties.CHUNKED_ENCODING_SIZE, chunkSize, Integer.class);
if (computedChunkSize < 0) {
LOGGER.warning(LocalizationMessages.NEGATIVE_CHUNK_SIZE(computedChunkSize, chunkSize));
computedChunkSize = chunkSize;
}
final boolean computedUseFixedLengthStreaming = ClientProperties.getValue(properties,
USE_FIXED_LENGTH_STREAMING, useFixedLengthStreaming, Boolean.class);
final boolean computedUseSetMethodWorkaround = ClientProperties.getValue(properties,
SET_METHOD_WORKAROUND, useSetMethodWorkaround, Boolean.class);
return new HttpUrlConnector(
connectionFactory,
computedChunkSize,
computedUseFixedLengthStreaming,
computedUseSetMethodWorkaround);
}
/**
* A factory for {@link java.net.HttpURLConnection} instances.
*
* A factory may be used to create a {@link java.net.HttpURLConnection} and configure
* it in a custom manner that is not possible using the Client API.
*
* A custom factory instance may be registered in the {@code HttpUrlConnectorProvider} instance
* via {@link #connectionFactory(ConnectionFactory)} method.
*/
public interface ConnectionFactory {
/**
* Get a {@link java.net.HttpURLConnection} for a given URL.
*
* Implementation of the method MUST be thread-safe and MUST ensure that
* a dedicated {@link java.net.HttpURLConnection} instance is returned for concurrent
* requests.
*
*
* @param url the endpoint URL.
* @return the {@link java.net.HttpURLConnection}.
* @throws java.io.IOException in case the connection cannot be provided.
*/
public HttpURLConnection getConnection(URL url) throws IOException;
}
private static class DefaultConnectionFactory implements ConnectionFactory {
@Override
public HttpURLConnection getConnection(URL url) throws IOException {
return (HttpURLConnection) url.openConnection();
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HttpUrlConnectorProvider that = (HttpUrlConnectorProvider) o;
if (chunkSize != that.chunkSize) return false;
if (useFixedLengthStreaming != that.useFixedLengthStreaming) return false;
return connectionFactory.equals(that.connectionFactory);
}
@Override
public int hashCode() {
int result = connectionFactory.hashCode();
result = 31 * result + chunkSize;
result = 31 * result + (useFixedLengthStreaming ? 1 : 0);
return result;
}
}