From ab1227c50746b70b3e0ed40d59225e1bb46adac1 Mon Sep 17 00:00:00 2001 From: Tommy Ludwig <8924140+shakuzen@users.noreply.github.com> Date: Tue, 12 Jul 2022 01:15:23 +0900 Subject: [PATCH] Http client instrumentation TCK (#3258) Introduce test suite for HTTP client instrumentations to check that all implementations have the naming and tags expected. This can also help instrumentors ensure backwards compatibility of the HTTP client instrumentation across changes to it. Introduced as incubating until we get feedback from instrumentation outside of Micrometer on the usability and usefulness of the TCK in this form. --- micrometer-core/gradle.lockfile | 12 +- micrometer-test/build.gradle | 2 + micrometer-test/gradle.lockfile | 9 +- ...imingInstrumentationVerificationTests.java | 154 ++++++++++++++++++ .../InstrumentationVerificationTests.java | 28 ++++ .../core/instrument/package-info.java | 16 ++ ...imingInstrumentationVerificationTests.java | 72 ++++++++ ...imingInstrumentationVerificationTests.java | 60 +++++++ ...imingInstrumentationVerificationTests.java | 46 ++++++ .../gradle.lockfile | 12 +- 10 files changed, 402 insertions(+), 9 deletions(-) create mode 100644 micrometer-test/src/main/java/io/micrometer/core/instrument/HttpClientTimingInstrumentationVerificationTests.java create mode 100644 micrometer-test/src/main/java/io/micrometer/core/instrument/InstrumentationVerificationTests.java create mode 100644 micrometer-test/src/main/java/io/micrometer/core/instrument/package-info.java create mode 100644 micrometer-test/src/test/java/io/micrometer/core/instrument/ApacheHttpClientTimingInstrumentationVerificationTests.java create mode 100644 micrometer-test/src/test/java/io/micrometer/core/instrument/JettyClientTimingInstrumentationVerificationTests.java create mode 100644 micrometer-test/src/test/java/io/micrometer/core/instrument/OkHttpClientTimingInstrumentationVerificationTests.java diff --git a/micrometer-core/gradle.lockfile b/micrometer-core/gradle.lockfile index 3fc4452f7d..7f0f019e54 100644 --- a/micrometer-core/gradle.lockfile +++ b/micrometer-core/gradle.lockfile @@ -131,11 +131,15 @@ org.assertj:assertj-core:3.22.0=testCompileClasspath,testRuntimeClasspath org.awaitility:awaitility:4.2.0=testCompileClasspath,testRuntimeClasspath org.checkerframework:checker-qual:2.11.1=nohttp org.checkerframework:checker-qual:3.12.0=checkstyle,compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.eclipse.jetty:jetty-client:9.4.44.v20210927=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.eclipse.jetty:jetty-http:9.4.44.v20210927=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.eclipse.jetty:jetty-io:9.4.44.v20210927=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-client:9.4.44.v20210927=runtimeClasspath +org.eclipse.jetty:jetty-client:9.4.48.v20220622=compileClasspath,testCompileClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-http:9.4.44.v20210927=runtimeClasspath +org.eclipse.jetty:jetty-http:9.4.48.v20220622=compileClasspath,testCompileClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-io:9.4.44.v20210927=runtimeClasspath +org.eclipse.jetty:jetty-io:9.4.48.v20220622=compileClasspath,testCompileClasspath,testRuntimeClasspath org.eclipse.jetty:jetty-server:9.4.44.v20210927=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.eclipse.jetty:jetty-util:9.4.44.v20210927=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-util:9.4.44.v20210927=runtimeClasspath +org.eclipse.jetty:jetty-util:9.4.48.v20220622=compileClasspath,testCompileClasspath,testRuntimeClasspath org.ehcache:ehcache:3.9.7=testCompileClasspath,testRuntimeClasspath org.glassfish.hk2.external:aopalliance-repackaged:2.6.1=testRuntimeClasspath org.glassfish.hk2.external:jakarta.inject:2.6.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath diff --git a/micrometer-test/build.gradle b/micrometer-test/build.gradle index cda0a9329f..e9e8b7d29b 100644 --- a/micrometer-test/build.gradle +++ b/micrometer-test/build.gradle @@ -22,4 +22,6 @@ dependencies { testImplementation 'com.hazelcast:hazelcast' testImplementation 'com.squareup.okhttp3:okhttp' testImplementation 'io.projectreactor.netty:reactor-netty-http' + testImplementation 'org.apache.httpcomponents:httpclient' + testImplementation 'org.eclipse.jetty:jetty-client' } diff --git a/micrometer-test/gradle.lockfile b/micrometer-test/gradle.lockfile index e3d711d01d..9ad631e56f 100644 --- a/micrometer-test/gradle.lockfile +++ b/micrometer-test/gradle.lockfile @@ -20,8 +20,9 @@ com.puppycrawl.tools:checkstyle:9.0.1=checkstyle com.squareup.okhttp3:okhttp:5.0.0-alpha.2=testCompileClasspath,testRuntimeClasspath com.squareup.okio:okio:2.9.0=testCompileClasspath,testRuntimeClasspath commons-beanutils:commons-beanutils:1.9.4=checkstyle,nohttp +commons-codec:commons-codec:1.11=testCompileClasspath,testRuntimeClasspath commons-collections:commons-collections:3.2.2=checkstyle,nohttp -commons-logging:commons-logging:1.2=nohttp +commons-logging:commons-logging:1.2=nohttp,testCompileClasspath,testRuntimeClasspath info.picocli:picocli:3.9.5=nohttp-cli info.picocli:picocli:4.3.1=nohttp info.picocli:picocli:4.6.1=checkstyle @@ -62,10 +63,16 @@ net.sf.saxon:Saxon-HE:10.6=checkstyle net.sf.saxon:Saxon-HE:9.9.1-7=nohttp org.antlr:antlr4-runtime:4.8-1=nohttp org.antlr:antlr4-runtime:4.9.2=checkstyle +org.apache.httpcomponents:httpclient:4.5.13=testCompileClasspath,testRuntimeClasspath +org.apache.httpcomponents:httpcore:4.4.13=testCompileClasspath,testRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=compileClasspath,testCompileClasspath org.assertj:assertj-core:3.22.0=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.checkerframework:checker-qual:2.11.1=nohttp org.checkerframework:checker-qual:3.12.0=checkstyle,testCompileClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-client:9.4.48.v20220622=testCompileClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-http:9.4.48.v20220622=testCompileClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-io:9.4.48.v20220622=testCompileClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-util:9.4.48.v20220622=testCompileClasspath,testRuntimeClasspath org.hdrhistogram:HdrHistogram:2.1.12=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.javassist:javassist:3.26.0-GA=checkstyle org.jboss.spec.javax.interceptor:jboss-interceptors-api_1.1_spec:1.0.0.Beta1=testCompileClasspath,testRuntimeClasspath diff --git a/micrometer-test/src/main/java/io/micrometer/core/instrument/HttpClientTimingInstrumentationVerificationTests.java b/micrometer-test/src/main/java/io/micrometer/core/instrument/HttpClientTimingInstrumentationVerificationTests.java new file mode 100644 index 0000000000..d60f3ab435 --- /dev/null +++ b/micrometer-test/src/main/java/io/micrometer/core/instrument/HttpClientTimingInstrumentationVerificationTests.java @@ -0,0 +1,154 @@ +/* + * Copyright 2022 VMware, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micrometer.core.instrument; + +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo; +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import io.micrometer.core.annotation.Incubating; +import io.micrometer.core.lang.Nullable; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.URI; +import java.util.concurrent.TimeUnit; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test suite for HTTP client timing instrumentation that verifies the expected metrics + * are registered and recorded after different scenarios. Use this suite to ensure that + * your instrumentation has the expected naming and tags. A locally running server is used + * to receive real requests from an instrumented HTTP client. + */ +@WireMockTest +@Incubating(since = "1.9.2") +public abstract class HttpClientTimingInstrumentationVerificationTests extends InstrumentationVerificationTests { + + enum HttpMethod { + + GET, POST; + + } + + /** + * A default is provided that should be preferred by new instrumentations. Existing + * instrumentations that use a different value to maintain backwards compatibility may + * override this method to run tests with a different name used in assertions. + * @return name of the meter timing http client requests + */ + protected String timerName() { + return "http.client.requests"; + } + + /** + * Send an HTTP request using the instrumented HTTP client to the given base URL and + * path on the locally running server. The HTTP client instrumentation must be + * configured to tag the templated path to pass this test suite. The templated path + * will contain path variables surrounded by curly brackets to be substituted. For + * example, for the full templated URL {@literal http://localhost:8080/cart/{cartId}} + * the baseUrl would be {@literal http://localhost:8080}, the templatedPath would be + * {@literal /cart/{cartId}}. One string pathVariables argument is expected for + * substituting the cartId path variable. The number of pathVariables arguments SHOULD + * exactly match the number of path variables in the templatedPath. + * @param method http method to use to send the request + * @param baseUrl portion of the URL before the path where to send the request + * @param templatedPath the path portion of the URL after the baseUrl, starting with a + * forward slash, and optionally containing path variable placeholders + * @param pathVariables optional variables to substitute into the templatedPath + */ + abstract void sendHttpRequest(HttpMethod method, @Nullable byte[] body, URI baseUrl, String templatedPath, + String... pathVariables); + + /** + * Convenience method provided to substitute the template placeholders for the + * provided path variables. The number of pathVariables argument SHOULD match the + * number of placeholders in the templatedPath. Substitutions will be made in order. + * @param templatedPath a URL path optionally containing placeholders in curly + * brackets + * @param pathVariables path variable values for which placeholders should be + * substituted + * @return path string with substitutions, if any, performed + */ + protected String substitutePathVariables(String templatedPath, String... pathVariables) { + if (pathVariables.length == 0) { + return templatedPath; + } + String substituted = templatedPath; + for (String substitution : pathVariables) { + substituted = substituted.replaceFirst("\\{.*?}", substitution); + } + return substituted; + } + + @Test + void getTemplatedPathForUri(WireMockRuntimeInfo wmRuntimeInfo) { + stubFor(get(anyUrl()).willReturn(ok())); + + String templatedPath = "/customers/{customerId}/carts/{cartId}"; + sendHttpRequest(HttpMethod.GET, null, URI.create(wmRuntimeInfo.getHttpBaseUrl()), templatedPath, "112", "5"); + + Timer timer = getRegistry().get(timerName()).tags("method", "GET", "status", "200", "uri", templatedPath) + .timer(); + assertThat(timer.count()).isEqualTo(1); + assertThat(timer.totalTime(TimeUnit.NANOSECONDS)).isPositive(); + } + + @Test + @Disabled("apache/jetty http client instrumentation currently fails this test") + void timedWhenServerIsMissing() throws IOException { + int unusedPort = 0; + try (ServerSocket server = new ServerSocket(0)) { + unusedPort = server.getLocalPort(); + } + + try { + sendHttpRequest(HttpMethod.GET, null, URI.create("http://localhost:" + unusedPort), "/anything"); + } + catch (Throwable ignore) { + } + + Timer timer = getRegistry().get(timerName()).tags("method", "GET").timer(); + + assertThat(timer.count()).isEqualTo(1); + assertThat(timer.totalTime(TimeUnit.NANOSECONDS)).isPositive(); + } + + @Test + void serverException(WireMockRuntimeInfo wmRuntimeInfo) { + stubFor(get(anyUrl()).willReturn(serverError())); + + sendHttpRequest(HttpMethod.GET, null, URI.create(wmRuntimeInfo.getHttpBaseUrl()), "/socks"); + + Timer timer = getRegistry().get(timerName()).tags("method", "GET", "status", "500").timer(); + assertThat(timer.count()).isEqualTo(1); + assertThat(timer.totalTime(TimeUnit.NANOSECONDS)).isPositive(); + } + + @Test + void clientException(WireMockRuntimeInfo wmRuntimeInfo) { + stubFor(post(anyUrl()).willReturn(badRequest())); + + sendHttpRequest(HttpMethod.POST, new byte[0], URI.create(wmRuntimeInfo.getHttpBaseUrl()), "/socks"); + + Timer timer = getRegistry().get(timerName()).tags("method", "POST", "status", "400").timer(); + assertThat(timer.count()).isEqualTo(1); + assertThat(timer.totalTime(TimeUnit.NANOSECONDS)).isPositive(); + } + +} diff --git a/micrometer-test/src/main/java/io/micrometer/core/instrument/InstrumentationVerificationTests.java b/micrometer-test/src/main/java/io/micrometer/core/instrument/InstrumentationVerificationTests.java new file mode 100644 index 0000000000..b3b349f5e3 --- /dev/null +++ b/micrometer-test/src/main/java/io/micrometer/core/instrument/InstrumentationVerificationTests.java @@ -0,0 +1,28 @@ +/* + * Copyright 2022 VMware, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micrometer.core.instrument; + +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; + +abstract class InstrumentationVerificationTests { + + private final MeterRegistry registry = new SimpleMeterRegistry(); + + MeterRegistry getRegistry() { + return registry; + } + +} diff --git a/micrometer-test/src/main/java/io/micrometer/core/instrument/package-info.java b/micrometer-test/src/main/java/io/micrometer/core/instrument/package-info.java new file mode 100644 index 0000000000..f557a35c2a --- /dev/null +++ b/micrometer-test/src/main/java/io/micrometer/core/instrument/package-info.java @@ -0,0 +1,16 @@ +/* + * Copyright 2022 VMware, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micrometer.core.instrument; diff --git a/micrometer-test/src/test/java/io/micrometer/core/instrument/ApacheHttpClientTimingInstrumentationVerificationTests.java b/micrometer-test/src/test/java/io/micrometer/core/instrument/ApacheHttpClientTimingInstrumentationVerificationTests.java new file mode 100644 index 0000000000..f7f8e7ac68 --- /dev/null +++ b/micrometer-test/src/test/java/io/micrometer/core/instrument/ApacheHttpClientTimingInstrumentationVerificationTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2022 VMware, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micrometer.core.instrument; + +import io.micrometer.core.instrument.binder.httpcomponents.DefaultUriMapper; +import io.micrometer.core.instrument.binder.httpcomponents.MicrometerHttpRequestExecutor; +import io.micrometer.core.lang.Nullable; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.entity.BasicHttpEntity; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URI; + +class ApacheHttpClientTimingInstrumentationVerificationTests extends HttpClientTimingInstrumentationVerificationTests { + + private final HttpClient httpClient = HttpClientBuilder.create() + .setRequestExecutor(MicrometerHttpRequestExecutor.builder(getRegistry()).build()).build(); + + @Override + protected String timerName() { + return "httpcomponents.httpclient.request"; + } + + @Override + void sendHttpRequest(HttpMethod method, @Nullable byte[] body, URI baseUri, String templatedPath, + String... pathVariables) { + try { + EntityUtils.consume( + httpClient.execute(makeRequest(method, body, baseUri, templatedPath, pathVariables)).getEntity()); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + private HttpUriRequest makeRequest(HttpMethod method, @Nullable byte[] body, URI baseUri, String templatedPath, + String... pathVariables) { + HttpEntityEnclosingRequestBase request = new HttpEntityEnclosingRequestBase() { + @Override + public String getMethod() { + return method.name(); + } + }; + if (body != null) { + BasicHttpEntity entity = new BasicHttpEntity(); + entity.setContent(new ByteArrayInputStream(body)); + request.setEntity(entity); + } + request.setURI(URI.create(baseUri + substitutePathVariables(templatedPath, pathVariables))); + request.setHeader(DefaultUriMapper.URI_PATTERN_HEADER, templatedPath); + return request; + } + +} diff --git a/micrometer-test/src/test/java/io/micrometer/core/instrument/JettyClientTimingInstrumentationVerificationTests.java b/micrometer-test/src/test/java/io/micrometer/core/instrument/JettyClientTimingInstrumentationVerificationTests.java new file mode 100644 index 0000000000..df85afbe9f --- /dev/null +++ b/micrometer-test/src/test/java/io/micrometer/core/instrument/JettyClientTimingInstrumentationVerificationTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2022 VMware, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micrometer.core.instrument; + +import io.micrometer.core.instrument.binder.jetty.JettyClientMetrics; +import io.micrometer.core.lang.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.util.BytesContentProvider; +import org.junit.jupiter.api.BeforeEach; + +import java.net.URI; + +class JettyClientTimingInstrumentationVerificationTests extends HttpClientTimingInstrumentationVerificationTests { + + private final HttpClient httpClient = new HttpClient(); + + @Override + protected String timerName() { + return "jetty.client.requests"; + } + + @BeforeEach + void setup() throws Exception { + httpClient.getRequestListeners().add(JettyClientMetrics + .builder(getRegistry(), result -> result.getRequest().getHeaders().get("URI_PATTERN")).build()); + httpClient.start(); + } + + @Override + void sendHttpRequest(HttpMethod method, @Nullable byte[] body, URI baseUri, String templatedPath, + String... pathVariables) { + try { + Request request = httpClient.newRequest(baseUri + substitutePathVariables(templatedPath, pathVariables)) + .method(method.name()).header("URI_PATTERN", templatedPath); + if (body != null) { + request.content(new BytesContentProvider(body)); + } + request.send(); + httpClient.stop(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/micrometer-test/src/test/java/io/micrometer/core/instrument/OkHttpClientTimingInstrumentationVerificationTests.java b/micrometer-test/src/test/java/io/micrometer/core/instrument/OkHttpClientTimingInstrumentationVerificationTests.java new file mode 100644 index 0000000000..3aa24fe76f --- /dev/null +++ b/micrometer-test/src/test/java/io/micrometer/core/instrument/OkHttpClientTimingInstrumentationVerificationTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2022 VMware, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.micrometer.core.instrument; + +import io.micrometer.core.instrument.binder.okhttp3.OkHttpMetricsEventListener; +import io.micrometer.core.lang.Nullable; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +import java.io.IOException; +import java.net.URI; + +class OkHttpClientTimingInstrumentationVerificationTests extends HttpClientTimingInstrumentationVerificationTests { + + OkHttpClient httpClient = new OkHttpClient.Builder() + .eventListener(OkHttpMetricsEventListener.builder(getRegistry(), timerName()).build()).build(); + + @Override + void sendHttpRequest(HttpMethod method, @Nullable byte[] body, URI baseUri, String templatedPath, + String... pathVariables) { + Request request = new Request.Builder().method(method.name(), body == null ? null : RequestBody.create(body)) + .url(baseUri + substitutePathVariables(templatedPath, pathVariables)) + .header(OkHttpMetricsEventListener.URI_PATTERN, templatedPath).build(); + try (Response ignored = httpClient.newCall(request).execute()) { + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/samples/micrometer-samples-javalin/gradle.lockfile b/samples/micrometer-samples-javalin/gradle.lockfile index d08bbd6b02..3a6ee36e04 100644 --- a/samples/micrometer-samples-javalin/gradle.lockfile +++ b/samples/micrometer-samples-javalin/gradle.lockfile @@ -46,14 +46,18 @@ org.eclipse.jetty.websocket:websocket-client:9.4.44.v20210927=compileClasspath,d org.eclipse.jetty.websocket:websocket-common:9.4.44.v20210927=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.eclipse.jetty.websocket:websocket-server:9.4.44.v20210927=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.eclipse.jetty.websocket:websocket-servlet:9.4.44.v20210927=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.eclipse.jetty:jetty-client:9.4.44.v20210927=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.eclipse.jetty:jetty-http:9.4.44.v20210927=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.eclipse.jetty:jetty-io:9.4.44.v20210927=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-client:9.4.44.v20210927=default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-client:9.4.48.v20220622=compileClasspath +org.eclipse.jetty:jetty-http:9.4.44.v20210927=default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-http:9.4.48.v20220622=compileClasspath +org.eclipse.jetty:jetty-io:9.4.44.v20210927=default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-io:9.4.48.v20220622=compileClasspath org.eclipse.jetty:jetty-security:9.4.44.v20210927=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.eclipse.jetty:jetty-server:9.4.44.v20210927=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.eclipse.jetty:jetty-servlet:9.4.44.v20210927=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.eclipse.jetty:jetty-util-ajax:9.4.44.v20210927=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.eclipse.jetty:jetty-util:9.4.44.v20210927=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-util:9.4.44.v20210927=default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-util:9.4.48.v20220622=compileClasspath org.eclipse.jetty:jetty-webapp:9.4.44.v20210927=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.eclipse.jetty:jetty-xml:9.4.44.v20210927=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.hdrhistogram:HdrHistogram:2.1.12=compileClasspath,default,runtimeClasspath,testCompileClasspath,testRuntimeClasspath