
com.google.cloud.hadoop.util.HttpTransportFactory Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2015 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.cloud.hadoop.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.util.Objects.requireNonNullElseGet;
import com.google.api.client.googleapis.GoogleUtils;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.flogger.GoogleLogger;
import java.io.IOException;
import java.io.InputStream;
import java.net.Authenticator;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.Socket;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.time.Duration;
import javax.annotation.Nullable;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
/** Factory for creating HttpTransport types. */
public class HttpTransportFactory {
private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
/**
* Create an {@link HttpTransport} with socketKeepAlive true
*
* @return The resulting HttpTransport.
* @throws IllegalArgumentException If the proxy address is invalid.
* @throws IOException If there is an issue connecting to Google's Certification server.
*/
public static HttpTransport createHttpTransport() throws IOException {
return createHttpTransport(
/* proxyAddress= */ null, /* proxyUsername= */ null, /* proxyPassword= */ null);
}
/**
* Create an {@link HttpTransport} based on a type class and an optional HTTP proxy.
*
* @param proxyAddress The HTTP proxy to use with the transport. Of the form hostname:port. If
* empty no proxy will be used.
* @param proxyUsername The HTTP proxy username to use with the transport. If empty no proxy
* username will be used.
* @param proxyPassword The HTTP proxy password to use with the transport. If empty no proxy
* password will be used.
* @return The resulting HttpTransport.
* @throws IllegalArgumentException If the proxy address is invalid.
* @throws IOException If there is an issue connecting to Google's Certification server.
*/
public static HttpTransport createHttpTransport(
@Nullable String proxyAddress,
@Nullable RedactedString proxyUsername,
@Nullable RedactedString proxyPassword)
throws IOException {
return createHttpTransport(proxyAddress, proxyUsername, proxyPassword, /* readTimeout= */ null);
}
/**
* Create an {@link HttpTransport} based on a type class, optional HTTP proxy and optional socket
* read timeout.
*
* @param proxyAddress The HTTP proxy to use with the transport. Of the form hostname:port. If
* empty no proxy will be used.
* @param proxyUsername The HTTP proxy username to use with the transport. If empty no proxy
* username will be used.
* @param proxyPassword The HTTP proxy password to use with the transport. If empty no proxy
* password will be used.
* @param readTimeout The socket read timeout to apply immediately on all HTTP requests. If empty,
* no socket read timeout will be applied.
* @return The resulting HttpTransport.
* @throws IllegalArgumentException If the proxy address is invalid.
* @throws IOException If there is an issue connecting to Google's Certification server.
*/
public static HttpTransport createHttpTransport(
@Nullable String proxyAddress,
@Nullable RedactedString proxyUsername,
@Nullable RedactedString proxyPassword,
@Nullable Duration readTimeout)
throws IOException {
logger.atFiner().log(
"createHttpTransport(%s, %s, %s, %s)",
proxyAddress, proxyUsername, proxyPassword, readTimeout);
checkArgument(
proxyAddress != null || (proxyUsername == null && proxyPassword == null),
"if proxyAddress is null then proxyUsername and proxyPassword should be null too");
checkArgument(
(proxyUsername == null) == (proxyPassword == null),
"both proxyUsername and proxyPassword should be null or not null together");
URI proxyUri = parseProxyAddress(proxyAddress);
try {
PasswordAuthentication proxyAuth =
proxyUsername != null
? new PasswordAuthentication(
proxyUsername.value(), proxyPassword.value().toCharArray())
: null;
return createNetHttpTransport(proxyUri, proxyAuth, readTimeout);
} catch (GeneralSecurityException e) {
throw new IOException(e);
}
}
/**
* Create an {@link NetHttpTransport} for calling Google APIs with an optional HTTP proxy.
*
* @param proxyUri Optional HTTP proxy URI to use with the transport.
* @param proxyAuth Optional HTTP proxy credentials to authenticate with the transport proxy.
* @param readTimeout Optional socket read timeout to apply immediately on all HTTP requests.
* @return The resulting HttpTransport.
* @throws IOException If there is an issue connecting to Google's certification server.
* @throws GeneralSecurityException If there is a security issue with the keystore.
*/
public static NetHttpTransport createNetHttpTransport(
@Nullable URI proxyUri,
@Nullable PasswordAuthentication proxyAuth,
@Nullable Duration readTimeout)
throws IOException, GeneralSecurityException {
checkArgument(
proxyUri != null || proxyAuth == null,
"if proxyUri is null than proxyAuth should be null too");
if (proxyAuth != null) {
// Enable "Basic" authentication on JDK 8+
System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
Authenticator.setDefault(
new Authenticator() {
@Nullable
@Override
protected PasswordAuthentication getPasswordAuthentication() {
if (getRequestorType() == RequestorType.PROXY
&& getRequestingHost().equalsIgnoreCase(proxyUri.getHost())
&& getRequestingPort() == proxyUri.getPort()) {
return proxyAuth;
}
return null;
}
});
}
return createNetHttpTransportBuilder(proxyUri, readTimeout).build();
}
@VisibleForTesting
static NetHttpTransport.Builder createNetHttpTransportBuilder(
@Nullable URI proxyUri, @Nullable Duration readTimeout)
throws IOException, GeneralSecurityException {
NetHttpTransport.Builder builder =
new NetHttpTransport.Builder().trustCertificates(GoogleUtils.getCertificateTrustStore());
SSLSocketFactory wrappedSslSocketFactory =
requireNonNullElseGet(
builder.getSslSocketFactory(), HttpsURLConnection::getDefaultSSLSocketFactory);
return builder
.setSslSocketFactory(new CustomSslSocketFactory(wrappedSslSocketFactory, readTimeout))
.setProxy(
proxyUri == null
? null
: new Proxy(
Proxy.Type.HTTP,
new InetSocketAddress(proxyUri.getHost(), proxyUri.getPort())));
}
/**
* Parse an HTTP proxy from a String address.
*
* @param proxyAddress The address of the proxy of the form (https?://)HOST:PORT.
* @return The URI of the proxy.
* @throws IllegalArgumentException If the address is invalid.
*/
@VisibleForTesting
static URI parseProxyAddress(@Nullable String proxyAddress) {
if (isNullOrEmpty(proxyAddress)) {
return null;
}
String uriString = (proxyAddress.contains("//") ? "" : "//") + proxyAddress;
try {
URI uri = new URI(uriString);
String scheme = uri.getScheme();
String host = uri.getHost();
int port = uri.getPort();
checkArgument(
isNullOrEmpty(scheme) || scheme.matches("https?"),
"HTTP proxy address '%s' has invalid scheme '%s'.",
proxyAddress,
scheme);
checkArgument(!isNullOrEmpty(host), "Proxy address '%s' has no host.", proxyAddress);
checkArgument(port != -1, "Proxy address '%s' has no port.", proxyAddress);
checkArgument(
uri.equals(new URI(scheme, null, host, port, null, null, null)),
"Invalid proxy address '%s'.",
proxyAddress);
return uri;
} catch (URISyntaxException e) {
throw new IllegalArgumentException(
String.format("Invalid proxy address '%s'.", proxyAddress), e);
}
}
/** Wrapper class to have socketKeepAlive property while creating the socket */
@VisibleForTesting
static final class CustomSslSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory wrappedSockedFactory;
private final Integer readTimeoutMillis;
public CustomSslSocketFactory(SSLSocketFactory wrappedSocketFactory, Duration readTimeout) {
this.wrappedSockedFactory = wrappedSocketFactory;
this.readTimeoutMillis = readTimeout != null ? Math.toIntExact(readTimeout.toMillis()) : null;
}
@Override
public String[] getDefaultCipherSuites() {
return wrappedSockedFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return wrappedSockedFactory.getSupportedCipherSuites();
}
@Override
public Socket createSocket() throws IOException {
return customizeSocket(wrappedSockedFactory.createSocket());
}
@Override
public Socket createSocket(Socket s, InputStream consumed, boolean autoClose)
throws IOException {
return customizeSocket(wrappedSockedFactory.createSocket(s, consumed, autoClose));
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose)
throws IOException {
return customizeSocket(wrappedSockedFactory.createSocket(s, host, port, autoClose));
}
public Socket createSocket(String host, int port) throws IOException {
return customizeSocket(wrappedSockedFactory.createSocket(host, port));
}
public Socket createSocket(InetAddress address, int port) throws IOException {
return customizeSocket(wrappedSockedFactory.createSocket(address, port));
}
public Socket createSocket(String host, int port, InetAddress clientAddress, int clientPort)
throws IOException {
return customizeSocket(
wrappedSockedFactory.createSocket(host, port, clientAddress, clientPort));
}
public Socket createSocket(
InetAddress address, int port, InetAddress clientAddress, int clientPort)
throws IOException {
return customizeSocket(
wrappedSockedFactory.createSocket(address, port, clientAddress, clientPort));
}
private Socket customizeSocket(Socket socket) throws SocketException {
// Enable TCP keep-alive.
socket.setKeepAlive(true);
// Set socket read timeout. This shouldn't be necessary, because we generally set the timeout
// through other layers, such as com.google.api.client.http.HttpRequest#setReadTimeout(int).
// However, setting it here guarantees that the timeout is enforced during TLS handshake when
// using Conscrypt as the security provider. (See discussion in
// https://github.com/google/conscrypt/issues/864 .)
if (readTimeoutMillis != null) {
socket.setSoTimeout(readTimeoutMillis);
}
return socket;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy