Ember HTTP client implementation for http4s with connection pooling and cross-platform support
npx @tessl/cli install tessl/maven-org-http4s--http4s-ember-client@0.23.0Http4s Ember Client is a high-performance HTTP client implementation for the http4s library, built on the ember networking library. It provides connection pooling, HTTP/2 support, cross-platform compatibility (JVM, JavaScript, Native), and comprehensive TLS configuration options.
build.sbt:
libraryDependencies += "org.http4s" %% "http4s-ember-client" % "0.23.30"import org.http4s.ember.client.EmberClientBuilder
import org.http4s.ember.client.EmberClient
import cats.effect.IO
import org.http4s.client.Clientimport org.http4s.ember.client.EmberClientBuilder
import cats.effect.IO
import org.http4s._
// Create a client with default settings
EmberClientBuilder.default[IO].build.use { client =>
// Make HTTP requests
val request = Request[IO](Method.GET, uri"https://api.example.com/users")
client.expect[String](request)
}
// Configure the client
EmberClientBuilder.default[IO]
.withMaxTotal(200)
.withTimeout(30.seconds)
.withHttp2
.build
.use { client =>
// Use configured client
}The ember client is built around several key components:
Configuration and creation of HTTP clients with comprehensive customization options.
/**
* Builder for configuring EmberClient instances with fluent API
*/
final class EmberClientBuilder[F[_]: Async: Network] private (
// Internal configuration fields omitted
) {
/** Create the configured HTTP client */
def build: Resource[F, Client[F]]
/** Set custom TLS context */
def withTLSContext(tlsContext: TLSContext[F]): EmberClientBuilder[F]
/** Use system default TLS context */
def withoutTLSContext: EmberClientBuilder[F]
/** Set socket group for connections */
def withSocketGroup(sg: SocketGroup[F]): EmberClientBuilder[F]
/** Set total maximum connections in pool */
def withMaxTotal(maxTotal: Int): EmberClientBuilder[F]
/** Set maximum connections per request key */
def withMaxPerKey(maxPerKey: RequestKey => Int): EmberClientBuilder[F]
/** Set idle timeout for pooled connections */
def withIdleTimeInPool(idleTimeInPool: Duration): EmberClientBuilder[F]
/** Set idle timeout on individual connections */
def withIdleConnectionTime(idleConnectionTime: Duration): EmberClientBuilder[F]
/** Set logger for client operations */
def withLogger(logger: Logger[F]): EmberClientBuilder[F]
/** Set chunk size for reading from sockets */
def withChunkSize(chunkSize: Int): EmberClientBuilder[F]
/** Set maximum response header size */
def withMaxResponseHeaderSize(maxResponseHeaderSize: Int): EmberClientBuilder[F]
/** Set header receive timeout */
def withTimeout(timeout: Duration): EmberClientBuilder[F]
/** Set additional socket options */
def withAdditionalSocketOptions(additionalSocketOptions: List[SocketOption]): EmberClientBuilder[F]
/** Set default User-Agent header */
def withUserAgent(userAgent: `User-Agent`): EmberClientBuilder[F]
/** Clear default User-Agent header */
def withoutUserAgent: EmberClientBuilder[F]
/** Enable/disable endpoint authentication verification */
def withCheckEndpointAuthentication(checkEndpointIdentification: Boolean): EmberClientBuilder[F]
/** Disable endpoint authentication verification */
def withoutCheckEndpointAuthentication: EmberClientBuilder[F]
/** Enable/disable Server Name Indication */
def withServerNameIndication(serverNameIndication: Boolean): EmberClientBuilder[F]
/** Disable Server Name Indication */
def withoutServerNameIndication: EmberClientBuilder[F]
/** Set retry policy for failed requests */
def withRetryPolicy(retryPolicy: RetryPolicy[F]): EmberClientBuilder[F]
/** Enable Unix socket support */
def withUnixSockets(unixSockets: UnixSockets[F]): EmberClientBuilder[F]
/** Enable HTTP/2 support */
def withHttp2: EmberClientBuilder[F]
/** Disable HTTP/2 support */
def withoutHttp2: EmberClientBuilder[F]
/** Set HTTP/2 push promise handler */
def withPushPromiseSupport(
f: (Request[fs2.Pure], F[Response[F]]) => F[Outcome[F, Throwable, Unit]]
): EmberClientBuilder[F]
/** Disable HTTP/2 push promise support */
def withoutPushPromiseSupport: EmberClientBuilder[F]
}
/**
* Factory methods for creating EmberClientBuilder instances
*/
object EmberClientBuilder {
/** Create builder with default configuration */
def default[F[_]: Async: Network]: EmberClientBuilder[F]
/** Create builder with default configuration (deprecated) */
@deprecated("Use default[F[_]: Async: Network] instead", "0.23.16")
def default[F[_]](async: Async[F]): EmberClientBuilder[F]
}Usage Examples:
import org.http4s.ember.client.EmberClientBuilder
import cats.effect.IO
import scala.concurrent.duration._
// Basic client with defaults
val basicClient = EmberClientBuilder.default[IO].build
// Custom configuration
val customClient = EmberClientBuilder.default[IO]
.withMaxTotal(500)
.withMaxPerKey(_ => 50)
.withTimeout(45.seconds)
.withIdleConnectionTime(60.seconds)
.withChunkSize(64 * 1024)
.build
// HTTP/2 enabled client
val http2Client = EmberClientBuilder.default[IO]
.withHttp2
.withPushPromiseSupport { case (request, response) =>
// Handle server push promises
IO.pure(cats.effect.Outcome.Succeeded(IO.unit))
}
.build
// TLS configured client
val tlsClient = EmberClientBuilder.default[IO]
.withCheckEndpointAuthentication(true)
.withServerNameIndication(true)
.build
// Unix socket client
import fs2.io.net.unixsocket.UnixSockets
val unixClient = EmberClientBuilder.default[IO]
.withUnixSockets(UnixSockets.forAsync[IO])
.buildMain HTTP client functionality for executing requests with connection pooling and state monitoring.
/**
* HTTP client implementation with connection pooling
*/
final class EmberClient[F[_]] private[client] (
private val client: Client[F],
private val pool: KeyPool[F, RequestKey, EmberConnection[F]]
)(implicit F: MonadCancelThrow[F]) extends DefaultClient[F] {
/** Execute HTTP request and return response resource */
def run(req: Request[F]): Resource[F, Response[F]]
/** Get current connection pool state */
def state: F[(Int, Map[RequestKey, Int])]
/** Default error handling for unsuccessful responses */
override def defaultOnError(req: Request[F])(resp: Response[F])(implicit G: Applicative[F]): F[Throwable]
}Usage Examples:
import org.http4s._
import org.http4s.ember.client.EmberClientBuilder
import cats.effect.IO
// Basic request execution
EmberClientBuilder.default[IO].build.use { client =>
val request = Request[IO](Method.GET, uri"https://api.github.com/users/octocat")
// Get response as Resource
client.run(request).use { response =>
response.body.compile.toVector.map(bytes => new String(bytes.toArray))
}
// Or use convenience methods
client.expect[String](request)
}
// Monitor connection pool state
EmberClientBuilder.default[IO].build.use { client =>
for {
_ <- client.expect[String](Request[IO](Method.GET, uri"https://example.com"))
(totalConns, perKeyConns) <- client.state
_ <- IO.println(s"Total connections: $totalConns")
_ <- IO.println(s"Per-key connections: $perKeyConns")
} yield ()
}
// Handle different response types
EmberClientBuilder.default[IO].build.use { client =>
val jsonRequest = Request[IO](Method.GET, uri"https://api.github.com/users/octocat")
.withHeaders(Header.Raw(ci"Accept", "application/json"))
// Expect JSON response
import io.circe.Json
import org.http4s.circe._
client.expect[Json](jsonRequest)
}// From cats-effect and fs2
import cats.effect.{Async, Resource, MonadCancelThrow}
import fs2.io.net.{Network, SocketGroup, SocketOption, TLSContext}
import fs2.io.net.unixsocket.{UnixSockets, UnixSocketAddress}
// From http4s
import org.http4s.{Request, Response, Method, Uri}
import org.http4s.client.{Client, RequestKey, RetryPolicy}
import org.http4s.headers.`User-Agent`
// From log4cats
import org.typelevel.log4cats.Logger
// From keypool
import org.typelevel.keypool.KeyPool
// Scala standard library
import scala.concurrent.duration.{Duration, FiniteDuration}
// Connection pool state: (total connections, connections per request key)
type PoolState = (Int, Map[RequestKey, Int])
// Push promise handler for HTTP/2
type PushPromiseHandler[F[_]] = (Request[fs2.Pure], F[Response[F]]) => F[cats.effect.Outcome[F, Throwable, Unit]]The ember client provides comprehensive error handling:
import org.http4s.ember.client.EmberClientBuilder
import cats.effect.IO
import org.http4s._
EmberClientBuilder.default[IO].build.use { client =>
val request = Request[IO](Method.GET, uri"https://httpbin.org/status/404")
client.run(request).use { response =>
if (response.status.isSuccess) {
response.body.compile.toVector
} else {
IO.raiseError(new RuntimeException(s"Request failed: ${response.status}"))
}
}
}Common exceptions: