/** Log response data. Returns replacement {@link TypedInput}. */
  private static TypedInput logResponse(
      String url, int statusCode, TypedInput body, long elapsedTime) throws IOException {
    LOGGER.fine("<--- HTTP " + statusCode + " " + url + " (" + elapsedTime + "ms)");

    byte[] bodyBytes = Utils.streamToBytes(body.in());
    String bodyString = new String(bodyBytes, UTF_8);
    for (int i = 0; i < bodyString.length(); i += LOG_CHUNK_SIZE) {
      int end = Math.min(bodyString.length(), i + LOG_CHUNK_SIZE);
      LOGGER.fine(bodyString.substring(i, end));
    }

    LOGGER.fine("<--- END HTTP");

    // Since we consumed the entire input stream, return a new, identical one from its bytes.
    return new TypedByteArray(body.mimeType(), bodyBytes);
  }
    /**
     * Execute an HTTP request.
     *
     * @return HTTP response object of specified {@code type}.
     * @throws RetrofitError Thrown if any error occurs during the HTTP request.
     */
    private Object invokeRequest(RestMethodInfo methodDetails, Object[] args) {
      methodDetails.init(); // Ensure all relevant method information has been loaded.

      String url = server.getUrl();
      try {
        Request request =
            new RequestBuilder(converter) //
                .setApiUrl(server.getUrl())
                .setArgs(args)
                .setHeaders(headers.get())
                .setMethodInfo(methodDetails)
                .build();
        url = request.getUrl();

        if (!methodDetails.isSynchronous) {
          // If we are executing asynchronously then update the current thread with a useful name.
          Thread.currentThread().setName(THREAD_PREFIX + url);
        }

        if (LOGGER.isLoggable(Level.FINE)) {
          logRequest(request);
        }

        Object profilerObject = null;
        if (profiler != null) {
          profilerObject = profiler.beforeCall();
        }

        long start = System.nanoTime();
        Response response = clientProvider.get().execute(request);
        long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);

        int statusCode = response.getStatus();
        if (profiler != null) {
          RequestInformation requestInfo = getRequestInfo(server, methodDetails, request);
          profiler.afterCall(requestInfo, elapsedTime, statusCode, profilerObject);
        }

        TypedInput body = response.getBody();
        if (LOGGER.isLoggable(Level.FINE)) {
          // Replace the response since the logger needs to consume the entire input stream.
          body = logResponse(url, response.getStatus(), body, elapsedTime);
        }

        List<Header> headers = response.getHeaders();
        for (Header header : headers) {
          if (HTTP.CONTENT_TYPE.equalsIgnoreCase(header.getName()) //
              && !UTF_8.equalsIgnoreCase(Utils.parseCharset(header.getValue()))) {
            throw new IOException("Only UTF-8 charset supported.");
          }
        }

        Type type = methodDetails.type;
        if (statusCode >= 200 && statusCode < 300) { // 2XX == successful request
          if (type.equals(Response.class)) {
            return response;
          }
          if (body == null) {
            return null;
          }
          try {
            return converter.fromBody(body, type);
          } catch (ConversionException e) {
            throw RetrofitError.conversionError(url, response, converter, type, e);
          }
        }
        throw RetrofitError.httpError(url, response, converter, type);
      } catch (RetrofitError e) {
        throw e; // Pass through our own errors.
      } catch (IOException e) {
        throw RetrofitError.networkError(url, e);
      } catch (Throwable t) {
        throw RetrofitError.unexpectedError(url, t);
      }
    }