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:
Lauri Tulmin 2021-02-17 22:03:29 +02:00 committed by GitHub
parent 3fc1765446
commit 50cc8ddc8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 921 additions and 0 deletions

View File

@ -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'
}

View File

@ -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
}
}
}

View File

@ -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);
}
}
}

View File

@ -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";
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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)
}
}

View File

@ -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
})
}
}

View File

@ -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'