HttpUrlConnectorConfiguration.java
/*
* Copyright (c) 2025 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.client.internal;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.ClientRequest;
import org.glassfish.jersey.client.HttpUrlConnectorProvider;
import org.glassfish.jersey.client.innate.ConnectorConfiguration;
import org.glassfish.jersey.internal.util.PropertiesHelper;
import org.glassfish.jersey.internal.util.collection.Ref;
import javax.net.ssl.SSLContext;
import javax.ws.rs.core.Configuration;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URL;
import java.security.AccessController;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.logging.Logger;
public abstract class HttpUrlConnectorConfiguration<C extends HttpUrlConnectorConfiguration<C>>
extends ConnectorConfiguration<C> {
private static final Logger LOGGER = Logger.getLogger(HttpUrlConnectorConfiguration.class.getName());
/* package */ static final String ALLOW_RESTRICTED_HEADERS_SYSTEM_PROPERTY = "sun.net.http.allowRestrictedHeaders";
/**
* Default connection factory to be used.
*/
protected static final HttpUrlConnectorProvider.ConnectionFactory DEFAULT_CONNECTION_FACTORY =
new DefaultConnectionFactory();
protected NullableRef<HttpUrlConnectorProvider.ConnectionFactory> connectionFactory = NullableRef.empty();
protected Ref<Integer> chunkSize = NullableRef.empty();
/* package */ Ref<Boolean> isRestrictedHeaderPropertySet = NullableRef.empty();
protected Ref<Boolean> useFixedLengthStreaming = NullableRef.empty();
protected Ref<Boolean> useSetMethodWorkaround = NullableRef.empty();
protected void preInit(Map<String, Object> properties) {
connectionFactory.ifEmptySet(DEFAULT_CONNECTION_FACTORY);
((NullableRef<Integer>) chunkSize).ifEmptySet(ClientProperties.DEFAULT_CHUNK_SIZE);
((NullableRef<Boolean>) useFixedLengthStreaming).ifEmptySet(Boolean.FALSE);
((NullableRef<Boolean>) useSetMethodWorkaround).ifEmptySet(Boolean.FALSE);
int computedChunkSize = ClientProperties.getValue(properties,
_prefixed(ClientProperties.CHUNKED_ENCODING_SIZE), chunkSize.get(), Integer.class);
if (computedChunkSize < 0) {
LOGGER.warning(LocalizationMessages.NEGATIVE_CHUNK_SIZE(computedChunkSize, chunkSize.get()));
} else {
chunkSize.set(computedChunkSize);
}
useFixedLengthStreaming(ClientProperties.getValue(properties,
_prefixed(HttpUrlConnectorProvider.USE_FIXED_LENGTH_STREAMING),
useFixedLengthStreaming.get(), Boolean.class));
useSetMethodWorkaround(ClientProperties.getValue(properties,
_prefixed(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND),
useSetMethodWorkaround.get(), Boolean.class));
}
private String _prefixed(String property) {
return prefix.ifPresentOrElse("") + property;
}
/**
* Set a custom {@link java.net.HttpURLConnection} factory.
*
* @param connectionFactory custom HTTP URL connection factory. Must not be {@code null}.
* @return updated configuration.
* @throws java.lang.NullPointerException in case the supplied connectionFactory is {@code null}.
*/
public C connectionFactory(final HttpUrlConnectorProvider.ConnectionFactory connectionFactory) {
if (connectionFactory == null) {
throw new NullPointerException(LocalizationMessages.NULL_INPUT_PARAMETER("connectionFactory"));
}
this.connectionFactory.set(connectionFactory);
return self();
}
/**
* Set chunk size for requests transferred using a
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1">HTTP chunked transfer coding</a>.
*
* If no value is set, the default chunk size of 4096 bytes will be used.
* <p>
* 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.
* </p>
*
* @param chunkSize chunked transfer coding chunk size to be used.
* @return updated configuration.
* @throws java.lang.IllegalArgumentException in case the specified chunk size is negative.
*/
public C chunkSize(final int chunkSize) {
if (chunkSize < 0) {
throw new IllegalArgumentException(LocalizationMessages.NEGATIVE_INPUT_PARAMETER("chunkSize"));
}
this.chunkSize.set(chunkSize);
return self();
}
/**
* 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 HttpUrlConnectorProvider#USE_FIXED_LENGTH_STREAMING} property documentation for more details.
* <p>
* 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.
* </p>
*
* @return updated configuration.
*/
public C useFixedLengthStreaming(boolean use) {
this.useFixedLengthStreaming.set(use);
return self();
}
/**
* Instruct the provided connectors to use reflection when setting the
* HTTP method value. See {@link HttpUrlConnectorProvider#SET_METHOD_WORKAROUND} property documentation for more details.
* <p>
* 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.
* </p>
*
* @return updated configuration.
*/
public C useSetMethodWorkaround(boolean use) {
this.useSetMethodWorkaround.set(use);
return self();
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final HttpUrlConnectorConfiguration<?> that = (HttpUrlConnectorConfiguration<?>) o;
if (!chunkSize.equals(that.chunkSize)) {
return false;
}
if (!useFixedLengthStreaming.equals(that.useFixedLengthStreaming)) {
return false;
}
if (!useSetMethodWorkaround.equals(that.useSetMethodWorkaround)) {
return false;
}
if (!isRestrictedHeaderPropertySet.equals(that.isRestrictedHeaderPropertySet)) {
return false;
}
return connectionFactory.equals(that.connectionFactory);
}
@Override
public int hashCode() {
return Objects.hash(connectionFactory, chunkSize, useFixedLengthStreaming,
useSetMethodWorkaround, isRestrictedHeaderPropertySet);
}
/* package */ ReadWrite rw() {
final ReadWrite readWrite = this instanceof ReadWrite ? ((ReadWrite) this).instance() : new ReadWrite();
readWrite.setNonEmpty(this);
return readWrite;
}
protected static class ReadWrite
extends HttpUrlConnectorConfiguration<ReadWrite>
implements ConnectorConfiguration.Read<ReadWrite> {
@Override
public <X extends ConnectorConfiguration<?>> void setNonEmpty(X otherC) {
HttpUrlConnectorConfiguration<?> other = (HttpUrlConnectorConfiguration<?>) otherC;
Read.super.setNonEmpty(other);
this.connectionFactory.setNonEmpty(other.connectionFactory);
((NullableRef<Integer>) this.chunkSize).setNonEmpty((NullableRef<Integer>) other.chunkSize);
((NullableRef<Boolean>) this.isRestrictedHeaderPropertySet).setNonEmpty(
(NullableRef<Boolean>) other.isRestrictedHeaderPropertySet);
((NullableRef<Boolean>) this.useFixedLengthStreaming).setNonEmpty(
(NullableRef<Boolean>) other.useFixedLengthStreaming);
((NullableRef<Boolean>) this.useSetMethodWorkaround).setNonEmpty((NullableRef<Boolean>) other.useSetMethodWorkaround);
}
@Override
public ReadWrite init() {
ConnectorConfiguration.Read.super.init();
preInit(Collections.emptyMap());
isRestrictedHeaderPropertySet.set(Boolean.FALSE);
return self();
}
ReadWrite fromClient(Configuration configuration) {
ReadWrite clientConfiguration = copyFromClient(configuration);
clientConfiguration.preInit(configuration.getProperties());
// check if sun.net.http.allowRestrictedHeaders system property has been set and log the result
// the property is being cached in the HttpURLConnection, so this is only informative - there might
// already be some connection(s), that existed before the property was set/changed.
isRestrictedHeaderPropertySet.set(Boolean.valueOf(AccessController.doPrivileged(
PropertiesHelper.getSystemProperty(ALLOW_RESTRICTED_HEADERS_SYSTEM_PROPERTY, "false")
)));
LOGGER.config(isRestrictedHeaderPropertySet.get()
? LocalizationMessages.RESTRICTED_HEADER_PROPERTY_SETTING_TRUE(ALLOW_RESTRICTED_HEADERS_SYSTEM_PROPERTY)
: LocalizationMessages.RESTRICTED_HEADER_PROPERTY_SETTING_FALSE(ALLOW_RESTRICTED_HEADERS_SYSTEM_PROPERTY)
);
return clientConfiguration;
}
ReadWrite fromRequest(ClientRequest request) {
ReadWrite requestConfiguration = copyFromRequest(request);
requestConfiguration.chunkSize(
request.resolveProperty(prefixed(ClientProperties.CHUNKED_ENCODING_SIZE),
requestConfiguration.chunkSize.get()));
requestConfiguration.useFixedLengthStreaming(
request.resolveProperty(prefixed(HttpUrlConnectorProvider.USE_FIXED_LENGTH_STREAMING),
requestConfiguration.useFixedLengthStreaming.get()));
requestConfiguration.useSetMethodWorkaround(
request.resolveProperty(prefixed(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND),
requestConfiguration.useSetMethodWorkaround.get()));
return requestConfiguration;
}
@Override
public ReadWrite instance() {
return new ReadWrite();
}
@Override
public ReadWrite me() {
return this;
}
public boolean isMethodWorkaround(ClientRequest request) {
return request.resolveProperty(
prefixed(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND), useSetMethodWorkaround.get());
}
public boolean isPrefixed() {
return !prefix.get().isEmpty();
}
public boolean isSslContextSupplier() {
return sslContextSupplier.get() != null;
}
/**
* Get {@link SSLContext} either from the {@link ClientProperties#SSL_CONTEXT_SUPPLIER}, or from this configuration.
*
* @param request the request used to get the {@link SSLContext}.
* @return the {@link SSLContext} supplier.
*/
public Supplier<SSLContext> sslContext(ClientRequest request) {
@SuppressWarnings("unchecked")
Supplier<SSLContext> supplier =
request.resolveProperty(prefixed(ClientProperties.SSL_CONTEXT_SUPPLIER), Supplier.class);
if (supplier == null) {
supplier = self().sslContextSupplier.get();
}
return supplier;
}
@Override
public ReadWrite self() {
return this;
}
}
private static class DefaultConnectionFactory implements HttpUrlConnectorProvider.ConnectionFactory {
@Override
public HttpURLConnection getConnection(final URL url) throws IOException {
return connect(url, null);
}
@Override
public HttpURLConnection getConnection(URL url, Proxy proxy) throws IOException {
return connect(url, proxy);
}
private HttpURLConnection connect(URL url, Proxy proxy) throws IOException {
return (proxy == null) ? (HttpURLConnection) url.openConnection() : (HttpURLConnection) url.openConnection(proxy);
}
}
}