Package com.google.api.client.http

Source Code of com.google.api.client.http.HttpRequest

/*
* Copyright (c) 2010 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.api.client.http;

import com.google.api.client.util.ObjectParser;
import com.google.api.client.util.StringUtils;
import com.google.common.base.Preconditions;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* HTTP request.
*
* <p>
* Implementation is not thread-safe.
* </p>
*
* @since 1.0
* @author Yaniv Inbar
*/
public final class HttpRequest {

  /**
   * Current version of the Google API Client Library for Java.
   *
   * @since 1.8
   */
  public static final String VERSION = "1.11.0-beta";

  /**
   * User agent suffix for all requests.
   *
   * <p>
   * Includes a {@code "(gzip)"} suffix in case the server -- as Google's servers may do -- checks
   * the {@code User-Agent} header to try to detect if the client accepts gzip-encoded responses.
   * </p>
   *
   * @since 1.4
   */
  public static final String USER_AGENT_SUFFIX = "Google-HTTP-Java-Client/" + VERSION + " (gzip)";

  /** Whether we've warned about {@link #setAllowEmptyContent} being deprecated. */
  private static volatile boolean warnedDeprecatedSetAllowEmptyContent;

  /** Whether we've warned about {@link #isAllowEmptyContent} being deprecated. */
  private static volatile boolean warnedDeprecatedIsAllowEmptyContent;

  /**
   * HTTP request execute interceptor to intercept the start of {@link #execute()} (before executing
   * the HTTP request) or {@code null} for none.
   */
  private HttpExecuteInterceptor interceptor;

  /** HTTP request headers. */
  private HttpHeaders headers = new HttpHeaders();

  /**
   * HTTP response headers.
   *
   * <p>
   * For example, this can be used if you want to use a subclass of {@link HttpHeaders} called
   * MyHeaders to process the response:
   * </p>
   *
   * <pre>
  static String executeAndGetValueOfSomeCustomHeader(HttpRequest request) {
    MyHeaders responseHeaders = new MyHeaders();
    request.responseHeaders = responseHeaders;
    HttpResponse response = request.execute();
    return responseHeaders.someCustomHeader;
  }
   * </pre>
   */
  private HttpHeaders responseHeaders = new HttpHeaders();

  /**
   * Some servers will fail to process a POST/PUT/PATCH unless Content-Length header >= 1. If this
   * value is set to {@code false} then " " is set as the content with Content-Length {@code 1} for
   * empty contents. Defaults to {@code true}.
   */
  @Deprecated
  private boolean allowEmptyContent = true;

  /**
   * Set the number of retries that will be allowed to execute as the result of an
   * {@link HttpUnsuccessfulResponseHandler} before being terminated or {@code 0} to not retry
   * requests. The default value is {@code 10}.
   */
  private int numRetries = 10;

  /**
   * Determines the limit to the content size that will be logged during {@link #execute()}.
   *
   * <p>
   * Content will only be logged if {@link #isLoggingEnabled} is {@code true}.
   * </p>
   *
   * <p>
   * If the content size is greater than this limit then it will not be logged.
   * </p>
   *
   * <p>
   * Can be set to {@code 0} to disable content logging. This is useful for example if content has
   * sensitive data such as authentication information.
   * </p>
   *
   * <p>
   * Defaults to 16KB.
   * </p>
   */
  private int contentLoggingLimit = 0x4000;

  /** Determines whether logging should be enabled for this request. Defaults to {@code true}. */
  private boolean loggingEnabled = true;

  /** Determines whether logging in form of curl commands should be enabled for this request. */
  private boolean curlLoggingEnabled = true;

  /** HTTP request content or {@code null} for none. */
  private HttpContent content;

  /** HTTP transport. */
  private final HttpTransport transport;

  /** HTTP request method. */
  private HttpMethod method;

  /** HTTP request URL. */
  private GenericUrl url;

  /** Timeout in milliseconds to establish a connection or {@code 0} for an infinite timeout. */
  private int connectTimeout = 20 * 1000;

  /**
   * Timeout in milliseconds to read data from an established connection or {@code 0} for an
   * infinite timeout.
   */
  private int readTimeout = 20 * 1000;

  /** HTTP unsuccessful (non-2XX) response handler or {@code null} for none. */
  private HttpUnsuccessfulResponseHandler unsuccessfulResponseHandler;

  /** Map from normalized content type to HTTP parser. */
  @Deprecated
  private final Map<String, HttpParser> contentTypeToParserMap = new HashMap<String, HttpParser>();

  /** Parser used to parse responses. */
  private ObjectParser objectParser;

  /** Whether to enable gzip compression of HTTP content ({@code false} by default). */
  private boolean enableGZipContent;

  /** The {@link BackOffPolicy} to use between retry attempts or {@code null} for none. */
  private BackOffPolicy backOffPolicy;

  /** Whether to automatically follow redirects ({@code true} by default). */
  private boolean followRedirects = true;

  /**
   * Whether to throw an exception at the end of {@link #execute()} on an HTTP error code (non-2XX)
   * after all retries and response handlers have been exhausted ({@code true} by default).
   */
  private boolean throwExceptionOnExecuteError = true;

  /**
   * Whether to retry the request if an {@link IOException} is encountered in
   * {@link LowLevelHttpRequest#execute()}.
   */
  private boolean retryOnExecuteIOException = false;

  /**
   * Whether to not add the suffix {@link #USER_AGENT_SUFFIX} to the User-Agent header
   * ({@code false} by default).
   */
  private boolean suppressUserAgentSuffix;

  /**
   * @param transport HTTP transport
   * @param method HTTP request method (may be {@code null}
   */
  HttpRequest(HttpTransport transport, HttpMethod method) {
    this.transport = transport;
    this.method = method;
  }

  /**
   * Returns the HTTP transport.
   *
   * @since 1.5
   */
  public HttpTransport getTransport() {
    return transport;
  }

  /**
   * Returns the HTTP request method.
   *
   * @since 1.5
   */
  public HttpMethod getMethod() {
    return method;
  }

  /**
   * Sets the HTTP request method.
   *
   * @since 1.5
   */
  public HttpRequest setMethod(HttpMethod method) {
    this.method = Preconditions.checkNotNull(method);
    return this;
  }

  /**
   * Returns the HTTP request URL.
   *
   * @since 1.5
   */
  public GenericUrl getUrl() {
    return url;
  }

  /**
   * Sets the HTTP request URL.
   *
   * @since 1.5
   */
  public HttpRequest setUrl(GenericUrl url) {
    this.url = Preconditions.checkNotNull(url);
    return this;
  }

  /**
   * Returns the HTTP request content or {@code null} for none.
   *
   * @since 1.5
   */
  public HttpContent getContent() {
    return content;
  }

  /**
   * Sets the HTTP request content or {@code null} for none.
   *
   * @since 1.5
   */
  public HttpRequest setContent(HttpContent content) {
    this.content = content;
    return this;
  }

  /**
   * Returns whether to enable gzip compression of HTTP content.
   *
   * @since 1.5
   */
  public boolean getEnableGZipContent() {
    return enableGZipContent;
  }

  /**
   * Returns whether to enable gzip compression of HTTP content.
   *
   * <p>
   * By default it is {@code false}.
   * </p>
   *
   * <p>
   * To avoid the overhead of GZip compression for small content, one may want to set this to
   * {@code true} only for {@link HttpContent#getLength()} above a certain limit. For example:
   * </p>
   *
   * <pre>
  public static class MyInterceptor implements HttpExecuteInterceptor {
    public void intercept(HttpRequest request) throws IOException {
      if (request.getContent() != null && request.getContent().getLength() >= 256) {
        request.setEnableGZipContent(true);
      }
    }
  }
   * </pre>
   *
   * @since 1.5
   */
  public HttpRequest setEnableGZipContent(boolean enableGZipContent) {
    this.enableGZipContent = enableGZipContent;
    return this;
  }

  /**
   * Returns the {@link BackOffPolicy} to use between retry attempts or {@code null} for none.
   *
   * @since 1.7
   */
  public BackOffPolicy getBackOffPolicy() {
    return backOffPolicy;
  }

  /**
   * Sets the {@link BackOffPolicy} to use between retry attempts or {@code null} for none.
   *
   * @since 1.7
   */
  public HttpRequest setBackOffPolicy(BackOffPolicy backOffPolicy) {
    this.backOffPolicy = backOffPolicy;
    return this;
  }

  /**
   * Returns the limit to the content size that will be logged during {@link #execute()}.
   *
   * <p>
   * If the content size is greater than this limit then it will not be logged.
   * </p>
   *
   * <p>
   * Content will only be logged if {@link #isLoggingEnabled} is {@code true}.
   * </p>
   *
   * <p>
   * Can be set to {@code 0} to disable content logging. This is useful for example if content has
   * sensitive data such as authentication information.
   * </p>
   *
   * <p>
   * Defaults to 16KB.
   * </p>
   *
   * @since 1.7
   */
  public int getContentLoggingLimit() {
    return contentLoggingLimit;
  }

  /**
   * Set the limit to the content size that will be logged during {@link #execute()}.
   *
   * <p>
   * If the content size is greater than this limit then it will not be logged.
   * </p>
   *
   * <p>
   * Content will only be logged if {@link #isLoggingEnabled} is {@code true}.
   * </p>
   *
   * <p>
   * Can be set to {@code 0} to disable content logging. This is useful for example if content has
   * sensitive data such as authentication information.
   * </p>
   *
   * <p>
   * Defaults to 16KB.
   * </p>
   *
   * @since 1.7
   */
  public HttpRequest setContentLoggingLimit(int contentLoggingLimit) {
    Preconditions.checkArgument(
        contentLoggingLimit >= 0, "The content logging limit must be non-negative.");
    this.contentLoggingLimit = contentLoggingLimit;
    return this;
  }

  /**
   * Returns whether logging should be enabled for this request.
   *
   * <p>
   * Defaults to {@code true}.
   * </p>
   *
   * @since 1.9
   */
  public boolean isLoggingEnabled() {
    return loggingEnabled;
  }

  /**
   * Sets whether logging should be enabled for this request.
   *
   * <p>
   * Defaults to {@code true}.
   * </p>
   *
   * @since 1.9
   */
  public HttpRequest setLoggingEnabled(boolean loggingEnabled) {
    this.loggingEnabled = loggingEnabled;
    return this;
  }

  /**
   * Returns whether logging in form of curl commands is enabled for this request.
   *
   * @since 1.11
   */
  public boolean isCurlLoggingEnabled() {
    return curlLoggingEnabled;
  }

  /**
   * Sets whether logging in form of curl commands should be enabled for this request.
   *
   * <p>
   * Defaults to {@code true}.
   * </p>
   *
   * @since 1.11
   */
  public HttpRequest setCurlLoggingEnabled(boolean curlLoggingEnabled) {
    this.curlLoggingEnabled = curlLoggingEnabled;
    return this;
  }

  /**
   * Returns the timeout in milliseconds to establish a connection or {@code 0} for an infinite
   * timeout.
   *
   * @since 1.5
   */
  public int getConnectTimeout() {
    return connectTimeout;
  }

  /**
   * Sets the timeout in milliseconds to establish a connection or {@code 0} for an infinite
   * timeout.
   *
   * <p>
   * By default it is 20000 (20 seconds).
   * </p>
   *
   * @since 1.5
   */
  public HttpRequest setConnectTimeout(int connectTimeout) {
    Preconditions.checkArgument(connectTimeout >= 0);
    this.connectTimeout = connectTimeout;
    return this;
  }

  /**
   * Returns the timeout in milliseconds to read data from an established connection or {@code 0}
   * for an infinite timeout.
   *
   * <p>
   * By default it is 20000 (20 seconds).
   * </p>
   *
   * @since 1.5
   */
  public int getReadTimeout() {
    return readTimeout;
  }

  /**
   * Sets the timeout in milliseconds to read data from an established connection or {@code 0} for
   * an infinite timeout.
   *
   * @since 1.5
   */
  public HttpRequest setReadTimeout(int readTimeout) {
    Preconditions.checkArgument(readTimeout >= 0);
    this.readTimeout = readTimeout;
    return this;
  }

  /**
   * Returns the HTTP request headers.
   *
   * @since 1.5
   */
  public HttpHeaders getHeaders() {
    return headers;
  }

  /**
   * Sets the HTTP request headers.
   *
   * <p>
   * By default, this is a new unmodified instance of {@link HttpHeaders}.
   * </p>
   *
   * @since 1.5
   */
  public HttpRequest setHeaders(HttpHeaders headers) {
    this.headers = Preconditions.checkNotNull(headers);
    return this;
  }

  /**
   * Returns the HTTP response headers.
   *
   * @since 1.5
   */
  public HttpHeaders getResponseHeaders() {
    return responseHeaders;
  }

  /**
   * Sets the HTTP response headers.
   *
   * <p>
   * By default, this is a new unmodified instance of {@link HttpHeaders}.
   * </p>
   *
   * <p>
   * For example, this can be used if you want to use a subclass of {@link HttpHeaders} called
   * MyHeaders to process the response:
   * </p>
   *
   * <pre>
  static String executeAndGetValueOfSomeCustomHeader(HttpRequest request) {
    MyHeaders responseHeaders = new MyHeaders();
    request.responseHeaders = responseHeaders;
    HttpResponse response = request.execute();
    return responseHeaders.someCustomHeader;
  }
   * </pre>
   *
   * @since 1.5
   */
  public HttpRequest setResponseHeaders(HttpHeaders responseHeaders) {
    this.responseHeaders = Preconditions.checkNotNull(responseHeaders);
    return this;
  }

  /**
   * Returns the HTTP request execute interceptor to intercept the start of {@link #execute()}
   * (before executing the HTTP request) or {@code null} for none.
   *
   * @since 1.5
   */
  public HttpExecuteInterceptor getInterceptor() {
    return interceptor;
  }

  /**
   * Sets the HTTP request execute interceptor to intercept the start of {@link #execute()} (before
   * executing the HTTP request) or {@code null} for none.
   *
   * @since 1.5
   */
  public HttpRequest setInterceptor(HttpExecuteInterceptor interceptor) {
    this.interceptor = interceptor;
    return this;
  }

  /**
   * Returns the HTTP unsuccessful (non-2XX) response handler or {@code null} for none.
   *
   * @since 1.5
   */
  public HttpUnsuccessfulResponseHandler getUnsuccessfulResponseHandler() {
    return unsuccessfulResponseHandler;
  }

  /**
   * Returns the HTTP unsuccessful (non-2XX) response handler or {@code null} for none.
   *
   * @since 1.5
   */
  public HttpRequest setUnsuccessfulResponseHandler(
      HttpUnsuccessfulResponseHandler unsuccessfulResponseHandler) {
    this.unsuccessfulResponseHandler = unsuccessfulResponseHandler;
    return this;
  }

  /**
   * Some servers will fail to process a POST/PUT/PATCH unless Content-Length header >= 1. If this
   * value is set to {@code false} then " " is set as the content with Content-Length {@code 1} for
   * empty contents. Defaults to {@code true}.
   *
   * @since 1.7
   * @deprecated (scheduled to be removed in 1.12) Use {@code setContent(new EmptyContent())}
   */
  @Deprecated
  public HttpRequest setAllowEmptyContent(boolean allowEmptyContent) {
    this.allowEmptyContent = allowEmptyContent;
    if (!warnedDeprecatedSetAllowEmptyContent) {
      HttpTransport.LOGGER.warning("setAllowEmptyContent is deprecated and will be removed in 1.12;"
          + " use setEmptyContent instead (if needed)");
      warnedDeprecatedSetAllowEmptyContent = true;
    }
    return this;
  }

  /**
   * Some servers will fail to process a POST/PUT/PATCH unless Content-Length header >= 1. If this
   * value is set to {@code false} then " " is set as the content with Content-Length {@code 1} for
   * empty contents. Defaults to {@code true}.
   *
   * @since 1.7
   * @deprecated (scheduled to be removed in 1.12)
   */
  @Deprecated
  public boolean isAllowEmptyContent() {
    if (!warnedDeprecatedIsAllowEmptyContent) {
      HttpTransport.LOGGER.warning("isAllowEmptyContent is deprecated and will be removed in 1.12");
      warnedDeprecatedIsAllowEmptyContent = true;
    }
    return allowEmptyContent;
  }

  /**
   * Returns the number of retries that will be allowed to execute as the result of an
   * {@link HttpUnsuccessfulResponseHandler} before being terminated or {@code 0} to not retry
   * requests.
   *
   * @since 1.5
   */
  public int getNumberOfRetries() {
    return numRetries;
  }

  /**
   * Returns the number of retries that will be allowed to execute as the result of an
   * {@link HttpUnsuccessfulResponseHandler} before being terminated or {@code 0} to not retry
   * requests.
   *
   * <p>
   * The default value is {@code 10}.
   * </p>
   *
   * @since 1.5
   */
  public HttpRequest setNumberOfRetries(int numRetries) {
    Preconditions.checkArgument(numRetries >= 0);
    this.numRetries = numRetries;
    return this;
  }

  /**
   * Adds an HTTP response content parser.
   * <p>
   * If there is already a previous parser defined for this new parser (as defined by
   * {@link #getParser(String)} then the previous parser will be removed.
   * </p>
   *
   * <p>
   * Any parser set by calling {@link #setParser(ObjectParser)} will be preferred over this parser.
   * </p>
   *
   * @since 1.4
   * @deprecated (scheduled to be removed in 1.12) Use {@link #setParser(ObjectParser)} instead.
   */
  @Deprecated
  public void addParser(HttpParser parser) {
    String contentType = normalizeMediaType(parser.getContentType());
    contentTypeToParserMap.put(contentType, parser);
  }

  /**
   * Sets the {@link ObjectParser} used to parse the response to this request or {@code null} for
   * none.
   *
   * <p>
   * This parser will be preferred over any registered HttpParser.
   * </p>
   *
   * @since 1.10
   */
  public HttpRequest setParser(ObjectParser parser) {
    this.objectParser = parser;
    return this;
  }

  /**
   * Returns the HTTP response content parser to use for the given content type or {@code null} if
   * none is defined.
   *
   * @param contentType content type or {@code null} for {@code null} result
   * @return HTTP response content parser or {@code null} for {@code null} input
   * @since 1.4
   * @deprecated (scheduled to be removed in 1.12) Use {@link #getParser()} instead.
   */
  @Deprecated
  public final HttpParser getParser(String contentType) {
    contentType = normalizeMediaType(contentType);
    return contentTypeToParserMap.get(contentType);
  }

  /**
   * Returns the {@link ObjectParser} used to parse the response or {@code null} for none.
   *
   * @since 1.10
   */
  public final ObjectParser getParser() {
    return objectParser;
  }

  /**
   * Returns whether to follow redirects automatically.
   *
   * @since 1.6
   */
  public boolean getFollowRedirects() {
    return followRedirects;
  }

  /**
   * Sets whether to follow redirects automatically.
   *
   * <p>
   * The default value is {@code true}.
   * </p>
   *
   * @since 1.6
   */
  public HttpRequest setFollowRedirects(boolean followRedirects) {
    this.followRedirects = followRedirects;
    return this;
  }

  /**
   * Returns whether to throw an exception at the end of {@link #execute()} on an HTTP error code
   * (non-2XX) after all retries and response handlers have been exhausted.
   *
   * @since 1.7
   */
  public boolean getThrowExceptionOnExecuteError() {
    return throwExceptionOnExecuteError;
  }

  /**
   * Sets whether to throw an exception at the end of {@link #execute()} on a HTTP error code
   * (non-2XX) after all retries and response handlers have been exhausted.
   *
   * <p>
   * The default value is {@code true}.
   * </p>
   *
   * @since 1.7
   */
  public HttpRequest setThrowExceptionOnExecuteError(boolean throwExceptionOnExecuteError) {
    this.throwExceptionOnExecuteError = throwExceptionOnExecuteError;
    return this;
  }

  /**
   * Returns whether to retry the request if an {@link IOException} is encountered in
   * {@link LowLevelHttpRequest#execute()}.
   *
   * @since 1.9
   */
  public boolean getRetryOnExecuteIOException() {
    return retryOnExecuteIOException;
  }

  /**
   * Sets whether to retry the request if an {@link IOException} is encountered in
   * {@link LowLevelHttpRequest#execute()}.
   *
   * <p>
   * The default value is {@code false}.
   * </p>
   *
   * @since 1.9
   */
  public HttpRequest setRetryOnExecuteIOException(boolean retryOnExecuteIOException) {
    this.retryOnExecuteIOException = retryOnExecuteIOException;
    return this;
  }

  /**
   * Returns whether to not add the suffix {@link #USER_AGENT_SUFFIX} to the User-Agent header.
   *
   * @since 1.11
   */
  public boolean getSuppressUserAgentSuffix() {
    return suppressUserAgentSuffix;
  }

  /**
   * Sets whether to not add the suffix {@link #USER_AGENT_SUFFIX} to the User-Agent header.
   *
   * <p>
   * The default value is {@code false}.
   * </p>
   *
   * @since 1.11
   */
  public HttpRequest setSuppressUserAgentSuffix(boolean suppressUserAgentSuffix) {
    this.suppressUserAgentSuffix = suppressUserAgentSuffix;
    return this;
  }

  /**
   * Execute the HTTP request and returns the HTTP response.
   * <p>
   * Note that regardless of the returned status code, the HTTP response content has not been parsed
   * yet, and must be parsed by the calling code.
   * <p>
   * Almost all details of the request and response are logged if {@link Level#CONFIG} is loggable.
   * The only exception is the value of the {@code Authorization} header which is only logged if
   * {@link Level#ALL} is loggable.
   * <p>
   * Callers should call {@link HttpResponse#disconnect} when the returned HTTP response object is
   * no longer needed. However, {@link HttpResponse#disconnect} does not have to be called if the
   * response stream is properly closed. Example usage:
   * </p>
   *
   * <pre>
     HttpResponse response = request.execute();
     try {
       // process the HTTP response object
     } finally {
       response.disconnect();
     }
   * </pre>
   *
   * @return HTTP response for an HTTP success response (or HTTP error response if
   *         {@link #getThrowExceptionOnExecuteError()} is {@code false})
   * @throws HttpResponseException for an HTTP error response (only if
   *         {@link #getThrowExceptionOnExecuteError()} is {@code true})
   * @see HttpResponse#isSuccessStatusCode()
   */
  public HttpResponse execute() throws IOException {
    boolean retrySupported = false;
    Preconditions.checkArgument(numRetries >= 0);
    int retriesRemaining = numRetries;
    if (backOffPolicy != null) {
      // Reset the BackOffPolicy at the start of each execute.
      backOffPolicy.reset();
    }
    HttpResponse response = null;
    IOException executeException;

    Preconditions.checkNotNull(method);
    Preconditions.checkNotNull(url);

    do {
      // Cleanup any unneeded response from a previous iteration
      if (response != null) {
        response.ignore();
      }

      response = null;
      executeException = null;

      // run the interceptor
      if (interceptor != null) {
        interceptor.intercept(this);
      }
      // build low-level HTTP request
      String urlString = url.build();
      LowLevelHttpRequest lowLevelHttpRequest;
      switch (method) {
        case DELETE:
          lowLevelHttpRequest = transport.buildDeleteRequest(urlString);
          break;
        default:
          lowLevelHttpRequest = transport.buildGetRequest(urlString);
          break;
        case HEAD:
          Preconditions.checkArgument(
              transport.supportsHead(), "HTTP transport doesn't support HEAD");
          lowLevelHttpRequest = transport.buildHeadRequest(urlString);
          break;
        case PATCH:
          Preconditions.checkArgument(
              transport.supportsPatch(), "HTTP transport doesn't support PATCH");
          lowLevelHttpRequest = transport.buildPatchRequest(urlString);
          break;
        case POST:
          lowLevelHttpRequest = transport.buildPostRequest(urlString);
          break;
        case PUT:
          lowLevelHttpRequest = transport.buildPutRequest(urlString);
          break;
      }
      Logger logger = HttpTransport.LOGGER;
      boolean loggable = loggingEnabled && logger.isLoggable(Level.CONFIG);
      StringBuilder logbuf = null;
      StringBuilder curlbuf = null;
      // log method and URL
      if (loggable) {
        logbuf = new StringBuilder();
        logbuf.append("-------------- REQUEST  --------------").append(StringUtils.LINE_SEPARATOR);
        logbuf.append(method).append(' ').append(urlString).append(StringUtils.LINE_SEPARATOR);

        // setup curl logging
        if (curlLoggingEnabled) {
          curlbuf = new StringBuilder("curl -v --compressed");
          if (!method.equals(HttpMethod.GET)) {
            curlbuf.append(" -X ").append(method);
          }
        }
      }
      // add to user agent
      String originalUserAgent = headers.getUserAgent();
      if (!suppressUserAgentSuffix) {
        if (originalUserAgent == null) {
          headers.setUserAgent(USER_AGENT_SUFFIX);
        } else {
          headers.setUserAgent(originalUserAgent + " " + USER_AGENT_SUFFIX);
        }
      }
      // headers
      HttpHeaders.serializeHeaders(headers, logbuf, curlbuf, logger, lowLevelHttpRequest);
      if (!suppressUserAgentSuffix) {
        // set the original user agent back so that retries do not keep appending to it
        headers.setUserAgent(originalUserAgent);
      }

      // content
      HttpContent content = this.content;
      if (!allowEmptyContent
          && (method == HttpMethod.PUT || method == HttpMethod.POST || method == HttpMethod.PATCH)
          && (content == null || content.getLength() == 0)) {
        content = ByteArrayContent.fromString(null, " ");
      }
      if (content != null) {
        String contentEncoding = content.getEncoding();
        long contentLength = content.getLength();
        String contentType = content.getType();
        // log content
        if (loggable) {
          content = new LogContent(
              content, contentType, contentEncoding, contentLength, contentLoggingLimit);
        }
        // gzip
        String underlyingEncoding = content.getEncoding();
        if (enableGZipContent) {
          content = new GZipContent(content, contentType);
          contentEncoding = content.getEncoding();
          contentLength = content.getLength();
        }
        // append content headers to log buffer
        if (loggable) {
          if (contentType != null) {
            String header = "Content-Type: " + contentType;
            logbuf.append(header).append(StringUtils.LINE_SEPARATOR);
            if (curlbuf != null) {
              curlbuf.append(" -H '" + header + "'");
            }
          }
          if (underlyingEncoding != null) {
            // do not use the gzip encoding as the content won't be logged as gzip
            String header = "Content-Encoding: " + underlyingEncoding;
            logbuf.append(header).append(StringUtils.LINE_SEPARATOR);
            if (curlbuf != null) {
              curlbuf.append(" -H '" + header + "'");
            }
          }
          if (contentLength >= 0) {
            String header = "Content-Length: " + contentLength;
            logbuf.append(header).append(StringUtils.LINE_SEPARATOR);
            // do not log @ curl as the user will most likely manipulate the content
          }
        }
        if (curlbuf != null) {
          curlbuf.append(" -d '@-'");
        }
        lowLevelHttpRequest.setContent(content);
      }
      // log from buffer
      if (loggable) {
        logger.config(logbuf.toString());
        if (curlbuf != null) {
          curlbuf.append(" -- ");
          curlbuf.append(urlString);
          if (content != null) {
            curlbuf.append(" << $$$");
          }
          logger.config(curlbuf.toString());
        }
      }

      // We need to make sure our content type can support retry
      // null content is inherently able to be retried
      retrySupported = retriesRemaining > 0 && (content == null || content.retrySupported());

      // execute
      lowLevelHttpRequest.setTimeout(connectTimeout, readTimeout);
      try {
        LowLevelHttpResponse lowLevelHttpResponse = lowLevelHttpRequest.execute();
        // Flag used to indicate if an exception is thrown before the response is constructed.
        boolean responseConstructed = false;
        try {
          response = new HttpResponse(this, lowLevelHttpResponse);
          responseConstructed = true;
        } finally {
          if (!responseConstructed) {
            lowLevelHttpResponse.getContent().close();
          }
        }
      } catch (IOException e) {
        if (!retryOnExecuteIOException) {
          throw e;
        }
        // Save the exception in case the retries do not work and we need to re-throw it later.
        executeException = e;
        logger.log(Level.WARNING, "exception thrown while executing request", e);
      }

      // Flag used to indicate if an exception is thrown before the response has completed
      // processing.
      boolean responseProcessed = false;
      try {
        if (response != null && !response.isSuccessStatusCode()) {
          boolean errorHandled = false;
          boolean redirectRequest = false;
          boolean backOffRetry = false;
          if (unsuccessfulResponseHandler != null) {
            // Even if we don't have the potential to retry, we might want to run the
            // handler to fix conditions (like expired tokens) that might cause us
            // trouble on our next request
            errorHandled =
                unsuccessfulResponseHandler.handleResponse(this, response, retrySupported);
          }
          if (!errorHandled) {
            if (handleRedirect(response.getStatusCode(), response.getHeaders())) {
              // The unsuccessful request's error could not be handled and it is a redirect request.
              redirectRequest = true;
            } else if (retrySupported && backOffPolicy != null
                && backOffPolicy.isBackOffRequired(response.getStatusCode())) {
              // The unsuccessful request's error could not be handled and should be backed off
              // before
              // retrying.
              long backOffTime = backOffPolicy.getNextBackOffMillis();
              if (backOffTime != BackOffPolicy.STOP) {
                sleep(backOffTime);
                backOffRetry = true;
              }
            }
          }
          // A retry is required if the error was successfully handled or if it is a redirect
          // request
          // or if the back off policy determined a retry is necessary.
          retrySupported &= (errorHandled || redirectRequest || backOffRetry);
          // need to close the response stream before retrying a request
          if (retrySupported) {
            response.ignore();
          }
        } else {
          // Retry is not required for a successful status code unless the response is null.
          retrySupported &= (response == null);
        }
        // Once there are no more retries remaining, this will be -1
        // Count redirects as retries, we want a finite limit of redirects.
        retriesRemaining--;

        responseProcessed = true;
      } finally {
        if (response != null && !responseProcessed) {
          response.disconnect();
        }
      }
    } while (retrySupported);

    if (response == null) {
      // Retries did not help resolve the execute exception, re-throw it.
      throw executeException;
    }

    if (throwExceptionOnExecuteError && !response.isSuccessStatusCode()) {
      try {
        throw new HttpResponseException(response);
      } finally {
        response.disconnect();
      }
    }
    return response;
  }

  /**
   * Sets up this request object to handle the necessary redirect if redirects are turned on, it is
   * a redirect status code and the header has a location.
   *
   * <p>
   * When the status code is {@code 303} the method on the request is changed to a GET as per the
   * RFC2616 specification.
   * </p>
   *
   * @since 1.11
   */
  public boolean handleRedirect(int statusCode, HttpHeaders headers) {
    String redirectLocation = headers.getLocation();
    if (getFollowRedirects() && HttpStatusCodes.isRedirect(statusCode)
        && redirectLocation != null) {
      setUrl(new GenericUrl(redirectLocation));

      if (statusCode == HttpStatusCodes.STATUS_CODE_SEE_OTHER) {
        setMethod(HttpMethod.GET);
      }
      return true;
    }
    return false;
  }

  /**
   * An exception safe sleep where if the sleeping is interrupted the exception is ignored.
   *
   * @param millis to sleep
   */
  private void sleep(long millis) {
    try {
      // TODO(rmistry): Provide a way to mock out Thread.sleep to check that sleep gets called with
      // expected values.
      Thread.sleep(millis);
    } catch (InterruptedException e) {
      // Ignore.
    }
  }

  /**
   * Returns the normalized media type without parameters of the form {@code type "/" subtype"} as
   * specified in <a href="http://tools.ietf.org/html/rfc2616#section-3.7">Media Types</a>.
   *
   * @param mediaType unnormalized media type with possible parameters or {@code null} for
   *        {@code null} result
   * @return normalized media type without parameters or {@code null} for {@code null} input
   * @since 1.4
   * @deprecated (scheduled to be removed in 1.12) Use
   *             {@link HttpMediaType#equalsIgnoreParameters(HttpMediaType)} instead
   */
  @Deprecated
  public static String normalizeMediaType(String mediaType) {
    if (mediaType == null) {
      return null;
    }
    int semicolon = mediaType.indexOf(';');
    return semicolon == -1 ? mediaType : mediaType.substring(0, semicolon);
  }
}
TOP

Related Classes of com.google.api.client.http.HttpRequest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.