All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.snowflake.client.core.HttpUtil Maven / Gradle / Ivy

There is a newer version: 3.19.0
Show newest version
/*
 * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved.
 */

package net.snowflake.client.core;

import static net.snowflake.client.jdbc.SnowflakeUtil.systemGetProperty;
import static org.apache.http.client.config.CookieSpecs.DEFAULT;
import static org.apache.http.client.config.CookieSpecs.IGNORE_COOKIES;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.Protocol;
import com.google.common.base.Strings;
import com.microsoft.azure.storage.OperationContext;
import com.snowflake.client.jdbc.SnowflakeDriver;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.net.ssl.TrustManager;
import net.snowflake.client.jdbc.ErrorCode;
import net.snowflake.client.jdbc.RestRequest;
import net.snowflake.client.jdbc.SnowflakeSQLException;
import net.snowflake.client.jdbc.SnowflakeUtil;
import net.snowflake.client.log.ArgSupplier;
import net.snowflake.client.log.SFLogger;
import net.snowflake.client.log.SFLoggerFactory;
import net.snowflake.client.util.SecretDetector;
import net.snowflake.common.core.SqlState;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultRedirectStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLInitializationException;
import org.apache.http.util.EntityUtils;

public class HttpUtil {
  static final SFLogger logger = SFLoggerFactory.getLogger(HttpUtil.class);

  static final int DEFAULT_MAX_CONNECTIONS = 300;
  static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 300;
  static final int DEFAULT_CONNECTION_TIMEOUT = 60000;
  static final int DEFAULT_HTTP_CLIENT_SOCKET_TIMEOUT = 300000; // ms
  static final int DEFAULT_TTL = -1; // secs
  static final int DEFAULT_IDLE_CONNECTION_TIMEOUT = 5; // secs
  static final int DEFAULT_DOWNLOADED_CONDITION_TIMEOUT = 3600; // secs

  public static final String JDBC_TTL = "net.snowflake.jdbc.ttl";
  public static final String JDBC_MAX_CONNECTIONS_PROPERTY = "net.snowflake.jdbc.max_connections";
  public static final String JDBC_MAX_CONNECTIONS_PER_ROUTE_PROPERTY =
      "net.snowflake.jdbc.max_connections_per_route";

  /**
   * The unique httpClient shared by all connections. This will benefit long- lived clients. Key =
   * proxy host + proxy port + nonProxyHosts, Value = Map of [OCSPMode, HttpClient]
   */
  public static Map httpClient =
      new ConcurrentHashMap<>();

  /**
   * The unique httpClient map shared by all connections that don't want decompression. This will
   * benefit long-lived clients. Key = proxy host + proxy port + nonProxyHosts, Value = Map
   * [OCSPMode, HttpClient]
   */
  private static Map httpClientWithoutDecompression =
      new ConcurrentHashMap<>();

  /** The map of snowflake route planners */
  private static Map
      httpClientRoutePlanner = new ConcurrentHashMap<>();

  /** Handle on the static connection manager, to gather statistics mainly */
  private static PoolingHttpClientConnectionManager connectionManager = null;

  /** default request configuration, to be copied on individual requests. */
  private static RequestConfig DefaultRequestConfig = null;

  private static boolean socksProxyDisabled = false;

  /** customized proxy properties */
  static boolean httpUseProxy = false;

  public static long getDownloadedConditionTimeoutInSeconds() {
    return DEFAULT_DOWNLOADED_CONDITION_TIMEOUT;
  }

  public static void closeExpiredAndIdleConnections() {
    if (connectionManager != null) {
      synchronized (connectionManager) {
        logger.debug("connection pool stats: {}", connectionManager.getTotalStats());
        connectionManager.closeExpiredConnections();
        connectionManager.closeIdleConnections(DEFAULT_IDLE_CONNECTION_TIMEOUT, TimeUnit.SECONDS);
      }
    }
  }

  /**
   * A static function to set S3 proxy params when there is a valid session
   *
   * @param key key to HttpClient map containing OCSP and proxy info
   * @param clientConfig the configuration needed by S3 to set the proxy
   */
  public static void setProxyForS3(HttpClientSettingsKey key, ClientConfiguration clientConfig) {
    if (key != null && key.usesProxy()) {
      clientConfig.setProxyProtocol(key.getProxyProtocol());
      clientConfig.setProxyHost(key.getProxyHost());
      clientConfig.setProxyPort(key.getProxyPort());
      clientConfig.setNonProxyHosts(key.getNonProxyHosts());
      if (!Strings.isNullOrEmpty(key.getProxyUser())
          && !Strings.isNullOrEmpty(key.getProxyPassword())) {
        clientConfig.setProxyUsername(key.getProxyUser());
        clientConfig.setProxyPassword(key.getProxyPassword());
      }
    }
  }

  /**
   * A static function to set S3 proxy params for sessionless connections using the proxy params
   * from the StageInfo
   *
   * @param proxyProperties proxy properties
   * @param clientConfig the configuration needed by S3 to set the proxy
   * @throws SnowflakeSQLException
   */
  public static void setSessionlessProxyForS3(
      Properties proxyProperties, ClientConfiguration clientConfig) throws SnowflakeSQLException {
    // do nothing yet
    if (proxyProperties != null
        && proxyProperties.size() > 0
        && proxyProperties.getProperty(SFSessionProperty.USE_PROXY.getPropertyKey()) != null) {
      Boolean useProxy =
          Boolean.valueOf(
              proxyProperties.getProperty(SFSessionProperty.USE_PROXY.getPropertyKey()));
      if (useProxy) {
        // set up other proxy related values.
        String proxyHost =
            proxyProperties.getProperty(SFSessionProperty.PROXY_HOST.getPropertyKey());
        int proxyPort;
        try {
          proxyPort =
              Integer.parseInt(
                  proxyProperties.getProperty(SFSessionProperty.PROXY_PORT.getPropertyKey()));
        } catch (NumberFormatException | NullPointerException e) {
          throw new SnowflakeSQLException(
              ErrorCode.INVALID_PROXY_PROPERTIES, "Could not parse port number");
        }
        String proxyUser =
            proxyProperties.getProperty(SFSessionProperty.PROXY_USER.getPropertyKey());
        String proxyPassword =
            proxyProperties.getProperty(SFSessionProperty.PROXY_PASSWORD.getPropertyKey());
        String nonProxyHosts =
            proxyProperties.getProperty(SFSessionProperty.NON_PROXY_HOSTS.getPropertyKey());
        String proxyProtocol =
            proxyProperties.getProperty(SFSessionProperty.PROXY_PROTOCOL.getPropertyKey());
        Protocol protocolEnum =
            (!Strings.isNullOrEmpty(proxyProtocol) && proxyProtocol.equalsIgnoreCase("https"))
                ? Protocol.HTTPS
                : Protocol.HTTP;
        clientConfig.setProxyHost(proxyHost);
        clientConfig.setProxyPort(proxyPort);
        clientConfig.setNonProxyHosts(nonProxyHosts);
        clientConfig.setProxyProtocol(protocolEnum);
        if (!Strings.isNullOrEmpty(proxyUser) && !Strings.isNullOrEmpty(proxyPassword)) {
          clientConfig.setProxyUsername(proxyUser);
          clientConfig.setProxyPassword(proxyPassword);
        }
      }
    }
  }

  /**
   * A static function to set Azure proxy params for sessionless connections using the proxy params
   * from the StageInfo
   *
   * @param proxyProperties proxy properties
   * @param opContext the configuration needed by Azure to set the proxy
   * @throws SnowflakeSQLException
   */
  public static void setSessionlessProxyForAzure(
      Properties proxyProperties, OperationContext opContext) throws SnowflakeSQLException {
    if (proxyProperties != null
        && proxyProperties.size() > 0
        && proxyProperties.getProperty(SFSessionProperty.USE_PROXY.getPropertyKey()) != null) {
      Boolean useProxy =
          Boolean.valueOf(
              proxyProperties.getProperty(SFSessionProperty.USE_PROXY.getPropertyKey()));
      if (useProxy) {
        String proxyHost =
            proxyProperties.getProperty(SFSessionProperty.PROXY_HOST.getPropertyKey());
        int proxyPort;
        try {
          proxyPort =
              Integer.parseInt(
                  proxyProperties.getProperty(SFSessionProperty.PROXY_PORT.getPropertyKey()));
        } catch (NumberFormatException | NullPointerException e) {
          throw new SnowflakeSQLException(
              ErrorCode.INVALID_PROXY_PROPERTIES, "Could not parse port number");
        }
        Proxy azProxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort));
        opContext.setProxy(azProxy);
      }
    }
  }

  /**
   * A static function to set Azure proxy params when there is a valid session
   *
   * @param key key to HttpClient map containing OCSP and proxy info
   * @param opContext the configuration needed by Azure to set the proxy
   */
  public static void setProxyForAzure(HttpClientSettingsKey key, OperationContext opContext) {
    if (key != null && key.usesProxy()) {
      Proxy azProxy =
          new Proxy(Proxy.Type.HTTP, new InetSocketAddress(key.getProxyHost(), key.getProxyPort()));
      opContext.setProxy(azProxy);
    }
  }

  /**
   * Constructs a user-agent header with the following pattern: connector_name/connector_version
   * (os-platform_info) language_implementation/language_version
   *
   * @return string for user-agent header
   */
  private static String buildUserAgent() {
    // Start with connector name
    StringBuilder builder = new StringBuilder("JDBC/");
    // Append connector version and parenthesis start
    builder.append(SnowflakeDriver.implementVersion);
    builder.append(" (");
    // Generate OS platform and version from system properties
    String osPlatform = (systemGetProperty("os.name") != null) ? systemGetProperty("os.name") : "";
    String osVersion =
        (systemGetProperty("os.version") != null) ? systemGetProperty("os.version") : "";
    // Append OS platform and version separated by a space
    builder.append(osPlatform);
    builder.append(" ");
    builder.append(osVersion);
    // Append language name
    builder.append(") JAVA/");
    // Generate string for language version from system properties and append it
    String languageVersion =
        (systemGetProperty("java.version") != null) ? systemGetProperty("java.version") : "";
    builder.append(languageVersion);
    String userAgent = builder.toString();
    return userAgent;
  }

  /**
   * Build an Http client using our set of default.
   *
   * @param key Key to HttpClient hashap containing OCSP mode and proxy information
   * @param ocspCacheFile OCSP response cache file. If null, the default OCSP response file will be
   *     used.
   * @param downloadCompressed Whether the HTTP client should be built requesting no decompression
   * @return HttpClient object
   */
  static CloseableHttpClient buildHttpClient(
      HttpClientSettingsKey key, File ocspCacheFile, boolean downloadCompressed) {
    // set timeout so that we don't wait forever.
    // Setup the default configuration for all requests on this client

    int timeToLive = convertSystemPropertyToIntValue(JDBC_TTL, DEFAULT_TTL);
    logger.debug("time to live in connection pooling manager: {}", timeToLive);

    // Set proxy settings for DefaultRequestConfig. If current proxy settings are the same as for
    // the last request, keep the current DefaultRequestConfig. If not, build a new
    // DefaultRequestConfig and set the new proxy settings for it
    HttpHost proxy =
        key.usesProxy()
            ? new HttpHost(
                key.getProxyHost(), key.getProxyPort(), key.getProxyProtocol().toString())
            : null;
    // If defaultrequestconfig is not initialized or its proxy settings do not match current proxy
    // settings, re-build it (current or old proxy settings could be null, so null check is
    // included)
    boolean noDefaultRequestConfig =
        DefaultRequestConfig == null || DefaultRequestConfig.getProxy() == null;
    if (noDefaultRequestConfig || !DefaultRequestConfig.getProxy().equals(proxy)) {
      RequestConfig.Builder builder =
          RequestConfig.custom()
              .setConnectTimeout(DEFAULT_CONNECTION_TIMEOUT)
              .setConnectionRequestTimeout(DEFAULT_CONNECTION_TIMEOUT)
              .setSocketTimeout(DEFAULT_HTTP_CLIENT_SOCKET_TIMEOUT);
      // only set the proxy settings if they are not null
      if (proxy != null) builder.setProxy(proxy);
      DefaultRequestConfig = builder.build();
    }

    TrustManager[] trustManagers = null;
    if (key.getOcspMode() != OCSPMode.INSECURE) {
      // A custom TrustManager is required only if insecureMode is disabled,
      // which is by default in the production. insecureMode can be enabled
      // 1) OCSP service is down for reasons, 2) PowerMock test tht doesn't
      // care OCSP checks.
      // OCSP FailOpen is ON by default
      try {
        TrustManager[] tm = {new SFTrustManager(key, ocspCacheFile)};
        trustManagers = tm;
      } catch (Exception | Error err) {
        // dump error stack
        StringWriter errors = new StringWriter();
        err.printStackTrace(new PrintWriter(errors));
        logger.error(errors.toString());
        throw new RuntimeException(err); // rethrow the exception
      }
    }
    try {
      Registry registry =
          RegistryBuilder.create()
              .register(
                  "https", new SFSSLConnectionSocketFactory(trustManagers, socksProxyDisabled))
              .register("http", new SFConnectionSocketFactory())
              .build();

      // Build a connection manager with enough connections
      connectionManager =
          new PoolingHttpClientConnectionManager(
              registry, null, null, null, timeToLive, TimeUnit.SECONDS);
      int maxConnections =
          convertSystemPropertyToIntValue(JDBC_MAX_CONNECTIONS_PROPERTY, DEFAULT_MAX_CONNECTIONS);
      int maxConnectionsPerRoute =
          convertSystemPropertyToIntValue(
              JDBC_MAX_CONNECTIONS_PER_ROUTE_PROPERTY, DEFAULT_MAX_CONNECTIONS_PER_ROUTE);
      logger.debug(
          "Max connections total in connection pooling manager: {}; max connections per route: {}",
          maxConnections,
          maxConnectionsPerRoute);
      connectionManager.setMaxTotal(maxConnections);
      connectionManager.setDefaultMaxPerRoute(maxConnectionsPerRoute);

      HttpClientBuilder httpClientBuilder =
          HttpClientBuilder.create()
              .setConnectionManager(connectionManager)
              // Support JVM proxy settings
              .useSystemProperties()
              .setRedirectStrategy(new DefaultRedirectStrategy())
              .setUserAgent(buildUserAgent()) // needed for Okta
              .disableCookieManagement(); // SNOW-39748

      if (key.usesProxy()) {
        // use the custom proxy properties
        SnowflakeMutableProxyRoutePlanner sdkProxyRoutePlanner =
            httpClientRoutePlanner.computeIfAbsent(
                key,
                k ->
                    new SnowflakeMutableProxyRoutePlanner(
                        key.getProxyHost(), key.getProxyPort(), key.getNonProxyHosts()));
        httpClientBuilder = httpClientBuilder.setProxy(proxy).setRoutePlanner(sdkProxyRoutePlanner);
        if (!Strings.isNullOrEmpty(key.getProxyUser())
            && !Strings.isNullOrEmpty(key.getProxyPassword())) {
          Credentials credentials =
              new UsernamePasswordCredentials(key.getProxyUser(), key.getProxyPassword());
          AuthScope authScope = new AuthScope(key.getProxyHost(), key.getProxyPort());
          CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
          credentialsProvider.setCredentials(authScope, credentials);
          httpClientBuilder = httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
        }
      }
      httpClientBuilder.setDefaultRequestConfig(DefaultRequestConfig);
      if (downloadCompressed) {
        httpClientBuilder = httpClientBuilder.disableContentCompression();
      }
      return httpClientBuilder.build();
    } catch (NoSuchAlgorithmException | KeyManagementException ex) {
      throw new SSLInitializationException(ex.getMessage(), ex);
    }
  }

  public static void updateRoutePlanner(HttpClientSettingsKey key) {
    if (httpClientRoutePlanner.containsKey(key)
        && !httpClientRoutePlanner
            .get(key)
            .getNonProxyHosts()
            .equalsIgnoreCase(key.getNonProxyHosts())) {
      httpClientRoutePlanner.get(key).setNonProxyHosts(key.getNonProxyHosts());
    }
  }

  /**
   * Gets HttpClient with insecureMode false
   *
   * @param ocspAndProxyKey OCSP mode and proxy settings for httpclient
   * @return HttpClient object shared across all connections
   */
  public static CloseableHttpClient getHttpClient(HttpClientSettingsKey ocspAndProxyKey) {
    return initHttpClient(ocspAndProxyKey, null);
  }

  /**
   * Gets HttpClient with insecureMode false and disabling decompression
   *
   * @param ocspAndProxyKey OCSP mode and proxy settings for httpclient
   * @return HttpClient object shared across all connections
   */
  public static CloseableHttpClient getHttpClientWithoutDecompression(
      HttpClientSettingsKey ocspAndProxyKey) {
    return initHttpClientWithoutDecompression(ocspAndProxyKey, null);
  }

  /**
   * Accessor for the HTTP client singleton.
   *
   * @param key contains information needed to build specific HttpClient
   * @param ocspCacheFile OCSP response cache file name. if null, the default file will be used.
   * @return HttpClient object shared across all connections
   */
  public static CloseableHttpClient initHttpClientWithoutDecompression(
      HttpClientSettingsKey key, File ocspCacheFile) {
    updateRoutePlanner(key);
    return httpClientWithoutDecompression.computeIfAbsent(
        key, k -> buildHttpClient(key, ocspCacheFile, true));
  }

  /**
   * Accessor for the HTTP client singleton.
   *
   * @param key contains information needed to build specific HttpClient
   * @param ocspCacheFile OCSP response cache file name. if null, the default file will be used.
   * @return HttpClient object shared across all connections
   */
  public static CloseableHttpClient initHttpClient(HttpClientSettingsKey key, File ocspCacheFile) {
    updateRoutePlanner(key);
    return httpClient.computeIfAbsent(key, k -> buildHttpClient(key, ocspCacheFile, false));
  }

  /**
   * Return a request configuration inheriting from the default request configuration of the shared
   * HttpClient with a different socket timeout.
   *
   * @param soTimeoutMs - custom socket timeout in milli-seconds
   * @param withoutCookies - whether this request should ignore cookies or not
   * @return RequestConfig object
   */
  public static RequestConfig getDefaultRequestConfigWithSocketTimeout(
      int soTimeoutMs, boolean withoutCookies) {
    final String cookieSpec = withoutCookies ? IGNORE_COOKIES : DEFAULT;
    return RequestConfig.copy(DefaultRequestConfig)
        .setSocketTimeout(soTimeoutMs)
        .setCookieSpec(cookieSpec)
        .build();
  }

  /**
   * Return a request configuration inheriting from the default request configuration of the shared
   * HttpClient with the coopkie spec set to ignore.
   *
   * @return RequestConfig object
   */
  public static RequestConfig getRequestConfigWithoutCookies() {
    return RequestConfig.copy(DefaultRequestConfig).setCookieSpec(IGNORE_COOKIES).build();
  }

  public static void setRequestConfig(RequestConfig requestConfig) {
    DefaultRequestConfig = requestConfig;
  }

  /**
   * Accessor for the HTTP client singleton.
   *
   * @return HTTP Client stats in string representation
   */
  private static String getHttpClientStats() {
    return connectionManager == null ? "" : connectionManager.getTotalStats().toString();
  }

  /**
   * Enables/disables use of the SOCKS proxy when creating sockets
   *
   * @param socksProxyDisabled new value
   */
  public static void setSocksProxyDisabled(boolean socksProxyDisabled) {
    HttpUtil.socksProxyDisabled = socksProxyDisabled;
  }

  /**
   * Returns whether the SOCKS proxy is disabled for this JVM
   *
   * @return whether the SOCKS proxy is disabled
   */
  public static boolean isSocksProxyDisabled() {
    return HttpUtil.socksProxyDisabled;
  }

  /**
   * Executes a HTTP request with the cookie spec set to IGNORE_COOKIES
   *
   * @param httpRequest HttpRequestBase
   * @param retryTimeout retry timeout
   * @param injectSocketTimeout injecting socket timeout
   * @param canceling canceling?
   * @param ocspAndProxyKey OCSP mode and proxy settings for httpclient
   * @return response
   * @throws SnowflakeSQLException if Snowflake error occurs
   * @throws IOException raises if a general IO error occurs
   */
  static String executeRequestWithoutCookies(
      HttpRequestBase httpRequest,
      int retryTimeout,
      int injectSocketTimeout,
      AtomicBoolean canceling,
      HttpClientSettingsKey ocspAndProxyKey)
      throws SnowflakeSQLException, IOException {
    return executeRequestInternal(
        httpRequest,
        retryTimeout,
        injectSocketTimeout,
        canceling,
        true, // no cookie
        false, // no retry parameter
        true, // guid? (do we need this?)
        false, // no retry on HTTP 403
        ocspAndProxyKey);
  }

  /**
   * Executes a HTTP request for Snowflake.
   *
   * @param httpRequest HttpRequestBase
   * @param retryTimeout retry timeout
   * @param ocspAndProxyKey OCSP mode and proxy settings for httpclient
   * @return response
   * @throws SnowflakeSQLException if Snowflake error occurs
   * @throws IOException raises if a general IO error occurs
   */
  public static String executeGeneralRequest(
      HttpRequestBase httpRequest, int retryTimeout, HttpClientSettingsKey ocspAndProxyKey)
      throws SnowflakeSQLException, IOException {
    return executeRequest(
        httpRequest,
        retryTimeout,
        0, // no inject socket timeout
        null, // no canceling
        false, // no retry parameter
        false, // no retry on HTTP 403
        ocspAndProxyKey);
  }

  /**
   * Executes a HTTP request for Snowflake.
   *
   * @param httpRequest HttpRequestBase
   * @param retryTimeout retry timeout
   * @param injectSocketTimeout injecting socket timeout
   * @param canceling canceling?
   * @param includeRetryParameters whether to include retry parameters in retried requests
   * @param retryOnHTTP403 whether to retry on HTTP 403 or not
   * @param ocspAndProxyKey OCSP mode and proxy settings for httpclient
   * @return response
   * @throws SnowflakeSQLException if Snowflake error occurs
   * @throws IOException raises if a general IO error occurs
   */
  public static String executeRequest(
      HttpRequestBase httpRequest,
      int retryTimeout,
      int injectSocketTimeout,
      AtomicBoolean canceling,
      boolean includeRetryParameters,
      boolean retryOnHTTP403,
      HttpClientSettingsKey ocspAndProxyKey)
      throws SnowflakeSQLException, IOException {
    return executeRequestInternal(
        httpRequest,
        retryTimeout,
        injectSocketTimeout,
        canceling,
        false, // with cookie (do we need cookie?)
        includeRetryParameters,
        true, // include request GUID
        retryOnHTTP403,
        ocspAndProxyKey);
  }

  /**
   * Helper to execute a request with retry and check and throw exception if response is not
   * success. This should be used only for small request has it execute the REST request and get
   * back the result as a string.
   *
   * 

Connection under the httpRequest is released. * * @param httpRequest request object contains all the information * @param retryTimeout retry timeout (in seconds) * @param injectSocketTimeout simulate socket timeout * @param canceling canceling flag * @param withoutCookies whether this request should ignore cookies * @param includeRetryParameters whether to include retry parameters in retried requests * @param includeRequestGuid whether to include request_guid * @param retryOnHTTP403 whether to retry on HTTP 403 * @param ocspAndProxyKey OCSPMode and proxy settings for httpclient * @return response in String * @throws SnowflakeSQLException if Snowflake error occurs * @throws IOException raises if a general IO error occurs */ private static String executeRequestInternal( HttpRequestBase httpRequest, int retryTimeout, int injectSocketTimeout, AtomicBoolean canceling, boolean withoutCookies, boolean includeRetryParameters, boolean includeRequestGuid, boolean retryOnHTTP403, HttpClientSettingsKey ocspAndProxyKey) throws SnowflakeSQLException, IOException { // HttpRequest.toString() contains request URI. Scrub any credentials, if // present, before logging String requestInfoScrubbed = SecretDetector.maskSASToken(httpRequest.toString()); logger.debug( "Pool: {} Executing: {}", (ArgSupplier) HttpUtil::getHttpClientStats, requestInfoScrubbed); String theString; StringWriter writer = null; CloseableHttpResponse response = null; try { response = RestRequest.execute( getHttpClient(ocspAndProxyKey), httpRequest, retryTimeout, injectSocketTimeout, canceling, withoutCookies, includeRetryParameters, includeRequestGuid, retryOnHTTP403); if (response == null || response.getStatusLine().getStatusCode() != 200) { logger.error("Error executing request: {}", requestInfoScrubbed); SnowflakeUtil.logResponseDetails(response, logger); if (response != null) { EntityUtils.consume(response.getEntity()); } throw new SnowflakeSQLException( SqlState.IO_ERROR, ErrorCode.NETWORK_ERROR.getMessageCode(), "HTTP status=" + ((response != null) ? response.getStatusLine().getStatusCode() : "null response")); } writer = new StringWriter(); try (InputStream ins = response.getEntity().getContent()) { IOUtils.copy(ins, writer, "UTF-8"); } theString = writer.toString(); } finally { IOUtils.closeQuietly(writer); IOUtils.closeQuietly(response); } logger.debug( "Pool: {} Request returned for: {}", (ArgSupplier) HttpUtil::getHttpClientStats, requestInfoScrubbed); return theString; } // This is a workaround for JDK-7036144. // // The GZIPInputStream prematurely closes its input if a) it finds // a whole GZIP block and b) input.available() returns 0. In order // to work around this issue, we inject a thin wrapper for the // InputStream whose available() method always returns at least 1. // // Further details on this bug: // http://bugs.java.com/bugdatabase/view_bug.do?bug_id=7036144 public static final class HttpInputStream extends InputStream { private final InputStream httpIn; public HttpInputStream(InputStream httpIn) { this.httpIn = httpIn; } // This is the only modified function, all other // methods are simple wrapper around the HTTP stream. @Override public final int available() throws IOException { int available = httpIn.available(); return available == 0 ? 1 : available; } // ONLY WRAPPER METHODS FROM HERE ON. @Override public final int read() throws IOException { return httpIn.read(); } @Override public final int read(byte b[]) throws IOException { return httpIn.read(b); } @Override public final int read(byte b[], int off, int len) throws IOException { return httpIn.read(b, off, len); } @Override public final long skip(long n) throws IOException { return httpIn.skip(n); } @Override public final void close() throws IOException { httpIn.close(); } @Override public synchronized void mark(int readlimit) { httpIn.mark(readlimit); } @Override public synchronized void reset() throws IOException { httpIn.reset(); } @Override public final boolean markSupported() { return httpIn.markSupported(); } } static final class SFConnectionSocketFactory extends PlainConnectionSocketFactory { @Override public Socket createSocket(HttpContext ctx) throws IOException { if (socksProxyDisabled) { return new Socket(Proxy.NO_PROXY); } return super.createSocket(ctx); } } /** * Helper function to convert system properties to integers * * @param systemProperty name of the system property * @param defaultValue default value used * @return the value of the system property, else the default value */ static int convertSystemPropertyToIntValue(String systemProperty, int defaultValue) { String systemPropertyValue = systemGetProperty(systemProperty); int returnVal = defaultValue; if (systemPropertyValue != null) { try { returnVal = Integer.parseInt(systemPropertyValue); } catch (NumberFormatException ex) { logger.info( "Failed to parse the system parameter {} with value {}", systemProperty, systemPropertyValue); } } return returnVal; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy