Add support for apache httpclient5 (#2254)
* Add support for apache httpclient5 * review fixes * copy commnet from httpclient-4 instrumentation * rebase
This commit is contained in:
parent
3fc1765446
commit
50cc8ddc8f
|
@ -0,0 +1,13 @@
|
|||
apply from: "$rootDir/gradle/instrumentation.gradle"
|
||||
|
||||
muzzle {
|
||||
pass {
|
||||
group = "org.apache.httpcomponents.client5"
|
||||
module = "httpclient5"
|
||||
versions = "[5.0,)"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
library group: 'org.apache.httpcomponents.client5', name: 'httpclient5', version: '5.0'
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0;
|
||||
|
||||
import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientTracer.tracer;
|
||||
|
||||
import io.opentelemetry.context.Context;
|
||||
import org.apache.hc.core5.http.HttpResponse;
|
||||
|
||||
public class ApacheHttpClientHelper {
|
||||
|
||||
public static void doMethodExit(Context context, Object result, Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
tracer().endExceptionally(context, throwable);
|
||||
} else if (result instanceof HttpResponse) {
|
||||
tracer().end(context, (HttpResponse) result);
|
||||
} else {
|
||||
// ended in WrappingStatusSettingResponseHandler
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,363 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0;
|
||||
|
||||
import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientTracer.tracer;
|
||||
import static io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge.currentContext;
|
||||
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.implementsInterface;
|
||||
import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.ClassLoaderMatcher.hasClassesNamed;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.not;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.context.Scope;
|
||||
import io.opentelemetry.javaagent.tooling.InstrumentationModule;
|
||||
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.method.MethodDescription;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.matcher.ElementMatcher;
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
|
||||
|
||||
@AutoService(InstrumentationModule.class)
|
||||
public class ApacheHttpClientInstrumentationModule extends InstrumentationModule {
|
||||
|
||||
public ApacheHttpClientInstrumentationModule() {
|
||||
super("apache-httpclient", "apache-httpclient-5.0");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TypeInstrumentation> typeInstrumentations() {
|
||||
return singletonList(new HttpClientInstrumentation());
|
||||
}
|
||||
|
||||
public static class HttpClientInstrumentation implements TypeInstrumentation {
|
||||
@Override
|
||||
public ElementMatcher<ClassLoader> classLoaderOptimization() {
|
||||
return hasClassesNamed("org.apache.hc.client5.http.classic.HttpClient");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||
return implementsInterface(named("org.apache.hc.client5.http.classic.HttpClient"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
|
||||
Map<ElementMatcher<? super MethodDescription>, String> transformers = new HashMap<>();
|
||||
// There are 8 execute(...) methods. Depending on the version, they may or may not delegate
|
||||
// to each other. Thus, all methods need to be instrumented. Because of argument position and
|
||||
// type, some methods can share the same advice class. The call depth tracking ensures only 1
|
||||
// span is created
|
||||
transformers.put(
|
||||
isMethod()
|
||||
.and(named("execute"))
|
||||
.and(not(isAbstract()))
|
||||
.and(takesArguments(1))
|
||||
.and(takesArgument(0, named("org.apache.hc.core5.http.ClassicHttpRequest"))),
|
||||
ApacheHttpClientInstrumentationModule.class.getName() + "$RequestAdvice");
|
||||
|
||||
transformers.put(
|
||||
isMethod()
|
||||
.and(named("execute"))
|
||||
.and(not(isAbstract()))
|
||||
.and(takesArguments(2))
|
||||
.and(takesArgument(0, named("org.apache.hc.core5.http.ClassicHttpRequest")))
|
||||
.and(takesArgument(1, named("org.apache.hc.core5.http.protocol.HttpContext"))),
|
||||
ApacheHttpClientInstrumentationModule.class.getName() + "$RequestAdvice");
|
||||
|
||||
transformers.put(
|
||||
isMethod()
|
||||
.and(named("execute"))
|
||||
.and(not(isAbstract()))
|
||||
.and(takesArguments(2))
|
||||
.and(takesArgument(0, named("org.apache.hc.core5.http.HttpHost")))
|
||||
.and(takesArgument(1, named("org.apache.hc.core5.http.ClassicHttpRequest"))),
|
||||
ApacheHttpClientInstrumentationModule.class.getName() + "$RequestWithHostAdvice");
|
||||
|
||||
transformers.put(
|
||||
isMethod()
|
||||
.and(named("execute"))
|
||||
.and(not(isAbstract()))
|
||||
.and(takesArguments(3))
|
||||
.and(takesArgument(0, named("org.apache.hc.core5.http.HttpHost")))
|
||||
.and(takesArgument(1, named("org.apache.hc.core5.http.ClassicHttpRequest")))
|
||||
.and(takesArgument(2, named("org.apache.hc.core5.http.protocol.HttpContext"))),
|
||||
ApacheHttpClientInstrumentationModule.class.getName() + "$RequestWithHostAdvice");
|
||||
|
||||
transformers.put(
|
||||
isMethod()
|
||||
.and(named("execute"))
|
||||
.and(not(isAbstract()))
|
||||
.and(takesArguments(2))
|
||||
.and(takesArgument(0, named("org.apache.hc.core5.http.ClassicHttpRequest")))
|
||||
.and(
|
||||
takesArgument(1, named("org.apache.hc.core5.http.io.HttpClientResponseHandler"))),
|
||||
ApacheHttpClientInstrumentationModule.class.getName() + "$RequestWithHandlerAdvice");
|
||||
|
||||
transformers.put(
|
||||
isMethod()
|
||||
.and(named("execute"))
|
||||
.and(not(isAbstract()))
|
||||
.and(takesArguments(3))
|
||||
.and(takesArgument(0, named("org.apache.hc.core5.http.ClassicHttpRequest")))
|
||||
.and(takesArgument(1, named("org.apache.hc.core5.http.protocol.HttpContext")))
|
||||
.and(
|
||||
takesArgument(2, named("org.apache.hc.core5.http.io.HttpClientResponseHandler"))),
|
||||
ApacheHttpClientInstrumentationModule.class.getName()
|
||||
+ "$RequestWithContextAndHandlerAdvice");
|
||||
|
||||
transformers.put(
|
||||
isMethod()
|
||||
.and(named("execute"))
|
||||
.and(not(isAbstract()))
|
||||
.and(takesArguments(3))
|
||||
.and(takesArgument(0, named("org.apache.hc.core5.http.HttpHost")))
|
||||
.and(takesArgument(1, named("org.apache.hc.core5.http.ClassicHttpRequest")))
|
||||
.and(
|
||||
takesArgument(2, named("org.apache.hc.core5.http.io.HttpClientResponseHandler"))),
|
||||
ApacheHttpClientInstrumentationModule.class.getName()
|
||||
+ "$RequestWithHostAndHandlerAdvice");
|
||||
|
||||
transformers.put(
|
||||
isMethod()
|
||||
.and(named("execute"))
|
||||
.and(not(isAbstract()))
|
||||
.and(takesArguments(4))
|
||||
.and(takesArgument(0, named("org.apache.hc.core5.http.HttpHost")))
|
||||
.and(takesArgument(1, named("org.apache.hc.core5.http.ClassicHttpRequest")))
|
||||
.and(takesArgument(2, named("org.apache.hc.core5.http.protocol.HttpContext")))
|
||||
.and(
|
||||
takesArgument(3, named("org.apache.hc.core5.http.io.HttpClientResponseHandler"))),
|
||||
ApacheHttpClientInstrumentationModule.class.getName()
|
||||
+ "$RequestWithHostAndContextAndHandlerAdvice");
|
||||
|
||||
return transformers;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RequestAdvice {
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void methodEnter(
|
||||
@Advice.Argument(0) ClassicHttpRequest request,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
Context parentContext = currentContext();
|
||||
if (!tracer().shouldStartSpan(parentContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context = tracer().startSpan(parentContext, request);
|
||||
scope = context.makeCurrent();
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void methodExit(
|
||||
@Advice.Return Object result,
|
||||
@Advice.Thrown Throwable throwable,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
if (scope == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.close();
|
||||
ApacheHttpClientHelper.doMethodExit(context, result, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
public static class RequestWithHandlerAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void methodEnter(
|
||||
@Advice.Argument(0) ClassicHttpRequest request,
|
||||
@Advice.Argument(value = 1, readOnly = false) HttpClientResponseHandler<?> handler,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
Context parentContext = currentContext();
|
||||
if (!tracer().shouldStartSpan(parentContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context = tracer().startSpan(parentContext, request);
|
||||
scope = context.makeCurrent();
|
||||
|
||||
// Wrap the handler so we capture the status code
|
||||
if (handler != null) {
|
||||
handler = new WrappingStatusSettingResponseHandler(context, parentContext, handler);
|
||||
}
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void methodExit(
|
||||
@Advice.Return Object result,
|
||||
@Advice.Thrown Throwable throwable,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
if (scope == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.close();
|
||||
ApacheHttpClientHelper.doMethodExit(context, result, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
public static class RequestWithContextAndHandlerAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void methodEnter(
|
||||
@Advice.Argument(0) ClassicHttpRequest request,
|
||||
@Advice.Argument(value = 2, readOnly = false) HttpClientResponseHandler<?> handler,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
Context parentContext = currentContext();
|
||||
if (!tracer().shouldStartSpan(parentContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context = tracer().startSpan(parentContext, request);
|
||||
scope = context.makeCurrent();
|
||||
|
||||
// Wrap the handler so we capture the status code
|
||||
if (handler != null) {
|
||||
handler = new WrappingStatusSettingResponseHandler(context, parentContext, handler);
|
||||
}
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void methodExit(
|
||||
@Advice.Return Object result,
|
||||
@Advice.Thrown Throwable throwable,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
if (scope == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.close();
|
||||
ApacheHttpClientHelper.doMethodExit(context, result, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
public static class RequestWithHostAdvice {
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void methodEnter(
|
||||
@Advice.Argument(0) HttpHost host,
|
||||
@Advice.Argument(1) ClassicHttpRequest request,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
Context parentContext = currentContext();
|
||||
if (!tracer().shouldStartSpan(parentContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context = tracer().startSpan(parentContext, host, request);
|
||||
scope = context.makeCurrent();
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void methodExit(
|
||||
@Advice.Return Object result,
|
||||
@Advice.Thrown Throwable throwable,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
if (scope == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.close();
|
||||
ApacheHttpClientHelper.doMethodExit(context, result, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
public static class RequestWithHostAndHandlerAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void methodEnter(
|
||||
@Advice.Argument(0) HttpHost host,
|
||||
@Advice.Argument(1) ClassicHttpRequest request,
|
||||
@Advice.Argument(value = 2, readOnly = false) HttpClientResponseHandler<?> handler,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
Context parentContext = currentContext();
|
||||
if (!tracer().shouldStartSpan(parentContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context = tracer().startSpan(parentContext, host, request);
|
||||
scope = context.makeCurrent();
|
||||
|
||||
// Wrap the handler so we capture the status code
|
||||
if (handler != null) {
|
||||
handler = new WrappingStatusSettingResponseHandler(context, parentContext, handler);
|
||||
}
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void methodExit(
|
||||
@Advice.Return Object result,
|
||||
@Advice.Thrown Throwable throwable,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
if (scope == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.close();
|
||||
ApacheHttpClientHelper.doMethodExit(context, result, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
public static class RequestWithHostAndContextAndHandlerAdvice {
|
||||
|
||||
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||
public static void methodEnter(
|
||||
@Advice.Argument(0) HttpHost host,
|
||||
@Advice.Argument(1) ClassicHttpRequest request,
|
||||
@Advice.Argument(value = 3, readOnly = false) HttpClientResponseHandler<?> handler,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
Context parentContext = currentContext();
|
||||
if (!tracer().shouldStartSpan(parentContext)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context = tracer().startSpan(parentContext, host, request);
|
||||
scope = context.makeCurrent();
|
||||
|
||||
// Wrap the handler so we capture the status code
|
||||
if (handler != null) {
|
||||
handler = new WrappingStatusSettingResponseHandler(context, parentContext, handler);
|
||||
}
|
||||
}
|
||||
|
||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||
public static void methodExit(
|
||||
@Advice.Return Object result,
|
||||
@Advice.Thrown Throwable throwable,
|
||||
@Advice.Local("otelContext") Context context,
|
||||
@Advice.Local("otelScope") Scope scope) {
|
||||
if (scope == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.close();
|
||||
ApacheHttpClientHelper.doMethodExit(context, result, throwable);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0;
|
||||
|
||||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.context.propagation.TextMapPropagator;
|
||||
import io.opentelemetry.instrumentation.api.tracer.HttpClientTracer;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.HttpMessage;
|
||||
import org.apache.hc.core5.http.HttpResponse;
|
||||
import org.apache.hc.core5.http.HttpVersion;
|
||||
import org.apache.hc.core5.http.ProtocolVersion;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class ApacheHttpClientTracer
|
||||
extends HttpClientTracer<ClassicHttpRequest, ClassicHttpRequest, HttpResponse> {
|
||||
|
||||
private static final ApacheHttpClientTracer TRACER = new ApacheHttpClientTracer();
|
||||
|
||||
public static ApacheHttpClientTracer tracer() {
|
||||
return TRACER;
|
||||
}
|
||||
|
||||
public Context startSpan(Context parentContext, HttpHost host, ClassicHttpRequest request) {
|
||||
return startSpan(parentContext, new RequestWithHost(host, request));
|
||||
}
|
||||
|
||||
public Context startSpan(Context parentContext, ClassicHttpRequest request) {
|
||||
return startSpan(parentContext, request, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String method(ClassicHttpRequest httpRequest) {
|
||||
return httpRequest.getMethod();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable String flavor(ClassicHttpRequest request) {
|
||||
ProtocolVersion protocolVersion = request.getVersion();
|
||||
if (protocolVersion == null) {
|
||||
protocolVersion = HttpVersion.HTTP_1_1;
|
||||
}
|
||||
return protocolVersion.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URI url(ClassicHttpRequest request) throws URISyntaxException {
|
||||
return request.getUri();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer status(HttpResponse httpResponse) {
|
||||
return httpResponse.getCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String requestHeader(ClassicHttpRequest request, String name) {
|
||||
return header(request, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String responseHeader(HttpResponse response, String name) {
|
||||
return header(response, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TextMapPropagator.Setter<ClassicHttpRequest> getSetter() {
|
||||
return HttpHeadersInjectAdapter.SETTER;
|
||||
}
|
||||
|
||||
private static String header(HttpMessage message, String name) {
|
||||
Header header = message.getFirstHeader(name);
|
||||
return header != null ? header.getValue() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getInstrumentationName() {
|
||||
return "io.opentelemetry.javaagent.apache-httpclient";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0;
|
||||
|
||||
import io.opentelemetry.context.propagation.TextMapPropagator;
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||
|
||||
class HttpHeadersInjectAdapter implements TextMapPropagator.Setter<ClassicHttpRequest> {
|
||||
|
||||
public static final HttpHeadersInjectAdapter SETTER = new HttpHeadersInjectAdapter();
|
||||
|
||||
@Override
|
||||
public void set(ClassicHttpRequest carrier, String key, String value) {
|
||||
carrier.addHeader(key, value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest;
|
||||
import org.apache.hc.core5.http.HttpEntity;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.message.HttpRequestWrapper;
|
||||
|
||||
public class RequestWithHost extends HttpRequestWrapper implements ClassicHttpRequest {
|
||||
|
||||
private final ClassicHttpRequest httpRequest;
|
||||
private final URI uri;
|
||||
|
||||
public RequestWithHost(HttpHost httpHost, ClassicHttpRequest httpRequest) {
|
||||
super(httpRequest);
|
||||
|
||||
this.httpRequest = httpRequest;
|
||||
|
||||
URI calculatedUri;
|
||||
try {
|
||||
// combine requested uri with host info
|
||||
URI requestUri = httpRequest.getUri();
|
||||
calculatedUri =
|
||||
new URI(
|
||||
httpHost.getSchemeName(),
|
||||
null,
|
||||
httpHost.getHostName(),
|
||||
httpHost.getPort(),
|
||||
requestUri.getPath(),
|
||||
requestUri.getQuery(),
|
||||
requestUri.getFragment());
|
||||
} catch (URISyntaxException e) {
|
||||
calculatedUri = null;
|
||||
}
|
||||
uri = calculatedUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getUri() throws URISyntaxException {
|
||||
if (uri != null) {
|
||||
return uri;
|
||||
}
|
||||
return super.getUri();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpEntity getEntity() {
|
||||
return httpRequest.getEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntity(HttpEntity entity) {
|
||||
httpRequest.setEntity(entity);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0;
|
||||
|
||||
import static io.opentelemetry.javaagent.instrumentation.apachehttpclient.v5_0.ApacheHttpClientTracer.tracer;
|
||||
|
||||
import io.opentelemetry.context.Context;
|
||||
import io.opentelemetry.context.Scope;
|
||||
import java.io.IOException;
|
||||
import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||
import org.apache.hc.core5.http.HttpException;
|
||||
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
|
||||
|
||||
public class WrappingStatusSettingResponseHandler<T> implements HttpClientResponseHandler<T> {
|
||||
final Context context;
|
||||
final Context parentContext;
|
||||
final HttpClientResponseHandler<T> handler;
|
||||
|
||||
public WrappingStatusSettingResponseHandler(
|
||||
Context context, Context parentContext, HttpClientResponseHandler<T> handler) {
|
||||
this.context = context;
|
||||
this.parentContext = parentContext;
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T handleResponse(ClassicHttpResponse response) throws IOException, HttpException {
|
||||
tracer().end(context, response);
|
||||
// ending the span before executing the callback handler (and scoping the callback handler to
|
||||
// the parent context), even though we are inside of a synchronous http client callback
|
||||
// underneath HttpClient.execute(..), in order to not attribute other CLIENT span timings that
|
||||
// may be performed in the callback handler to the http client span (and so we don't end up with
|
||||
// nested CLIENT spans, which we currently suppress)
|
||||
try (Scope ignored = parentContext.makeCurrent()) {
|
||||
return handler.handleResponse(response);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import io.opentelemetry.instrumentation.test.AgentTestTrait
|
||||
import io.opentelemetry.instrumentation.test.base.HttpClientTest
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase
|
||||
import org.apache.hc.client5.http.config.RequestConfig
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClients
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest
|
||||
import org.apache.hc.core5.http.ClassicHttpResponse
|
||||
import org.apache.hc.core5.http.HttpHost
|
||||
import org.apache.hc.core5.http.HttpRequest
|
||||
import org.apache.hc.core5.http.io.HttpClientResponseHandler
|
||||
import org.apache.hc.core5.http.message.BasicHeader
|
||||
import org.apache.hc.core5.http.protocol.BasicHttpContext
|
||||
import spock.lang.AutoCleanup
|
||||
import spock.lang.Shared
|
||||
|
||||
abstract class ApacheHttpClientResponseHandlerTest<T extends HttpRequest> extends HttpClientTest implements AgentTestTrait {
|
||||
|
||||
@Shared
|
||||
@AutoCleanup
|
||||
def client
|
||||
|
||||
@Shared
|
||||
def handler = new HttpClientResponseHandler<Integer>() {
|
||||
@Override
|
||||
Integer handleResponse(ClassicHttpResponse response) {
|
||||
return response.code
|
||||
}
|
||||
}
|
||||
|
||||
def setupSpec() {
|
||||
HttpClientBuilder builder = HttpClients.custom()
|
||||
builder.setDefaultRequestConfig(RequestConfig.custom()
|
||||
.setConnectTimeout(CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
|
||||
.build())
|
||||
|
||||
client = builder.build()
|
||||
}
|
||||
|
||||
abstract int executeRequest(T request)
|
||||
|
||||
@Override
|
||||
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
|
||||
def request = new HttpUriRequestBase(method, uri)
|
||||
headers.entrySet().each {
|
||||
request.addHeader(new BasicHeader(it.key, it.value))
|
||||
}
|
||||
|
||||
def status = executeRequest(request)
|
||||
|
||||
// handler execution is included within the client span, so we can't call the callback there.
|
||||
callback?.call()
|
||||
|
||||
return status
|
||||
}
|
||||
}
|
||||
|
||||
class ApacheClientHandlerRequest extends ApacheHttpClientResponseHandlerTest<ClassicHttpRequest> {
|
||||
|
||||
@Override
|
||||
int executeRequest(ClassicHttpRequest request) {
|
||||
return client.execute(request, handler)
|
||||
}
|
||||
}
|
||||
|
||||
class ApacheClientContextHandlerRequest extends ApacheHttpClientResponseHandlerTest<ClassicHttpRequest> {
|
||||
|
||||
@Override
|
||||
int executeRequest(ClassicHttpRequest request) {
|
||||
return client.execute(request, new BasicHttpContext(), handler)
|
||||
}
|
||||
}
|
||||
|
||||
class ApacheClientHostHandlerRequest extends ApacheHttpClientResponseHandlerTest<ClassicHttpRequest> {
|
||||
|
||||
@Override
|
||||
int executeRequest(ClassicHttpRequest request) {
|
||||
URI uri = request.getUri()
|
||||
return client.execute(new HttpHost(uri.getScheme(), uri.getHost(), uri.getPort()), request, handler)
|
||||
}
|
||||
}
|
||||
|
||||
class ApacheClientHostAndContextHandlerRequest extends ApacheHttpClientResponseHandlerTest<ClassicHttpRequest> {
|
||||
|
||||
@Override
|
||||
int executeRequest(ClassicHttpRequest request) {
|
||||
URI uri = request.getUri()
|
||||
return client.execute(new HttpHost(uri.getScheme(), uri.getHost(), uri.getPort()), request, new BasicHttpContext(), handler)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import io.opentelemetry.instrumentation.test.AgentTestTrait
|
||||
import io.opentelemetry.instrumentation.test.base.HttpClientTest
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase
|
||||
import org.apache.hc.client5.http.config.RequestConfig
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClients
|
||||
import org.apache.hc.core5.http.ClassicHttpRequest
|
||||
import org.apache.hc.core5.http.ClassicHttpResponse
|
||||
import org.apache.hc.core5.http.HttpHost
|
||||
import org.apache.hc.core5.http.HttpRequest
|
||||
import org.apache.hc.core5.http.message.BasicClassicHttpRequest
|
||||
import org.apache.hc.core5.http.message.BasicHeader
|
||||
import org.apache.hc.core5.http.protocol.BasicHttpContext
|
||||
import spock.lang.AutoCleanup
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Timeout
|
||||
|
||||
abstract class ApacheHttpClientTest<T extends HttpRequest> extends HttpClientTest implements AgentTestTrait {
|
||||
@Shared
|
||||
@AutoCleanup
|
||||
def client
|
||||
|
||||
def setupSpec() {
|
||||
HttpClientBuilder builder = HttpClients.custom()
|
||||
builder.setDefaultRequestConfig(RequestConfig.custom()
|
||||
.setConnectTimeout(CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
|
||||
.build())
|
||||
|
||||
client = builder.build()
|
||||
}
|
||||
|
||||
@Override
|
||||
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
|
||||
def request = createRequest(method, uri)
|
||||
headers.entrySet().each {
|
||||
request.addHeader(new BasicHeader(it.key, it.value))
|
||||
}
|
||||
|
||||
def response = executeRequest(request, uri, callback)
|
||||
response.close() // Make sure the connection is closed.
|
||||
|
||||
return response.code
|
||||
}
|
||||
|
||||
abstract T createRequest(String method, URI uri)
|
||||
|
||||
abstract ClassicHttpResponse executeRequest(T request, URI uri, Closure callback)
|
||||
|
||||
static String fullPathFromURI(URI uri) {
|
||||
StringBuilder builder = new StringBuilder()
|
||||
if (uri.getPath() != null) {
|
||||
builder.append(uri.getPath())
|
||||
}
|
||||
|
||||
if (uri.getQuery() != null) {
|
||||
builder.append('?')
|
||||
builder.append(uri.getQuery())
|
||||
}
|
||||
|
||||
if (uri.getFragment() != null) {
|
||||
builder.append('#')
|
||||
builder.append(uri.getFragment())
|
||||
}
|
||||
return builder.toString()
|
||||
}
|
||||
}
|
||||
|
||||
@Timeout(5)
|
||||
class ApacheClientHostRequest extends ApacheHttpClientTest<ClassicHttpRequest> {
|
||||
@Override
|
||||
ClassicHttpRequest createRequest(String method, URI uri) {
|
||||
return new BasicClassicHttpRequest(method, fullPathFromURI(uri))
|
||||
}
|
||||
|
||||
@Override
|
||||
ClassicHttpResponse executeRequest(ClassicHttpRequest request, URI uri, Closure callback) {
|
||||
def response = client.execute(new HttpHost(uri.getHost(), uri.getPort()), request)
|
||||
callback?.call()
|
||||
return response
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean testRemoteConnection() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@Timeout(5)
|
||||
class ApacheClientHostRequestContext extends ApacheHttpClientTest<ClassicHttpRequest> {
|
||||
@Override
|
||||
ClassicHttpRequest createRequest(String method, URI uri) {
|
||||
return new BasicClassicHttpRequest(method, fullPathFromURI(uri))
|
||||
}
|
||||
|
||||
@Override
|
||||
ClassicHttpResponse executeRequest(ClassicHttpRequest request, URI uri, Closure callback) {
|
||||
def response = client.execute(new HttpHost(uri.getHost(), uri.getPort()), request, new BasicHttpContext())
|
||||
callback?.call()
|
||||
return response
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean testRemoteConnection() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@Timeout(5)
|
||||
class ApacheClientHostRequestResponseHandler extends ApacheHttpClientTest<ClassicHttpRequest> {
|
||||
@Override
|
||||
ClassicHttpRequest createRequest(String method, URI uri) {
|
||||
return new BasicClassicHttpRequest(method, fullPathFromURI(uri))
|
||||
}
|
||||
|
||||
@Override
|
||||
ClassicHttpResponse executeRequest(ClassicHttpRequest request, URI uri, Closure callback) {
|
||||
return client.execute(new HttpHost(uri.getHost(), uri.getPort()), request, {
|
||||
callback?.call()
|
||||
return it
|
||||
})
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean testRemoteConnection() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@Timeout(5)
|
||||
class ApacheClientHostRequestResponseHandlerContext extends ApacheHttpClientTest<ClassicHttpRequest> {
|
||||
@Override
|
||||
ClassicHttpRequest createRequest(String method, URI uri) {
|
||||
return new BasicClassicHttpRequest(method, fullPathFromURI(uri))
|
||||
}
|
||||
|
||||
@Override
|
||||
ClassicHttpResponse executeRequest(ClassicHttpRequest request, URI uri, Closure callback) {
|
||||
return client.execute(new HttpHost(uri.getHost(), uri.getPort()), request, new BasicHttpContext(), {
|
||||
callback?.call()
|
||||
return it
|
||||
})
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean testRemoteConnection() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@Timeout(5)
|
||||
class ApacheClientUriRequest extends ApacheHttpClientTest<ClassicHttpRequest> {
|
||||
@Override
|
||||
ClassicHttpRequest createRequest(String method, URI uri) {
|
||||
return new HttpUriRequestBase(method, uri)
|
||||
}
|
||||
|
||||
@Override
|
||||
ClassicHttpResponse executeRequest(ClassicHttpRequest request, URI uri, Closure callback) {
|
||||
def response = client.execute(request)
|
||||
callback?.call()
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
@Timeout(5)
|
||||
class ApacheClientUriRequestContext extends ApacheHttpClientTest<ClassicHttpRequest> {
|
||||
@Override
|
||||
ClassicHttpRequest createRequest(String method, URI uri) {
|
||||
return new HttpUriRequestBase(method, uri)
|
||||
}
|
||||
|
||||
@Override
|
||||
ClassicHttpResponse executeRequest(ClassicHttpRequest request, URI uri, Closure callback) {
|
||||
def response = client.execute(request, new BasicHttpContext())
|
||||
callback?.call()
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
@Timeout(5)
|
||||
class ApacheClientUriRequestResponseHandler extends ApacheHttpClientTest<ClassicHttpRequest> {
|
||||
@Override
|
||||
ClassicHttpRequest createRequest(String method, URI uri) {
|
||||
return new HttpUriRequestBase(method, uri)
|
||||
}
|
||||
|
||||
@Override
|
||||
ClassicHttpResponse executeRequest(ClassicHttpRequest request, URI uri, Closure callback) {
|
||||
return client.execute(request, {
|
||||
callback?.call()
|
||||
it
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Timeout(5)
|
||||
class ApacheClientUriRequestResponseHandlerContext extends ApacheHttpClientTest<ClassicHttpRequest> {
|
||||
@Override
|
||||
ClassicHttpRequest createRequest(String method, URI uri) {
|
||||
return new HttpUriRequestBase(method, uri)
|
||||
}
|
||||
|
||||
@Override
|
||||
ClassicHttpResponse executeRequest(ClassicHttpRequest request, URI uri, Closure callback) {
|
||||
return client.execute(request, {
|
||||
callback?.call()
|
||||
it
|
||||
})
|
||||
}
|
||||
}
|
|
@ -60,6 +60,7 @@ include ':instrumentation:apache-camel-2.20:javaagent-unittests'
|
|||
include ':instrumentation:apache-httpasyncclient-4.1:javaagent'
|
||||
include ':instrumentation:apache-httpclient:apache-httpclient-2.0:javaagent'
|
||||
include ':instrumentation:apache-httpclient:apache-httpclient-4.0:javaagent'
|
||||
include ':instrumentation:apache-httpclient:apache-httpclient-5.0:javaagent'
|
||||
include ':instrumentation:armeria-1.3:javaagent'
|
||||
include ':instrumentation:armeria-1.3:library'
|
||||
include ':instrumentation:armeria-1.3:testing'
|
||||
|
|
Loading…
Reference in New Issue