Customizing a ClientFactory with ClientFactoryBuilder

A ClientFactory manages connections and protocol-specific properties. You can customize the properties via ClientFactoryBuilder. For example, let's say that you want to increase the connection timeout.

ClientFactory factory =
  ClientFactory
    .builder()
    // Increase the connection timeout to 5 seconds.
    .connectTimeoutMillis(5000)
    .build());
WebClient client =
  WebClient
   .builder("https://armeria.dev")
   .factory(factory)
   .build();
client.get("/api").aggregate().join();

The client created with the given ClientFactory will respect the settings such as connection timeout that you defined. You might notice that the clients, which are created using the same ClientFactory, share one connection pool. If you don't want to share the connections among the clients, you should use different ClientFactorys. For the complete list of properties, see ClientFactoryBuilder.

You can also use ClientFactoryOptions to build ClientFactory. It's more verbose than calling a dedicated setter method, but it can be useful when you need to set a series of options programmatically.

Customizing TLS

You can configure the client authentication for mutual TLS (mTLS) using ClientFactoryBuilder.tls() . Additionally, you can specify various options such as a different CA certificate chain or enforce certain cipher suites with ClientFactoryBuilder.tlsCustomizer() .

try (InputStream keyCertChainInputStream = ...;
     InputStream keyInputStream = ...) {
  ClientFactory factory =
    ClientFactory
      .builder()
      .tls(keyCertChainInputStream, keyInputStream)
      .build();
  WebClient
    .builder()
    .factory(factory)
    .build();
}

Lifecycle of ClientFactory

Most clients for a ClientFactory need to send requests to another server for the lifetime of an application. Therefore, you will generally avoid closing the ClientFactory, except for perhaps in a shutdown hook, to avoid losing any connection to the other servers. For unit tests where a ClientFactory is only used for a single test or test class, it is appropriate to close it when done using it.

Specifying Netty ChannelOptions

You can also specify Netty ChannelOption via ClientFactoryBuilder.channelOption():

ClientFactory factory =
  ClientFactory
    .builder()
    // Set the size of send socket buffer as 4096 bytes
    .channelOption(ChannelOption.SO_SNDBUF, 4096)
    .build();
WebClient
  .builder()
  .factory(factory)
  .build();

Specifying different EventLoop threads for different endpoints

By default, Armeria keeps at most 1 connection per endpoint (host and port pair) when using HTTP/2, which is desirable for typical HTTP/2 services. Armeria achieves this by assigning one EventLoop per endpoint from the ServerConfig.workerGroup(). However, there's a limit to the requests that a single connection(or EventLoop which is a thread) can handle, so you might want to increase the number of connections(or EventLoop threads) for certain or all endpoints.

You can do this via ClientFactoryBuilder.maxNumEventLoopsPerEndpoint():

ClientFactory factory =
  ClientFactory
    .builder()
    // A client will use maximum 5 EventLoops from the worker group per endpoint
    .maxNumEventLoopsPerEndpoint(5)
    .build();
WebClient
  .builder()
  .factory(factory)
  .build();

ClientFactory factory =
  ClientFactory
    .builder()
    // or you can just use Integer.MAX_VALUE to use all EventLoops
    .maxNumEventLoopsPerEndpoint(Integer.MAX_VALUE)
    .build();
WebClient
  .builder()
  .factory(factory)
  .build();

ClientFactory factory =
  ClientFactory
    .builder()
    // A client will use maximum 5 EventLoops per HTTP/1.1 endpoint
    .maxNumEventLoopsPerHttp1Endpoint(5)
    .build();

The client might need to send many requests to a certain endpoint, while sending small number of requests to others. In that situation, you can use ClientFactoryBuilder.maxNumEventLoopsFunction():

ClientFactory factory =
  ClientFactory
    .builder()
    .maxNumEventLoopsFunction(endpoint -> {
      if (endpoint.equals(Endpoint.of("lotsOfTraffic.com"))) {
        // We are going to use all EventLoops to send requests.
        return Integer.MAX_VALUE;
      }
      // We use just one `EventLoop` per endpoint for the rest.
      return 1;
    })
    .build();
WebClient
  .builder()
  .factory(factory)
  .build();