---
id: client
title: Client
---

`ZClient` is an HTTP client that enables us to make HTTP requests and handle responses in a purely functional manner. ZClient leverages the ZIO library's capabilities to provide a high-performance, asynchronous, and type-safe HTTP client solution.

## Key Features

**Purely Functional**: ZClient is built on top of the ZIO library, enabling a purely functional approach to handling HTTP requests and responses. This ensures referential transparency and composability, making it easy to build and reason about complex HTTP interactions.

**Type-Safe**: ZClient's API is designed to be type-safe, leveraging Scala's type system to catch errors at compile time and provide a seamless development experience. This helps prevent common runtime errors and enables developers to write robust and reliable HTTP client code.

**Asynchronous & Non-blocking**: ZClient is fully asynchronous and non-blocking, allowing us to perform multiple HTTP requests concurrently without blocking threads. This ensures optimal resource utilization and scalability, making it suitable for high-performance applications.

**Middleware Support**: ZClient provides support for middleware, allowing us to customize and extend its behavior to suit our specific requirements. We can easily plug in middleware to add functionalities such as logging, debugging, caching, and more.

**Flexible Configuration**: ZClient offers flexible configuration options, allowing us to fine-tune its behavior according to our needs. We can configure settings such as SSL, proxy, connection pooling, timeouts, and more to optimize the client's performance and behavior.

**WebSocket Support**: In addition to traditional HTTP requests, ZClient also supports WebSocket communication, enabling bidirectional, full-duplex communication between client and server over a single, long-lived connection.

**SSL Support**: ZClient provides built-in support for SSL (Secure Sockets Layer) connections, allowing secure communication over the network. Users can configure SSL settings such as certificates, trust stores, and encryption protocols to ensure data confidentiality and integrity.

## Making HTTP Requests

We can think of a `ZClient` as a function that takes a `Request` and returns a `ZIO` effect that calls the server with the given request and returns the response that the server sends back.
Requests can be executed in 2 modes:
- `batched`: The entire body of the request is materialized in memory, and the connection lifecycle is managed automatically by the client.
- `streaming`: The body of the request _might be_ streaming, and the connection lifecycle is managed through the `Scope` in the effect's environment.

The `Client`'s companion object contains methods that reflect the 2 modes of request execution:

```scala
object Client {
  def batched(request: Request): ZIO[Client, Throwable, Response] = ???
  def streaming(request: Request): ZIO[Client & Scope, Throwable, Response] = ???
}
```

### "Streaming" Client

The `streaming` mode is the default mode for executing HTTP requests. It requires the `Client` and `Scope` environments to perform the request and handle the response. The `Client` environment is used to make the request, while the `Scope` environment is used to manage the lifecycle of resources such as connections, sockets, and other I/O-related resources that are acquired and released during the request-response operation.

When making a request in the `streaming` mode, we need to explicitly close the `Scope` once we've collected the response body:

```scala mdoc:compile-only
import zio._
import zio.http._

// OK
val good =
  ZIO.scoped {
    Client
      .streaming(Request.get("http://jsonplaceholder.typicode.com/todos"))
      .flatMap(_.body.asString)
  }.flatMap(???)
  
// BAD: The server might be streaming the response body, and we've forcefully closed the connection before it finishes
val bad1 =
  ZIO.scoped {
    Client
      .streaming(Request.get("http://jsonplaceholder.typicode.com/todos"))
      .map(_.headers)
  }
    .flatMap(???)

// BAD: We're closing the scope before collecting the response body
val bad2 =
  ZIO.scoped {
      Client
        .streaming(Request.get("http://jsonplaceholder.typicode.com/todos"))
    }
    .flatMap(_.body.asString)
    .flatMap(???)

// VERY BAD: The connection will not be closed until the application exits, which will lead to resource leaks!
val bad3 =
  Client
    .streaming(Request.get("http://jsonplaceholder.typicode.com/todos"))
    .flatMap(_.body.asString)
    .flatMap(???)
    .provideSomeLayer[Client](Scope.default)
```

:::note
As a rule of thumb, you should **never** use `Scope.default` with Client!

To learn more about resource management and `Scope` in ZIO, refer to the [dedicated guide on this topic](https://zio.dev/reference/resource/scope) in the ZIO Core documentation.
:::

### "Batched" Client

Handling of `Scope` can quickly become cumbersome in cases where we simply want to execute an HTTP request and not handle the lifetime of the HTTP request.
The `batched` mode is simply a sub-implementation of the `streaming` mode where the `Scope` (i.e., connection lifecycle) is managed automatically.

Executing a request via the `batched` method can be done as simply as:

```scala mdoc:compile-only
import zio._
import zio.http._

val good =
  Client
    .batched(Request.get("http://jsonplaceholder.typicode.com/todos"))
    .flatMap(_.body.asString)
    .flatMap(???)
```

::: warning
The `batched` methods will materialize the entire body of the request to memory.
Use this only when you don't need to stream the request body!
:::

We can similarly use the `batched` method on an instance of `Client` to return a new instance where all the methods will be executed in the `batched` mode. Below is a realistic example showcasing the usage of the `batched` client:

```scala mdoc:compile-only
import zio._
import zio.http._
import zio.schema.DeriveSchema
import zio.schema.codec.JsonCodec.schemaBasedBinaryCodec

case class Todo(
  userId: Int,
  id: Int,
  title: String,
  completed: Boolean,
)

object Todo {
  implicit val todoSchema = DeriveSchema.gen[Todo]
}

final class JsonPlaceHolderService(baseClient: Client) {
  private val client = baseClient.batched

  def todos(): ZIO[Any, Throwable, List[Todo]] =
    client
      .request(Request.get("http://jsonplaceholder.typicode.com/todos"))
      .flatMap(_.body.to[List[Todo]])
}
```

ZIO HTTP has several utility methods to create different types of requests, such as `Client#get`, `Client#post`, `Client#put`, `Client#delete`, etc:

| Method                               | Description                                                           |
|--------------------------------------|-----------------------------------------------------------------------|
| `def get(suffix: String)`            | Performs a GET request with the given path suffix.                    |
| `def head(suffix: String)`           | Performs a HEAD request with the given path suffix.                   |
| `def patch(suffix: String)`          | Performs a PATCH request with the given path suffix.                  |
| `def post(suffix: String)(body: In)` | Performs a POST request with the given path suffix and provided body. |
| `def put(suffix: String)(body: In)`  | Performs a PUT request with the given path suffix and provided body.  |
| `def delete(suffix: String)`         | Performs a DELETE request with the given path suffix.                 |

## Performing WebSocket Connections

We can also think of a client as a function that takes a `WebSocketApp` and returns a `ZIO` effect that performs the WebSocket operations and returns a response:

```scala
object ZClient {
  def socket[R](socketApp: WebSocketApp[R]): ZIO[R with Client & Scope, Throwable, Response] = ???
}
```

:::note
The `socket` method is not available on the "Batched" client!
:::

Here is a simple example of how to use the `ZClient#socket` method to perform a WebSocket connection:

```scala mdoc:compile-only
import zio._
import zio.http._
import zio.http.ChannelEvent._

object WebSocketSimpleClient extends ZIOAppDefault {

  val url = "ws://ws.vi-server.org/mirror"

  val socketApp: WebSocketApp[Any] =
    Handler

      // Listen for all websocket channel events
      .webSocket { channel =>
        channel.receiveAll {

          // Send a "foo" message to the server once the connection is established
          case UserEventTriggered(UserEvent.HandshakeComplete) =>
            channel.send(Read(WebSocketFrame.text("foo"))) *>
              ZIO.debug("Connection established and the foo message sent to the server")

          // Send a "bar" if the server sends a "foo"
          case Read(WebSocketFrame.Text("foo")) =>
            channel.send(Read(WebSocketFrame.text("bar"))) *>
              ZIO.debug("Received the foo message from the server and the bar message sent to the server")

          // Close the connection if the server sends a "bar"
          case Read(WebSocketFrame.Text("bar")) =>
            ZIO.debug("Received the bar message from the server and Goodbye!") *>
              channel.send(Read(WebSocketFrame.close(1000)))

          case _ =>
            ZIO.unit
        }
      }

  val app: ZIO[Client, Throwable, Unit] =
    for {
      url    <- ZIO.fromEither(URL.decode("ws://ws.vi-server.org/mirror"))
      client <- ZIO.serviceWith[Client](_.url(url))
      _      <- ZIO.scoped(client.socket(socketApp) *> ZIO.never)
    } yield ()

  val run: ZIO[Any, Throwable, Any] =
    app.provide(Client.default)

}
```

In the above example, we defined a WebSocket client that connects to a mirror server and sends and receives messages. When the connection is established, it receives the `UserEvent.HandshakeComplete` event and then it sends a "foo" message to the server. Consequently, the server sends a "foo" message, and the client responds with a "bar" message. Finally, the server sends a "bar" message, and the client closes the connection.

## Configuring Headers

As the `ZClient` extends the `HeaderOps` trait, we have access to all operations that can be performed on headers inside the client.

For example, to add a custom header, we can use the `Client#addHeader` method:

```scala mdoc:compile-only
import zio._
import zio.http._
import zio.http.Header.Authorization

val program = for {
  client <- ZIO.serviceWith[Client](_.addHeader(Authorization.Bearer(token = "dummyBearerToken")))
  res    <- client.request(Request.get("http://localhost:8080/users"))
} yield ()
```

By default, the client adds the `User-Agent` header to all requests. Please note that it also automatically calculates and sets the `Content-Length` header, disregarding any `Content-Length` value manually configured by the user.

:::note
To learn more about headers and how they work, check out our dedicated section called [Header Operations](headers/headers.md#headers-operations) on the headers page.
:::

## Composable URLs

In ZIO HTTP, URLs are composable. This means that if we have two URLs, we can combine them to create a new URL. This is useful when we want to prevent duplication of the base URL in our code. For example, assume we have a base URL `http://localhost:8080` and we want to make several requests to different endpoints and query parameters under this base URL. We can configure the client with this URL using the `Client#url` and then every request will be made can be relative to this base URL:

```scala mdoc:compile-only
import zio._
import zio.http._
import zio.schema.DeriveSchema
import zio.schema.codec.JsonCodec.schemaBasedBinaryCodec

case class User(name: String, age: Int)
object User {
  implicit val schema = DeriveSchema.gen[User]
}

val program: ZIO[Client, Throwable, Unit] =
  for {
    client <- ZIO.serviceWith[Client](_.url(url"http://localhost:8080").batched)
    _      <- client.post("/users")(Body.from(User("John", 42)))
    res    <- client.get("/users")
    _      <- client.delete("/users/1")
    _      <- res.body.asString.debug
  } yield ()
```

The following methods are available for setting the base URL:

| Method Signature                | Description                                    |
|---------------------------------|------------------------------------------------|
| `Client#url(url: URL)`          | Sets the URL directly.                         |
| `Client#uri(uri: URI)`          | Sets the URL from the provided URI.            |
| `Client#path(path: String)`     | Sets the path of the URL from a string.        |
| `Client#path(path: Path)`       | Sets the path of the URL from a `Path` object. |
| `Client#port(port: Int)`        | Sets the port of the URL.                      |
| `Client#scheme(scheme: Scheme)` | Sets the scheme (protocol) for the URL.        |

The `Scheme` is a sealed trait that represents the different schemes (protocols) that can be used in a request. The available schemes are `HTTP` and `HTTPS` for HTTP requests, and `WS` and `WSS` for WebSockets.

Here is the list of methods that are available for adding URL, Path, and QueryParams to the client's configuration:

| Methods                                            | Description                                                            |
|----------------------------------------------------|------------------------------------------------------------------------|
| `Client#addUrl(url: URL)`                          | Adds another URL to the existing one.                                  |
| `Client#addPath(path: String)`                     | Adds a path segment to the URL.                                        |
| `Client#addPath(path: Path)`                       | Adds a path segment from a `Path` object to the URL.                   |
| `Client#addLeadingSlash`                           | Adds a leading slash to the URL path.                                  |
| `Client#addTrailingSlash`                          | Adds a trailing slash to the URL path.                                 |
| `Client#addQueryParam(key: String, value: String)` | Adds a query parameter with the specified key-value pair to the URL.   |
| `Client#addQueryParams(params: QueryParams)`       | Adds multiple query parameters to the URL from a `QueryParams` object. |

## Client Aspects/Middlewares

Client aspects are a powerful feature of ZIO HTTP, enabling us to intercept, modify, and extend client behavior. The `ZClientAspect` is represented as a function that takes a `ZClient` and returns a new `ZClient` with customized behavior. We apply aspects to a client using the `ZClient#@@` method, allowing modification of various execution aspects such as metrics, tracing, encoding, decoding, and debugging.

### Debugging Aspects

To debug the client, we can use the `ZClientAspect.debug` aspect, which logs the request details to the console. This is useful for debugging and troubleshooting client interactions, as it provides visibility into the low-level details of the HTTP requests and responses:

```scala mdoc:compile-only
import zio._
import zio.http._

object ClientWithDebugAspect extends ZIOAppDefault {
  val program =
    for {
      client <- ZIO.service[Client].map(_ @@ ZClientAspect.debug)
      _      <- client.batched(Request.get("http://jsonplaceholder.typicode.com/todos"))
    } yield ()

  override val run = program.provide(Client.default)
}
```

The `ZClientAspect.debug` also takes a partial function from `Response` to `String`, which enables us to customize the logging output based on the response. This is useful for logging specific details from the response, such as status code, headers, and body:

```scala
val debugResponse = ZClientAspect.debug { case res: Response => res.headers.mkString("\n") }

val program =
  for {
    client <- ZIO.service[Client].map(_ @@ debugResponse)
    _      <- client.request(Request.get("http://jsonplaceholder.typicode.com/todos"))
  } yield ()
```

### Logging Aspects

To log the client interactions, we can use the `ZClientAspect.requestLogging` which logs the request details such as method, duration, url, user-agent, status code and request size.

Let's try an example:

```scala mdoc:compile-only
import zio._
import zio.http._

val loggingAspect =
  ZClientAspect.requestLogging(
    loggedRequestHeaders = Set(Header.UserAgent),
    logResponseBody = true,
  )

val program =
  for {
    client <- ZIO.service[Client].map(_ @@ loggingAspect)
    _      <- client.request(Request.get("http://jsonplaceholder.typicode.com/todos"))
  } yield ()
```

### Follow Redirects

To follow redirects, we can apply the `ZClientAspect.followRedirects` aspect, which takes the maximum number of redirects to follow and a callback function that allows us to customize the behavior when a redirect is encountered:

```scala mdoc:compile-only
import zio._
import zio.http._

val followRedirects = ZClientAspect.followRedirects(3)((resp, message) => ZIO.logInfo(message).as(resp))

for {
  client   <- ZIO.service[Client].map(_ @@ followRedirects)
  response <- client.request(Request.get("http://google.com"))
  _        <- response.body.asString.debug
} yield ()
```

### Curl Logger

The `ZClientAspect.curlLogger` aspect converts HTTP requests into equivalent curl commands and logs them. This is particularly useful for debugging, documentation, and testing, as it allows you to see the exact curl command that corresponds to each HTTP request made by your client.

The aspect takes two optional parameters:
- `verbose: Boolean` - Whether to include the `--verbose` flag in the curl command (default: `true`)
- `logEffect: String => UIO[Unit]` - A custom logging function (default: `ZIO.log`)

Here's a basic example:

```scala mdoc:passthrough
import utils._

printSource("zio-http-example/src/main/scala/example/CurlLoggerExample.scala")
```

This will log a curl command similar to:

```bash
curl \
  --verbose \
  --request POST \
  --header 'user-agent:Zio-Http-Client/2.1.21 (Scala 2.13.16)' \
  --header 'content-type:application/json' \
  --header 'authorization:Bearer <YOUR_GOREST_ACCESS_TOKEN>' \
  --data '{
  "name": "John Doe",
  "gender": "male",
  "email": "john.doe.unique123@example.com",
  "status": "active"
}' \
  'https://gorest.co.in/public/v2/users'
```

You can customize the logging behavior by providing your own logging function. For example, to write curl commands to a file.

You can also use the `CurlLogger.formatCurlCommand` helper directly without applying it as an aspect to the client:

```scala mdoc:compile-only
import zio._
import zio.http._

val program =
  for {
    request     <- ZIO.succeed(Request.get("https://example.com/"))
    curlCommand <- ZClientAspect.CurlLogger.formatCurlCommand(request, verbose = true)
    _           <- Console.printLine(curlCommand)
  } yield ()
```

The curl logger is useful when you want to generate curl commands for documentation or testing purposes without actually executing the requests.

### Forwarding Headers

This is essential when your application acts as an intermediary (proxy, API gateway, or microservice) and needs to forward specific headers from the original request to downstream services.

Assume you have an API gateway that handles incoming requests and forwards them to various backend services. You want to ensure that certain headers, such as authentication tokens or tracing information, are preserved during this forwarding process.

To achieve this, you can use the `ZClientAspect.forwardHeaders` aspect. This aspect works in conjunction with the `Middleware.forwardHeaders` middleware.

The Two-Part System:

- **Server Middleware** (`Middleware.forwardHeaders`) - Extracts specified headers from incoming requests and stores them in request-scoped storage (`RequestStore`)
- **Client Aspect** (`ZClientAspect.forwardHeaders`) - Retrieves headers from the request-scoped storage and automatically adds them to outgoing requests

The process begins when an external client makes a request to your API gateway. For example, a client might send a GET request to retrieve order information, including an authorization bearer token and a request ID for distributed tracing. When this request arrives at your server, the `Middleware.forwardHeaders` middleware immediately goes to work. You configure this middleware by specifying which headers you want to forward—for instance, the `Authorization` header and a custom `X-Request-ID` header. The middleware extracts these headers from the incoming request and creates a request-scoped storage container. This storage uses ZIO's `FiberRef` mechanism under the hood, ensuring that the headers are available throughout the entire request handling process but are completely isolated from other concurrent requests.

The beauty of this approach is that the storage is request-scoped, meaning it lives only for the duration of that specific request and is automatically cleaned up when the request completes. You don't need to worry about memory leaks or manual cleanup. More about `RequestStore` can be found in the [dedicated section](contextual/request-store.md).

Once the middleware has stored the headers, your request handler executes normally. In your handler, you make HTTP requests to various backend services using a ZIO HTTP Client that has been configured with the `ZClientAspect.forwardHeaders` aspect. This is where the magic happens—you don't need to manually add the forwarded headers to each outgoing request. Instead, you simply make your HTTP calls as you normally would.

Behind the scenes, the client aspect intercepts each outgoing request before it's sent. It reads the forwarded headers from the request-scoped storage and automatically merges them with the outgoing request. So when you make a request to your order service or user service, the aspect ensures that the `Authorization` token and `X-Request-ID` are automatically included in those requests.

Here is an example demonstrating this flow:

```scala mdoc:invisible
import zio._
import zio.http._
import zio.http.codec.TextBinaryCodec.fromSchema
import zio.json.{DeriveJsonCodec, JsonCodec}
import zio.schema._

case class Order(id: String, item: String, quantity: Int)
object Order {
  implicit val schema: Schema[Order]       = zio.schema.DeriveSchema.gen[Order]
  implicit val jsonCodec: JsonCodec[Order] = DeriveJsonCodec.gen
}

case class User(id: String, name: String, email: String)
object User {
  implicit val schema: Schema[User]       = zio.schema.DeriveSchema.gen[User]
  implicit val jsonCodec: JsonCodec[User] = DeriveJsonCodec.gen
}

case class Inventory(item: String, available: Int)
object Inventory    {
  implicit val schema: Schema[Inventory]       = zio.schema.DeriveSchema.gen[Inventory]
  implicit val jsonCodec: JsonCodec[Inventory] = DeriveJsonCodec.gen
}

case class OrderDetails(
  order: Order,
  user: User,
  inventory: Inventory,
)
object OrderDetails {
  implicit val schema: Schema[OrderDetails]       = zio.schema.DeriveSchema.gen[OrderDetails]
  implicit val jsonCodec: JsonCodec[OrderDetails] = DeriveJsonCodec.gen
}
```

```scala mdoc:compile-only
import zio._
import zio.http._
import zio.http.codec.TextBinaryCodec.fromSchema

object ApiGateway extends ZIOAppDefault {
  val userServiceUrl      = "http://user-service:8081"
  val orderServiceUrl     = "http://order-service:8082"
  val inventoryServiceUrl = "http://inventory-service:8083"

  val routes = Routes(
    Method.GET / "api" / "orders" / string("orderId") ->
      Handler.scoped {
        handler { (orderId: String, req: Request) =>
          for {
            client <- ZIO.serviceWith[Client](_ @@ ZClientAspect.forwardHeaders)

            order <- client
              .request(Request.get(s"$orderServiceUrl/orders/$orderId"))
              .flatMap(_.body.to[Order])

            user <- client
              .request(Request.get(s"$userServiceUrl/users/me"))
              .flatMap(_.body.to[User])

            inventory <- client
              .request(Request.get(s"$inventoryServiceUrl/stock/$orderId"))
              .flatMap(_.body.to[Inventory])

          } yield Response(
            headers = Headers(Header.ContentType(MediaType.application.json)),
            body = Body.from[OrderDetails](OrderDetails(order, user, inventory)),
          )
        }
      },
  ).sandbox @@ Middleware.forwardHeaders(Header.Authorization)

  def run = Server.serve(routes).provide(Server.default, Client.default)
}
```

The result is that all your downstream services receive the necessary headers without any manual header manipulation in your code. Here is the visual flow:

```text
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. INCOMING CLIENT REQUEST                                                  │
│                                                                             │
│    curl -H "Authorization: Bearer token123" \                               │
│         -H "X-Request-ID: req-456" \                                        │
│         http://api-gateway/api/orders/123                                   │
│                                                                             │
│    ┌──────────────────────────────────────────────┐                         │
│    │  GET /api/orders/123                         │                         │
│    │  Authorization: Bearer token123              │                         │
│    │  X-Request-ID: req-456                       │                         │
│    └──────────────────────────────────────────────┘                         │
└─────────────────────────────────┬───────────────────────────────────────────┘
                                  │
                                  ▼
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ 2. SERVER MIDDLEWARE: Middleware.forwardHeaders(...)                      ┃
┃    ─────────────────────────────────────────────────────────────────      ┃
┃                                                                           ┃
┃    YOU WRITE:                                                             ┃
┃    ═════════                                                              ┃
┃    routes @@ Middleware.forwardHeaders(                                   ┃
┃                Header.Authorization,                                      ┃
┃                Header.Custom("X-Request-ID")                              ┃
┃              )                                                            ┃
┃                                                                           ┃
┃    AUTOMATICALLY HAPPENS:                                                 ┃
┃    ════════════════════                                                   ┃
┃    ┌────────────────────────────────────────────────────────┐             ┃
┃    │ a) Extract specified headers from incoming request:    │             ┃
┃    │    • Authorization: Bearer token123                    │             ┃
┃    │    • X-Request-ID: req-456                             │             ┃
┃    │                                                        │             ┃
┃    │ b) Create request-scoped storage (FiberRef-based):     │             ┃
┃    │    RequestStore[ForwardedHeaders] = {                  │             ┃
┃    │      headers: Headers(                                 │             ┃
┃    │        Authorization: Bearer token123,                 │             ┃
┃    │        X-Request-ID: req-456                           │             ┃
┃    │      )                                                 │             ┃
┃    │    }                                                   │             ┃
┃    │                                                        │             ┃
┃    │ c) Storage scope: Lives only for this request          │             ┃
┃    │    Automatically cleaned up when request completes     │             ┃
┃    └────────────────────────────────────────────────────────┘             ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
                                 │
                       ╔═════════╧════════════════════════════════╗
                       ║  REQUEST-SCOPED CONTEXT CREATED          ║
                       ║  ┌────────────────────────────────────┐  ║
                       ║  │ RequestStore[ForwardedHeaders]     │  ║
                       ║  │ • Authorization: Bearer token123   │  ║
                       ║  │ • X-Request-ID: req-456            │  ║
                       ║  └────────────────────────────────────┘  ║
                       ║  (Available to entire request lifecycle) ║
                       ╚═══════════════════╤══════════════════════╝
                                           │
                                           ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 3. YOUR HANDLER EXECUTES                                                    │
│    ─────────────────────                                                    │
│                                                                             │
│    YOU WRITE:                                                               │
│    ═════════                                                                │
│    for {                                                                    │
│      client <- ZIO.serviceWith[Client](_ @@ ZClientAspect.forwardHeaders)   │
│                                                                             │
│      order <- client.request(                                               │
│        Request.get("http://order-service:8082/orders/123")                  │
│      ).flatMap(_.body.to[Order])                                            │
│                                                                             │
│      user <- client.request(                                                │
│        Request.get("http://user-service:8081/users/me")                     │
│      ).flatMap(_.body.to[User])                                             │
│    } yield Response(...)                                                    │
│                                                                             │
│    NOTE: You don't manually add headers - that's the whole point!           │
└─────────────────────────────────┬───────────────────────────────────────────┘
                                  │
                                  ▼
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ 4. CLIENT ASPECT: ZClientAspect.forwardHeaders                            ┃
┃    ───────────────────────────────────────────────────────────            ┃
┃                                                                           ┃
┃    YOU WRITE:                                                             ┃
┃    ═════════                                                              ┃
┃    @@ ZClientAspect.forwardHeaders                                        ┃
┃                                                                           ┃
┃    AUTOMATICALLY HAPPENS (for EACH outgoing request):                     ┃
┃    ═════════════════════════════════════════════════                      ┃
┃    ┌────────────────────────────────────────────────────────┐             ┃
┃    │ a) Read from RequestStore[ForwardedHeaders]:           │             ┃
┃    │    headers = {                                         │             ┃
┃    │      Authorization: Bearer token123,                   │             ┃
┃    │      X-Request-ID: req-456                             │             ┃
┃    │    }                                                   │             ┃
┃    │                                                        │             ┃
┃    │ b) Merge with outgoing request:                        │             ┃
┃    │    Original: Request.get("http://order-service/...")   │             ┃
┃    │    Enhanced: Request.get("http://order-service/...")   │             ┃
┃    │              + Authorization: Bearer token123          │             ┃
┃    │              + X-Request-ID: req-456                   │             ┃
┃    │                                                        │             ┃
┃    │ c) Send enhanced request to backend service            │             ┃
┃    └────────────────────────────────────────────────────────┘             ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
                                 │
               ┌─────────────────┴─────────────────┐
               │                                   │
               ▼                                   ▼
  ┌────────────────────────────────┐   ┌────────────────────────────────┐
  │ 5a. ORDER SERVICE RECEIVES     │   │ 5b. USER SERVICE RECEIVES      │
  │                                │   │                                │
  │  GET /orders/123               │   │  GET /users/me                 │
  │  Authorization: Bearer token123│   │  Authorization: Bearer token123│
  │  X-Request-ID: req-456         │   │  X-Request-ID: req-456         │
  │  ✓ Forwarded automatically!    │   │  ✓ Forwarded automatically!    │
  └────────────────────────────────┘   └────────────────────────────────┘
```

## Configuring ZIO HTTP Client

The ZIO HTTP Client provides a flexible configuration mechanism through the `ZClient.Config` class. This class allows us to customize various aspects of the HTTP client, including SSL settings, proxy configuration, connection pool size, timeouts, and more. The `ZClient.Config.default` provides a default configuration that can be customized using `copy` method or by using the utility methods provided by the `ZClient.Config` class.

Let's take a look at the available configuration options:

- **SSL Configuration**: Allows us to specify SSL settings for secure connections.
- **Proxy Configuration**: Enables us to configure a proxy server for outgoing HTTP requests.
- **Connection Pool Configuration**: Defines the size of the connection pool.
- **Max Initial Line Length**: Sets the maximum length of the initial line in an HTTP request or response. The default is set to 4096 characters.
- **Max Header Size**: Specifies the maximum size of HTTP headers in bytes. The default is set to 8192 bytes.
- **Request Decompression**: Specifies whether the client should decompress the response body if it's compressed.
- **Local Address**: Specifies the local network interface or address to use for outgoing connections. It's set to None, indicating that the client will use the default local address.
- **Add User-Agent Header**: Indicates whether the client should automatically add a User-Agent header to outgoing requests. It's set to true in the default configuration.
- **WebSocket Configuration**: Configures settings specific to WebSocket connections. In this example, the default WebSocket configuration is used.
- **Idle Timeout**: Specifies the maximum idle time for persistent connections in seconds. The default is set to 50 seconds.
- **Connection Timeout**: Specifies the maximum time to wait for establishing a connection in seconds. By default, the client has no connection timeout.

Here are some of the above configuration options in more detail:

### Configuring SSL

The default SSL configuration of `ZClient.Config.default` is `None`. To enable and configure SSL for the client, we can use the `ZClient.Config#ssl` method. This method takes a config of type `ClientSSLConfig` which supports different SSL configurations such as `Default`, `FromCertFile`, `FromCertResource`, `FromTrustStoreFile`, and `FromTrustStoreResource.

Let's see an example of how to configure SSL for the client:

```scala mdoc:passthrough
import utils._

printSource("zio-http-example/src/main/scala/example/HttpsClient.scala")
```

### Configuring Proxy

To configure a proxy for the client, we can use the `Client#proxy` method. This method takes a `Proxy` and updates the client's configuration to use the specified proxy for all requests:

```scala mdoc:compile-only
import zio._
import zio.http._

val program = for {
  proxyUrl <- ZIO.fromEither(URL.decode("http://localhost:8123"))
  client   <- ZIO.serviceWith[Client](_.proxy(Proxy(url = proxyUrl)))
  res      <- client.request(Request.get("https://jsonplaceholder.typicode.com/todos"))
} yield ()
```

### Connection Pooling

Connection pooling is a crucial mechanism in ZIO HTTP for optimizing the management of HTTP connections. By default, ZIO HTTP uses a fixed-size connection pool with a capacity of 10 connections. This means that the client can maintain up to 10 idle connections to the server for reuse. When the client makes a request, it checks the connection pool for an available connection to the server. If a connection is available, it reuses it for the request. If no connection is available, it creates a new connection and adds it to the pool.

To configure the connection pool, we have to update the `ZClient.Config#connectionPool` field with the preferred configuration. The `ConnectionPoolConfig` trait serves as a base trait for different connection pool configurations. It is a sealed trait with five different implementations:

- `Disabled`: Indicates that connection pooling is disabled.
- `Fixed`: Takes a single parameter, `size`, which specifies a fixed size connection pool.
- `FixedPerHost`: Takes a map of `URL.Location.Absolute` to `Fixed` to specify a fixed size connection pool per host.
- `Dynamic`: Takes three parameters, `minimum`, `maximum`, and `ttl`, to configure a dynamic connection pool with minimum and maximum sizes and a time-to-live (TTL) duration.
- `DynamicPerHost`: Similar to Dynamic, but with configurations per host.

Also the `ZClient.Config` has some utility methods to update the connection pool configuration, e.g. `ZClient.Config#fixedConnectionPool` and `ZClient.Config#dynamicConnectionPool`. Let's see an example of how to configure the connection pool:

```scala mdoc:passthrough
import utils._

printSource("zio-http-example/src/main/scala/example/ClientWithConnectionPooling.scala")
```

### Enabling Response Decompression

When making HTTP requests using a client, such as a web browser or a custom HTTP client, it's essential to optimize data transfer for efficiency and performance.

By default, most HTTP clients do not advertise compression support when making requests to web servers. However, servers often compress response bodies when they detect that the client supports compression. To enable response compression, we need to add the `Accept-Encoding` header to our HTTP requests. The `Accept-Encoding` header specifies the compression algorithms supported by the client. Common values include `gzip` and `deflate`. When a server receives a request with the `Accept-Encoding` header, it may compress the response body using one of the specified algorithms.

Here's an example of an HTTP request with the Accept-Encoding header:

```http
GET https://example.com/
Accept-Encoding: gzip, deflate
```

When a server responds with a compressed body, it includes the Content-Encoding header to specify the compression algorithm used. The client then needs to decompress the body before processing its contents.

For example, a compressed response might look like this:

```http
200 OK
content-encoding: gzip
content-type: application/json; charset=utf-8

<compressed-body>
```

To decompress the response body with `ZClient`, we need to enable response decompression by using the `ZClient.Config#requestDecompression` method:

```scala mdoc:passthrough
import utils._

printSource("zio-http-example/src/main/scala/example/ClientWithDecompression.scala")
```

## Customizing `ClientDriver` and `DnsResolver`

Rather than utilizing the default layer, `Client.default`, we have the option to employ the `Client.customized` layer. This layer requires `ClientDriver`, `DnsResolver`, and the `Client.Config` layers:

```scala
object Client {
  val customized: ZLayer[Config with ClientDriver with DnsResolver, Throwable, Client] = ???
}
```

This empowers us to interchange the client driver with alternatives beyond the default Netty driver or to customize it to our specific requirements. Also, we can customize the DNS resolver to use a different DNS resolution mechanism.

## Examples

### Simple Client Example

```scala mdoc:passthrough
import utils._

printSource("zio-http-example/src/main/scala/example/SimpleClient.scala")
```

### ClientServer Example

```scala mdoc:passthrough
import utils._

printSource("zio-http-example/src/main/scala/example/ClientServer.scala")
```

### Authentication Client Example

This example code demonstrates accessing a protected route in an [authentication server](https://github.com/zio/zio-http/blob/main/zio-http-example/src/main/scala/example/AuthenticationClient.scala) by first obtaining a JWT token through a login request and then using that token to access the protected route:

```scala mdoc:passthrough
import utils._

printSource("zio-http-example/src/main/scala/example/AuthenticationClient.scala")
```

### Reconnecting WebSocket Client Example

This example represents a WebSocket client application that automatically attempts to reconnect upon encountering errors or disconnections. It uses the `Promise` to notify about WebSocket errors:

```scala mdoc:passthrough
import utils._

printSource("zio-http-example/src/main/scala/example/websocket/WebSocketReconnectingClient.scala")
```
