Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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,139 @@
/*
* Copyright 2026 LY Corporation
*
* LY 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.
*/

package com.linecorp.armeria.client.athenz;

import static java.util.Objects.requireNonNull;

import java.net.URI;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;

import org.slf4j.LoggerFactory;

import com.linecorp.armeria.client.ClientFactory;
import com.linecorp.armeria.client.ClientFactoryBuilder;
import com.linecorp.armeria.client.WebClient;
import com.linecorp.armeria.client.WebClientBuilder;
import com.linecorp.armeria.client.logging.LoggingClient;
import com.linecorp.armeria.client.retry.RetryRule;
import com.linecorp.armeria.client.retry.RetryingClient;
import com.linecorp.armeria.common.CommonPools;
import com.linecorp.armeria.common.TlsKeyPair;
import com.linecorp.armeria.common.TlsProvider;
import com.linecorp.armeria.common.annotation.Nullable;
import com.linecorp.armeria.common.logging.LogLevel;
import com.linecorp.armeria.common.logging.LogWriter;
import com.linecorp.armeria.common.util.AbstractListenable;

/**
* The default {@link ZtsBaseClient} implementation that manages a {@link ClientFactory} with
* {@link TlsKeyPair} rotation and proxy configuration.
*/
final class DefaultZtsBaseClient implements ZtsBaseClient {

private final TlsKeyPairListener tlsKeyPairListener = new TlsKeyPairListener();
private final URI ztsUri;
private final ClientFactory clientFactory;
@Nullable
private final Consumer<WebClientBuilder> webClientConfigurer;
private final WebClient defaultWebClient;

DefaultZtsBaseClient(URI ztsUri, Supplier<TlsKeyPair> keyPairSupplier,
List<X509Certificate> trustedCertificates, int autoKeyRefreshIntervalMillis,
@Nullable Consumer<ClientFactoryBuilder> clientFactoryConfigurer,
@Nullable Consumer<WebClientBuilder> webClientConfigurer) {

this.ztsUri = ztsUri;
final TlsProvider tlsProvider =
TlsProvider.ofScheduled(keyPairSupplier,
trustedCertificates,
tlsKeyPairListener::onTlsKeyPairUpdated,
Duration.ofMillis(autoKeyRefreshIntervalMillis),
CommonPools.blockingTaskExecutor());

final ClientFactoryBuilder factoryBuilder = ClientFactory.builder().tlsProvider(tlsProvider);
if (clientFactoryConfigurer != null) {
clientFactoryConfigurer.accept(factoryBuilder);
}
clientFactory = factoryBuilder.build();
this.webClientConfigurer = webClientConfigurer;
defaultWebClient = webClient(builder -> {});
}

@Override
public ClientFactory clientFactory() {
return clientFactory;
}

@Override
public WebClient webClient() {
return defaultWebClient;
}

@Override
public WebClient webClient(Consumer<? super WebClientBuilder> configurer) {
requireNonNull(configurer, "configurer");

final WebClientBuilder clientBuilder =
WebClient.builder(ztsUri)
.decorator(RetryingClient.newDecorator(RetryRule.failsafe()));

clientBuilder.factory(clientFactory);

if (LoggerFactory.getLogger(DefaultZtsBaseClient.class).isTraceEnabled()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chaining the logger name would be a weak breaking change.

Suggested change
if (LoggerFactory.getLogger(DefaultZtsBaseClient.class).isTraceEnabled()) {
if (LoggerFactory.getLogger(ZtsBaseClient.class).isTraceEnabled()) {

final LogWriter logWriter = LogWriter.builder()
.logger(DefaultZtsBaseClient.class.getName())
.requestLogLevel(LogLevel.TRACE)
.successfulResponseLogLevel(LogLevel.TRACE)
.build();
clientBuilder.decorator(LoggingClient.builder()
.logWriter(logWriter)
.newDecorator());
}
if (webClientConfigurer != null) {
webClientConfigurer.accept(clientBuilder);
}
configurer.accept(clientBuilder);
return clientBuilder.build();
}

@Override
public void addTlsKeyPairListener(Consumer<TlsKeyPair> listener) {
requireNonNull(listener, "listener");
tlsKeyPairListener.addListener(listener);
}

@Override
public void removeTlsKeyPairListener(Consumer<TlsKeyPair> listener) {
requireNonNull(listener, "listener");
tlsKeyPairListener.removeListener(listener);
}

@Override
public void close() {
defaultWebClient.options().factory().closeAsync();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Close clientFactory directly?

}

private static final class TlsKeyPairListener extends AbstractListenable<TlsKeyPair> {
void onTlsKeyPairUpdated(TlsKeyPair newTlsKeyPair) {
notifyListeners(newTlsKeyPair);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,14 @@
import static java.util.Objects.requireNonNull;

import java.net.URI;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;

import org.slf4j.LoggerFactory;

import com.linecorp.armeria.client.ClientFactory;
import com.linecorp.armeria.client.ClientFactoryBuilder;
import com.linecorp.armeria.client.WebClient;
import com.linecorp.armeria.client.WebClientBuilder;
import com.linecorp.armeria.client.logging.LoggingClient;
import com.linecorp.armeria.client.retry.RetryRule;
import com.linecorp.armeria.client.retry.RetryingClient;
import com.linecorp.armeria.common.CommonPools;
import com.linecorp.armeria.common.TlsKeyPair;
import com.linecorp.armeria.common.TlsProvider;
import com.linecorp.armeria.common.annotation.Nullable;
import com.linecorp.armeria.common.annotation.UnstableApi;
import com.linecorp.armeria.common.logging.LogLevel;
import com.linecorp.armeria.common.logging.LogWriter;
import com.linecorp.armeria.common.util.AbstractListenable;
import com.linecorp.armeria.common.util.SafeCloseable;
import com.linecorp.armeria.server.athenz.AthenzService;

Expand All @@ -61,20 +45,20 @@
* }</pre>
*/
@UnstableApi
public final class ZtsBaseClient implements SafeCloseable {
public interface ZtsBaseClient extends SafeCloseable {

/**
* Returns a new {@link ZtsBaseClientBuilder} for the specified ZTS URI.
*/
public static ZtsBaseClientBuilder builder(String ztsUri) {
static ZtsBaseClientBuilder builder(String ztsUri) {
requireNonNull(ztsUri, "ztsUri");
return builder(URI.create(ztsUri));
}

/**
* Returns a new {@link ZtsBaseClientBuilder} for the specified ZTS {@link URI}.
*/
public static ZtsBaseClientBuilder builder(URI ztsUri) {
static ZtsBaseClientBuilder builder(URI ztsUri) {
requireNonNull(ztsUri, "ztsUri");
return new ZtsBaseClientBuilder(normalizeZtsUri(ztsUri));
}
Expand All @@ -91,120 +75,59 @@ private static URI normalizeZtsUri(URI ztsUri) {
return URI.create(rawUri);
}

private final TlsKeyPairListener tlsKeyPairListener = new TlsKeyPairListener();
private final URI ztsUri;
@Nullable
private final URI proxyUri;
private final ClientFactory clientFactory;
@Nullable
private final Consumer<WebClientBuilder> webClientConfigurer;
private final WebClient defaultWebClient;

ZtsBaseClient(URI ztsUri, @Nullable URI proxyUri, Supplier<TlsKeyPair> keyPairSupplier,
List<X509Certificate> trustedCertificates, int autoKeyRefreshIntervalMillis,
@Nullable Consumer<ClientFactoryBuilder> clientFactoryConfigurer,
@Nullable Consumer<WebClientBuilder> webClientConfigurer) {

this.ztsUri = ztsUri;
this.proxyUri = proxyUri;
final TlsProvider tlsProvider =
TlsProvider.ofScheduled(keyPairSupplier,
trustedCertificates,
tlsKeyPairListener::onTlsKeyPairUpdated,
Duration.ofMillis(autoKeyRefreshIntervalMillis),
CommonPools.blockingTaskExecutor());

final ClientFactoryBuilder factoryBuilder = ClientFactory.builder().tlsProvider(tlsProvider);
if (clientFactoryConfigurer != null) {
clientFactoryConfigurer.accept(factoryBuilder);
}
clientFactory = factoryBuilder.build();
this.webClientConfigurer = webClientConfigurer;
defaultWebClient = webClient(builder -> {});
}

/**
* Returns the ZTS {@link URL} that this client connects to.
* Returns the ZTS {@link URI} that this client connects to.
*
* @deprecated Use {@code webClient().uri()} instead.
*/
public URI ztsUri() {
return ztsUri;
@Deprecated
default URI ztsUri() {
return webClient().uri();
}

/**
* Returns the proxy {@link URL} if it was configured, or {@code null} if no proxy is set.
* Returns the proxy {@link URI} if it was configured, or {@code null} if no proxy is set.
*
* @deprecated The proxy is configured at the {@link ClientFactory} level via
* {@link ZtsBaseClientBuilder#proxyUri(URI)} and is already applied to the {@link WebClient}.
*/
@Deprecated
@Nullable
public URI proxyUri() {
return proxyUri;
default URI proxyUri() {
return null;
}

/**
* Returns the {@link ClientFactory} that is used to create the {@link WebClient} instances.
*/
public ClientFactory clientFactory() {
return clientFactory;
default ClientFactory clientFactory() {
return webClient().options().factory();
}

/**
* Returns the {@link WebClient} that can connect to the ZTS server.
*/
public WebClient webClient() {
return defaultWebClient;
}
WebClient webClient();

/**
* Returns a new {@link WebClient} that can connect to the ZTS server with the specified configurer.
*/
public WebClient webClient(Consumer<? super WebClientBuilder> configurer) {
default WebClient webClient(Consumer<? super WebClientBuilder> configurer) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

configurer is ignored. Can we make this method as just a method instead of default method?

requireNonNull(configurer, "configurer");

final WebClientBuilder clientBuilder =
WebClient.builder(ztsUri)
.decorator(RetryingClient.newDecorator(RetryRule.failsafe()));

clientBuilder.factory(clientFactory);

if (LoggerFactory.getLogger(ZtsBaseClient.class).isTraceEnabled()) {
final LogWriter logWriter = LogWriter.builder()
.logger(ZtsBaseClient.class.getName())
.requestLogLevel(LogLevel.TRACE)
.successfulResponseLogLevel(LogLevel.TRACE)
.build();
clientBuilder.decorator(LoggingClient.builder()
.logWriter(logWriter)
.newDecorator());
}
if (webClientConfigurer != null) {
webClientConfigurer.accept(clientBuilder);
}
configurer.accept(clientBuilder);
return clientBuilder.build();
return webClient();
}
Comment thread
jrhee17 marked this conversation as resolved.
Outdated

/**
* Adds a listener that will be notified when the {@link TlsKeyPair} is updated.
*/
public void addTlsKeyPairListener(Consumer<TlsKeyPair> listener) {
default void addTlsKeyPairListener(Consumer<TlsKeyPair> listener) {
requireNonNull(listener, "listener");
tlsKeyPairListener.addListener(listener);
}

/**
* Removes a listener that was previously added with {@link #addTlsKeyPairListener(Consumer)}.
*/
public void removeTlsKeyPairListener(Consumer<TlsKeyPair> listener) {
default void removeTlsKeyPairListener(Consumer<TlsKeyPair> listener) {
requireNonNull(listener, "listener");
tlsKeyPairListener.removeListener(listener);
}

@Override
public void close() {
defaultWebClient.options().factory().closeAsync();
}

private static final class TlsKeyPairListener extends AbstractListenable<TlsKeyPair> {
void onTlsKeyPairUpdated(TlsKeyPair newTlsKeyPair) {
notifyListeners(newTlsKeyPair);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@
public final class ZtsBaseClientBuilder {

private final URI ztsUri;
@Nullable
private URI proxyUri;

@Nullable
private Supplier<TlsKeyPair> athenzKeyPairSupplier;
Expand Down Expand Up @@ -155,7 +153,6 @@ public ZtsBaseClientBuilder proxyUri(String proxyUrl) {
*/
public ZtsBaseClientBuilder proxyUri(URI proxyUri) {
requireNonNull(proxyUri, "proxyUri");
this.proxyUri = proxyUri;
final boolean isTls = "https".equalsIgnoreCase(proxyUri.getScheme());
final int port = proxyUri.getPort() == -1 ? (isTls ? 443 : 80) : proxyUri.getPort();
final InetSocketAddress proxyAddress = InetSocketAddress.createUnresolved(proxyUri.getHost(), port);
Expand Down Expand Up @@ -211,7 +208,8 @@ public ZtsBaseClient build() {
if (athenzKeyPairSupplier == null) {
throw new IllegalStateException("Athenz key pair must be set");
}
return new ZtsBaseClient(ztsUri, proxyUri, athenzKeyPairSupplier, trustedCertificates,
autoKeyRefreshIntervalMillis, clientFactoryConfigurer, webClientConfigurer);
return new DefaultZtsBaseClient(ztsUri, athenzKeyPairSupplier, trustedCertificates,
autoKeyRefreshIntervalMillis, clientFactoryConfigurer,
webClientConfigurer);
}
}
Loading
Loading