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")