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
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.
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
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.
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.
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.