This is a wrapper around the akka-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-http-client" % "5.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
AwsRequestSigneris given, but the request already includes an "Authorization" header =>AlreadyAuthorizedException - weird akka-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 akka.http.scaladsl.model.headers:
import akka.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.headersSee HeaderExample.scala for a complete example.
Tag the new version (e.g. v5.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-http-client> +publishSignedThen close and release the repository.
sbt:scala-http-client> +sonatypeRelease Afterwards, add the release to GitHub.