Skip to content

Commit 606bf40

Browse files
Addressing review.
1 parent a9c15f0 commit 606bf40

File tree

7 files changed

+451
-40
lines changed

7 files changed

+451
-40
lines changed

core/src/main/java/com/linecorp/armeria/client/ClientFactoryBuilder.java

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@
5454
import com.google.common.primitives.Ints;
5555

5656
import com.linecorp.armeria.client.metric.ClientRequestLifecycleListener;
57-
import com.linecorp.armeria.client.metric.ClientRequestMetrics;
5857
import com.linecorp.armeria.client.proxy.ProxyConfig;
5958
import com.linecorp.armeria.client.proxy.ProxyConfigSelector;
6059
import com.linecorp.armeria.common.CommonPools;
@@ -263,13 +262,21 @@ public <T> ClientFactoryBuilder channelOption(ChannelOption<T> option, T value)
263262
channelOptions(ImmutableMap.of(option, value));
264263
return this;
265264
}
266-
267-
public ClientFactoryBuilder clientRequestLifeCycleListener(ClientRequestLifecycleListener clientRequestLifecycleListener) {
268-
requireNonNull(clientRequestLifecycleListener, "clientRequestLifecycleListener");
269-
this.clientRequestLifecycleListener = clientRequestLifecycleListener;
265+
266+
/**
267+
* Sets the {@link ClientRequestLifecycleListener} that listens to the lifecycle events of
268+
* client requests created by the {@link ClientFactory}.
269+
*
270+
* <p>If not set, {@link ClientRequestLifecycleListener#noop()} is used by default.
271+
*
272+
* @param listener the listener to be notified of client request lifecycle events. Must not be {@code null}.
273+
* @return {@code this} to support method chaining.
274+
*/
275+
public ClientFactoryBuilder clientRequestLifecycleListener(ClientRequestLifecycleListener listener) {
276+
requireNonNull(listener, "listener");
277+
clientRequestLifecycleListener = listener;
270278
return this;
271279
}
272-
273280

274281
private void channelOptions(Map<ChannelOption<?>, Object> newChannelOptions) {
275282
@SuppressWarnings("unchecked")
@@ -1137,8 +1144,8 @@ private ClientFactoryOptions buildOptions() {
11371144
public ClientFactory build() {
11381145
return new DefaultClientFactory(
11391146
new HttpClientFactory(
1140-
buildOptions(),
1141-
autoCloseConnectionPoolListener,
1147+
buildOptions(),
1148+
autoCloseConnectionPoolListener,
11421149
clientRequestLifecycleListener
11431150
)
11441151
);

core/src/main/java/com/linecorp/armeria/client/HttpClientDelegate.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ public HttpResponse execute(ClientRequestContext ctx, HttpRequest req) throws Ex
9999
final DecodedHttpResponse res = new DecodedHttpResponse(eventLoop);
100100
updateCancellationTask(ctx, req, res);
101101

102+
factory.clientRequestLifecycleListener().onRequestPending(ctx);
103+
ctx.log().addListener(
104+
factory.clientRequestLifecycleListener()
105+
.asRequestLogListener()
106+
);
107+
102108
try {
103109
resolveProxyConfig(protocol, endpoint, ctx, (proxyConfig, thrown) -> {
104110
if (thrown != null) {

core/src/main/java/com/linecorp/armeria/client/HttpClientFactory.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@
3232
import java.util.function.Supplier;
3333
import java.util.stream.Stream;
3434

35-
import com.linecorp.armeria.client.metric.ClientRequestLifecycleListener;
3635
import org.slf4j.Logger;
3736
import org.slf4j.LoggerFactory;
3837

3938
import com.google.common.annotations.VisibleForTesting;
4039
import com.google.common.collect.MapMaker;
4140

4241
import com.linecorp.armeria.client.endpoint.EndpointGroup;
42+
import com.linecorp.armeria.client.metric.ClientRequestLifecycleListener;
4343
import com.linecorp.armeria.client.proxy.ProxyConfigSelector;
4444
import com.linecorp.armeria.client.redirect.RedirectConfig;
4545
import com.linecorp.armeria.common.Http1HeaderNaming;
@@ -145,14 +145,18 @@ private static void setupTlsMetrics(List<X509Certificate> certificates, MeterReg
145145
private final AsyncCloseableSupport closeable = AsyncCloseableSupport.of(this::closeAsync);
146146
private final ClientRequestLifecycleListener clientRequestLifecycleListener;
147147

148+
HttpClientFactory(ClientFactoryOptions options, boolean autoCloseConnectionPoolListener) {
149+
this(options, autoCloseConnectionPoolListener, ClientRequestLifecycleListener.noop());
150+
}
151+
148152
HttpClientFactory(ClientFactoryOptions options, boolean autoCloseConnectionPoolListener,
149153
ClientRequestLifecycleListener clientRequestLifecycleListener) {
150154
workerGroup = options.workerGroup();
151155

152156
@SuppressWarnings("unchecked")
153157
final AddressResolverGroup<InetSocketAddress> group =
154158
(AddressResolverGroup<InetSocketAddress>) options.addressResolverGroupFactory()
155-
.apply(workerGroup);
159+
.apply(workerGroup);
156160
addressResolverGroup = group;
157161

158162
final Bootstrap bootstrap = new Bootstrap();
@@ -192,10 +196,10 @@ private static void setupTlsMetrics(List<X509Certificate> certificates, MeterReg
192196
final TlsEngineType tlsEngineType = options.tlsEngineType();
193197
sslCtxHttp1Or2 = SslContextUtil
194198
.createSslContext(SslContextBuilder::forClient, false, tlsEngineType,
195-
tlsAllowUnsafeCiphers, tlsCustomizer, keyCertChainCaptor);
199+
tlsAllowUnsafeCiphers, tlsCustomizer, keyCertChainCaptor);
196200
sslCtxHttp1Only = SslContextUtil
197201
.createSslContext(SslContextBuilder::forClient, true, tlsEngineType,
198-
tlsAllowUnsafeCiphers, tlsCustomizer, keyCertChainCaptor);
202+
tlsAllowUnsafeCiphers, tlsCustomizer, keyCertChainCaptor);
199203
setupTlsMetrics(keyCertChainCaptor, options.meterRegistry());
200204

201205
final TlsProvider tlsProvider = options.tlsProvider();
@@ -205,7 +209,7 @@ private static void setupTlsMetrics(List<X509Certificate> certificates, MeterReg
205209
clientTlsConfig = null;
206210
}
207211
sslContextFactory = new SslContextFactory(tlsProvider, options.tlsEngineType(), clientTlsConfig,
208-
options.meterRegistry());
212+
options.meterRegistry());
209213
} else {
210214
sslContextFactory = null;
211215
}
@@ -342,8 +346,10 @@ ProxyConfigSelector proxyConfigSelector() {
342346
Http1HeaderNaming http1HeaderNaming() {
343347
return http1HeaderNaming;
344348
}
345-
346-
ClientRequestLifecycleListener clientRequestLifecycleListener() {return clientRequestLifecycleListener;}
349+
350+
ClientRequestLifecycleListener clientRequestLifecycleListener() {
351+
return clientRequestLifecycleListener;
352+
}
347353

348354
@VisibleForTesting
349355
AddressResolverGroup<InetSocketAddress> addressResolverGroup() {

core/src/main/java/com/linecorp/armeria/client/metric/ClientRequestLifecycleListener.java

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,58 @@
1+
/*
2+
* Copyright 2025 LY Corporation
3+
*
4+
* LY Corporation licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
117
package com.linecorp.armeria.client.metric;
218

319
import com.linecorp.armeria.client.ClientRequestContext;
420
import com.linecorp.armeria.common.ResponseHeaders;
521
import com.linecorp.armeria.common.annotation.Nullable;
622
import com.linecorp.armeria.common.annotation.UnstableApi;
23+
import com.linecorp.armeria.common.logging.RequestLogListener;
724

25+
/**
26+
* Listens to the lifecycle events of client requests.
27+
*
28+
* <p>This interface provides a high-level view of a request's lifecycle (e.g., pending, started, completed).
29+
* unlike {@link RequestLogListener} which provides low-level property changes.
30+
*
31+
* <p>Note: The methods in this interface are typically not invoked directly by the
32+
* transport layer. Instead, they are triggered by inspecting the changes in
33+
* {@link com.linecorp.armeria.common.logging.RequestLog}.
34+
* Implementations should use {@link #asRequestLogListener()} to bridge
35+
* {@link com.linecorp.armeria.common.logging.RequestLogProperty}
36+
* changes to these lifecycle methods.
37+
*
38+
* <p>For example, a standard implementation might map:
39+
* <ul>
40+
* <li>{@link com.linecorp.armeria.common.logging.RequestLogProperty#REQUEST_FIRST_BYTES_TRANSFERRED_TIME}
41+
* to {@link #onRequestStart(ClientRequestContext)}</li>
42+
* <li>{@link com.linecorp.armeria.common.logging.RequestLogProperty#ALL_COMPLETE}
43+
* to {@link #onRequestComplete(ClientRequestContext, Throwable)}</li>
44+
* </ul>
45+
*
46+
* @see com.linecorp.armeria.client.ClientFactoryBuilder#clientRequestLifecycleListener(
47+
* ClientRequestLifecycleListener)
48+
*/
849
@UnstableApi
950
public interface ClientRequestLifecycleListener {
1051

1152
/**
12-
* Called when a request is created and scheduled but not yet executed.
53+
* Invoked when a request is created and scheduled for execution but has not yet started.
54+
* Note: This method is explicitly invoked by HttpClientDelegate when
55+
* HttpClientDelegate starts to call execute().
1356
*/
1457
void onRequestPending(ClientRequestContext ctx);
1558

@@ -37,19 +80,41 @@ public interface ClientRequestLifecycleListener {
3780
* Called when a request is completed with either success or failure.
3881
*/
3982
void onRequestComplete(ClientRequestContext ctx, @Nullable Throwable cause);
40-
83+
84+
/**
85+
* Returns a {@link RequestLogListener} that delegates
86+
* {@link com.linecorp.armeria.common.logging.RequestLog}
87+
* events to this lifecycle listener.
88+
* This method bridges the low-level {@link com.linecorp.armeria.common.logging.RequestLog}
89+
* updates to the high-level lifecycle methods
90+
* defined in this interface. The returned listener is registered to the {@link ClientRequestContext}
91+
* automatically when the request starts.
92+
*/
93+
RequestLogListener asRequestLogListener();
94+
95+
/**
96+
* Returns a {@link ClientRequestMetrics} that collects the number of pending and active requests.
97+
*/
4198
static ClientRequestMetrics counting() {
4299
return new ClientRequestMetrics();
43100
}
44-
101+
102+
/**
103+
* Returns a {@link ClientRequestLifecycleListener} that does nothing.
104+
*/
45105
static ClientRequestLifecycleListener noop() {
46106
return NoopClientRequestLifecycleListener.INSTANCE;
47107
}
48-
108+
109+
/**
110+
* A {@link ClientRequestLifecycleListener} that does nothing.
111+
*/
49112
class NoopClientRequestLifecycleListener implements ClientRequestLifecycleListener {
50-
51-
private static final NoopClientRequestLifecycleListener INSTANCE = new NoopClientRequestLifecycleListener();
52-
113+
114+
private static final NoopClientRequestLifecycleListener INSTANCE =
115+
new NoopClientRequestLifecycleListener();
116+
private final RequestLogListener requestLogListener = ((property, log) -> {});
117+
53118
@Override
54119
public void onRequestPending(ClientRequestContext ctx) {
55120
// no-op
@@ -79,6 +144,11 @@ public void onResponseComplete(ClientRequestContext ctx) {
79144
public void onRequestComplete(ClientRequestContext ctx, @Nullable Throwable cause) {
80145
// no-op
81146
}
147+
148+
@Override
149+
public RequestLogListener asRequestLogListener() {
150+
// no-op
151+
return requestLogListener;
152+
}
82153
}
83-
84154
}

0 commit comments

Comments
 (0)