Skip to content

Extends the pekko-http client with retry logic, error handling, logging and signing

License

Notifications You must be signed in to change notification settings

moia-oss/scala-pekko-http-client

This branch is 154 commits ahead of, 3 commits behind moia-oss/scala-http-client:master.

Folders and files

NameName
Last commit message
Last commit date

Latest commit

a63db11 · Feb 10, 2025
Sep 11, 2023
Jan 27, 2025
Jan 13, 2025
Jan 13, 2025
Sep 26, 2023
Jan 27, 2025
May 3, 2023
Feb 20, 2020
Feb 20, 2020
Sep 26, 2023
Feb 10, 2025

Repository files navigation

Scala-Pekko-HTTP-Client

This is a wrapper around the pekko-http-client that adds

  • handling for domain errors as HTTP 400 returns
  • retry logic
  • deadlines
  • error handling
  • logging
  • AWS request signing

Build & Test Scala 2.13 Scala Steward badge

Usage

libraryDependencies += "io.moia" %% "scala-pekko-http-client" % "1.0.0"
// create the client
val httpClient = new HttpClient(
  config           = HttpClientConfig("http", "127.0.0.1", 8888),
  name             = "TestClient",
  httpMetrics      = HttpMetrics.none,
  retryConfig      = RetryConfig.default,
  clock            = Clock.systemUTC(),
  awsRequestSigner = None
)
// make a request
val response: Future[HttpClientResponse] = httpClient.request(
  method   = HttpMethods.POST,
  entity   = HttpEntity("Example"),
  path     = "/test",
  headers  = Seq.empty,
  deadline = Deadline.now + 10.seconds
)
// map the response to your model
response.flatMap {
  case HttpClientSuccess(content) => Unmarshal(content).to[MySuccessObject].map(Right(_))
  case DomainError(content)       => Unmarshal(content).to[DomainErrorObject].map(Left(_))
  case failure: HttpClientFailure => throw GatewayException(failure.toString)
}

See SimpleExample.scala for a complete example.

HttpClientResponses

The lib outputs the following response objects (see io.moia.scalaHttpClient.HttpClientResponse):

  • HTTP 2xx Success => HttpClientSuccess
  • HTTP 3xx Redirect => not implemented yet
  • HTTP 400 Bad Request with entity => is mapped to DomainError ⚠️
  • HTTP 400 Bad Request without entity => HttpClientError
  • HTTP 4xx, 5xx, others => HttpClientError
  • if the deadline expired => DeadlineExpired
  • if an AwsRequestSigner is given, but the request already includes an "Authorization" header => AlreadyAuthorizedException
  • weird pekko-errors => ExceptionOccurred

Custom Logging

To use a custom logger (for correlation ids etc), you can use the typed LoggingHttpClient. First create a custom LoggerTakingImplicit:

import com.typesafe.scalalogging._
import org.slf4j.LoggerFactory

object CustomLogging {
  final case class LoggingContext(context: String)

  implicit val canLogString: CanLog[LoggingContext] = new CanLog[LoggingContext] {
    override def logMessage(originalMsg: String, ctx: LoggingContext): String = ???
    override def afterLog(ctx: LoggingContext): Unit                          = ???
  }

  val theLogger: LoggerTakingImplicit[LoggingContext] = Logger.takingImplicit(LoggerFactory.getLogger(getClass.getName))
}

Then create a LoggingHttpClient typed to the LoggingContext:

// create the client
val httpClient = new LoggingHttpClient[LoggingContext](
  config           = HttpClientConfig("http", "127.0.0.1", 8888),
  name             = "TestClient",
  httpMetrics      = HttpMetrics.none[LoggingContext],
  retryConfig      = RetryConfig.default,
  clock            = Clock.systemUTC(),
  logger           = CustomLogging.theLogger,
  awsRequestSigner = None
)

// create an implicit logging context
implicit val ctx: LoggingContext = LoggingContext("Logging Context")

// make a request
httpClient.request(HttpMethods.POST, HttpEntity.Empty, "/test", Seq.empty, Deadline.now + 10.seconds)

The request function will use the ctx implicitly.

See LoggingExample.scala for a complete example.

Custom Headers

To use custom-defined headers, you can extend ModeledCustomHeader from org.apache.pekko.http.scaladsl.model.headers:

import org.apache.pekko.http.scaladsl.model.headers.{ModeledCustomHeader, ModeledCustomHeaderCompanion}
import scala.util.Try

final class CustomHeader(id: String) extends ModeledCustomHeader[CustomHeader] {
  override def renderInRequests(): Boolean                           = true
  override def renderInResponses(): Boolean                          = true
  override def companion: ModeledCustomHeaderCompanion[CustomHeader] = CustomHeader
  override def value(): String                                       = id
}
object CustomHeader extends ModeledCustomHeaderCompanion[CustomHeader] {
  override def name: String                            = "custom-header"
  override def parse(value: String): Try[CustomHeader] = Try(new CustomHeader(value))
}

Then simply send them in the request:

val response: Future[HttpClientResponse] = httpClient.request(
  method   = HttpMethods.POST,
  entity   = HttpEntity("Example"),
  path     = "/test",
  headers  = Seq(new CustomHeader("foobar")),
  deadline = Deadline.now + 10.seconds
)

Note: If you want to access the headers from the response, you can do so from the data inside the HttpClientSuccess:

case HttpClientSuccess(content) => content.headers

See HeaderExample.scala for a complete example.

Publishing

Tag the new version (e.g. v1.0.0) and push the tags (git push origin --tags).

You need a public GPG key with your MOIA email and an account on https://oss.sonatype.org that can access the io.moia group.

Add your credentials to ~/.sbt/sonatype_credential and run

sbt:scala-pekko-http-client> +publishSigned

Then close and release the repository.

sbt:scala-pekko-http-client> +sonatypeRelease

Afterwards, add the release to GitHub.

About

Extends the pekko-http client with retry logic, error handling, logging and signing

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Scala 100.0%