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

com.sinch.sdk.http.HttpClientApache Maven / Gradle / Ivy

There is a newer version: 1.4.0
Show newest version
package com.sinch.sdk.http;

import static com.sinch.sdk.auth.adapters.OAuthManager.BEARER_AUTHENTICATE_RESPONSE_HEADER_KEYWORD;
import static com.sinch.sdk.auth.adapters.OAuthManager.BEARER_EXPIRED_KEYWORD;
import static com.sinch.sdk.core.http.HttpContentType.CONTENT_TYPE_HEADER;
import static com.sinch.sdk.core.http.URLParameterUtils.encodeParametersAsString;

import com.sinch.sdk.auth.adapters.OAuthManager;
import com.sinch.sdk.core.exceptions.ApiException;
import com.sinch.sdk.core.http.AuthManager;
import com.sinch.sdk.core.http.HttpContentType;
import com.sinch.sdk.core.http.HttpMethod;
import com.sinch.sdk.core.http.HttpRequest;
import com.sinch.sdk.core.http.HttpResponse;
import com.sinch.sdk.core.http.HttpStatus;
import com.sinch.sdk.core.http.URLParameter;
import com.sinch.sdk.core.models.ServerConfiguration;
import com.sinch.sdk.core.utils.Pair;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Scanner;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.io.support.ClassicRequestBuilder;

public class HttpClientApache implements com.sinch.sdk.core.http.HttpClient {

  private static final Logger LOGGER = Logger.getLogger(HttpClientApache.class.getName());

  private Map headersToBeAdded;

  private CloseableHttpClient client;

  public HttpClientApache() {
    this.client = HttpClients.createDefault();
  }

  public void setRequestHeaders(Map headers) {
    this.headersToBeAdded = headers;
  }

  private static HttpResponse processResponse(ClassicHttpResponse response) throws IOException {

    int statusCode = response.getCode();
    Map> headers = transformResponseHeaders(response.getHeaders());
    String message = response.getReasonPhrase();
    LOGGER.finest("response: " + statusCode + ", headers:" + headers);

    if (statusCode == org.apache.hc.core5.http.HttpStatus.SC_NO_CONTENT) {
      return new HttpResponse(statusCode, message, headers, null);
    }

    HttpEntity entity = response.getEntity();
    Scanner s = new Scanner(entity.getContent()).useDelimiter("\\A");
    String content = (s.hasNext() ? s.next() : "");

    return new HttpResponse(statusCode, message, headers, content.getBytes(StandardCharsets.UTF_8));
  }

  private static Map> transformResponseHeaders(Header[] headers) {
    Map> headersMap = new HashMap<>();
    for (Header header : headers) {
      List valuesList = headersMap.get(header.getName());
      if (valuesList != null) {
        valuesList.add(header.getValue());
      } else {
        valuesList = new ArrayList<>();
        valuesList.add(header.getValue());
        headersMap.put(header.getName(), valuesList);
      }
    }
    return headersMap;
  }

  @Override
  public HttpResponse invokeAPI(
      ServerConfiguration serverConfiguration,
      Map authManagersByOasSecuritySchemes,
      HttpRequest httpRequest)
      throws ApiException {

    try {
      String path = serverConfiguration.getUrl() + httpRequest.getPath().orElse("");
      HttpMethod method = httpRequest.getMethod();
      Collection queryParameters = httpRequest.getQueryParameters();

      String body = httpRequest.getBody();
      Map headerParams = httpRequest.getHeaderParams();
      Collection accept = httpRequest.getAccept();
      Collection contentType = httpRequest.getContentType();
      Collection authNames = httpRequest.getAuthNames();

      LOGGER.fine("Invoke '" + method + "' onto '" + path + "'");
      LOGGER.fine("queryParameters: " + queryParameters);
      LOGGER.fine("body: " + body);
      LOGGER.fine("headerParams: " + headerParams);
      LOGGER.fine("accept: " + accept);
      LOGGER.fine("contentType: " + contentType);
      LOGGER.fine("authNames: " + authNames);

      ClassicRequestBuilder requestBuilder = ClassicRequestBuilder.create(method.name());

      setUri(requestBuilder, path, queryParameters);

      addCollectionHeader(requestBuilder, CONTENT_TYPE_HEADER, contentType);
      addCollectionHeader(requestBuilder, "Accept", accept);

      addHeaders(requestBuilder, headerParams);
      addHeaders(requestBuilder, headersToBeAdded);

      addBody(requestBuilder, body);

      addAuth(requestBuilder, authManagersByOasSecuritySchemes, authNames, body);

      ClassicHttpRequest request = requestBuilder.build();

      HttpResponse response = processRequest(client, request);
      LOGGER.finest("connection response: " + response);

      // UNAUTHORIZED (HTTP 401) error code could imply refreshing the OAuth token
      if (response.getCode() == HttpStatus.UNAUTHORIZED) {
        boolean couldRetryRequest =
            processUnauthorizedResponse(httpRequest, response, authManagersByOasSecuritySchemes);
        if (couldRetryRequest) {
          // refresh authorization
          addAuth(requestBuilder, authManagersByOasSecuritySchemes, authNames, body);
          request = requestBuilder.build();
          response = processRequest(client, request);
          LOGGER.finest("connection response on retry: " + response);
        }
      }
      return response;
    } catch (Exception e) {
      LOGGER.severe("Error:" + e);
      throw new ApiException(e);
    }
  }

  private boolean processUnauthorizedResponse(
      HttpRequest request,
      HttpResponse response,
      Map authManagersByOasSecuritySchemes) {

    Map authManagersByAuthSchemes =
        authManagersByOasSecuritySchemes.values().stream()
            .map(authManager -> new AbstractMap.SimpleEntry<>(authManager.getSchema(), authManager))
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a1, a2) -> a1));
    AuthManager bearerAuthManager =
        authManagersByAuthSchemes.get(OAuthManager.SCHEMA_KEYWORD_BEARER);
    if (null == bearerAuthManager) {
      // no bearer manager registered
      return false;
    }

    Collection auths = request.getAuthNames();
    Optional requestSupportBearerAuthentication =
        auths.stream()
            .filter(
                f ->
                    null != authManagersByOasSecuritySchemes.get(f)
                        && authManagersByOasSecuritySchemes
                            .get(f)
                            .getSchema()
                            .equals(OAuthManager.SCHEMA_KEYWORD_BEARER))
            .findFirst();

    if (!requestSupportBearerAuthentication.isPresent()) {
      return false;
    }
    // looking for "expired" keyword present in "www-authenticate" header
    Map> responseHeaders = response.getHeaders();
    Collection header = responseHeaders.get(BEARER_AUTHENTICATE_RESPONSE_HEADER_KEYWORD);

    boolean headerPresent = header.stream().anyMatch(e -> e.contains(BEARER_EXPIRED_KEYWORD));
    if (headerPresent) {
      bearerAuthManager.resetToken();
    }
    return headerPresent;
  }

  private void setUri(
      ClassicRequestBuilder requestBuilder, String path, Collection parameters) {

    if (null == parameters || parameters.isEmpty()) {
      requestBuilder.setUri(path);
      return;
    }
    String requestParameters = "?" + encodeParametersAsString(parameters);
    LOGGER.finest("Request parameters: " + requestParameters);
    requestBuilder.setUri(path + requestParameters);
  }

  private void addBody(ClassicRequestBuilder requestBuilder, String body) {

    if (null != body) {
      Charset charset = extractCharset(requestBuilder).orElse(StandardCharsets.UTF_8);
      requestBuilder.setEntity(new StringEntity(body, charset));
    }
  }

  private void addCollectionHeader(
      ClassicRequestBuilder requestBuilder, String header, Collection values) {
    if (null != values && !values.isEmpty()) {
      requestBuilder.setHeader(header, String.join(",", values));
    }
  }

  private void addHeaders(ClassicRequestBuilder requestBuilder, Map headers) {

    if (null == headers) {
      return;
    }
    headers
        .entrySet()
        .iterator()
        .forEachRemaining(f -> requestBuilder.setHeader(f.getKey(), f.getValue()));
  }

  private void addAuth(
      ClassicRequestBuilder requestBuilder,
      Map authManagersByOasSecuritySchemes,
      Collection values,
      String body) {

    if (null == values || values.isEmpty() || null == authManagersByOasSecuritySchemes) {
      return;
    }

    for (String entry : values) {
      if (authManagersByOasSecuritySchemes.containsKey(entry)) {
        AuthManager authManager = authManagersByOasSecuritySchemes.get(entry);
        Collection> headers =
            authManager.getAuthorizationHeaders(
                requestBuilder.getMethod(),
                null != requestBuilder.getFirstHeader(CONTENT_TYPE_HEADER)
                    ? requestBuilder.getFirstHeader(CONTENT_TYPE_HEADER).getValue()
                    : null,
                requestBuilder.getPath(),
                body);
        headers.stream()
            .iterator()
            .forEachRemaining(f -> requestBuilder.setHeader(f.getLeft(), f.getRight()));
        return;
      } else {
        LOGGER.finest("Ignore unknown authentication value: '" + entry + "'");
      }
    }
  }

  private HttpResponse processRequest(CloseableHttpClient client, ClassicHttpRequest request)
      throws IOException {
    return client.execute(request, HttpClientApache::processResponse);
  }

  private Optional extractCharset(ClassicRequestBuilder requestBuilder) {

    Optional
charsetHeader = Arrays.stream(requestBuilder.getHeaders(CONTENT_TYPE_HEADER)) .filter(f -> f.getValue().contains("charset=")) .findFirst(); return charsetHeader.flatMap( header -> HttpContentType.getCharsetValue(header.getValue()).map(Charset::forName)); } @Override public boolean isClosed() { return null == client; } @Override public void close() throws Exception { if (!isClosed()) { try { client.close(); } finally { client = null; } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy