Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor to use different Http clients #90

Merged
merged 3 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build_java_maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ name: Java CI with Maven

on:
push:
branches: ["main"]
branches: ["main", "develop"]
pull_request:
branches: ["main"]
branches: ["main", "develop"]

jobs:
build:
Expand Down
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,110 @@
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);
protected static final String REQUEST_BODY_FORMAT = "Request Body : {}";
protected static final String RESPONSE_CODE_FORMAT = "Response Code : {}";
protected static final String RESPONSE_FORMAT = "Response : {}";

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_FORMAT, 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
Loading