-
Notifications
You must be signed in to change notification settings - Fork 997
Refactor ZtsBaseClient to an interface and add ArmeriaJwtsSigningKeyResolver
#6848
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jrhee17
wants to merge
3
commits into
line:main
Choose a base branch
from
jrhee17:feat/athenz-refactor
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+418
−158
Open
Changes from 1 commit
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
139 changes: 139 additions & 0 deletions
139
athenz/src/main/java/com/linecorp/armeria/client/athenz/DefaultZtsBaseClient.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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()) { | ||
| 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(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
||
|
|
@@ -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)); | ||
| } | ||
|
|
@@ -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) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
| } | ||
|
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); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.