From fad7b2425337254dcc6cdd7be1096565f5981559 Mon Sep 17 00:00:00 2001 From: Phil <39240633+PhilKes@users.noreply.github.com> Date: Thu, 23 Feb 2023 16:54:13 +0100 Subject: [PATCH] Add Jodd-Http instrumentation (#7868) This PR resolves #7629 This adds javaagent instrumentation for the [jodd-http](https://http.jodd.org/) `HttpRequest`. It creates `Http Client Spans` and `Http Client Metrics`, the lowest supported version is `org.jodd:jodd-http:4.2.0` (most recent: `6.3.0`), since this is the first version of the library supporting java 8, having follow-redirect capability and `HttpRequest#overwriteHeader()` method. The instrumented method's signature and return type `HttpRequest#send()` has not been modified since, and therefore the instrumentation works for all `jodd-http` versions above `4.2.0`. Since this is my first contribution/instrumentation, I orientated myself on the `apache-httpclient-5.0` instrumentation, but obviously I would be glad to get some feedback on this --------- Co-authored-by: Mateusz Rzeszutek --- docs/supported-libraries.md | 3 +- .../jodd-http-4.2/javaagent/build.gradle.kts | 19 ++++ .../joddhttp/v4_2/HttpHeaderSetter.java | 22 ++++ .../v4_2/JoddHttpHttpAttributesGetter.java | 76 +++++++++++++ .../v4_2/JoddHttpInstrumentation.java | 68 +++++++++++ .../v4_2/JoddHttpInstrumentationModule.java | 25 ++++ .../v4_2/JoddHttpNetAttributesGetter.java | 32 ++++++ .../joddhttp/v4_2/JoddHttpSingletons.java | 51 +++++++++ .../JoddHttpHttpAttributesGetterTest.java | 107 ++++++++++++++++++ .../joddhttp/v4_2/JoddHttpTest.java | 64 +++++++++++ settings.gradle.kts | 1 + 11 files changed, 467 insertions(+), 1 deletion(-) create mode 100644 instrumentation/jodd-http-4.2/javaagent/build.gradle.kts create mode 100644 instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/HttpHeaderSetter.java create mode 100644 instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpHttpAttributesGetter.java create mode 100644 instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpInstrumentation.java create mode 100644 instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpInstrumentationModule.java create mode 100644 instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpNetAttributesGetter.java create mode 100644 instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpSingletons.java create mode 100644 instrumentation/jodd-http-4.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpHttpAttributesGetterTest.java create mode 100644 instrumentation/jodd-http-4.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpTest.java diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index 78468a627b..a56ac113a6 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -77,6 +77,7 @@ These are the supported libraries and frameworks: | [JDBC](https://docs.oracle.com/javase/8/docs/api/java/sql/package-summary.html) | Java 8+ | [opentelemetry-jdbc](../instrumentation/jdbc/library) | [Database Client Spans] | | [Jedis](https://github.com/xetorthio/jedis) | 1.4+ | N/A | [Database Client Spans] | | [JMS](https://javaee.github.io/javaee-spec/javadocs/javax/jms/package-summary.html) | 1.1+ | N/A | [Messaging Spans] | +| [Jodd Http](https://javadoc.io/doc/org.jodd/jodd-http/latest/index.html) | 4.2+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] | | [JSP](https://javaee.github.io/javaee-spec/javadocs/javax/servlet/jsp/package-summary.html) | 2.3+ | N/A | none | | [Kotlin Coroutines](https://kotlinlang.org/docs/coroutines-overview.html) | 1.0+ | N/A | Context propagation | | [Ktor](https://github.com/ktorio/ktor) | 1.0+ | [opentelemetry-ktor-1.0](../instrumentation/ktor/ktor-1.0/library),
[opentelemetry-ktor-2.0](../instrumentation/ktor/ktor-2.0/library) | [HTTP Server Spans], [HTTP Server Metrics] | @@ -101,7 +102,7 @@ These are the supported libraries and frameworks: | [Rediscala](https://github.com/etaty/rediscala) | 1.8+ | N/A | [Database Client Spans] | | [Redisson](https://github.com/redisson/redisson) | 3.0+ | N/A | [Database Client Spans] | | [RESTEasy](https://resteasy.github.io/) | 3.0+ | N/A | Provides `http.route` [2], Controller Spans [3] | -| [Restlet](https://restlet.github.io/) | 1.0+ | [opentelemetry-restlet-1.1](../instrumentation/restlet/restlet-1.1/library),
[opentelemetry-restlet-2.0](../instrumentation/restlet/restlet-2.0/library) | [HTTP Server Spans], [HTTP Server Metrics] | +| [Restlet](https://restlet.github.io/) | 1.0+ | [opentelemetry-restlet-1.1](../instrumentation/restlet/restlet-1.1/library),
[opentelemetry-restlet-2.0](../instrumentation/restlet/restlet-2.0/library) | [HTTP Server Spans], [HTTP Server Metrics] | | [RMI](https://docs.oracle.com/en/java/javase/11/docs/api/java.rmi/java/rmi/package-summary.html) | Java 8+ | | [RPC Client Spans], [RPC Server Spans] | | [RxJava](https://github.com/ReactiveX/RxJava) | 1.0+ | [opentelemetry-rxjava-1.0](../instrumentation/rxjava/rxjava-1.0/library),
[opentelemetry-rxjava-2.0](../instrumentation/rxjava/rxjava-2.0/library),
[opentelemetry-rxjava-3.0](../instrumentation/rxjava/rxjava-3.0/library),
[opentelemetry-rxjava-3.1.1](../instrumentation/rxjava/rxjava-3.1.1/library) | Context propagation | | [Scala ForkJoinPool](https://www.scala-lang.org/api/2.12.0/scala/concurrent/forkjoin/package$$ForkJoinPool$.html) | 2.8+ | N/A | Context propagation | diff --git a/instrumentation/jodd-http-4.2/javaagent/build.gradle.kts b/instrumentation/jodd-http-4.2/javaagent/build.gradle.kts new file mode 100644 index 0000000000..e091128cbd --- /dev/null +++ b/instrumentation/jodd-http-4.2/javaagent/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +muzzle { + pass { + group.set("org.jodd") + module.set("jodd-http") + versions.set("[4.2.0,)") + } +} + +dependencies { + // 4.2 is the first version with java 8, follow-redirects and HttpRequest#headerOverwrite method + library("org.jodd:jodd-http:4.2.0") + + testImplementation(project(":instrumentation:jodd-http-4.2:javaagent")) + testImplementation(project(":instrumentation-api-semconv")) +} diff --git a/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/HttpHeaderSetter.java b/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/HttpHeaderSetter.java new file mode 100644 index 0000000000..1dad15503c --- /dev/null +++ b/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/HttpHeaderSetter.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2; + +import io.opentelemetry.context.propagation.TextMapSetter; +import javax.annotation.Nullable; +import jodd.http.HttpRequest; + +enum HttpHeaderSetter implements TextMapSetter { + INSTANCE; + + @Override + public void set(@Nullable HttpRequest carrier, String key, String value) { + if (carrier == null) { + return; + } + carrier.headerOverwrite(key, value); + } +} diff --git a/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpHttpAttributesGetter.java b/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpHttpAttributesGetter.java new file mode 100644 index 0000000000..f4d221fddd --- /dev/null +++ b/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpHttpAttributesGetter.java @@ -0,0 +1,76 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2; + +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 static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HttpFlavorValues.HTTP_3_0; + +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesGetter; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; +import jodd.http.HttpRequest; +import jodd.http.HttpResponse; + +final class JoddHttpHttpAttributesGetter + implements HttpClientAttributesGetter { + private static final Logger logger = + Logger.getLogger(JoddHttpHttpAttributesGetter.class.getName()); + private static final Set ALLOWED_HTTP_FLAVORS = + new HashSet<>(Arrays.asList(HTTP_1_0, HTTP_1_1, HTTP_2_0, HTTP_3_0)); + + @Override + public String getMethod(HttpRequest request) { + return request.method(); + } + + @Override + public String getUrl(HttpRequest request) { + return request.url(); + } + + @Override + public List getRequestHeader(HttpRequest request, String name) { + return request.headers(name); + } + + @Override + public Integer getStatusCode( + HttpRequest request, HttpResponse response, @Nullable Throwable error) { + return response.statusCode(); + } + + @Override + @Nullable + public String getFlavor(HttpRequest request, @Nullable HttpResponse response) { + String httpVersion = request.httpVersion(); + if (httpVersion == null && response != null) { + httpVersion = response.httpVersion(); + } + if (httpVersion != null) { + if (httpVersion.contains("/")) { + httpVersion = httpVersion.substring(httpVersion.lastIndexOf("/") + 1); + } + + if (ALLOWED_HTTP_FLAVORS.contains(httpVersion)) { + return httpVersion; + } + } + logger.log(Level.FINE, "unexpected http protocol version: {0}", httpVersion); + return null; + } + + @Override + public List getResponseHeader(HttpRequest request, HttpResponse response, String name) { + return response.headers(name); + } +} diff --git a/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpInstrumentation.java b/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpInstrumentation.java new file mode 100644 index 0000000000..e6177ee1c5 --- /dev/null +++ b/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpInstrumentation.java @@ -0,0 +1,68 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2; + +import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; +import static io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2.JoddHttpSingletons.instrumenter; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import jodd.http.HttpRequest; +import jodd.http.HttpResponse; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +class JoddHttpInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return named("jodd.http.HttpRequest"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod().and(named("send")).and(takesArguments(0)), + this.getClass().getName() + "$RequestAdvice"); + } + + @SuppressWarnings("unused") + public static class RequestAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void methodEnter( + @Advice.This HttpRequest request, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + Context parentContext = currentContext(); + if (!instrumenter().shouldStart(parentContext, request)) { + return; + } + context = instrumenter().start(parentContext, request); + scope = context.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void methodExit( + @Advice.This HttpRequest request, + @Advice.Return HttpResponse response, + @Advice.Thrown Throwable throwable, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + if (scope == null) { + return; + } + scope.close(); + instrumenter().end(context, request, response, throwable); + } + } +} diff --git a/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpInstrumentationModule.java b/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpInstrumentationModule.java new file mode 100644 index 0000000000..11f674fb4d --- /dev/null +++ b/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpInstrumentationModule.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import java.util.Collections; +import java.util.List; + +@AutoService(InstrumentationModule.class) +public class JoddHttpInstrumentationModule extends InstrumentationModule { + + public JoddHttpInstrumentationModule() { + super("jodd-http", "jodd-http-4.2"); + } + + @Override + public List typeInstrumentations() { + return Collections.singletonList(new JoddHttpInstrumentation()); + } +} diff --git a/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpNetAttributesGetter.java b/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpNetAttributesGetter.java new file mode 100644 index 0000000000..23cc33e3a2 --- /dev/null +++ b/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpNetAttributesGetter.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2; + +import io.opentelemetry.instrumentation.api.instrumenter.net.NetClientAttributesGetter; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import javax.annotation.Nullable; +import jodd.http.HttpRequest; +import jodd.http.HttpResponse; + +final class JoddHttpNetAttributesGetter + implements NetClientAttributesGetter { + + @Override + public String getTransport(HttpRequest request, @Nullable HttpResponse response) { + return SemanticAttributes.NetTransportValues.IP_TCP; + } + + @Override + @Nullable + public String getPeerName(HttpRequest request) { + return request.host(); + } + + @Override + public Integer getPeerPort(HttpRequest request) { + return request.port(); + } +} diff --git a/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpSingletons.java b/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpSingletons.java new file mode 100644 index 0000000000..639620e179 --- /dev/null +++ b/instrumentation/jodd-http-4.2/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpSingletons.java @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpClientMetrics; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.net.PeerServiceAttributesExtractor; +import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig; +import jodd.http.HttpRequest; +import jodd.http.HttpResponse; + +public final class JoddHttpSingletons { + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jodd-http-4.2"; + + private static final Instrumenter INSTRUMENTER; + + static { + JoddHttpHttpAttributesGetter httpAttributesGetter = new JoddHttpHttpAttributesGetter(); + JoddHttpNetAttributesGetter netAttributesGetter = new JoddHttpNetAttributesGetter(); + + INSTRUMENTER = + Instrumenter.builder( + GlobalOpenTelemetry.get(), + INSTRUMENTATION_NAME, + HttpSpanNameExtractor.create(httpAttributesGetter)) + .setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributesGetter)) + .addAttributesExtractor( + HttpClientAttributesExtractor.builder(httpAttributesGetter, netAttributesGetter) + .setCapturedRequestHeaders(CommonConfig.get().getClientRequestHeaders()) + .setCapturedResponseHeaders(CommonConfig.get().getClientResponseHeaders()) + .build()) + .addAttributesExtractor( + PeerServiceAttributesExtractor.create( + netAttributesGetter, CommonConfig.get().getPeerServiceMapping())) + .addOperationMetrics(HttpClientMetrics.get()) + .buildClientInstrumenter(HttpHeaderSetter.INSTANCE); + } + + public static Instrumenter instrumenter() { + return INSTRUMENTER; + } + + private JoddHttpSingletons() {} +} diff --git a/instrumentation/jodd-http-4.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpHttpAttributesGetterTest.java b/instrumentation/jodd-http-4.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpHttpAttributesGetterTest.java new file mode 100644 index 0000000000..04c6a6409e --- /dev/null +++ b/instrumentation/jodd-http-4.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpHttpAttributesGetterTest.java @@ -0,0 +1,107 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2; + +import static jodd.http.HttpStatus.HTTP_FORBIDDEN; +import static jodd.http.HttpStatus.HTTP_INTERNAL_ERROR; +import static jodd.http.HttpStatus.HTTP_NOT_FOUND; +import static jodd.http.HttpStatus.HTTP_OK; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HttpFlavorValues; +import java.util.Arrays; +import java.util.List; +import jodd.http.HttpBase; +import jodd.http.HttpRequest; +import jodd.http.HttpResponse; +import org.junit.jupiter.api.Test; + +class JoddHttpHttpAttributesGetterTest { + + private static final JoddHttpHttpAttributesGetter attributesGetter = + new JoddHttpHttpAttributesGetter(); + + @Test + void getMethod() { + for (String method : Arrays.asList("GET", "PUT", "POST", "PATCH")) { + assertEquals(method, attributesGetter.getMethod(new HttpRequest().method(method))); + } + } + + @Test + void getUrl() { + HttpRequest request = + HttpRequest.get("/test/subpath") + .host("test.com") + .query("param1", "val1") + .query("param2", "val1") + .query("param2", "val2"); + assertEquals( + "http://test.com/test/subpath?param1=val1¶m2=val1¶m2=val2", + attributesGetter.getUrl(request)); + } + + @Test + void getRequestHeader() { + HttpRequest request = + HttpRequest.get("/test") + .header("single", "val1") + .header("multiple", "val1") + .header("multiple", "val2"); + List headerVals = attributesGetter.getRequestHeader(request, "single"); + assertEquals(1, headerVals.size()); + assertEquals("val1", headerVals.get(0)); + headerVals = attributesGetter.getRequestHeader(request, "multiple"); + assertEquals(2, headerVals.size()); + assertEquals("val1", headerVals.get(0)); + assertEquals("val2", headerVals.get(1)); + headerVals = attributesGetter.getRequestHeader(request, "not-existing"); + assertEquals(0, headerVals.size()); + } + + @Test + void getStatusCode() { + for (Integer code : + Arrays.asList(HTTP_OK, HTTP_FORBIDDEN, HTTP_INTERNAL_ERROR, HTTP_NOT_FOUND)) { + assertEquals( + code, attributesGetter.getStatusCode(null, new HttpResponse().statusCode(code), null)); + } + } + + @Test + void getFlavor() { + HttpRequest request = HttpRequest.get("/test").httpVersion(HttpBase.HTTP_1_1); + assertEquals(HttpFlavorValues.HTTP_1_1, attributesGetter.getFlavor(request, null)); + request.httpVersion(null); + assertNull(attributesGetter.getFlavor(request, null)); + request.httpVersion("INVALID-HTTP-Version"); + assertNull(attributesGetter.getFlavor(request, null)); + request.httpVersion(null); + HttpResponse response = new HttpResponse().httpVersion(HttpBase.HTTP_1_0); + assertEquals(HttpFlavorValues.HTTP_1_0, attributesGetter.getFlavor(request, response)); + response.httpVersion(null); + assertNull(attributesGetter.getFlavor(request, response)); + } + + @Test + void getResponseHeader() { + HttpResponse response = + new HttpResponse() + .header("single", "val1") + .header("multiple", "val1") + .header("multiple", "val2"); + List headerVals = attributesGetter.getResponseHeader(null, response, "single"); + assertEquals(1, headerVals.size()); + assertEquals("val1", headerVals.get(0)); + headerVals = attributesGetter.getResponseHeader(null, response, "multiple"); + assertEquals(2, headerVals.size()); + assertEquals("val1", headerVals.get(0)); + assertEquals("val2", headerVals.get(1)); + headerVals = attributesGetter.getResponseHeader(null, response, "not-existing"); + assertEquals(0, headerVals.size()); + } +} diff --git a/instrumentation/jodd-http-4.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpTest.java b/instrumentation/jodd-http-4.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpTest.java new file mode 100644 index 0000000000..e883611e37 --- /dev/null +++ b/instrumentation/jodd-http-4.2/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/joddhttp/v4_2/JoddHttpTest.java @@ -0,0 +1,64 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.joddhttp.v4_2; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.AbstractHttpClientTest; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestOptions; +import java.net.URI; +import java.util.Map; +import jodd.http.HttpRequest; +import org.jetbrains.annotations.Nullable; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class JoddHttpTest extends AbstractHttpClientTest { + + @RegisterExtension + static final InstrumentationExtension testing = HttpClientInstrumentationExtension.forAgent(); + + @Nullable + @Override + protected String userAgent() { + return "Jodd HTTP"; + } + + @Override + public HttpRequest buildRequest(String method, URI uri, Map headers) { + HttpRequest request = + new HttpRequest() + .method(method) + .set(uri.toString()) + .followRedirects(true) + .connectionKeepAlive(true) + .header("user-agent", userAgent()); + for (Map.Entry header : headers.entrySet()) { + request.headerOverwrite(header.getKey(), header.getValue()); + } + if (uri.toString().contains("/read-timeout")) { + request.timeout((int) READ_TIMEOUT.toMillis()); + } + return request; + } + + @Override + public int sendRequest(HttpRequest request, String method, URI uri, Map headers) + throws Exception { + request.method(method).set(uri.toString()); + for (Map.Entry header : headers.entrySet()) { + request.headerOverwrite(header.getKey(), header.getValue()); + } + return request.send().statusCode(); + } + + @Override + protected void configure(HttpClientTestOptions.Builder optionsBuilder) { + optionsBuilder.enableTestReadTimeout(); + optionsBuilder.disableTestCallback(); + // Circular Redirects are not explicitly handled by jodd-http + optionsBuilder.disableTestCircularRedirects(); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 5dcd2ed418..760a0d8ffc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -300,6 +300,7 @@ hideFromDependabot(":instrumentation:jms:jms-common:javaagent") hideFromDependabot(":instrumentation:jms:jms-common:javaagent-unit-tests") hideFromDependabot(":instrumentation:jmx-metrics:javaagent") hideFromDependabot(":instrumentation:jmx-metrics:library") +hideFromDependabot(":instrumentation:jodd-http-4.2:javaagent") hideFromDependabot(":instrumentation:jsf:jsf-javax-common:javaagent") hideFromDependabot(":instrumentation:jsf:jsf-javax-common:testing") hideFromDependabot(":instrumentation:jsf:jsf-jakarta-common:javaagent")