Skip to content

Commit b003541

Browse files
jetty-httpclient-12: send method must pass along implemented response listeners (#12326)
Co-authored-by: Lauri Tulmin <[email protected]>
1 parent c6c0bb1 commit b003541

File tree

3 files changed

+128
-7
lines changed

3 files changed

+128
-7
lines changed

instrumentation/jetty-httpclient/jetty-httpclient-12.0/library/src/main/java/io/opentelemetry/instrumentation/jetty/httpclient/v12_0/TracingHttpRequest.java

+3-7
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.opentelemetry.context.Scope;
1010
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
1111
import io.opentelemetry.instrumentation.jetty.httpclient.v12_0.internal.JettyClientTracingListener;
12+
import io.opentelemetry.instrumentation.jetty.httpclient.v12_0.internal.JettyClientWrapUtil;
1213
import java.net.URI;
1314
import java.nio.ByteBuffer;
1415
import org.eclipse.jetty.client.HttpClient;
@@ -34,14 +35,9 @@ public TracingHttpRequest(
3435
@Override
3536
public void send(Response.CompleteListener listener) {
3637
parentContext = Context.current();
37-
// start span and attach listeners.
38+
// start span and attach listeners - must handle all listeners, not just CompleteListener.
3839
JettyClientTracingListener.handleRequest(parentContext, this, instrumenter);
39-
super.send(
40-
result -> {
41-
try (Scope scope = openScope()) {
42-
listener.onComplete(result);
43-
}
44-
});
40+
super.send(JettyClientWrapUtil.wrapTheListener(listener, parentContext));
4541
}
4642

4743
private Scope openScope() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.jetty.httpclient.v12_0.internal;
7+
8+
import io.opentelemetry.context.Context;
9+
import io.opentelemetry.context.Scope;
10+
import java.lang.reflect.InvocationTargetException;
11+
import java.lang.reflect.Proxy;
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
import org.eclipse.jetty.client.Response;
15+
16+
/**
17+
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
18+
* any time.
19+
*/
20+
public final class JettyClientWrapUtil {
21+
private static final Class<?>[] LISTENER_INTERFACES = {
22+
Response.CompleteListener.class,
23+
Response.FailureListener.class,
24+
Response.SuccessListener.class,
25+
Response.AsyncContentListener.class,
26+
Response.ContentSourceListener.class,
27+
Response.ContentListener.class,
28+
Response.HeadersListener.class,
29+
Response.HeaderListener.class,
30+
Response.BeginListener.class
31+
};
32+
33+
private JettyClientWrapUtil() {}
34+
35+
/**
36+
* Utility to wrap the response listeners only, this includes the important CompleteListener.
37+
*
38+
* @param context top level context that is above the Jetty client span context
39+
* @param listener listener passed to Jetty client send() method
40+
* @return wrapped listener
41+
*/
42+
public static Response.CompleteListener wrapTheListener(
43+
Response.CompleteListener listener, Context context) {
44+
if (listener == null) {
45+
return listener;
46+
}
47+
48+
Class<?> listenerClass = listener.getClass();
49+
List<Class<?>> interfaces = new ArrayList<>();
50+
for (Class<?> type : LISTENER_INTERFACES) {
51+
if (type.isInstance(listener)) {
52+
interfaces.add(type);
53+
}
54+
}
55+
if (interfaces.isEmpty()) {
56+
return listener;
57+
}
58+
59+
return (Response.CompleteListener)
60+
Proxy.newProxyInstance(
61+
listenerClass.getClassLoader(),
62+
interfaces.toArray(new Class<?>[0]),
63+
(proxy, method, args) -> {
64+
try (Scope ignored = context.makeCurrent()) {
65+
return method.invoke(listener, args);
66+
} catch (InvocationTargetException exception) {
67+
throw exception.getCause();
68+
}
69+
});
70+
}
71+
}

instrumentation/jetty-httpclient/jetty-httpclient-12.0/testing/src/main/java/io/opentelemetry/instrumentation/jetty/httpclient/v12_0/AbstractJettyClient12Test.java

+54
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66
package io.opentelemetry.instrumentation.jetty.httpclient.v12_0;
77

8+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
9+
10+
import io.opentelemetry.api.trace.SpanKind;
811
import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest;
912
import io.opentelemetry.instrumentation.testing.junit.http.HttpClientResult;
1013
import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestOptions;
@@ -15,13 +18,16 @@
1518
import java.util.concurrent.TimeUnit;
1619
import java.util.concurrent.TimeoutException;
1720
import org.eclipse.jetty.client.ContentResponse;
21+
import org.eclipse.jetty.client.FutureResponseListener;
1822
import org.eclipse.jetty.client.HttpClient;
1923
import org.eclipse.jetty.client.Request;
2024
import org.eclipse.jetty.client.Response;
25+
import org.eclipse.jetty.client.Result;
2126
import org.eclipse.jetty.http.HttpField;
2227
import org.eclipse.jetty.util.ssl.SslContextFactory;
2328
import org.junit.jupiter.api.AfterEach;
2429
import org.junit.jupiter.api.BeforeEach;
30+
import org.junit.jupiter.api.Test;
2531

2632
public abstract class AbstractJettyClient12Test extends AbstractHttpClientTest<Request> {
2733

@@ -108,6 +114,54 @@ public void sendRequestWithCallback(
108114
});
109115
}
110116

117+
@Test
118+
void callbacksCalled() throws InterruptedException, ExecutionException {
119+
URI uri = resolveAddress("/success");
120+
Request request = client.newRequest(uri).method("GET");
121+
FutureResponseListener responseListener =
122+
new FutureResponseListener(request) {
123+
@Override
124+
public void onHeaders(Response response) {
125+
testing.runWithSpan("onHeaders", () -> super.onHeaders(response));
126+
}
127+
128+
@Override
129+
public void onSuccess(Response response) {
130+
testing.runWithSpan("onSuccess", () -> super.onSuccess(response));
131+
}
132+
133+
@Override
134+
public void onComplete(Result result) {
135+
testing.runWithSpan("onComplete", () -> super.onComplete(result));
136+
}
137+
};
138+
testing.runWithSpan("parent", () -> request.send(responseListener));
139+
Response response = responseListener.get();
140+
141+
assertThat(response.getStatus()).isEqualTo(200);
142+
testing.waitAndAssertTraces(
143+
trace ->
144+
trace.hasSpansSatisfyingExactly(
145+
span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
146+
span -> span.hasName("GET").hasKind(SpanKind.CLIENT).hasParent(trace.getSpan(0)),
147+
span ->
148+
span.hasName("test-http-server")
149+
.hasKind(SpanKind.SERVER)
150+
.hasParent(trace.getSpan(1)),
151+
span ->
152+
span.hasName("onHeaders")
153+
.hasKind(SpanKind.INTERNAL)
154+
.hasParent(trace.getSpan(0)),
155+
span ->
156+
span.hasName("onSuccess")
157+
.hasKind(SpanKind.INTERNAL)
158+
.hasParent(trace.getSpan(0)),
159+
span ->
160+
span.hasName("onComplete")
161+
.hasKind(SpanKind.INTERNAL)
162+
.hasParent(trace.getSpan(0))));
163+
}
164+
111165
private static class JettyClientListener
112166
implements Request.FailureListener, Response.FailureListener {
113167
volatile Throwable failure;

0 commit comments

Comments
 (0)