Customizing a ClientFactory
with ClientFactoryBuilder
Table of contents
- Customizing TLS
- Lifecycle of ClientFactory
- Specifying Netty ChannelOptions
- Specifying different EventLoop threads for different endpoints
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 ChannelOption
s
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();