Skip to content

Commit eee2dea

Browse files
authored
Added a circuit breaker handler using failsafe (#64)
* Added a circuit breaker handler using failsafe
1 parent 14c6e57 commit eee2dea

File tree

2 files changed

+167
-0
lines changed

2 files changed

+167
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.stubbornjava.common.undertow.handlers;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import io.undertow.server.HttpHandler;
7+
import io.undertow.server.HttpServerExchange;
8+
import net.jodah.failsafe.CircuitBreaker;
9+
import net.jodah.failsafe.Failsafe;
10+
11+
// {{start:handler}}
12+
public class CircuitBreakerHandler implements HttpHandler {
13+
private static final Logger log = LoggerFactory.getLogger(CircuitBreakerHandler.class);
14+
15+
private final CircuitBreaker circuitBreaker;
16+
private final HttpHandler delegate;
17+
private final HttpHandler failureHandler;
18+
19+
public CircuitBreakerHandler(CircuitBreaker circuitBreaker, HttpHandler delegate, HttpHandler failureHandler) {
20+
super();
21+
this.circuitBreaker = circuitBreaker;
22+
this.delegate = delegate;
23+
this.failureHandler = failureHandler;
24+
}
25+
26+
@Override
27+
public void handleRequest(HttpServerExchange exchange) throws Exception {
28+
Failsafe.with(circuitBreaker)
29+
.withFallback(() -> failureHandler.handleRequest(exchange))
30+
// We need to call get here instead of execute so we can return the
31+
// mutated exchange to run checks on it
32+
.get(() -> {
33+
delegate.handleRequest(exchange);
34+
return exchange;
35+
});
36+
}
37+
}
38+
// {{end:handler}}
39+
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package com.stubbornjava.examples.failsafe;
2+
3+
import java.io.IOException;
4+
import java.util.concurrent.Executors;
5+
import java.util.concurrent.ScheduledExecutorService;
6+
import java.util.concurrent.TimeUnit;
7+
8+
import org.slf4j.Logger;
9+
import org.slf4j.LoggerFactory;
10+
11+
import com.stubbornjava.common.HttpClient;
12+
import com.stubbornjava.common.undertow.Exchange;
13+
import com.stubbornjava.common.undertow.SimpleServer;
14+
import com.stubbornjava.common.undertow.handlers.CircuitBreakerHandler;
15+
import com.stubbornjava.common.undertow.handlers.CustomHandlers;
16+
17+
import io.undertow.server.HttpHandler;
18+
import io.undertow.server.HttpServerExchange;
19+
import io.undertow.util.StatusCodes;
20+
import net.jodah.failsafe.CircuitBreaker;
21+
import okhttp3.HttpUrl;
22+
import okhttp3.Request;
23+
24+
public class FailsafeWebserver {
25+
private static final Logger log = LoggerFactory.getLogger(FailsafeWebserver.class);
26+
27+
// {{start:handlers}}
28+
private static final void serverError(HttpServerExchange exchange) {
29+
exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
30+
Exchange.body().sendText(exchange, "500 - Internal Server Error");
31+
}
32+
33+
// This handler helps simulate errors, bad requests, and successful requests.
34+
private static final void circuitClosed(HttpServerExchange exchange) {
35+
boolean error = Exchange.queryParams().queryParamAsBoolean(exchange, "error").orElse(false);
36+
boolean exception = Exchange.queryParams().queryParamAsBoolean(exchange, "exception").orElse(false);
37+
if (error) {
38+
exchange.setStatusCode(StatusCodes.BAD_REQUEST);
39+
Exchange.body().sendText(exchange, "Bad Request");
40+
} else if (exception) {
41+
throw new RuntimeException("boom");
42+
} else {
43+
Exchange.body().sendText(exchange, "Circuit is open everything is functioning properly.");
44+
}
45+
}
46+
47+
private static final HttpHandler CIRCUIT_BREAKER_HANDLER;
48+
static {
49+
CircuitBreaker breaker = new CircuitBreaker()
50+
// Trigger circuit breaker failure on exceptions or bad requests
51+
.failIf((HttpServerExchange exchange, Throwable ex) -> {
52+
return (exchange != null && StatusCodes.BAD_REQUEST == exchange.getStatusCode())
53+
|| ex != null;
54+
})
55+
// If 7 out of 10 requests fail Open the circuit
56+
.withFailureThreshold(7, 10)
57+
// When half open if 3 out of 5 requests succeed close the circuit
58+
.withSuccessThreshold(3, 5)
59+
// Delay this long before half opening the circuit
60+
.withDelay(2, TimeUnit.SECONDS)
61+
.onClose(() -> log.info("Circuit Closed"))
62+
.onOpen(() -> log.info("Circuit Opened"))
63+
.onHalfOpen(() -> log.info("Circuit Half-Open"));
64+
65+
CIRCUIT_BREAKER_HANDLER = new CircuitBreakerHandler(breaker,
66+
FailsafeWebserver::circuitClosed,
67+
FailsafeWebserver::serverError);
68+
}
69+
// {{end:handlers}}
70+
71+
// {{start:request}}
72+
private static void request(boolean error, boolean exception) {
73+
HttpUrl url = HttpUrl.parse("http://localhost:8080")
74+
.newBuilder()
75+
.addQueryParameter("error", String.valueOf(error))
76+
.addQueryParameter("exception", String.valueOf(exception))
77+
.build();
78+
79+
Request request = new Request.Builder().get().url(url).build();
80+
try {
81+
log.info(HttpClient.globalClient().newCall(request).execute().body().string());
82+
} catch (IOException e) {
83+
e.printStackTrace();
84+
}
85+
}
86+
// {{end:request}}
87+
88+
// {{start:main}}
89+
public static void main(String[] args) {
90+
91+
HttpHandler exceptionHandler =
92+
CustomHandlers.exception(CIRCUIT_BREAKER_HANDLER)
93+
.addExceptionHandler(Throwable.class, FailsafeWebserver::serverError);
94+
95+
SimpleServer server = SimpleServer.simpleServer(exceptionHandler);
96+
server.start();
97+
98+
99+
// Warm-up the circuit breaker it needs to hit at least max executions
100+
// Before it will reject anything. This will make that easier.
101+
for (int i = 0; i < 10; i++) {
102+
request(false, false);
103+
}
104+
ScheduledExecutorService schedExec = Executors.newScheduledThreadPool(1);
105+
106+
// A simple request that should always succeed
107+
schedExec.scheduleAtFixedRate(() -> request(false, false), 0, 500, TimeUnit.MILLISECONDS);
108+
109+
// Send a batch of 15 bad requests to trigger the circuit breaker
110+
Runnable errors = () -> {
111+
log.info("Executing bad requests!");
112+
for (int i = 0; i < 15; i++) {
113+
request(true, false);
114+
}
115+
};
116+
schedExec.schedule(errors, 1, TimeUnit.SECONDS);
117+
118+
// Send a batch of 15 requests that throw exceptions
119+
Runnable exceptions = () -> {
120+
log.info("Executing requests that throw exceptions!");
121+
for (int i = 0; i < 15; i++) {
122+
request(false, true);
123+
}
124+
};
125+
schedExec.schedule(exceptions, 5, TimeUnit.SECONDS);
126+
}
127+
// {{end:main}}
128+
}

0 commit comments

Comments
 (0)