Skip to content

Commit

Permalink
Refactor to use different Http clients
Browse files Browse the repository at this point in the history
  • Loading branch information
sashirestela committed Jan 10, 2025
1 parent b1b6caa commit d3a5d5c
Show file tree
Hide file tree
Showing 26 changed files with 443 additions and 772 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.github.sashirestela.cleverclient;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.sashirestela.cleverclient.client.HttpClientAdapter;
import io.github.sashirestela.cleverclient.client.JavaHttpClientAdapter;
import io.github.sashirestela.cleverclient.http.HttpProcessor;
import io.github.sashirestela.cleverclient.http.HttpRequestData;
import io.github.sashirestela.cleverclient.support.Configurator;
Expand All @@ -12,7 +14,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.http.HttpClient;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
Expand All @@ -21,8 +22,8 @@
import java.util.function.UnaryOperator;

/**
* Main class and entry point to use this library. This is a kind of wrapper that makes it easier to
* use the Java's HttpClient component to call http services by using annotated interfaces.
* Main class and entry point to use this library. This is a smart wrapper that makes it easier to
* use a Http client component to call http services by using annotated interfaces.
*/
@Getter
public class CleverClient {
Expand All @@ -31,7 +32,7 @@ public class CleverClient {

private final String baseUrl;
private final Map<String, String> headers;
private final HttpClient httpClient;
private final HttpClientAdapter clientAdapter;
private final UnaryOperator<HttpRequestData> requestInterceptor;
private final Consumer<Object> bodyInspector;
private final HttpProcessor httpProcessor;
Expand All @@ -41,27 +42,27 @@ public class CleverClient {
*
* @param baseUrl Root of the url of the API service to call. Mandatory.
* @param headers Http headers for all the API service. Optional.
* @param httpClient Custom Java's HttpClient component. One is created by default if none
* is passed. Optional.
* @param clientAdapter Component to call http services. If none is passed the
* JavaHttpClientAdapter will be used. Optional.
* @param requestInterceptor Function to modify the request once it has been built.
* @param bodyInspector Function to inspect the Body request parameter.
* @param endsOfStream Texts used to mark the final of streams when handling server sent
* events (SSE). Optional.
* @param objectMapper Provides Json conversions either to and from objects. Optional.
*/
@Builder
public CleverClient(@NonNull String baseUrl, @Singular Map<String, String> headers, HttpClient httpClient,
public CleverClient(@NonNull String baseUrl, @Singular Map<String, String> headers, HttpClientAdapter clientAdapter,
UnaryOperator<HttpRequestData> requestInterceptor, Consumer<Object> bodyInspector,
@Singular("endOfStream") List<String> endsOfStream, ObjectMapper objectMapper) {
this.baseUrl = baseUrl;
this.headers = Optional.ofNullable(headers).orElse(Map.of());
this.httpClient = Optional.ofNullable(httpClient).orElse(HttpClient.newHttpClient());
this.clientAdapter = Optional.ofNullable(clientAdapter).orElse(new JavaHttpClientAdapter());
this.requestInterceptor = requestInterceptor;
this.bodyInspector = bodyInspector;
this.httpProcessor = HttpProcessor.builder()
.baseUrl(this.baseUrl)
.headers(CommonUtil.mapToListOfString(this.headers))
.httpClient(this.httpClient)
.clientAdapter(this.clientAdapter)
.requestInterceptor(this.requestInterceptor)
.bodyInspector(bodyInspector)
.build();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package io.github.sashirestela.cleverclient.client;

import io.github.sashirestela.cleverclient.ResponseInfo;
import io.github.sashirestela.cleverclient.ResponseInfo.RequestInfo;
import io.github.sashirestela.cleverclient.http.HttpRequestData;
import io.github.sashirestela.cleverclient.support.CleverClientException;
import io.github.sashirestela.cleverclient.util.CommonUtil;
import io.github.sashirestela.cleverclient.util.Constant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.function.UnaryOperator;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public abstract class HttpClientAdapter {

private static Logger logger = LoggerFactory.getLogger(HttpClientAdapter.class);

public Object sendRequest(RequestData originalRequest, UnaryOperator<HttpRequestData> requestInterceptor) {
var actualRequest = interceptRequest(originalRequest, requestInterceptor);
logger.debug("Http Call : {} {}", actualRequest.getHttpMethod(), actualRequest.getUrl());
var formattedHeaders = formattedHeaders(actualRequest.getHeaders());
logger.debug("Request Headers : {}", formattedHeaders);
if (actualRequest.getReturnType().isAsync()) {
return sendAsync(actualRequest);
} else {
return send(actualRequest);
}
}

protected abstract Object sendAsync(RequestData request);

protected abstract Object send(RequestData request);

private RequestData interceptRequest(RequestData originalRequest,
UnaryOperator<HttpRequestData> requestInterceptor) {
if (requestInterceptor != null) {
var httpRequestData = originalRequest.getHttpRequestData();
httpRequestData = requestInterceptor.apply(httpRequestData);
return originalRequest
.withUrl(httpRequestData.getUrl())
.withBody(httpRequestData.getBody())
.withHeaders(CommonUtil.mapToListOfString(httpRequestData.getHeaders()));
} else {
return originalRequest;
}
}

@SuppressWarnings("unchecked")
protected void throwExceptionIfErrorIsPresent(ResponseData response) {
if (!CommonUtil.isInHundredsOf(response.getStatusCode(), Constant.HTTP_SUCCESSFUL)) {
String data = "";
if (response.getBody() instanceof Stream) {
data = ((Stream<String>) response.getBody())
.collect(Collectors.joining(System.getProperty("line.separator")));
} else if (response.getBody() instanceof InputStream) {
try {
data = new String(((InputStream) response.getBody()).readAllBytes(), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new CleverClientException(e);
}
} else {
data = (String) response.getBody();
}
logger.error("Response : {}", data);
throw new CleverClientException(fillResponseInfo(response, data));
}
}

private ResponseInfo fillResponseInfo(ResponseData response, String data) {
var request = response.getRequest();
return ResponseInfo.builder()
.statusCode(response.getStatusCode())
.data(data)
.headers(response.getHeaders())
.request(request != null ? RequestInfo.builder()
.httpMethod(request.getHttpMethod())
.url(request.getUrl())
.headers(request.getHeaders())
.build() : null)
.build();
}

private String formattedHeaders(List<String> headers) {
final String RESERVED_REGEX = "(authorization|api.?key)";
var pattern = Pattern.compile(RESERVED_REGEX, Pattern.CASE_INSENSITIVE);
var print = new StringBuilder("{");
for (var i = 0; i < headers.size(); i += 2) {
if (i > 1) {
print.append(", ");
}
var headerKey = headers.get(i);
var matcher = pattern.matcher(headerKey);
var headerVal = matcher.find() ? "*".repeat(10) : headers.get(i + 1);
print.append(headerKey + " = " + headerVal);
}
print.append("}");
return print.toString();
}

}
Loading

0 comments on commit d3a5d5c

Please sign in to comment.