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

net.snowflake.client.jdbc.DefaultResultStreamProvider Maven / Gradle / Ivy

There is a newer version: 3.21.0
Show newest version
package net.snowflake.client.jdbc;

import static net.snowflake.client.core.Constants.MB;

import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import net.snowflake.client.core.HttpUtil;
import net.snowflake.client.log.ArgSupplier;
import net.snowflake.client.util.SecretDetector;
import net.snowflake.common.core.SqlState;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;

public class DefaultResultStreamProvider implements ResultStreamProvider {
  // SSE-C algorithm header
  private static final String SSE_C_ALGORITHM = "x-amz-server-side-encryption-customer-algorithm";

  // SSE-C customer key header
  private static final String SSE_C_KEY = "x-amz-server-side-encryption-customer-key";

  // SSE-C algorithm value
  private static final String SSE_C_AES = "AES256";

  private static final int STREAM_BUFFER_SIZE = MB;

  @Override
  public InputStream getInputStream(ChunkDownloadContext context) throws Exception {
    HttpResponse response;
    try {
      response = getResultChunk(context);
    } catch (URISyntaxException | IOException ex) {
      throw new SnowflakeSQLLoggedException(
          context.getSession(),
          ErrorCode.NETWORK_ERROR.getMessageCode(),
          SqlState.IO_ERROR,
          "Error encountered when request a result chunk URL: "
              + context.getResultChunk().getUrl()
              + " "
              + ex.getLocalizedMessage());
    }

    /*
     * return error if we don't get a response or the response code
     * means failure.
     */
    if (response == null || response.getStatusLine().getStatusCode() != 200) {
      SnowflakeResultSetSerializableV1.logger.error(
          "Error fetching chunk from: {}", context.getResultChunk().getScrubbedUrl());

      SnowflakeUtil.logResponseDetails(response, SnowflakeResultSetSerializableV1.logger);

      throw new SnowflakeSQLException(
          SqlState.IO_ERROR,
          ErrorCode.NETWORK_ERROR.getMessageCode(),
          "Error encountered when downloading a result chunk: HTTP "
              + "status="
              + ((response != null) ? response.getStatusLine().getStatusCode() : "null response"));
    }

    InputStream inputStream;
    final HttpEntity entity = response.getEntity();
    try {
      // read the chunk data
      inputStream = detectContentEncodingAndGetInputStream(response, entity.getContent());
    } catch (Exception ex) {
      SnowflakeResultSetSerializableV1.logger.error("Failed to decompress data: {}", response);

      throw new SnowflakeSQLLoggedException(
          context.getSession(),
          ErrorCode.INTERNAL_ERROR.getMessageCode(),
          SqlState.INTERNAL_ERROR,
          "Failed to decompress data: " + response.toString());
    }

    // trace the response if requested
    SnowflakeResultSetSerializableV1.logger.debug("Json response: {}", response);

    return inputStream;
  }

  private HttpResponse getResultChunk(ChunkDownloadContext context) throws Exception {
    URIBuilder uriBuilder = new URIBuilder(context.getResultChunk().getUrl());

    HttpGet httpRequest = new HttpGet(uriBuilder.build());

    if (context.getChunkHeadersMap() != null && context.getChunkHeadersMap().size() != 0) {
      for (Map.Entry entry : context.getChunkHeadersMap().entrySet()) {
        SnowflakeResultSetSerializableV1.logger.debug(
            "Adding header key={}, value={}", entry.getKey(), entry.getValue());
        httpRequest.addHeader(entry.getKey(), entry.getValue());
      }
    }
    // Add SSE-C headers
    else if (context.getQrmk() != null) {
      httpRequest.addHeader(SSE_C_ALGORITHM, SSE_C_AES);
      httpRequest.addHeader(SSE_C_KEY, context.getQrmk());
      SnowflakeResultSetSerializableV1.logger.debug("Adding SSE-C headers", false);
    }

    SnowflakeResultSetSerializableV1.logger.debug(
        "Thread {} Fetching result #chunk{}: {}",
        Thread.currentThread().getId(),
        context.getChunkIndex(),
        context.getResultChunk().getScrubbedUrl());

    // TODO move this s3 request to HttpUtil class. In theory, upper layer
    // TODO does not need to know about http client
    CloseableHttpClient httpClient =
        HttpUtil.getHttpClient(context.getChunkDownloader().getHttpClientSettingsKey());

    // fetch the result chunk
    HttpResponse response =
        RestRequest.execute(
            httpClient,
            httpRequest,
            context.getNetworkTimeoutInMilli() / 1000, // retry timeout
            context.getAuthTimeout(),
            context.getSocketTimeout(),
            0,
            0, // no socketime injection
            null, // no canceling
            false, // no cookie
            false, // no retry parameters in url
            false, // no request_guid
            true // retry on HTTP403 for AWS S3
            );

    SnowflakeResultSetSerializableV1.logger.debug(
        "Thread {} Call #chunk{} returned for URL: {}, response={}",
        Thread.currentThread().getId(),
        context.getChunkIndex(),
        (ArgSupplier) () -> SecretDetector.maskSASToken(context.getResultChunk().getUrl()),
        response);
    return response;
  }

  private InputStream detectContentEncodingAndGetInputStream(HttpResponse response, InputStream is)
      throws IOException, SnowflakeSQLException {
    InputStream inputStream = is; // Determine the format of the response, if it is not
    // either plain text or gzip, raise an error.
    Header encoding = response.getFirstHeader("Content-Encoding");
    if (encoding != null) {
      if ("gzip".equalsIgnoreCase(encoding.getValue())) {
        /* specify buffer size for GZIPInputStream */
        inputStream = new GZIPInputStream(is, STREAM_BUFFER_SIZE);
      } else {
        throw new SnowflakeSQLException(
            SqlState.INTERNAL_ERROR,
            ErrorCode.INTERNAL_ERROR.getMessageCode(),
            "Exception: unexpected compression got " + encoding.getValue());
      }
    } else {
      inputStream = detectGzipAndGetStream(is);
    }

    return inputStream;
  }

  public static InputStream detectGzipAndGetStream(InputStream is) throws IOException {
    PushbackInputStream pb = new PushbackInputStream(is, 2);
    byte[] signature = new byte[2];
    int len = pb.read(signature);
    pb.unread(signature, 0, len);
    // https://tools.ietf.org/html/rfc1952
    if (signature[0] == (byte) 0x1f && signature[1] == (byte) 0x8b) {
      return new GZIPInputStream(pb);
    } else {
      return pb;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy