Reference: Failsafe
Installation (Gradle)
dependencies {
implementation(platform("org.http4k:http4k-bom:5.37.1.1"))
implementation("org.http4k:http4k-failsafe")
}
About
This module provides a configurable Filter to provide fault tolerance (CircuitBreaking, RateLimiting, Retrying, Bulkheading, Timeouts etc.), by integrating with the Failsafe library.
Basic example
Here’s an example that uses BulkHeading to demonstrate how easy it is to use the filter with configured Failsafe policies.
package content.ecosystem.http4k.reference.failsafe
import dev.failsafe.Bulkhead
import dev.failsafe.Failsafe
import org.http4k.core.Method.GET
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.core.Status.Companion.OK
import org.http4k.core.then
import org.http4k.filter.FailsafeFilter
import kotlin.concurrent.thread
fun main() {
// Configure a Failsafe policy
val failsafeExecutor = Failsafe.with(
Bulkhead.of<Response>(5)
)
// Use the filter in a filter chain
val app = FailsafeFilter(failsafeExecutor).then {
Thread.sleep(100)
Response(OK)
}
// Throw a bunch of requests at the filter - only 5 should pass
for (it in 1..10) {
thread {
println(app(Request(GET, "/")).status)
}
}
}
Example of using multiple policies
Using multiple Failsafe policies in the filter is just as easy, as the following example shows.
package content.ecosystem.http4k.reference.failsafe
import dev.failsafe.CircuitBreaker
import dev.failsafe.Failsafe
import dev.failsafe.Fallback
import dev.failsafe.RetryPolicy
import dev.failsafe.Timeout
import org.http4k.core.Method.GET
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.core.Status.Companion.INTERNAL_SERVER_ERROR
import org.http4k.core.Status.Companion.OK
import org.http4k.core.then
import org.http4k.filter.FailsafeFilter
import java.time.Duration
import kotlin.random.Random
fun main() {
// Configure multiple Failsafe policies
val failsafeExecutor = Failsafe.with(
Fallback.of(Response(OK).body("Fallback")),
RetryPolicy.builder<Response>()
.withMaxAttempts(2)
.onRetry { println("Retrying") }
.handleResultIf { !it.status.successful }
.build(),
CircuitBreaker.builder<Response>()
.withFailureThreshold(3)
.withDelay(Duration.ofSeconds(3))
.onOpen { println("Circuit open") }
.onHalfOpen { println("Circuit half open") }
.onClose { println("Circuit closed") }
.handleResultIf { it.status.serverError }
.build(),
Timeout.of(Duration.ofMillis(100))
)
// We then create a very unstable client using the filter
val client = FailsafeFilter(failsafeExecutor).then {
when (Random.nextInt(0, 7)) {
0 -> Response(INTERNAL_SERVER_ERROR).body("Oh no!")
1, 2 -> {
Thread.sleep(200)
Response(OK).body("Slow!")
}
else -> Response(OK).body("All good!")
}.also { println("Call result: ${it.bodyString()}") }
}
// Throw a bunch of request at the filter - some will fail and be retried until
// the circuit breaker opens and the fallback value will be used after that.
repeat(1000) {
client(Request(GET, "/"))
Thread.sleep(1000)
}
}