package co.teemo.blog.handlers; import io.vertx.circuitbreaker.CircuitBreaker; import io.vertx.circuitbreaker.CircuitBreakerOptions; import io.vertx.circuitbreaker.CircuitBreakerState; import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.json.Json; import io.vertx.core.json.JsonArray; import io.vertx.core.logging.Logger; import io.vertx.core.logging.LoggerFactory; import io.vertx.ext.healthchecks.HealthChecks; import io.vertx.ext.healthchecks.Status; import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.client.WebClient; import io.vertx.ext.web.client.WebClientOptions; import java.util.function.Function; public class PokemonHandler implements Handler<RoutingContext> { private static final Logger logger = LoggerFactory.getLogger(PokemonHandler.class); private static final JsonArray FALLBACK = new JsonArray(); private final WebClient webClient; private final CircuitBreaker circuitBreaker; private final HealthChecks healthChecks; private int pokeApiPort; private String pokeApiHost; private String pokeApiPath; public PokemonHandler(Vertx vertx) { WebClientOptions options = new WebClientOptions().setKeepAlive(true).setSsl(true); this.webClient = WebClient.create(vertx, options); CircuitBreakerOptions circuitBreakerOptions = new CircuitBreakerOptions() .setMaxFailures(3) .setTimeout(1000) .setFallbackOnFailure(true) .setResetTimeout(60000); this.circuitBreaker = CircuitBreaker.create("pokeapi", vertx, circuitBreakerOptions); this.circuitBreaker.openHandler(v -> logger.info("{} circuit breaker is open", "pokeapi")); this.circuitBreaker.closeHandler(v -> logger.info("{} circuit breaker is closed", "pokeapi")); this.circuitBreaker.halfOpenHandler(v -> logger.info("{} circuit breaker is half open", "pokeapi")); this.healthChecks = HealthChecks.create(vertx); healthChecks.register("pokeApiHealthcheck", 1000, future -> { if (circuitBreaker.state().equals(CircuitBreakerState.CLOSED)) { future.complete(Status.OK()); } else { future.complete(Status.KO()); } }); } @Override public void handle(RoutingContext context) { Function<Throwable, JsonArray> fallback = future -> FALLBACK; Handler<Future<JsonArray>> processor = future -> { webClient.get(pokeApiPort, pokeApiHost, pokeApiPath).send(result -> { if (result.succeeded()) { future.complete(result.result().bodyAsJsonObject().getJsonArray("results")); } else { future.fail(result.cause()); } }); }; Handler<AsyncResult<JsonArray>> callback = result -> { if (result.succeeded()) { JsonArray pokemons = result.result(); context.response().setStatusCode(200).end(Json.encodePrettily(pokemons)); } else { Throwable cause = result.cause(); logger.error(cause.getMessage(), cause); context.response().setStatusCode(500).end(cause.getMessage()); } }; circuitBreaker.executeWithFallback(processor, fallback).setHandler(callback); } public void setPokeApiUrl(String pokeApiHost, int pokeApiPort, String pokeApiPath) { this.pokeApiHost = pokeApiHost; this.pokeApiPort = pokeApiPort; this.pokeApiPath = pokeApiPath; } public HealthChecks getHealthchecks() { return healthChecks; } }