This module provides support to use Resilience4j with julian-http-client
. Resilience4j is a fault tolerance library.
<dependency>
<groupId>com.github.ljtfreitas.julian-http-client</groupId>
<artifactId>julian-http-client-http-client-resilience4j</artifactId>
<version>${julian-http-client-version}</version>
</dependency>
dependencies {
implementation("com.github.ljtfreitas.julian-http-client:julian-http-client-resilience4j:$julianHttpClientVersion")
}
julian-http-client
provides support for several features from Resilience4j, using interceptors.
CircuitBreakerHTTPRequestInterceptor
wraps the HTTP request inside a CircuitBreaker.
import com.github.ljtfreitas.julian.http.resilience4j.CircuitBreakerHTTPRequestInterceptor;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
interface MyApi {}
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("my-circuit-breaker");
CircuitBreakerHTTPRequestInterceptor circuitBreakerInterceptor = new CircuitBreakerHTTPRequestInterceptor(circuitBreaker);
MyApi myApi = new ProxyBuilder()
.http()
.interceptors()
.add(circuitBreakerInterceptor)
.and()
.and()
.build(MyApi.class, "http://my.api.com");
2xx HTTP responses will be counted as success and 4xx/5xx responses will be counted as errors. We can customize this behavior passing a predicate in order to implement a fine control over what HTTP response codes are counted as an "error":
import com.github.ljtfreitas.julian.http.resilience4j.CircuitBreakerHTTPRequestInterceptor;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
interface MyApi {}
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("my-circuit-breaker");
Predicate<HTTPResponse<?>> myPredicate = r -> r.status().isSuccess() || r.status().is(HTTPStatusCode.NOT_FOUND); // 404 (NotFound) responses will not be counted as errors on circuit breaker
CircuitBreakerHTTPRequestInterceptor circuitBreakerInterceptor = new CircuitBreakerHTTPRequestInterceptor(circuitBreaker);
MyApi myApi = new ProxyBuilder()
.http()
.interceptors()
.add(circuitBreakerInterceptor)
.and()
.and()
.build(MyApi.class, "http://my.api.com");
With the circuit breaker in place, we can just call our interface method. In case circuit breaker is OPEN, an exception will be throw, so we just need to be careful about error handling:
import com.github.ljtfreitas.julian.Attempt;
import com.github.ljtfreitas.julian.http.resilience4j.CircuitBreakerHTTPRequestInterceptor;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
interface MyApi {
@GET("/get-something")
Attempt<String> get();
}
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("my-circuit-breaker");
CircuitBreakerHTTPRequestInterceptor circuitBreakerInterceptor = new CircuitBreakerHTTPRequestInterceptor(circuitBreaker);
MyApi myApi = new ProxyBuilder()
.http()
.interceptors()
.add(circuitBreakerInterceptor)
.and()
.and()
.build(MyApi.class, "http://my.api.com");
Attempt<String> result = myApi.get(); // success or failure
RateLimiterHTTPRequestInterceptor
wraps the HTTP request inside a RateLimiter.
import com.github.ljtfreitas.julian.http.resilience4j.RateLimiterHTTPRequestInterceptor;
import io.github.resilience4j.ratelimiter.RateLimiter;
interface MyApi {}
RateLimiter rateLimiter = RateLimiter.ofDefaults("my-rate-limiter");
RateLimiterHTTPRequestInterceptor rateLimiterInterceptor = new RateLimiterHTTPRequestInterceptor(rateLimiter);
MyApi myApi = new ProxyBuilder()
.http()
.interceptors()
.add(rateLimiterInterceptor)
.and()
.and()
.build(MyApi.class, "http://my.api.com");
In case of RateLimiter
thows a RequestNotPermitted
exception, julian-http-client
will return a 409 (Too Many Requests) HTTP response.
RetryHTTPRequestInterceptor
wraps the HTTP request inside a Retry component.
Because julian-http-client
requests are async and don't block the main thread, we need to run retries using a ScheduledExecutorService.
import com.github.ljtfreitas.julian.http.resilience4j.RetryHTTPRequestInterceptor;
import io.github.resilience4j.retry.Retry;
interface MyApi {}
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
Retry retry = Retry.ofDefaults("my-retry");
RetryHTTPRequestInterceptor retryInterceptor = new RetryHTTPRequestInterceptor(retry, scheduler);
MyApi myApi = new ProxyBuilder()
.http()
.interceptors()
.add(retryInterceptor)
.and()
.and()
.build(MyApi.class, "http://my.api.com");
By default, just exceptions are retried, and julian-http-client
doesn't handle HTTP response failures (4xx or 5xx) as exceptions. In case we need to retry this kind of response too, we can configure Retry
:
import com.github.ljtfreitas.julian.http.resilience4j.RetryHTTPRequestInterceptor;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryConfig;
interface MyApi {}
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
Retry retry = Retry.of("my-retry", RetryConfig.<HTTPResponse<String>> custom()
.retryOnResult(r -> r.status().is(HTTPStatusGroup.SERVER_ERROR)) // retry for any 5xx (server errors) responses
.build());
RetryHTTPRequestInterceptor retryInterceptor = new RetryHTTPRequestInterceptor(retry, scheduler);
MyApi myApi = new ProxyBuilder()
.http()
.interceptors()
.add(retryInterceptor)
.and()
.and()
.build(MyApi.class, "http://my.api.com");
For exceptions, we can choose what errors should be retried:
import com.github.ljtfreitas.julian.http.resilience4j.RetryHTTPRequestInterceptor;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryConfig;
interface MyApi {}
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
Retry retry = Retry.of("my-retry", RetryConfig.<HTTPResponse<String>> custom()
.retryOnException(e -> e instanceof HTTPClientException) // just retry HTTPClientException failures
.build());
RetryHTTPRequestInterceptor retryInterceptor = new RetryHTTPRequestInterceptor(retry, scheduler);
MyApi myApi = new ProxyBuilder()
.http()
.interceptors()
.add(retryInterceptor)
.and()
.and()
.build(MyApi.class, "http://my.api.com");
TimeLimiterHTTPRequestInterceptor
wraps the HTTP request inside a TimeLimter component.
Because julian-http-client
requests are async and don't block the main thread, we need to use a ScheduledExecutorService in order to schedule a timeout.
import com.github.ljtfreitas.julian.http.resilience4j.TimeLimiterHTTPRequestInterceptor;
import io.github.resilience4j.timelimiter.TimeLimiter;
interface MyApi {}
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
TimeLimiter timeLimiter = TimeLimiter.of(Duration.ofMillis(2000));
TimeLimiterHTTPRequestInterceptor timeLimiter = new TimeLimiterHTTPRequestInterceptor(timeLimiter, scheduler);
MyApi myApi = new ProxyBuilder()
.http()
.interceptors()
.add(timeLimiter)
.and()
.and()
.build(MyApi.class, "http://my.api.com");
In case of TimeLimiter
timeout is exceeded, a java.util.concurrent.TimeoutException
will be throw.