ProxyOptions.java
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.core.http;
import com.azure.core.util.Configuration;
import com.azure.core.util.CoreUtils;
import com.azure.core.util.logging.ClientLogger;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
/**
* This represents proxy configuration to be used in http clients..
*/
public class ProxyOptions {
private static final ClientLogger LOGGER = new ClientLogger(ProxyOptions.class);
private static final String INVALID_CONFIGURATION_MESSAGE = "'configuration' cannot be 'Configuration.NONE'.";
private static final String INVALID_AZURE_PROXY_URL = "Configuration {} is an invalid URL and is being ignored.";
/*
* This indicates whether Java environment proxy configurations are allowed to be used.
*/
private static final String JAVA_PROXY_PREREQUISITE = "java.net.useSystemProxies";
/*
* Java environment variables related to proxies. The protocol is removed since these are the same for 'https' and
* 'http', the exception is 'http.nonProxyHosts' as it is used for both.
*/
private static final String JAVA_PROXY_HOST = "proxyHost";
private static final String JAVA_PROXY_PORT = "proxyPort";
private static final String JAVA_PROXY_USER = "proxyUser";
private static final String JAVA_PROXY_PASSWORD = "proxyPassword";
private static final String JAVA_NON_PROXY_HOSTS = "http.nonProxyHosts";
private static final String HTTPS = "https";
private static final int DEFAULT_HTTPS_PORT = 443;
private static final String HTTP = "http";
private static final int DEFAULT_HTTP_PORT = 80;
private static final List<Function<Configuration, ProxyOptions>> ENVIRONMENT_LOAD_ORDER = Arrays.asList(
configuration -> attemptToLoadAzureProxy(configuration, Configuration.PROPERTY_HTTPS_PROXY),
configuration -> attemptToLoadAzureProxy(configuration, Configuration.PROPERTY_HTTP_PROXY),
configuration -> attemptToLoadJavaProxy(configuration, HTTPS),
configuration -> attemptToLoadJavaProxy(configuration, HTTP)
);
private final InetSocketAddress address;
private final Type type;
private String username;
private String password;
private String nonProxyHosts;
/**
* Creates ProxyOptions.
*
* @param type the proxy type
* @param address the proxy address (ip and port number)
*/
public ProxyOptions(Type type, InetSocketAddress address) {
this.type = type;
this.address = address;
}
/**
* Set the proxy credentials.
*
* @param username proxy user name
* @param password proxy password
* @return the updated ProxyOptions object
*/
public ProxyOptions setCredentials(String username, String password) {
this.username = Objects.requireNonNull(username, "'username' cannot be null.");
this.password = Objects.requireNonNull(password, "'password' cannot be null.");
return this;
}
/**
* Sets the hosts which bypass the proxy.
*
* <p>
* The expected format of the passed string is a {@code '|'} delimited list of hosts which should bypass the proxy.
* Individual host strings may contain regex characters such as {@code '*'}.
*
* @param nonProxyHosts Hosts that bypass the proxy.
* @return the updated ProxyOptions object
*/
public ProxyOptions setNonProxyHosts(String nonProxyHosts) {
this.nonProxyHosts = nonProxyHosts;
return this;
}
/**
* @return the address of the proxy.
*/
public InetSocketAddress getAddress() {
return address;
}
/**
* @return the type of the proxy.
*/
public Type getType() {
return type;
}
/**
* @return the proxy user name.
*/
public String getUsername() {
return this.username;
}
/**
* @return the proxy password.
*/
public String getPassword() {
return this.password;
}
/**
* @return the hosts that bypass the proxy.
*/
public String getNonProxyHosts() {
return this.nonProxyHosts;
}
/**
* Attempts to load a proxy from the environment.
*
* <p>
* Environment configurations are loaded in this order:
* <ol>
* <li>Azure HTTPS</li>
* <li>Azure HTTP</li>
* <li>Java HTTPS</li>
* <li>Java HTTP</li>
* </ol>
*
* Azure proxy configurations will be preferred over Java proxy configurations as they are more closely scoped to
* the purpose of the SDK. Additionally, more secure protocols, HTTPS vs HTTP, will be preferred.
*
* <p>
* {@code null} will be returned if no proxy was found in the environment.
*
* @param configuration The {@link Configuration} that is used to load proxy configurations from the environment.
* If {@code null} is passed then {@link Configuration#getGlobalConfiguration()} will be used. If
* {@link Configuration#NONE} is passed {@link IllegalArgumentException} will be thrown.
* @return A {@link ProxyOptions} reflecting a proxy loaded from the environment, if no proxy is found {@code null}
* will be returned.
* @throws IllegalArgumentException If {@code configuration} is {@link Configuration#NONE}.
*/
public static ProxyOptions fromConfiguration(Configuration configuration) {
if (configuration == Configuration.NONE) {
throw LOGGER.logExceptionAsWarning(new IllegalArgumentException(INVALID_CONFIGURATION_MESSAGE));
}
Configuration proxyConfiguration = (configuration == null)
? Configuration.getGlobalConfiguration()
: configuration;
for (Function<Configuration, ProxyOptions> loader : ENVIRONMENT_LOAD_ORDER) {
ProxyOptions proxyOptions = loader.apply(proxyConfiguration);
if (proxyOptions != null) {
return proxyOptions;
}
}
return null;
}
private static ProxyOptions attemptToLoadAzureProxy(Configuration configuration, String proxyProperty) {
String proxyConfiguration = configuration.get(proxyProperty);
// No proxy configuration setup.
if (CoreUtils.isNullOrEmpty(proxyConfiguration)) {
return null;
}
try {
URL proxyUrl = new URL(proxyConfiguration);
int port = (proxyUrl.getPort() == -1) ? proxyUrl.getDefaultPort() : proxyUrl.getPort();
ProxyOptions proxyOptions = new ProxyOptions(Type.HTTP, new InetSocketAddress(proxyUrl.getHost(), port))
.setNonProxyHosts(configuration.get(Configuration.PROPERTY_NO_PROXY));
String userInfo = proxyUrl.getUserInfo();
if (userInfo != null) {
String[] usernamePassword = userInfo.split(":", 2);
if (usernamePassword.length == 2) {
try {
proxyOptions.setCredentials(
URLDecoder.decode(usernamePassword[0], StandardCharsets.UTF_8.toString()),
URLDecoder.decode(usernamePassword[1], StandardCharsets.UTF_8.toString())
);
} catch (UnsupportedEncodingException e) {
return null;
}
}
}
return proxyOptions;
} catch (MalformedURLException ex) {
LOGGER.warning(INVALID_AZURE_PROXY_URL, proxyProperty);
return null;
}
}
private static ProxyOptions attemptToLoadJavaProxy(Configuration configuration, String type) {
// Not allowed to use Java proxies
if (!Boolean.parseBoolean(configuration.get(JAVA_PROXY_PREREQUISITE))) {
return null;
}
String host = configuration.get(type + "." + JAVA_PROXY_HOST);
// No proxy configuration setup.
if (CoreUtils.isNullOrEmpty(host)) {
return null;
}
int port;
try {
port = Integer.parseInt(configuration.get(type + "." + JAVA_PROXY_PORT));
} catch (NumberFormatException ex) {
port = HTTPS.equals(type) ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT;
}
ProxyOptions proxyOptions = new ProxyOptions(Type.HTTP, new InetSocketAddress(host, port))
.setNonProxyHosts(configuration.get(JAVA_NON_PROXY_HOSTS));
String username = configuration.get(type + "." + JAVA_PROXY_USER);
String password = configuration.get(type + "." + JAVA_PROXY_PASSWORD);
if (username != null && password != null) {
proxyOptions.setCredentials(username, password);
}
return proxyOptions;
}
/**
* The type of the proxy.
*/
public enum Type {
/**
* HTTP proxy type.
*/
HTTP(Proxy.Type.HTTP),
/**
* SOCKS4 proxy type.
*/
SOCKS4(Proxy.Type.SOCKS),
/**
* SOCKS5 proxy type.
*/
SOCKS5(Proxy.Type.SOCKS);
private final Proxy.Type proxyType;
Type(Proxy.Type proxyType) {
this.proxyType = proxyType;
}
/**
* Get the {@link Proxy.Type} equivalent of this type.
*
* @return the proxy type
*/
public Proxy.Type toProxyType() {
return proxyType;
}
}
}