Reference: Ops: Failsafe
Installation (Gradle)#
dependencies {
implementation(platform("org.http4k:http4k-bom:6.45.1.0"))
implementation("org.http4k:http4k-ops-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.
Kotlin
example_bulkheading.kt
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.
Kotlin
example_multiple_policies.kt
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)
}
}
