Add customizable retry for SDK http client (#2523)
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -58,15 +58,20 @@ import lombok.AllArgsConstructor;
|
|||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.hc.client5.http.HttpRequestRetryStrategy;
|
||||||
import org.apache.hc.client5.http.classic.HttpClient;
|
import org.apache.hc.client5.http.classic.HttpClient;
|
||||||
import org.apache.hc.client5.http.classic.methods.HttpGet;
|
import org.apache.hc.client5.http.classic.methods.HttpGet;
|
||||||
|
import org.apache.hc.client5.http.impl.DefaultHttpRequestRetryStrategy;
|
||||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
||||||
|
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
|
||||||
import org.apache.hc.client5.http.impl.classic.HttpClients;
|
import org.apache.hc.client5.http.impl.classic.HttpClients;
|
||||||
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
|
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
|
||||||
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
|
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
|
||||||
|
import org.apache.hc.client5.http.ssl.TlsSocketStrategy;
|
||||||
import org.apache.hc.client5.http.ssl.TrustAllStrategy;
|
import org.apache.hc.client5.http.ssl.TrustAllStrategy;
|
||||||
import org.apache.hc.core5.http.ClassicHttpResponse;
|
import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||||
import org.apache.hc.core5.ssl.SSLContextBuilder;
|
import org.apache.hc.core5.ssl.SSLContextBuilder;
|
||||||
|
import org.apache.hc.core5.util.TimeValue;
|
||||||
import org.springframework.hateoas.Link;
|
import org.springframework.hateoas.Link;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@@ -93,7 +98,7 @@ public class HawkbitClient {
|
|||||||
"Basic " + Base64.getEncoder().encodeToString(
|
"Basic " + Base64.getEncoder().encodeToString(
|
||||||
(Objects.requireNonNull(tenant.getUsername(), "User is null!") +
|
(Objects.requireNonNull(tenant.getUsername(), "User is null!") +
|
||||||
":" +
|
":" +
|
||||||
Objects.requireNonNull(tenant.getPassword(),"Password is not available!"))
|
Objects.requireNonNull(tenant.getPassword(), "Password is not available!"))
|
||||||
.getBytes(StandardCharsets.ISO_8859_1)))
|
.getBytes(StandardCharsets.ISO_8859_1)))
|
||||||
: template -> {
|
: template -> {
|
||||||
if (!ObjectUtils.isEmpty(tenant.getGatewayToken())) {
|
if (!ObjectUtils.isEmpty(tenant.getGatewayToken())) {
|
||||||
@@ -108,6 +113,12 @@ public class HawkbitClient {
|
|||||||
log.trace("REST API call failed!", e);
|
log.trace("REST API call failed!", e);
|
||||||
return e;
|
return e;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static final HttpRequestRetryStrategy DEFAULT_HTTP_REQUEST_RETRY_STRATEGY =
|
||||||
|
new DefaultHttpRequestRetryStrategy(
|
||||||
|
Integer.getInteger("hawkbit.sdk.http.maxRetry", 3),
|
||||||
|
TimeValue.ofSeconds(Integer.getInteger("hawkbit.sdk.http.defaultRetryIntervalSec", 10)));
|
||||||
|
|
||||||
private final HawkbitServer hawkBitServer;
|
private final HawkbitServer hawkBitServer;
|
||||||
|
|
||||||
private final Encoder encoder;
|
private final Encoder encoder;
|
||||||
@@ -117,18 +128,23 @@ public class HawkbitClient {
|
|||||||
private final ErrorDecoder errorDecoder;
|
private final ErrorDecoder errorDecoder;
|
||||||
private final BiFunction<Tenant, Controller, RequestInterceptor> requestInterceptorFn;
|
private final BiFunction<Tenant, Controller, RequestInterceptor> requestInterceptorFn;
|
||||||
|
|
||||||
|
private final HttpRequestRetryStrategy httpRequestRetryStrategy;
|
||||||
|
|
||||||
public HawkbitClient(
|
public HawkbitClient(
|
||||||
final HawkbitServer hawkBitServer, final Encoder encoder, final Decoder decoder, final Contract contract) {
|
final HawkbitServer hawkBitServer, final Encoder encoder, final Decoder decoder, final Contract contract) {
|
||||||
this(hawkBitServer, encoder, decoder, contract, null, null);
|
this(hawkBitServer, encoder, decoder, contract, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Customizers gets default ones and could
|
|
||||||
*/
|
|
||||||
public HawkbitClient(
|
public HawkbitClient(
|
||||||
final HawkbitServer hawkBitServer, final Encoder encoder, final Decoder decoder, final Contract contract,
|
final HawkbitServer hawkBitServer, final Encoder encoder, final Decoder decoder, final Contract contract,
|
||||||
final ErrorDecoder errorDecoder,
|
final ErrorDecoder errorDecoder, final BiFunction<Tenant, Controller, RequestInterceptor> requestInterceptorFn) {
|
||||||
final BiFunction<Tenant, Controller, RequestInterceptor> requestInterceptorFn) {
|
this(hawkBitServer, encoder, decoder, contract, errorDecoder, requestInterceptorFn, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HawkbitClient(
|
||||||
|
final HawkbitServer hawkBitServer, final Encoder encoder, final Decoder decoder, final Contract contract,
|
||||||
|
final ErrorDecoder errorDecoder, final BiFunction<Tenant, Controller, RequestInterceptor> requestInterceptorFn,
|
||||||
|
final HttpRequestRetryStrategy httpRequestRetryStrategy) {
|
||||||
this.hawkBitServer = hawkBitServer;
|
this.hawkBitServer = hawkBitServer;
|
||||||
this.encoder = encoder;
|
this.encoder = encoder;
|
||||||
this.decoder = decoder;
|
this.decoder = decoder;
|
||||||
@@ -136,6 +152,8 @@ public class HawkbitClient {
|
|||||||
|
|
||||||
this.errorDecoder = errorDecoder == null ? DEFAULT_ERROR_DECODER : errorDecoder;
|
this.errorDecoder = errorDecoder == null ? DEFAULT_ERROR_DECODER : errorDecoder;
|
||||||
this.requestInterceptorFn = requestInterceptorFn == null ? DEFAULT_REQUEST_INTERCEPTOR_FN : requestInterceptorFn;
|
this.requestInterceptorFn = requestInterceptorFn == null ? DEFAULT_REQUEST_INTERCEPTOR_FN : requestInterceptorFn;
|
||||||
|
|
||||||
|
this.httpRequestRetryStrategy = httpRequestRetryStrategy == null ? DEFAULT_HTTP_REQUEST_RETRY_STRATEGY : httpRequestRetryStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> T mgmtService(final Class<T> serviceType, final Tenant tenantProperties) {
|
public <T> T mgmtService(final Class<T> serviceType, final Tenant tenantProperties) {
|
||||||
@@ -176,9 +194,9 @@ public class HawkbitClient {
|
|||||||
|
|
||||||
final T result;
|
final T result;
|
||||||
if (linkType.isAssignableFrom(ClassicHttpResponse.class)) {
|
if (linkType.isAssignableFrom(ClassicHttpResponse.class)) {
|
||||||
result = (T)response;
|
result = (T) response;
|
||||||
} else if (linkType == InputStream.class) {
|
} else if (linkType == InputStream.class) {
|
||||||
result = (T)response.getEntity().getContent();
|
result = (T) response.getEntity().getContent();
|
||||||
} else {
|
} else {
|
||||||
result = new ObjectMapper().readValue(response.getEntity().getContent(), linkType);
|
result = new ObjectMapper().readValue(response.getEntity().getContent(), linkType);
|
||||||
}
|
}
|
||||||
@@ -294,7 +312,7 @@ public class HawkbitClient {
|
|||||||
throw toFeignException(responseCode, conn, Request.create(
|
throw toFeignException(responseCode, conn, Request.create(
|
||||||
Request.HttpMethod.POST, conn.getURL().toString(),
|
Request.HttpMethod.POST, conn.getURL().toString(),
|
||||||
conn.getHeaderFields().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)),
|
conn.getHeaderFields().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)),
|
||||||
(Request.Body)null, null));
|
(Request.Body) null, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
return method.getReturnType() == ResponseEntity.class
|
return method.getReturnType() == ResponseEntity.class
|
||||||
@@ -307,7 +325,8 @@ public class HawkbitClient {
|
|||||||
: deserialize(conn.getInputStream(), method.getReturnType(), objectMapper);
|
: deserialize(conn.getInputStream(), method.getReturnType(), objectMapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FeignException toFeignException(final int responseCode, final HttpURLConnection conn, final Request request) throws IOException {
|
private static FeignException toFeignException(final int responseCode, final HttpURLConnection conn, final Request request)
|
||||||
|
throws IOException {
|
||||||
if (responseCode >= 500 && responseCode < 600) {
|
if (responseCode >= 500 && responseCode < 600) {
|
||||||
// server error
|
// server error
|
||||||
return switch (responseCode) {
|
return switch (responseCode) {
|
||||||
@@ -395,19 +414,23 @@ public class HawkbitClient {
|
|||||||
final HttpClientWrapper httpClientWrapper = HTTP_CLIENTS.get(key);
|
final HttpClientWrapper httpClientWrapper = HTTP_CLIENTS.get(key);
|
||||||
HttpClient client = httpClientWrapper == null ? null : httpClientWrapper.get();
|
HttpClient client = httpClientWrapper == null ? null : httpClientWrapper.get();
|
||||||
if (client == null) { // create
|
if (client == null) { // create
|
||||||
final CloseableHttpClient newClient;
|
final HttpClientBuilder builder = HttpClients.custom().setRetryStrategy(DEFAULT_HTTP_REQUEST_RETRY_STRATEGY);
|
||||||
|
|
||||||
if (key.isHttps()) {
|
if (key.isHttps()) {
|
||||||
// mTLS could be requested
|
// mTLS could be used / setup
|
||||||
try {
|
try {
|
||||||
newClient = tlsClient(key.getClientCertificate(), key.getServerCertificates());
|
builder.setConnectionManager(
|
||||||
|
PoolingHttpClientConnectionManagerBuilder.create()
|
||||||
|
.setTlsSocketStrategy(getTlsSocketStragegy(key.getClientCertificate(), key.getServerCertificates()))
|
||||||
|
.build());
|
||||||
} catch (final RuntimeException e) {
|
} catch (final RuntimeException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
throw new IllegalStateException("Failed to create mTLS client", e);
|
throw new IllegalStateException("Failed to create mTLS client", e);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
newClient = HttpClients.createDefault();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final CloseableHttpClient newClient = builder.build();
|
||||||
HTTP_CLIENTS.put(key, new HttpClientWrapper(key, newClient));
|
HTTP_CLIENTS.put(key, new HttpClientWrapper(key, newClient));
|
||||||
return newClient;
|
return newClient;
|
||||||
} else {
|
} else {
|
||||||
@@ -415,7 +438,8 @@ public class HawkbitClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private static CloseableHttpClient tlsClient(final Certificate clientCertificate, final X509Certificate[] serverCertificates)
|
|
||||||
|
private static TlsSocketStrategy getTlsSocketStragegy(final Certificate clientCertificate, final X509Certificate[] serverCertificates)
|
||||||
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException, CertificateException,
|
throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException, CertificateException,
|
||||||
IOException {
|
IOException {
|
||||||
final SSLContextBuilder sslContextBuilder = SSLContextBuilder.create();
|
final SSLContextBuilder sslContextBuilder = SSLContextBuilder.create();
|
||||||
@@ -433,13 +457,7 @@ public class HawkbitClient {
|
|||||||
}
|
}
|
||||||
sslContextBuilder.loadTrustMaterial(trustStore, null);
|
sslContextBuilder.loadTrustMaterial(trustStore, null);
|
||||||
}
|
}
|
||||||
return HttpClients
|
return new DefaultClientTlsStrategy(sslContextBuilder.build());
|
||||||
.custom()
|
|
||||||
.setConnectionManager(
|
|
||||||
PoolingHttpClientConnectionManagerBuilder.create()
|
|
||||||
.setTlsSocketStrategy(new DefaultClientTlsStrategy(sslContextBuilder.build()))
|
|
||||||
.build())
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
|
|||||||
Reference in New Issue
Block a user