Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2026 LINE Corporation
*
* LINE Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
Comment thread
JAEKWANG97 marked this conversation as resolved.
package com.linecorp.armeria.client;

import com.linecorp.armeria.common.annotation.UnstableApi;

/**
* Configures a built-in default {@link ClientFactory} using the specified {@link ClientFactoryBuilder}.
*
* <p>This configurator is invoked while creating the built-in default {@link ClientFactory}s returned by
* {@link ClientFactory#ofDefault()} and {@link ClientFactory#insecure()}.
*
* <p>This configurator is applied to both the default and insecure built-in
* {@link ClientFactory}s, so it must not call
* {@link ClientFactory#ofDefault()} or {@link ClientFactory#insecure()}.
*
* <p>Because {@link ClientFactory#insecure()} applies {@link ClientFactoryBuilder#tlsNoVerify()} after this
* configurator runs, TLS verification-related customization is unsupported.
*/
@UnstableApi
@FunctionalInterface
public interface ClientFactoryConfigurator {

/**
* Configures the built-in default {@link ClientFactory} using the specified
* {@link ClientFactoryBuilder}.
*
* <p>Note that {@link ClientFactoryBuilder#tlsNoVerify()} is applied after this method returns when
* creating {@link ClientFactory#insecure()}.
*/
void configureDefault(ClientFactoryBuilder builder);

/**
* Returns a {@link ClientFactoryConfigurator} that does not customize the specified
* {@link ClientFactoryBuilder}.
*/
static ClientFactoryConfigurator noop() {
return builder -> {
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import com.google.common.collect.Streams;

import com.linecorp.armeria.client.endpoint.EndpointGroup;
import com.linecorp.armeria.common.Flags;
import com.linecorp.armeria.common.Scheme;
import com.linecorp.armeria.common.SessionProtocol;
import com.linecorp.armeria.common.TlsProvider;
Expand Down Expand Up @@ -70,11 +71,14 @@ final class DefaultClientFactory implements ClientFactory {

private static volatile boolean shutdownHookDisabled;

private static final ClientFactoryConfigurator DEFAULT_CLIENT_FACTORY_CONFIGURATOR =
Flags.defaultClientFactoryConfigurator();

static final DefaultClientFactory DEFAULT =
(DefaultClientFactory) ClientFactory.builder().build();
newDefaultClientFactory(false, DEFAULT_CLIENT_FACTORY_CONFIGURATOR);

static final DefaultClientFactory INSECURE =
(DefaultClientFactory) ClientFactory.builder().tlsNoVerify().build();
newDefaultClientFactory(true, DEFAULT_CLIENT_FACTORY_CONFIGURATOR);

static {
if (DefaultClientFactory.class.getClassLoader() == ClassLoader.getSystemClassLoader()) {
Expand All @@ -94,6 +98,16 @@ static void disableShutdownHook0() {
shutdownHookDisabled = true;
}

private static DefaultClientFactory newDefaultClientFactory(boolean insecure,
ClientFactoryConfigurator configurator) {
final ClientFactoryBuilder builder = ClientFactory.builder();
configurator.configureDefault(builder);
if (insecure) {
builder.tlsNoVerify();
}
return (DefaultClientFactory) builder.build();
}

private final HttpClientFactory httpClientFactory;
private final Multimap<Scheme, ClientFactory> clientFactories;
private final List<ClientFactory> clientFactoriesToClose;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;

import com.linecorp.armeria.client.ClientFactoryConfigurator;
import com.linecorp.armeria.client.ResponseTimeoutMode;
import com.linecorp.armeria.common.multipart.MultipartFilenameDecodingMode;
import com.linecorp.armeria.common.util.Sampler;
Expand Down Expand Up @@ -374,6 +375,11 @@ public Long defaultRequestAutoAbortDelayMillis() {
return DEFAULT_REQUEST_AUTO_ABORT_DELAY_MILLIS;
}

@Override
public ClientFactoryConfigurator defaultClientFactoryConfigurator() {
return ClientFactoryConfigurator.noop();
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

@Override
public String routeCacheSpec() {
return ROUTE_CACHE_SPEC;
Expand Down
17 changes: 17 additions & 0 deletions core/src/main/java/com/linecorp/armeria/common/Flags.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@

import com.linecorp.armeria.client.ClientBuilder;
import com.linecorp.armeria.client.ClientFactoryBuilder;
import com.linecorp.armeria.client.ClientFactoryConfigurator;
import com.linecorp.armeria.client.ClientTlsSpec;
import com.linecorp.armeria.client.DnsResolverGroupBuilder;
import com.linecorp.armeria.client.Endpoint;
Expand Down Expand Up @@ -1634,6 +1635,22 @@ public static MeterRegistry meterRegistry() {
return METER_REGISTRY;
}

/**
* Returns the {@link ClientFactoryConfigurator} that customizes the built-in default
* {@link com.linecorp.armeria.client.ClientFactory}s.
*
* <p>This value is consulted while initializing the built-in default client factories.
*
* @see ClientFactoryConfigurator
*/
@UnstableApi
public static ClientFactoryConfigurator defaultClientFactoryConfigurator() {
final ClientFactoryConfigurator configurator =
getValue(FlagsProvider::defaultClientFactoryConfigurator,
"defaultClientFactoryConfigurator");
return configurator != null ? configurator : ClientFactoryConfigurator.noop();
}

/**
* Returns the default interval in milliseconds between the reports on unlogged exceptions.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

import com.linecorp.armeria.client.ClientBuilder;
import com.linecorp.armeria.client.ClientFactoryBuilder;
import com.linecorp.armeria.client.ClientFactoryConfigurator;
import com.linecorp.armeria.client.DnsResolverGroupBuilder;
import com.linecorp.armeria.client.ResponseTimeoutMode;
import com.linecorp.armeria.client.retry.Backoff;
Expand Down Expand Up @@ -822,6 +823,29 @@ default Long defaultRequestAutoAbortDelayMillis() {
return null;
}

/**
* Returns a {@link ClientFactoryConfigurator} that customizes the built-in default
* {@link com.linecorp.armeria.client.ClientFactory}s.
*
* <p>If {@code null} is returned, the next available {@link FlagsProvider} is consulted.</p>
*
* <p>The returned {@link ClientFactoryConfigurator} is applied while creating the built-in default
* {@link com.linecorp.armeria.client.ClientFactory}s, so it must not call
* {@link com.linecorp.armeria.client.ClientFactory#ofDefault()} or
* {@link com.linecorp.armeria.client.ClientFactory#insecure()}.</p>
*
* <p>This configurator is applied to both the default and insecure built-in
* {@link com.linecorp.armeria.client.ClientFactory}s. Because
* {@link com.linecorp.armeria.client.ClientFactory#insecure()} applies
* {@link com.linecorp.armeria.client.ClientFactoryBuilder#tlsNoVerify()} after the configurator runs,
* TLS verification-related customization is unsupported.</p>
*/
@UnstableApi
@Nullable
default ClientFactoryConfigurator defaultClientFactoryConfigurator() {
return null;
}

/**
* Returns the {@linkplain CaffeineSpec Caffeine specification string} of the cache that stores the recent
* request routing history for all {@link Service}s.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.net.InetAddress;
import java.util.function.Predicate;

import com.linecorp.armeria.client.ClientFactoryConfigurator;
import com.linecorp.armeria.common.util.InetAddressPredicates;

import io.micrometer.core.instrument.MeterRegistry;
Expand Down Expand Up @@ -71,6 +72,11 @@ public Long defaultServerConnectionDrainDurationMicros() {
return 500L;
}

@Override
public ClientFactoryConfigurator defaultClientFactoryConfigurator() {
return builder -> builder.connectTimeoutMillis(4242);
}

@Override
public String routeCacheSpec() {
return "off";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,21 @@
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLConnection;
import java.util.Map;

import org.assertj.core.api.ObjectAssert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junitpioneer.jupiter.ClearSystemProperty;
import org.junitpioneer.jupiter.SetSystemProperty;

import com.linecorp.armeria.client.ClientFactory;
import com.linecorp.armeria.common.util.Exceptions;
import com.linecorp.armeria.common.util.InetAddressPredicates;
import com.linecorp.armeria.common.util.TlsEngineType;

import io.micrometer.core.instrument.Metrics;
import io.netty.channel.ChannelOption;

@SetSystemProperty(
key = "com.linecorp.armeria.requestContextStorageProvider",
Expand All @@ -45,11 +48,13 @@
class FlagsProviderTest {

private Class<?> flags;
private Class<?> clientFactoryClass;

@BeforeEach
void reloadFlags() throws ClassNotFoundException {
final FlagsClassLoader classLoader = new FlagsClassLoader();
flags = classLoader.loadClass(Flags.class.getCanonicalName());
clientFactoryClass = classLoader.loadClass(ClientFactory.class.getName());
}

@Test
Expand Down Expand Up @@ -159,11 +164,55 @@ void testDistributionStatisticConfig() {
.isEqualTo(DistributionStatisticConfigUtil.DEFAULT_DIST_STAT_CFG);
}

@Test
void defaultClientFactoryConfiguratorIsAppliedToDefaultClientFactory() throws Throwable {
try {
assertClientFactoryConnectTimeoutMillis("ofDefault").isEqualTo(4242);
} finally {
closeDefaultClientFactories();
}
}

@Test
void defaultClientFactoryConfiguratorIsAppliedToInsecureClientFactory() throws Throwable {
try {
assertClientFactoryConnectTimeoutMillis("insecure").isEqualTo(4242);
} finally {
closeDefaultClientFactories();
}
}

private ObjectAssert<Object> assertFlags(String flagsMethod) throws Throwable {
final Method method = flags.getDeclaredMethod(flagsMethod);
return assertThat(method.invoke(null));
}

private ObjectAssert<Object> assertClientFactoryConnectTimeoutMillis(String factoryMethod)
throws Throwable {
final Object options = clientFactoryOptions(factoryMethod);
final Method channelOptionsMethod = options.getClass().getDeclaredMethod("channelOptions");
@SuppressWarnings("unchecked")
final Map<ChannelOption<?>, Object> channelOptions =
(Map<ChannelOption<?>, Object>) channelOptionsMethod.invoke(options);
return assertThat(channelOptions.get(ChannelOption.CONNECT_TIMEOUT_MILLIS));
}

private Object clientFactoryOptions(String factoryMethod) throws Throwable {
final Object clientFactory = clientFactory(factoryMethod);
final Method optionsMethod = clientFactoryClass.getDeclaredMethod("options");
return optionsMethod.invoke(clientFactory);
}

private Object clientFactory(String factoryMethod) throws Throwable {
final Method factoryGetter = clientFactoryClass.getDeclaredMethod(factoryMethod);
return factoryGetter.invoke(null);
}

private void closeDefaultClientFactories() throws Throwable {
final Method closeDefaultMethod = clientFactoryClass.getDeclaredMethod("closeDefault");
closeDefaultMethod.invoke(null);
}

private static class FlagsClassLoader extends ClassLoader {
FlagsClassLoader() {
super(getSystemClassLoader());
Expand Down
Loading