sttp tips and tricks
(, en)
Retry stuff
First check sttp resilience and probably softwaremill/retry and then perhaps Retrying function calls in Scala.
Based on a post on SO, proposing a simple retry function
// src/main/scala/FuncUtil.scala
@annotation.tailrec
def retry[T](n: Int)(fn: => T): Try[T] = {
Try(fn) match {
case Failure(_) if n > 1 => retry(n - 1)(fn)
case fn => fn
}
}
you can build something like
// src/main/scala/Sttp3Client.scala
@annotation.tailrec
private def handleRequest[Result](
nRetries: Int = 0,
shouldRetry: HttpError[String] => Boolean = _ => true
)(
fn: RequestT[Empty, Either[String, String], Any] => Try[
Response[Either[ResponseException[String, Nothing], Result]]
]
): Try[Result] = {
fn(basicRequest).map(_.body) match {
// request was successful, but the http status code indicates error
// retry route
case Success(Left(err: HttpError[String])) if shouldRetry(err) && nRetries > 0 =>
handleRequest(nRetries = nRetries - 1, shouldRetry = shouldRetry)(fn)
// everything worked, yay!
case Success(Right(data)) =>
Success(data)
// request was successful, but the http status code indicates error
// retries exhausted route
case Success(Left(HttpError(body, code))) =>
Failure(
HttpResponseException(
s"""Failed HTTP Response with code ${code}. Returned body: "${body}"""",
code
)
)
// this should not happen, because we do not deserialise json
// which means we can only get HttpErrors
case Success(Left(ex: ResponseException[_, _])) => Failure(ex)
// we have already an exception, just pass it on
// this is more serious, so no retries here
case Failure(ex) => Failure(ex)
}
}
which you can use in this way
// src/main/scala/Sttp3Client.scala
handleRequest(nRetries = 1, shouldRetry = shouldRetryRequest) { req =>
Try {
req
.get(uri"https://www.bargsten.org/lsjkdaf")
.response(asEmptyResponse)
.send(backend)
}
}
// src/main/scala/Sttp3Client.scala
def shouldRetryRequest(error: HttpError[String]): Boolean = {
error.statusCode match {
case StatusCode.NotFound => true
case _ => false
}
}
If you are not interested in the return body, but still want to deal with errors
(including body), you cannot use the ignore
function supplied by sttp. Instead you
have to construct your own response description:
// src/main/scala/Sttp3Client.scala
def asEmptyResponse: ResponseAs[Either[ResponseException[String, Nothing], Unit], Any] =
asEither(asStringAlways.mapWithMetadata((body, meta) => HttpError(body, meta.code)), ignore)
It returns Unit
on success, but returns the body and status code as HttpError
on
failure.