Jetty-9 httpclient instrumentation, comments please (#3079)
* feat: jetty-9 http client, commit for PR comments * chore: compensate for build break due to new TypeTransformer * WIP: jetty9 with wrappers working * feat: moved structure to match 9.2 * chore: fix package names again, now 9.2, clean up Nullables and Muzzle range * chore: added latestDepTestLibrary for 10+ but class loader issues due to underlying jetty-server from the test framework * chore: migrate to new instrumenter api * chore: adjust to all the gradle plugin changes * chore: added layers of builders * chore: update final easy code review comments * chore: remove comments in gradle * chore: code cleanup, package cleanup, reduce surface area, iterators * chore: cleanup of null checks, using HttpFlavorVals enum * chore: null check removal again * chore: set muzzle plugin correctly for 9.2 up to 9.4.+ * chore: convert gradle plugins to not use apply syntax * Rename to build.gradle * chore: move TypeInstrumention into standalone class Co-authored-by: Anuraag Agrawal <aanuraag@amazon.co.jp>
This commit is contained in:
parent
785dc6adf2
commit
9e2fcbaecd
|
@ -0,0 +1,28 @@
|
||||||
|
plugins {
|
||||||
|
id("otel.javaagent-instrumentation")
|
||||||
|
}
|
||||||
|
|
||||||
|
muzzle {
|
||||||
|
pass {
|
||||||
|
group = "org.eclipse.jetty"
|
||||||
|
module = 'jetty-client'
|
||||||
|
versions = "[9.2,9.4.+)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Jetty client 9.2 is the best starting point, HttpClient.send() is stable there
|
||||||
|
def jettyVers_base9 = '9.2.0.v20140526'
|
||||||
|
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(':instrumentation:jetty-httpclient:jetty-httpclient-9.2:library')
|
||||||
|
|
||||||
|
library "org.eclipse.jetty:jetty-client:${jettyVers_base9}"
|
||||||
|
latestDepTestLibrary "org.eclipse.jetty:jetty-client:9.+"
|
||||||
|
|
||||||
|
testImplementation project(':instrumentation:jetty-httpclient:jetty-httpclient-9.2:testing')
|
||||||
|
testImplementation("org.eclipse.jetty:jetty-server:${jettyVers_base9}") {
|
||||||
|
exclude group: 'org.eclipse.jetty', module: 'jetty-client'
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.jetty.httpclient.v9_2;
|
||||||
|
|
||||||
|
import static io.opentelemetry.instrumentation.jetty.httpclient.v9_2.internal.JettyClientWrapUtil.wrapResponseListeners;
|
||||||
|
import static io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge.currentContext;
|
||||||
|
import static io.opentelemetry.javaagent.instrumentation.jetty.httpclient.v9_2.JettyHttpClientSingletons.instrumenter;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
|
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||||
|
|
||||||
|
import io.opentelemetry.context.Context;
|
||||||
|
import io.opentelemetry.context.Scope;
|
||||||
|
import io.opentelemetry.instrumentation.jetty.httpclient.v9_2.internal.JettyHttpClient9TracingInterceptor;
|
||||||
|
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||||
|
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
|
||||||
|
import java.util.List;
|
||||||
|
import net.bytebuddy.asm.Advice;
|
||||||
|
import net.bytebuddy.description.type.TypeDescription;
|
||||||
|
import net.bytebuddy.matcher.ElementMatcher;
|
||||||
|
import org.eclipse.jetty.client.HttpRequest;
|
||||||
|
import org.eclipse.jetty.client.api.Response;
|
||||||
|
|
||||||
|
public class JettyHttpClient9Instrumentation implements TypeInstrumentation {
|
||||||
|
@Override
|
||||||
|
public ElementMatcher<TypeDescription> typeMatcher() {
|
||||||
|
return named("org.eclipse.jetty.client.HttpClient");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void transform(TypeTransformer transformer) {
|
||||||
|
transformer.applyAdviceToMethod(
|
||||||
|
isMethod()
|
||||||
|
.and(named("send"))
|
||||||
|
.and(takesArgument(0, named("org.eclipse.jetty.client.HttpRequest")))
|
||||||
|
.and(takesArgument(1, List.class)),
|
||||||
|
JettyHttpClient9Instrumentation.class.getName() + "$JettyHttpClient9Advice");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static class JettyHttpClient9Advice {
|
||||||
|
|
||||||
|
@Advice.OnMethodEnter(suppress = Throwable.class)
|
||||||
|
public static void addTracingEnter(
|
||||||
|
@Advice.Argument(value = 0) HttpRequest httpRequest,
|
||||||
|
@Advice.Argument(value = 1, readOnly = false) List<Response.ResponseListener> listeners,
|
||||||
|
@Advice.Local("otelContext") Context context,
|
||||||
|
@Advice.Local("otelScope") Scope scope) {
|
||||||
|
Context parentContext = currentContext();
|
||||||
|
if (!instrumenter().shouldStart(parentContext, httpRequest)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First step is to attach the tracer to the Jetty request. Request listeners are wrapped here
|
||||||
|
JettyHttpClient9TracingInterceptor requestInterceptor =
|
||||||
|
new JettyHttpClient9TracingInterceptor(parentContext, instrumenter());
|
||||||
|
requestInterceptor.attachToRequest(httpRequest);
|
||||||
|
|
||||||
|
// Second step is to wrap all the important result callback
|
||||||
|
listeners = wrapResponseListeners(parentContext, listeners);
|
||||||
|
|
||||||
|
context = requestInterceptor.getContext();
|
||||||
|
scope = context.makeCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
|
||||||
|
public static void exitTracingInterceptor(
|
||||||
|
@Advice.Argument(value = 0) HttpRequest httpRequest,
|
||||||
|
@Advice.Thrown Throwable throwable,
|
||||||
|
@Advice.Local("otelContext") Context context,
|
||||||
|
@Advice.Local("otelScope") Scope scope) {
|
||||||
|
|
||||||
|
if (scope == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// not ending span here unless error, span ended in the interceptor
|
||||||
|
scope.close();
|
||||||
|
if (throwable != null) {
|
||||||
|
instrumenter().end(context, httpRequest, null, throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.jetty.httpclient.v9_2;
|
||||||
|
|
||||||
|
import static io.opentelemetry.javaagent.extension.matcher.ClassLoaderMatcher.hasClassesNamed;
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
|
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
|
||||||
|
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||||
|
import java.util.List;
|
||||||
|
import net.bytebuddy.matcher.ElementMatcher;
|
||||||
|
|
||||||
|
@AutoService(InstrumentationModule.class)
|
||||||
|
public class JettyHttpClient9InstrumentationModule extends InstrumentationModule {
|
||||||
|
|
||||||
|
public JettyHttpClient9InstrumentationModule() {
|
||||||
|
super("jetty-httpclient", "jetty-httpclient-9.2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<TypeInstrumentation> typeInstrumentations() {
|
||||||
|
return singletonList(new JettyHttpClient9Instrumentation());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
|
||||||
|
// AbstractTypedContentProvider showed up in version Jetty Client 9.2 on to 10.x
|
||||||
|
return hasClassesNamed("org.eclipse.jetty.client.util.AbstractTypedContentProvider");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.jetty.httpclient.v9_2;
|
||||||
|
|
||||||
|
import io.opentelemetry.api.GlobalOpenTelemetry;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
|
||||||
|
import io.opentelemetry.instrumentation.jetty.httpclient.v9_2.internal.JettyClientInstrumenterBuilder;
|
||||||
|
import io.opentelemetry.instrumentation.jetty.httpclient.v9_2.internal.JettyHttpClientNetAttributesExtractor;
|
||||||
|
import io.opentelemetry.javaagent.instrumentation.api.instrumenter.PeerServiceAttributesExtractor;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.eclipse.jetty.client.api.Response;
|
||||||
|
|
||||||
|
public class JettyHttpClientSingletons {
|
||||||
|
|
||||||
|
private static final Instrumenter<Request, Response> INSTRUMENTER;
|
||||||
|
|
||||||
|
private JettyHttpClientSingletons() {}
|
||||||
|
|
||||||
|
static {
|
||||||
|
JettyClientInstrumenterBuilder builder =
|
||||||
|
new JettyClientInstrumenterBuilder(GlobalOpenTelemetry.get());
|
||||||
|
|
||||||
|
PeerServiceAttributesExtractor<Request, Response> peerServiceAttributesExtractor =
|
||||||
|
PeerServiceAttributesExtractor.create(new JettyHttpClientNetAttributesExtractor());
|
||||||
|
INSTRUMENTER = builder.addAttributeExtractor(peerServiceAttributesExtractor).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Instrumenter<Request, Response> instrumenter() {
|
||||||
|
return INSTRUMENTER;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.javaagent.instrumentation.jetty.httpclient.v9_2
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.jetty.httpclient.v9_2.AbstractJettyClient9Test
|
||||||
|
import io.opentelemetry.instrumentation.test.AgentTestTrait
|
||||||
|
import org.eclipse.jetty.client.HttpClient
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory
|
||||||
|
|
||||||
|
class JettyHttpClient9AgentTest extends AbstractJettyClient9Test implements AgentTestTrait {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
HttpClient createStandardClient() {
|
||||||
|
return new HttpClient()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
HttpClient createHttpsClient(SslContextFactory sslContextFactory) {
|
||||||
|
return new HttpClient(sslContextFactory)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
plugins {
|
||||||
|
id("otel.library-instrumentation")
|
||||||
|
id("net.ltgt.errorprone")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Jetty client 9.2 is the best starting point, HttpClient.send() is stable there
|
||||||
|
def jettyVers_base9 = '9.2.0.v20140526'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
library "org.eclipse.jetty:jetty-client:${jettyVers_base9}"
|
||||||
|
latestDepTestLibrary "org.eclipse.jetty:jetty-client:9.+"
|
||||||
|
testImplementation project(':instrumentation:jetty-httpclient::jetty-httpclient-9.2:testing')
|
||||||
|
|
||||||
|
implementation "org.slf4j:slf4j-api"
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.jetty.httpclient.v9_2;
|
||||||
|
|
||||||
|
import io.opentelemetry.api.OpenTelemetry;
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
|
||||||
|
/** JettyClientTracing, the Entrypoint for tracing Jetty client. */
|
||||||
|
public final class JettyClientTracing {
|
||||||
|
|
||||||
|
private final HttpClient httpClient;
|
||||||
|
|
||||||
|
public static JettyClientTracing create(OpenTelemetry openTelemetry) {
|
||||||
|
JettyClientTracingBuilder builder = newBuilder(openTelemetry);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JettyClientTracingBuilder newBuilder(OpenTelemetry openTelemetry) {
|
||||||
|
return new JettyClientTracingBuilder(openTelemetry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClient getHttpClient() {
|
||||||
|
return httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
JettyClientTracing(HttpClient httpClient) {
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.jetty.httpclient.v9_2;
|
||||||
|
|
||||||
|
import io.opentelemetry.api.OpenTelemetry;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
|
||||||
|
import io.opentelemetry.instrumentation.jetty.httpclient.v9_2.internal.JettyClientInstrumenterBuilder;
|
||||||
|
import org.eclipse.jetty.client.HttpClientTransport;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.eclipse.jetty.client.api.Response;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
|
||||||
|
public final class JettyClientTracingBuilder {
|
||||||
|
|
||||||
|
private final OpenTelemetry openTelemetry;
|
||||||
|
private HttpClientTransport httpClientTransport;
|
||||||
|
private SslContextFactory sslContextFactory;
|
||||||
|
|
||||||
|
public JettyClientTracingBuilder(OpenTelemetry openTelemetry) {
|
||||||
|
this.openTelemetry = openTelemetry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JettyClientTracingBuilder setHttpClientTransport(HttpClientTransport httpClientTransport) {
|
||||||
|
this.httpClientTransport = httpClientTransport;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JettyClientTracingBuilder setSslContextFactory(SslContextFactory sslContextFactory) {
|
||||||
|
this.sslContextFactory = sslContextFactory;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JettyClientTracing build() {
|
||||||
|
JettyClientInstrumenterBuilder instrumenterBuilder =
|
||||||
|
new JettyClientInstrumenterBuilder(this.openTelemetry);
|
||||||
|
Instrumenter<Request, Response> instrumenter = instrumenterBuilder.build();
|
||||||
|
|
||||||
|
TracingHttpClient tracingHttpClient =
|
||||||
|
TracingHttpClient.buildNew(instrumenter, this.sslContextFactory, this.httpClientTransport);
|
||||||
|
|
||||||
|
return new JettyClientTracing(tracingHttpClient);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.jetty.httpclient.v9_2;
|
||||||
|
|
||||||
|
import static io.opentelemetry.instrumentation.jetty.httpclient.v9_2.internal.JettyClientWrapUtil.wrapResponseListeners;
|
||||||
|
|
||||||
|
import io.opentelemetry.context.Context;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
|
||||||
|
import io.opentelemetry.instrumentation.jetty.httpclient.v9_2.internal.JettyHttpClient9TracingInterceptor;
|
||||||
|
import java.util.List;
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.HttpClientTransport;
|
||||||
|
import org.eclipse.jetty.client.HttpRequest;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.eclipse.jetty.client.api.Response;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
|
||||||
|
class TracingHttpClient extends HttpClient {
|
||||||
|
|
||||||
|
private final Instrumenter<Request, Response> instrumenter;
|
||||||
|
|
||||||
|
TracingHttpClient(Instrumenter<Request, Response> instrumenter) {
|
||||||
|
super();
|
||||||
|
this.instrumenter = instrumenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
TracingHttpClient(
|
||||||
|
Instrumenter<Request, Response> instrumenter, SslContextFactory sslContextFactory) {
|
||||||
|
super(sslContextFactory);
|
||||||
|
this.instrumenter = instrumenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
TracingHttpClient(
|
||||||
|
Instrumenter<Request, Response> instrumenter,
|
||||||
|
HttpClientTransport transport,
|
||||||
|
SslContextFactory sslContextFactory) {
|
||||||
|
super(transport, sslContextFactory);
|
||||||
|
this.instrumenter = instrumenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
static TracingHttpClient buildNew(
|
||||||
|
Instrumenter<Request, Response> instrumenter,
|
||||||
|
SslContextFactory sslContextFactory,
|
||||||
|
HttpClientTransport httpClientTransport) {
|
||||||
|
TracingHttpClient tracingHttpClient = null;
|
||||||
|
if (sslContextFactory != null && httpClientTransport != null) {
|
||||||
|
tracingHttpClient =
|
||||||
|
new TracingHttpClient(instrumenter, httpClientTransport, sslContextFactory);
|
||||||
|
} else if (sslContextFactory != null) {
|
||||||
|
tracingHttpClient = new TracingHttpClient(instrumenter, sslContextFactory);
|
||||||
|
} else {
|
||||||
|
tracingHttpClient = new TracingHttpClient(instrumenter);
|
||||||
|
}
|
||||||
|
return tracingHttpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void send(HttpRequest request, List<Response.ResponseListener> listeners) {
|
||||||
|
Context parentContext = Context.current();
|
||||||
|
JettyHttpClient9TracingInterceptor requestInterceptor =
|
||||||
|
new JettyHttpClient9TracingInterceptor(parentContext, this.instrumenter);
|
||||||
|
requestInterceptor.attachToRequest(request);
|
||||||
|
List<Response.ResponseListener> wrapped = wrapResponseListeners(parentContext, listeners);
|
||||||
|
super.send(request, wrapped);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.jetty.httpclient.v9_2.internal;
|
||||||
|
|
||||||
|
import io.opentelemetry.context.propagation.TextMapSetter;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
|
||||||
|
final class HttpHeaderSetter implements TextMapSetter<Request> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(Request request, String key, String value) {
|
||||||
|
if (request != null) {
|
||||||
|
// dedupe header fields here with a put()
|
||||||
|
request.getHeaders().put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,156 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.jetty.httpclient.v9_2.internal;
|
||||||
|
|
||||||
|
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HttpFlavorValues.HTTP_1_0;
|
||||||
|
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HttpFlavorValues.HTTP_1_1;
|
||||||
|
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HttpFlavorValues.HTTP_2_0;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpAttributesExtractor;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.eclipse.jetty.client.api.Response;
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
final class JettyClientHttpAttributesExtractor extends HttpAttributesExtractor<Request, Response> {
|
||||||
|
private static final Logger LOG =
|
||||||
|
LoggerFactory.getLogger(JettyClientHttpAttributesExtractor.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected String method(Request request) {
|
||||||
|
return request.getMethod();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected String url(Request request) {
|
||||||
|
return request.getURI().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected String target(Request request) {
|
||||||
|
String queryString = request.getQuery();
|
||||||
|
return queryString != null ? request.getPath() + "?" + queryString : request.getPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected String host(Request request) {
|
||||||
|
return request.getHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected String route(Request request) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected String scheme(Request request) {
|
||||||
|
return request.getScheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected String userAgent(Request request) {
|
||||||
|
HttpField agentField = request.getHeaders().getField(HttpHeader.USER_AGENT);
|
||||||
|
return agentField != null ? agentField.getValue() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected Long requestContentLength(Request request, @Nullable Response response) {
|
||||||
|
HttpField requestContentLengthField = request.getHeaders().getField(HttpHeader.CONTENT_LENGTH);
|
||||||
|
return getLongFromJettyHttpField(requestContentLengthField);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected Long requestContentLengthUncompressed(Request request, @Nullable Response response) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected String flavor(Request request, @Nullable Response response) {
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
return HTTP_1_1;
|
||||||
|
}
|
||||||
|
HttpVersion httpVersion = response.getVersion();
|
||||||
|
httpVersion = (httpVersion != null) ? httpVersion : HttpVersion.HTTP_1_1;
|
||||||
|
switch (httpVersion) {
|
||||||
|
case HTTP_0_9:
|
||||||
|
case HTTP_1_0:
|
||||||
|
return HTTP_1_0;
|
||||||
|
case HTTP_1_1:
|
||||||
|
return HTTP_1_1;
|
||||||
|
default:
|
||||||
|
// version 2.0 enum name difference in later versions 9.2 and 9.4 versions
|
||||||
|
if (httpVersion.toString().endsWith("2.0")) {
|
||||||
|
return HTTP_2_0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HTTP_1_1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected String serverName(Request request, @Nullable Response response) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected String clientIp(Request request, @Nullable Response response) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected Integer statusCode(Request request, Response response) {
|
||||||
|
return response.getStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected Long responseContentLength(Request request, Response response) {
|
||||||
|
Long respContentLength = null;
|
||||||
|
if (response != null) {
|
||||||
|
HttpField requestContentLengthField =
|
||||||
|
response.getHeaders().getField(HttpHeader.CONTENT_LENGTH);
|
||||||
|
respContentLength = getLongFromJettyHttpField(requestContentLengthField);
|
||||||
|
}
|
||||||
|
return respContentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected Long responseContentLengthUncompressed(Request request, Response response) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Long getLongFromJettyHttpField(HttpField httpField) {
|
||||||
|
Long longFromField = null;
|
||||||
|
try {
|
||||||
|
longFromField = httpField != null ? Long.getLong(httpField.getValue()) : null;
|
||||||
|
} catch (NumberFormatException t) {
|
||||||
|
LOG.debug(
|
||||||
|
"Value {} is not not valid number format for header field: {}",
|
||||||
|
httpField.getValue(),
|
||||||
|
httpField.getName());
|
||||||
|
}
|
||||||
|
return longFromField;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.jetty.httpclient.v9_2.internal;
|
||||||
|
|
||||||
|
import io.opentelemetry.api.OpenTelemetry;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpAttributesExtractor;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.eclipse.jetty.client.api.Response;
|
||||||
|
|
||||||
|
public final class JettyClientInstrumenterBuilder {
|
||||||
|
|
||||||
|
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jetty-httpclient-9.2";
|
||||||
|
|
||||||
|
private final OpenTelemetry openTelemetry;
|
||||||
|
|
||||||
|
private final List<AttributesExtractor<? super Request, ? super Response>> additionalExtractors =
|
||||||
|
new ArrayList<>();
|
||||||
|
|
||||||
|
public JettyClientInstrumenterBuilder addAttributeExtractor(
|
||||||
|
AttributesExtractor<? super Request, ? super Response> attributesExtractor) {
|
||||||
|
additionalExtractors.add(attributesExtractor);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JettyClientInstrumenterBuilder(OpenTelemetry openTelemetry) {
|
||||||
|
this.openTelemetry = openTelemetry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instrumenter<Request, Response> build() {
|
||||||
|
HttpAttributesExtractor<Request, Response> httpAttributesExtractor =
|
||||||
|
new JettyClientHttpAttributesExtractor();
|
||||||
|
SpanNameExtractor<Request> spanNameExtractor =
|
||||||
|
HttpSpanNameExtractor.create(httpAttributesExtractor);
|
||||||
|
SpanStatusExtractor<Request, Response> spanStatusExtractor =
|
||||||
|
HttpSpanStatusExtractor.create(httpAttributesExtractor);
|
||||||
|
JettyHttpClientNetAttributesExtractor netAttributesExtractor =
|
||||||
|
new JettyHttpClientNetAttributesExtractor();
|
||||||
|
|
||||||
|
Instrumenter<Request, Response> instrumenter =
|
||||||
|
Instrumenter.<Request, Response>newBuilder(
|
||||||
|
this.openTelemetry, INSTRUMENTATION_NAME, spanNameExtractor)
|
||||||
|
.setSpanStatusExtractor(spanStatusExtractor)
|
||||||
|
.addAttributesExtractor(httpAttributesExtractor)
|
||||||
|
.addAttributesExtractor(netAttributesExtractor)
|
||||||
|
.addAttributesExtractors(additionalExtractors)
|
||||||
|
.newClientInstrumenter(new HttpHeaderSetter());
|
||||||
|
return instrumenter;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.jetty.httpclient.v9_2.internal;
|
||||||
|
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
|
import io.opentelemetry.context.Context;
|
||||||
|
import io.opentelemetry.context.Scope;
|
||||||
|
import java.util.List;
|
||||||
|
import org.eclipse.jetty.client.api.Response;
|
||||||
|
|
||||||
|
public final class JettyClientWrapUtil {
|
||||||
|
|
||||||
|
private JettyClientWrapUtil() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to wrap the response listeners only, this includes the important CompleteListener.
|
||||||
|
*
|
||||||
|
* @param parentContext top level context that is above the Jetty client span context
|
||||||
|
* @param listeners all listeners passed to Jetty client send() method
|
||||||
|
* @return list of wrapped ResponseListeners
|
||||||
|
*/
|
||||||
|
public static List<Response.ResponseListener> wrapResponseListeners(
|
||||||
|
Context parentContext, List<Response.ResponseListener> listeners) {
|
||||||
|
|
||||||
|
return listeners.stream()
|
||||||
|
.map(listener -> wrapTheListener(listener, parentContext))
|
||||||
|
.collect(toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Response.ResponseListener wrapTheListener(
|
||||||
|
Response.ResponseListener listener, Context context) {
|
||||||
|
Response.ResponseListener wrappedListener = listener;
|
||||||
|
if (listener instanceof Response.CompleteListener
|
||||||
|
&& !(listener instanceof JettyHttpClient9TracingInterceptor)) {
|
||||||
|
wrappedListener =
|
||||||
|
(Response.CompleteListener)
|
||||||
|
result -> {
|
||||||
|
try (Scope ignored = context.makeCurrent()) {
|
||||||
|
((Response.CompleteListener) listener).onComplete(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return wrappedListener;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,154 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.jetty.httpclient.v9_2.internal;
|
||||||
|
|
||||||
|
import io.opentelemetry.api.trace.Span;
|
||||||
|
import io.opentelemetry.context.Context;
|
||||||
|
import io.opentelemetry.context.Scope;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
|
||||||
|
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.eclipse.jetty.client.api.Response;
|
||||||
|
import org.eclipse.jetty.client.api.Result;
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JettyHttpClient9TracingInterceptor does three jobs stimulated from the Jetty Request object from
|
||||||
|
* attachToRequest() 1. Start the CLIENT span and create the tracer 2. Set the listener callbacks
|
||||||
|
* for each important lifecycle actions that would cause the start and close of the span 3. Set
|
||||||
|
* callback wrappers on two important request-based callbacks
|
||||||
|
*/
|
||||||
|
public class JettyHttpClient9TracingInterceptor
|
||||||
|
implements Request.BeginListener,
|
||||||
|
Request.FailureListener,
|
||||||
|
Response.SuccessListener,
|
||||||
|
Response.FailureListener,
|
||||||
|
Response.CompleteListener {
|
||||||
|
|
||||||
|
private static final Logger LOG =
|
||||||
|
LoggerFactory.getLogger(JettyHttpClient9TracingInterceptor.class);
|
||||||
|
|
||||||
|
@Nullable private Context context;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Context getContext() {
|
||||||
|
return this.context;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Context parentContext;
|
||||||
|
|
||||||
|
private final Instrumenter<Request, Response> instrumenter;
|
||||||
|
|
||||||
|
public JettyHttpClient9TracingInterceptor(
|
||||||
|
Context parentCtx, Instrumenter<Request, Response> instrumenter) {
|
||||||
|
this.parentContext = parentCtx;
|
||||||
|
this.instrumenter = instrumenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void attachToRequest(Request jettyRequest) {
|
||||||
|
List<JettyHttpClient9TracingInterceptor> current =
|
||||||
|
jettyRequest.getRequestListeners(JettyHttpClient9TracingInterceptor.class);
|
||||||
|
|
||||||
|
if (!current.isEmpty()) {
|
||||||
|
LOG.warn("A tracing interceptor is already in place for this request! ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
startSpan(jettyRequest);
|
||||||
|
|
||||||
|
// wrap all important request-based listeners that may already be attached, null should ensure
|
||||||
|
// are returned here
|
||||||
|
List<Request.RequestListener> existingListeners = jettyRequest.getRequestListeners(null);
|
||||||
|
wrapRequestListeners(existingListeners);
|
||||||
|
|
||||||
|
jettyRequest
|
||||||
|
.onRequestBegin(this)
|
||||||
|
.onRequestFailure(this)
|
||||||
|
.onResponseFailure(this)
|
||||||
|
.onResponseSuccess(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void wrapRequestListeners(List<Request.RequestListener> requestListeners) {
|
||||||
|
|
||||||
|
ListIterator<Request.RequestListener> iterator = requestListeners.listIterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Request.RequestListener requestListener = iterator.next();
|
||||||
|
if (requestListener instanceof Request.FailureListener) {
|
||||||
|
iterator.set(
|
||||||
|
(Request.FailureListener)
|
||||||
|
(request, throwable) -> {
|
||||||
|
try (Scope ignore = context.makeCurrent()) {
|
||||||
|
((Request.FailureListener) requestListener).onFailure(request, throwable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (requestListener instanceof Request.BeginListener) {
|
||||||
|
iterator.set(
|
||||||
|
(Request.FailureListener)
|
||||||
|
(request, throwable) -> {
|
||||||
|
try (Scope ignore = context.makeCurrent()) {
|
||||||
|
((Request.BeginListener) requestListener).onBegin(request);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startSpan(Request request) {
|
||||||
|
|
||||||
|
if (!instrumenter.shouldStart(this.parentContext, request)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.context = instrumenter.start(this.parentContext, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBegin(Request request) {
|
||||||
|
if (this.context != null) {
|
||||||
|
Span span = Span.fromContext(this.context);
|
||||||
|
HttpField agentField = request.getHeaders().getField(HttpHeader.USER_AGENT);
|
||||||
|
span.setAttribute(SemanticAttributes.HTTP_USER_AGENT, agentField.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete(Result result) {
|
||||||
|
closeIfPossible(result.getResponse());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Response response) {
|
||||||
|
closeIfPossible(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Request request, Throwable t) {
|
||||||
|
if (this.context != null) {
|
||||||
|
instrumenter.end(this.context, request, null, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Response response, Throwable t) {
|
||||||
|
if (this.context != null) {
|
||||||
|
instrumenter.end(this.context, response.getRequest(), response, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeIfPossible(Response response) {
|
||||||
|
|
||||||
|
if (this.context != null) {
|
||||||
|
instrumenter.end(this.context, response.getRequest(), response, null);
|
||||||
|
} else {
|
||||||
|
LOG.debug("onComplete - could not find an otel context");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.jetty.httpclient.v9_2.internal;
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.net.NetAttributesExtractor;
|
||||||
|
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.eclipse.jetty.client.api.Response;
|
||||||
|
|
||||||
|
public class JettyHttpClientNetAttributesExtractor
|
||||||
|
extends NetAttributesExtractor<Request, Response> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String transport(Request request) {
|
||||||
|
return SemanticAttributes.NetTransportValues.IP_TCP;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String peerName(Request request, @Nullable Response response) {
|
||||||
|
return request.getHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Integer peerPort(Request request, @Nullable Response response) {
|
||||||
|
return request.getPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String peerIp(Request request, @Nullable Response response) {
|
||||||
|
// Return null unless the library supports resolution to something similar to SocketAddress
|
||||||
|
// https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/3012/files#r633188645
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.jetty.httpclient.v9_2
|
||||||
|
|
||||||
|
|
||||||
|
import io.opentelemetry.instrumentation.test.LibraryTestTrait
|
||||||
|
import org.eclipse.jetty.client.HttpClient
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory
|
||||||
|
|
||||||
|
class JettyHttpClient9LibraryTest extends AbstractJettyClient9Test implements LibraryTestTrait {
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean testWithClientParent() {
|
||||||
|
//The client parent test does not work well in the context of library only tests.
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
HttpClient createStandardClient() {
|
||||||
|
JettyClientTracingBuilder jettyClientTracingBuilder = new JettyClientTracingBuilder(getOpenTelemetry())
|
||||||
|
return jettyClientTracingBuilder.build().getHttpClient()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
HttpClient createHttpsClient(SslContextFactory sslContextFactory) {
|
||||||
|
JettyClientTracingBuilder jettyClientTracingBuilder = new JettyClientTracingBuilder(getOpenTelemetry())
|
||||||
|
return jettyClientTracingBuilder
|
||||||
|
.setSslContextFactory(sslContextFactory)
|
||||||
|
.build()
|
||||||
|
.getHttpClient()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
plugins {
|
||||||
|
id("otel.java-conventions")
|
||||||
|
}
|
||||||
|
|
||||||
|
//Jetty client 9.2 is the best starting point, HttpClient.send() is stable there
|
||||||
|
def jettyVers_base9 = '9.2.0.v20140526'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(project(':testing-common')) {
|
||||||
|
exclude group: 'org.eclipse.jetty', module: 'jetty-client'
|
||||||
|
exclude group: 'org.eclipse.jetty', module: 'jetty-server'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
api "org.eclipse.jetty:jetty-client:${jettyVers_base9}"
|
||||||
|
|
||||||
|
implementation "org.junit.jupiter:junit-jupiter-api"
|
||||||
|
|
||||||
|
implementation "org.codehaus.groovy:groovy-all"
|
||||||
|
implementation "io.opentelemetry:opentelemetry-api"
|
||||||
|
implementation "org.spockframework:spock-core"
|
||||||
|
}
|
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.jetty.httpclient.v9_2
|
||||||
|
|
||||||
|
import io.opentelemetry.api.common.AttributeKey
|
||||||
|
import io.opentelemetry.instrumentation.test.base.HttpClientTest
|
||||||
|
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
|
||||||
|
import org.eclipse.jetty.client.HttpClient
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse
|
||||||
|
import org.eclipse.jetty.client.api.Request
|
||||||
|
import org.eclipse.jetty.client.api.Response
|
||||||
|
import org.eclipse.jetty.client.api.Result
|
||||||
|
import org.eclipse.jetty.http.HttpMethod
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.rules.TestName
|
||||||
|
import spock.lang.Shared
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
abstract class AbstractJettyClient9Test extends HttpClientTest<Request> {
|
||||||
|
|
||||||
|
abstract HttpClient createStandardClient()
|
||||||
|
|
||||||
|
abstract HttpClient createHttpsClient(SslContextFactory sslContextFactory)
|
||||||
|
|
||||||
|
|
||||||
|
@Shared
|
||||||
|
def client = createStandardClient()
|
||||||
|
@Shared
|
||||||
|
def httpsClient = null
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
TestName name = new TestName()
|
||||||
|
|
||||||
|
Request jettyRequest = null
|
||||||
|
|
||||||
|
def setupSpec() {
|
||||||
|
|
||||||
|
//Start the main Jetty HttpClient and a https client
|
||||||
|
client.start()
|
||||||
|
|
||||||
|
SslContextFactory tlsCtx = new SslContextFactory()
|
||||||
|
httpsClient = createHttpsClient(tlsCtx)
|
||||||
|
httpsClient.setFollowRedirects(false)
|
||||||
|
httpsClient.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Request buildRequest(String method, URI uri, Map<String, String> headers) {
|
||||||
|
|
||||||
|
HttpClient theClient = uri.scheme == 'https' ? httpsClient : client
|
||||||
|
|
||||||
|
Request request = theClient.newRequest(uri)
|
||||||
|
|
||||||
|
HttpMethod methodObj = HttpMethod.valueOf(method)
|
||||||
|
request.method(methodObj)
|
||||||
|
request.timeout(CONNECT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
|
||||||
|
|
||||||
|
jettyRequest = request
|
||||||
|
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String userAgent() {
|
||||||
|
if (name.methodName.startsWith('connection error') && jettyRequest.getAgent() == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return "Jetty"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int sendRequest(Request request, String method, URI uri, Map<String, String> headers) {
|
||||||
|
headers.each { k, v ->
|
||||||
|
request.header(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentResponse response = request.send()
|
||||||
|
|
||||||
|
return response.status
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class JettyClientListener implements Request.FailureListener, Response.FailureListener {
|
||||||
|
|
||||||
|
volatile Throwable failure
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void onFailure(Request requestF, Throwable failure) {
|
||||||
|
this.failure = failure
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void onFailure(Response responseF, Throwable failure) {
|
||||||
|
this.failure = failure
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void sendRequestWithCallback(Request request, String method, URI uri, Map<String, String> headers, RequestResult requestResult) {
|
||||||
|
|
||||||
|
JettyClientListener jcl = new JettyClientListener()
|
||||||
|
|
||||||
|
request.onRequestFailure(jcl)
|
||||||
|
request.onResponseFailure(jcl)
|
||||||
|
headers.each { k, v ->
|
||||||
|
request.header(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
request.send(new Response.CompleteListener() {
|
||||||
|
@Override
|
||||||
|
void onComplete(Result result) {
|
||||||
|
|
||||||
|
if (jcl.failure != null) {
|
||||||
|
requestResult.complete(jcl.failure)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
requestResult.complete(result.response.status)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean testRedirects() {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean testCausality() {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Set<AttributeKey<?>> httpAttributes(URI uri) {
|
||||||
|
Set<AttributeKey<?>> extra = [
|
||||||
|
SemanticAttributes.HTTP_SCHEME,
|
||||||
|
SemanticAttributes.HTTP_TARGET,
|
||||||
|
SemanticAttributes.HTTP_HOST
|
||||||
|
]
|
||||||
|
super.httpAttributes(uri) + extra
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -185,6 +185,9 @@ include ':instrumentation:jedis:jedis-3.0:javaagent'
|
||||||
include ':instrumentation:jetty:jetty-8.0:javaagent'
|
include ':instrumentation:jetty:jetty-8.0:javaagent'
|
||||||
include ':instrumentation:jetty:jetty-11.0:javaagent'
|
include ':instrumentation:jetty:jetty-11.0:javaagent'
|
||||||
include ':instrumentation:jetty:jetty-common:javaagent'
|
include ':instrumentation:jetty:jetty-common:javaagent'
|
||||||
|
include ':instrumentation:jetty-httpclient:jetty-httpclient-9.2:javaagent'
|
||||||
|
include ':instrumentation:jetty-httpclient:jetty-httpclient-9.2:library'
|
||||||
|
include ':instrumentation:jetty-httpclient:jetty-httpclient-9.2:testing'
|
||||||
include ':instrumentation:jms-1.1:javaagent'
|
include ':instrumentation:jms-1.1:javaagent'
|
||||||
include ':instrumentation:jms-1.1:javaagent-unit-tests'
|
include ':instrumentation:jms-1.1:javaagent-unit-tests'
|
||||||
include ':instrumentation:jsf:jsf-common:library'
|
include ':instrumentation:jsf:jsf-common:library'
|
||||||
|
|
Loading…
Reference in New Issue