Browse Source

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 <mrzeszutek@splunk.com>
Phil 2 years ago
parent
commit
fad7b24253

+ 2 - 1
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),<br>[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),<br>[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),<br>[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),<br>[opentelemetry-rxjava-2.0](../instrumentation/rxjava/rxjava-2.0/library),<br>[opentelemetry-rxjava-3.0](../instrumentation/rxjava/rxjava-3.0/library),<br>[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                                                                    |

+ 19 - 0
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"))
+}

+ 22 - 0
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<HttpRequest> {
+  INSTANCE;
+
+  @Override
+  public void set(@Nullable HttpRequest carrier, String key, String value) {
+    if (carrier == null) {
+      return;
+    }
+    carrier.headerOverwrite(key, value);
+  }
+}

+ 76 - 0
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<HttpRequest, HttpResponse> {
+  private static final Logger logger =
+      Logger.getLogger(JoddHttpHttpAttributesGetter.class.getName());
+  private static final Set<String> 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<String> 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<String> getResponseHeader(HttpRequest request, HttpResponse response, String name) {
+    return response.headers(name);
+  }
+}

+ 68 - 0
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<TypeDescription> 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);
+    }
+  }
+}

+ 25 - 0
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<TypeInstrumentation> typeInstrumentations() {
+    return Collections.singletonList(new JoddHttpInstrumentation());
+  }
+}

+ 32 - 0
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<HttpRequest, HttpResponse> {
+
+  @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();
+  }
+}

+ 51 - 0
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<HttpRequest, HttpResponse> INSTRUMENTER;
+
+  static {
+    JoddHttpHttpAttributesGetter httpAttributesGetter = new JoddHttpHttpAttributesGetter();
+    JoddHttpNetAttributesGetter netAttributesGetter = new JoddHttpNetAttributesGetter();
+
+    INSTRUMENTER =
+        Instrumenter.<HttpRequest, HttpResponse>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<HttpRequest, HttpResponse> instrumenter() {
+    return INSTRUMENTER;
+  }
+
+  private JoddHttpSingletons() {}
+}

+ 107 - 0
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&param2=val1&param2=val2",
+        attributesGetter.getUrl(request));
+  }
+
+  @Test
+  void getRequestHeader() {
+    HttpRequest request =
+        HttpRequest.get("/test")
+            .header("single", "val1")
+            .header("multiple", "val1")
+            .header("multiple", "val2");
+    List<String> 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<String> 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());
+  }
+}

+ 64 - 0
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<HttpRequest> {
+
+  @RegisterExtension
+  static final InstrumentationExtension testing = HttpClientInstrumentationExtension.forAgent();
+
+  @Nullable
+  @Override
+  protected String userAgent() {
+    return "Jodd HTTP";
+  }
+
+  @Override
+  public HttpRequest buildRequest(String method, URI uri, Map<String, String> headers) {
+    HttpRequest request =
+        new HttpRequest()
+            .method(method)
+            .set(uri.toString())
+            .followRedirects(true)
+            .connectionKeepAlive(true)
+            .header("user-agent", userAgent());
+    for (Map.Entry<String, String> 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<String, String> headers)
+      throws Exception {
+    request.method(method).set(uri.toString());
+    for (Map.Entry<String, String> 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();
+  }
+}

+ 1 - 0
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")