getMethodsMatcher() {
+ return named(ENHANCE_CLASS_NAME).and(takesArguments(1));
+ }
+
+ @Override
+ public String getMethodsInterceptor() {
+ return ASYNC_SEND_INTERCEPTOR;
+ }
+
@Override
public boolean isOverrideArgs() {
return false;
diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/define/ResponseNotifierInstrumentation.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/define/ResponseNotifierInstrumentation.java
new file mode 100644
index 000000000..c4f39c414
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v90/client/define/ResponseNotifierInstrumentation.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.skywalking.apm.plugin.jetty.v90.client.define;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
+import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
+import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch;
+
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+
+/**
+ * {@link ResponseNotifierInstrumentation} enhance the send method in
+ * org.eclipse.jetty.client.ResponseNotifier by
+ * org.apache.skywalking.apm.plugin.jetty.v9.client.ResponseNotifierInterceptor
+ *
+ * It is implemented to be able to stop the asynchronous span in the attributes of the request.
+ */
+public class ResponseNotifierInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
+
+ private static final String ENHANCE_CLASS = "org.eclipse.jetty.client.tar";
+ private static final String ENHANCE_CLASS_NAME = "notifyComplete";
+ public static final String SYNC_SEND_INTERCEPTOR =
+ "org.apache.skywalking.apm.plugin.jetty.v9.client.ResponseNotifierInterceptor";
+
+ @Override
+ public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
+ return new ConstructorInterceptPoint[0];
+ }
+
+ @Override
+ public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
+ return new InstanceMethodsInterceptPoint[]{
+ new InstanceMethodsInterceptPoint() {
+ @Override
+ public ElementMatcher getMethodsMatcher() {
+ return named(ENHANCE_CLASS_NAME).and(takesArguments(2)).and(takesArgument(0, List.class));
+ }
+
+ @Override
+ public String getMethodsInterceptor() {
+ return SYNC_SEND_INTERCEPTOR;
+ }
+
+ @Override
+ public boolean isOverrideArgs() {
+ return false;
+ }
+ }
+ };
+ }
+
+ @Override
+ protected ClassMatch enhanceClass() {
+ return NameMatch.byName(ENHANCE_CLASS);
+ }
+
+ @Override
+ protected String[] witnessClasses() {
+ return new String[]{"org.eclipse.jetty.client.api.ProxyConfiguration"};
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/resources/skywalking-plugin.def
index b8d07b97d..c78967226 100644
--- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/resources/skywalking-plugin.def
+++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/main/resources/skywalking-plugin.def
@@ -15,3 +15,4 @@
# limitations under the License.
jetty-client-9.0=org.apache.skywalking.apm.plugin.jetty.v90.client.define.HttpRequestInstrumentation
+jetty-client-9.0=org.apache.skywalking.apm.plugin.jetty.v90.client.define.ResponseNotifierInstrumentation
diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/test/java/org/apache/skywalking/apm/plugin/jetty/v90/client/AsyncHttpRequestSendInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/test/java/org/apache/skywalking/apm/plugin/jetty/v90/client/AsyncHttpRequestSendInterceptorTest.java
new file mode 100644
index 000000000..10487bb44
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.0-plugin/src/test/java/org/apache/skywalking/apm/plugin/jetty/v90/client/AsyncHttpRequestSendInterceptorTest.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.skywalking.apm.plugin.jetty.v90.client;
+
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan;
+import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
+import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment;
+import org.apache.skywalking.apm.agent.core.context.util.TagValuePair;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.apache.skywalking.apm.agent.test.helper.SegmentHelper;
+import org.apache.skywalking.apm.agent.test.helper.SpanHelper;
+import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule;
+import org.apache.skywalking.apm.agent.test.tools.SegmentStorage;
+import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint;
+import org.apache.skywalking.apm.agent.test.tools.SpanAssert;
+import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner;
+import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpRequest;
+import org.eclipse.jetty.client.ResponseNotifier;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.hamcrest.MatcherAssert;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.skywalking.apm.agent.test.tools.SpanAssert.assertComponent;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+@RunWith(TracingSegmentRunner.class)
+public class AsyncHttpRequestSendInterceptorTest {
+ @SegmentStoragePoint
+ private SegmentStorage segmentStorage;
+
+ @Rule
+ public AgentServiceRule serviceRule = new AgentServiceRule();
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+
+ private MockHttpRequest httpRequestEnhancedInstance;
+ private AsyncHttpRequestSendInterceptor asyncHttpRequestSendInterceptor;
+
+ private MockResponseNotifier responseNotifierEnhancedInstance;
+ private ResponseNotifierInterceptor responseNotifierInterceptor;
+
+ @Mock
+ private HttpClient httpClient;
+ @Mock
+ private Response response;
+ private Object[] allArguments;
+ private Class[] argumentTypes;
+ private URI uri = URI.create("http://localhost:8080/test");
+
+ @Before
+ public void setUp() throws Exception {
+ httpRequestEnhancedInstance = new MockHttpRequest(httpClient, uri);
+ responseNotifierEnhancedInstance = new MockResponseNotifier(httpClient);
+
+ Result results = new Result(httpRequestEnhancedInstance, response);
+ allArguments = new Object[]{(Response.CompleteListener) result -> { }, results};
+ argumentTypes = new Class[]{List.class, Result.class};
+
+ asyncHttpRequestSendInterceptor = new AsyncHttpRequestSendInterceptor();
+ responseNotifierInterceptor = new ResponseNotifierInterceptor();
+ }
+
+ @Test
+ public void testHttpMethodsAround() throws Throwable {
+ asyncHttpRequestSendInterceptor.beforeMethod(httpRequestEnhancedInstance, null, allArguments, argumentTypes, null);
+ asyncHttpRequestSendInterceptor.afterMethod(httpRequestEnhancedInstance, null, allArguments, argumentTypes, null);
+ responseNotifierInterceptor.beforeMethod(responseNotifierEnhancedInstance, null, allArguments, argumentTypes, null);
+ responseNotifierInterceptor.afterMethod(responseNotifierEnhancedInstance, null, allArguments, argumentTypes, null);
+
+ Map attributes = httpRequestEnhancedInstance.getAttributes();
+ AbstractSpan asyncSpan = (AbstractSpan) attributes.get(Constants.SW_JETTY_EXIT_SPAN_KEY);
+ assertNotNull(asyncSpan);
+
+ assertJettySpan();
+
+ Assert.assertEquals(false, SpanHelper.getErrorOccurred(asyncSpan));
+ }
+
+ @Test
+ public void testMethodsAroundError() throws Throwable {
+ asyncHttpRequestSendInterceptor.beforeMethod(httpRequestEnhancedInstance, null, allArguments, argumentTypes, null);
+ asyncHttpRequestSendInterceptor.handleMethodException(httpRequestEnhancedInstance, null, allArguments, argumentTypes, new RuntimeException());
+ asyncHttpRequestSendInterceptor.afterMethod(httpRequestEnhancedInstance, null, allArguments, argumentTypes, null);
+ responseNotifierInterceptor.beforeMethod(responseNotifierEnhancedInstance, null, allArguments, argumentTypes, null);
+ responseNotifierInterceptor.handleMethodException(responseNotifierEnhancedInstance, null, allArguments, argumentTypes, new RuntimeException());
+ responseNotifierInterceptor.afterMethod(responseNotifierEnhancedInstance, null, allArguments, argumentTypes, null);
+
+ Map attributes = httpRequestEnhancedInstance.getAttributes();
+ AbstractSpan asyncSpan = (AbstractSpan) attributes.get(Constants.SW_JETTY_EXIT_SPAN_KEY);
+ assertNotNull(asyncSpan);
+
+ assertJettySpan();
+
+ Assert.assertEquals(true, SpanHelper.getErrorOccurred(asyncSpan));
+ SpanAssert.assertException(SpanHelper.getLogs(asyncSpan).get(0), RuntimeException.class);
+ }
+
+ private void assertJettySpan() {
+ assertThat(segmentStorage.getTraceSegments().size(), is(1));
+ TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0);
+
+ Assert.assertEquals(1, SegmentHelper.getSpans(traceSegment).size());
+ AbstractTracingSpan finishedSpan = SegmentHelper.getSpans(traceSegment).get(0);
+ assertNotNull(finishedSpan);
+ assertComponent(finishedSpan, ComponentsDefine.JETTY_CLIENT);
+ MatcherAssert.assertThat(finishedSpan.isExit(), is(true));
+ SpanAssert.assertLayer(finishedSpan, SpanLayer.HTTP);
+
+ List tags = SpanHelper.getTags(finishedSpan);
+ assertThat(tags.size(), is(2));
+ assertThat(tags.get(0).getValue(), is("GET"));
+ assertThat(tags.get(1).getValue(), is(uri.toString()));
+ }
+
+ private class MockHttpRequest extends HttpRequest implements EnhancedInstance {
+ public MockHttpRequest(HttpClient httpClient, URI uri) {
+ super(httpClient, uri);
+ }
+
+ @Override
+ public URI getURI() {
+ return uri;
+ }
+
+ @Override
+ public Object getSkyWalkingDynamicField() {
+ return null;
+ }
+
+ @Override
+ public void setSkyWalkingDynamicField(Object value) {
+
+ }
+ }
+
+ private class MockResponseNotifier extends ResponseNotifier implements EnhancedInstance {
+ public MockResponseNotifier(HttpClient client) {
+ super(client);
+ }
+
+ @Override
+ public Object getSkyWalkingDynamicField() {
+ return null;
+ }
+
+ @Override
+ public void setSkyWalkingDynamicField(Object value) {
+
+ }
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/AsyncHttpRequestSendInterceptor.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/AsyncHttpRequestSendInterceptor.java
new file mode 100644
index 000000000..6f8870af4
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/AsyncHttpRequestSendInterceptor.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.skywalking.apm.plugin.jetty.v9.client;
+
+import org.apache.skywalking.apm.agent.core.context.CarrierItem;
+import org.apache.skywalking.apm.agent.core.context.ContextCarrier;
+import org.apache.skywalking.apm.agent.core.context.ContextManager;
+import org.apache.skywalking.apm.agent.core.context.tag.Tags;
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
+import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
+import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
+import org.eclipse.jetty.client.HttpRequest;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.http.HttpFields;
+
+import java.lang.reflect.Method;
+
+public class AsyncHttpRequestSendInterceptor implements InstanceMethodsAroundInterceptor {
+ @Override
+ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
+ MethodInterceptResult result) throws Throwable {
+ HttpRequest request = (HttpRequest) objInst;
+ ContextCarrier contextCarrier = new ContextCarrier();
+ AbstractSpan span = ContextManager.createExitSpan(request.getURI().getPath(), contextCarrier,
+ request.getHost() + ":" + request.getPort());
+ span.setComponent(ComponentsDefine.JETTY_CLIENT);
+
+ Tags.HTTP.METHOD.set(span, getHttpMethod(request));
+ Tags.URL.set(span, request.getURI().toString());
+ SpanLayer.asHttp(span);
+
+ CarrierItem next = contextCarrier.items();
+ HttpFields field = request.getHeaders();
+ while (next.hasNext()) {
+ next = next.next();
+ field.add(next.getHeadKey(), next.getHeadValue());
+ }
+
+ span.prepareForAsync();
+ request.attribute(Constants.SW_JETTY_EXIT_SPAN_KEY, span);
+ Response.CompleteListener callback = (Response.CompleteListener) allArguments[0];
+ allArguments[0] = new CompleteListenerWrapper(callback, ContextManager.capture());
+ }
+
+ @Override
+ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
+ Object ret) throws Throwable {
+ ContextManager.stopSpan();
+ return ret;
+ }
+
+ @Override
+ public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
+ Class>[] argumentsTypes, Throwable t) {
+ ContextManager.activeSpan().log(t);
+ }
+
+ public String getHttpMethod(HttpRequest request) {
+ String method = request.getMethod();
+
+ if (method == null || method.length() == 0) {
+ method = "GET";
+ }
+
+ return method;
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/CompleteListenerWrapper.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/CompleteListenerWrapper.java
new file mode 100644
index 000000000..50697bf3e
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/CompleteListenerWrapper.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.skywalking.apm.plugin.jetty.v9.client;
+
+import org.apache.skywalking.apm.agent.core.context.ContextManager;
+import org.apache.skywalking.apm.agent.core.context.ContextSnapshot;
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
+import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
+import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+
+public class CompleteListenerWrapper implements Response.CompleteListener {
+ private Response.CompleteListener callback;
+ private ContextSnapshot context;
+
+ public CompleteListenerWrapper(Response.CompleteListener callback, ContextSnapshot context) {
+ this.callback = callback;
+ this.context = context;
+ }
+
+ @Override
+ public void onComplete(Result result) {
+ AbstractSpan span = ContextManager.createLocalSpan(Constants.PLUGIN_NAME + "/CompleteListener/onComplete");
+ span.setComponent(ComponentsDefine.JETTY_CLIENT);
+ SpanLayer.asHttp(span);
+ if (context != null) {
+ ContextManager.continued(context);
+ }
+ if (callback != null) {
+ callback.onComplete(result);
+ }
+ ContextManager.stopSpan();
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/Constants.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/Constants.java
new file mode 100644
index 000000000..614e3ff45
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/Constants.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.skywalking.apm.plugin.jetty.v9.client;
+
+public class Constants {
+ public final static String SW_JETTY_EXIT_SPAN_KEY = "SW_JETTY_EXIT_SPAN";
+
+ public final static String PLUGIN_NAME = "JettyClient9.x";
+}
diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/ResponseNotifierInterceptor.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/ResponseNotifierInterceptor.java
new file mode 100644
index 000000000..e1ba9c9ef
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/ResponseNotifierInterceptor.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.skywalking.apm.plugin.jetty.v9.client;
+
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Result;
+
+import java.lang.reflect.Method;
+import java.util.Optional;
+
+public class ResponseNotifierInterceptor implements InstanceMethodsAroundInterceptor {
+ @Override
+ public void beforeMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
+ MethodInterceptResult result) throws Throwable {
+ }
+
+ @Override
+ public Object afterMethod(EnhancedInstance objInst, Method method, Object[] allArguments, Class>[] argumentsTypes,
+ Object ret) throws Throwable {
+ // get async span and stop it.
+ Optional.ofNullable(getAsyncSpan(allArguments)).ifPresent(v -> v.asyncFinish());
+ return ret;
+ }
+
+ @Override
+ public void handleMethodException(EnhancedInstance objInst, Method method, Object[] allArguments,
+ Class>[] argumentsTypes, Throwable t) {
+ Optional.ofNullable(getAsyncSpan(allArguments)).ifPresent(v -> v.log(t));
+ }
+
+ private AbstractSpan getAsyncSpan(Object[] allArguments) {
+ Result results = (Result) allArguments[1];
+ if (results == null) {
+ return null;
+ }
+ Request request = results.getRequest();
+ if (request == null) {
+ return null;
+ }
+
+ return (AbstractSpan) request.getAttributes().get(Constants.SW_JETTY_EXIT_SPAN_KEY);
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/define/HttpRequestInstrumentation.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/define/HttpRequestInstrumentation.java
index d0456c11d..5d8cb05ee 100644
--- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/define/HttpRequestInstrumentation.java
+++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/define/HttpRequestInstrumentation.java
@@ -35,15 +35,20 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
/**
* {@link HttpRequestInstrumentation} enhance the send method without argument in
- * org.eclipse.jetty.client.HttpRequest by org.apache.skywalking.apm.plugin.jetty.client.SyncHttpRequestSendInterceptor
- * and enhance the send with org.eclipse.jetty.client.api.Response$CompleteListener parameter
- * by org.apache.skywalking.apm.plugin.jetty.client.AsyncHttpRequestSendInterceptor
+ * org.eclipse.jetty.client.HttpRequest by
+ * org.apache.skywalking.apm.plugin.jetty.client.SyncHttpRequestSendInterceptor and enhance the
+ * send with org.eclipse.jetty.client.api.Response$CompleteListener parameter by
+ * org.apache.skywalking.apm.plugin.jetty.client.AsyncHttpRequestSendInterceptor
*/
public class HttpRequestInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
private static final String ENHANCE_CLASS = "org.eclipse.jetty.client.HttpRequest";
private static final String ENHANCE_CLASS_NAME = "send";
- public static final String SYNC_SEND_INTERCEPTOR = "org.apache.skywalking.apm.plugin.jetty.v9.client.SyncHttpRequestSendInterceptor";
+ public static final String SYNC_SEND_INTERCEPTOR =
+ "org.apache.skywalking.apm.plugin.jetty.v9.client.SyncHttpRequestSendInterceptor";
+
+ public static final String ASYNC_SEND_INTERCEPTOR =
+ "org.apache.skywalking.apm.plugin.jetty.v9.client.AsyncHttpRequestSendInterceptor";
@Override
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
@@ -54,7 +59,7 @@ public class HttpRequestInstrumentation extends ClassInstanceMethodsEnhancePlugi
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
return new InstanceMethodsInterceptPoint[] {
new InstanceMethodsInterceptPoint() {
- //sync call interceptor point
+ // sync call interceptor point
@Override
public ElementMatcher getMethodsMatcher() {
return named(ENHANCE_CLASS_NAME).and(takesArguments(0));
@@ -69,6 +74,23 @@ public class HttpRequestInstrumentation extends ClassInstanceMethodsEnhancePlugi
public boolean isOverrideArgs() {
return false;
}
+ },
+ new InstanceMethodsInterceptPoint() {
+ // async call interceptor point
+ @Override
+ public ElementMatcher getMethodsMatcher() {
+ return named(ENHANCE_CLASS_NAME).and(takesArguments(1));
+ }
+
+ @Override
+ public String getMethodsInterceptor() {
+ return ASYNC_SEND_INTERCEPTOR;
+ }
+
+ @Override
+ public boolean isOverrideArgs() {
+ return true;
+ }
}
};
}
diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/define/ResponseNotifierInstrumentation.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/define/ResponseNotifierInstrumentation.java
new file mode 100644
index 000000000..d61a61ca5
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/java/org/apache/skywalking/apm/plugin/jetty/v9/client/define/ResponseNotifierInstrumentation.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.skywalking.apm.plugin.jetty.v9.client.define;
+
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+import org.apache.skywalking.apm.agent.core.plugin.WitnessMethod;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine;
+import org.apache.skywalking.apm.agent.core.plugin.match.ClassMatch;
+import org.apache.skywalking.apm.agent.core.plugin.match.NameMatch;
+
+import java.util.Collections;
+import java.util.List;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+
+/**
+ * {@link ResponseNotifierInstrumentation} enhance the send method in
+ * org.eclipse.jetty.client.ResponseNotifier by
+ * org.apache.skywalking.apm.plugin.jetty.v9.client.ResponseNotifierInterceptor
+ *
+ * It is implemented to be able to stop the asynchronous span in the attributes of the request.
+ */
+public class ResponseNotifierInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
+
+ private static final String ENHANCE_CLASS = "org.eclipse.jetty.client.ResponseNotifier";
+ private static final String ENHANCE_CLASS_NAME = "notifyComplete";
+ public static final String SYNC_SEND_INTERCEPTOR =
+ "org.apache.skywalking.apm.plugin.jetty.v9.client.ResponseNotifierInterceptor";
+
+ @Override
+ public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
+ return new ConstructorInterceptPoint[0];
+ }
+
+ @Override
+ public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
+ return new InstanceMethodsInterceptPoint[]{
+ new InstanceMethodsInterceptPoint() {
+ @Override
+ public ElementMatcher getMethodsMatcher() {
+ return named(ENHANCE_CLASS_NAME).and(takesArguments(2)).and(takesArgument(0, List.class));
+ }
+
+ @Override
+ public String getMethodsInterceptor() {
+ return SYNC_SEND_INTERCEPTOR;
+ }
+
+ @Override
+ public boolean isOverrideArgs() {
+ return false;
+ }
+ }
+ };
+ }
+
+ @Override
+ protected ClassMatch enhanceClass() {
+ return NameMatch.byName(ENHANCE_CLASS);
+ }
+
+ @Override
+ protected String[] witnessClasses() {
+ return new String[]{"org.eclipse.jetty.client.AbstractHttpClientTransport"};
+ }
+
+ @Override
+ protected List witnessMethods() {
+ return Collections.singletonList(new WitnessMethod(
+ "org.eclipse.jetty.http.HttpFields",
+ named("getQuality")
+ ));
+ }
+}
diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/resources/skywalking-plugin.def b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/resources/skywalking-plugin.def
index d430a9b9b..cc6343bc3 100644
--- a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/resources/skywalking-plugin.def
+++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/main/resources/skywalking-plugin.def
@@ -15,3 +15,4 @@
# limitations under the License.
jetty-client-9.x=org.apache.skywalking.apm.plugin.jetty.v9.client.define.HttpRequestInstrumentation
+jetty-client-9.x=org.apache.skywalking.apm.plugin.jetty.v9.client.define.ResponseNotifierInstrumentation
\ No newline at end of file
diff --git a/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jetty/v9/client/AsyncHttpRequestSendInterceptorTest.java b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jetty/v9/client/AsyncHttpRequestSendInterceptorTest.java
new file mode 100644
index 000000000..ae3dc47db
--- /dev/null
+++ b/apm-sniffer/apm-sdk-plugin/jetty-plugin/jetty-client-9.x-plugin/src/test/java/org/apache/skywalking/apm/plugin/jetty/v9/client/AsyncHttpRequestSendInterceptorTest.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.skywalking.apm.plugin.jetty.v9.client;
+
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractSpan;
+import org.apache.skywalking.apm.agent.core.context.trace.AbstractTracingSpan;
+import org.apache.skywalking.apm.agent.core.context.trace.SpanLayer;
+import org.apache.skywalking.apm.agent.core.context.trace.TraceSegment;
+import org.apache.skywalking.apm.agent.core.context.util.TagValuePair;
+import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
+import org.apache.skywalking.apm.agent.test.helper.SegmentHelper;
+import org.apache.skywalking.apm.agent.test.helper.SpanHelper;
+import org.apache.skywalking.apm.agent.test.tools.AgentServiceRule;
+import org.apache.skywalking.apm.agent.test.tools.SegmentStorage;
+import org.apache.skywalking.apm.agent.test.tools.SegmentStoragePoint;
+import org.apache.skywalking.apm.agent.test.tools.SpanAssert;
+import org.apache.skywalking.apm.agent.test.tools.TracingSegmentRunner;
+import org.apache.skywalking.apm.network.trace.component.ComponentsDefine;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpRequest;
+import org.eclipse.jetty.client.ResponseNotifier;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.api.Result;
+import org.hamcrest.MatcherAssert;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.skywalking.apm.agent.test.tools.SpanAssert.assertComponent;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+@RunWith(TracingSegmentRunner.class)
+public class AsyncHttpRequestSendInterceptorTest {
+ @SegmentStoragePoint
+ private SegmentStorage segmentStorage;
+
+ @Rule
+ public AgentServiceRule serviceRule = new AgentServiceRule();
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+
+ private MockHttpRequest httpRequestEnhancedInstance;
+ private AsyncHttpRequestSendInterceptor asyncHttpRequestSendInterceptor;
+
+ private MockResponseNotifier responseNotifierEnhancedInstance;
+ private ResponseNotifierInterceptor responseNotifierInterceptor;
+
+ @Mock
+ private HttpClient httpClient;
+ @Mock
+ private Response response;
+ private Object[] allArguments;
+ private Class[] argumentTypes;
+ private URI uri = URI.create("http://localhost:8080/test");
+
+ @Before
+ public void setUp() throws Exception {
+ httpRequestEnhancedInstance = new MockHttpRequest(httpClient, uri);
+ responseNotifierEnhancedInstance = new MockResponseNotifier(httpClient);
+
+ Result results = new Result(httpRequestEnhancedInstance, response);
+ allArguments = new Object[]{(Response.CompleteListener) result -> { }, results};
+ argumentTypes = new Class[]{List.class, Result.class};
+
+ asyncHttpRequestSendInterceptor = new AsyncHttpRequestSendInterceptor();
+ responseNotifierInterceptor = new ResponseNotifierInterceptor();
+ }
+
+ @Test
+ public void testHttpMethodsAround() throws Throwable {
+ asyncHttpRequestSendInterceptor.beforeMethod(httpRequestEnhancedInstance, null, allArguments, argumentTypes, null);
+ asyncHttpRequestSendInterceptor.afterMethod(httpRequestEnhancedInstance, null, allArguments, argumentTypes, null);
+ responseNotifierInterceptor.beforeMethod(responseNotifierEnhancedInstance, null, allArguments, argumentTypes, null);
+ responseNotifierInterceptor.afterMethod(responseNotifierEnhancedInstance, null, allArguments, argumentTypes, null);
+
+ Map attributes = httpRequestEnhancedInstance.getAttributes();
+ AbstractSpan asyncSpan = (AbstractSpan) attributes.get(Constants.SW_JETTY_EXIT_SPAN_KEY);
+ assertNotNull(asyncSpan);
+
+ assertJettySpan();
+
+ Assert.assertEquals(false, SpanHelper.getErrorOccurred(asyncSpan));
+ }
+
+ @Test
+ public void testMethodsAroundError() throws Throwable {
+ asyncHttpRequestSendInterceptor.beforeMethod(httpRequestEnhancedInstance, null, allArguments, argumentTypes, null);
+ asyncHttpRequestSendInterceptor.handleMethodException(httpRequestEnhancedInstance, null, allArguments, argumentTypes, new RuntimeException());
+ asyncHttpRequestSendInterceptor.afterMethod(httpRequestEnhancedInstance, null, allArguments, argumentTypes, null);
+ responseNotifierInterceptor.beforeMethod(responseNotifierEnhancedInstance, null, allArguments, argumentTypes, null);
+ responseNotifierInterceptor.handleMethodException(responseNotifierEnhancedInstance, null, allArguments, argumentTypes, new RuntimeException());
+ responseNotifierInterceptor.afterMethod(responseNotifierEnhancedInstance, null, allArguments, argumentTypes, null);
+
+ Map attributes = httpRequestEnhancedInstance.getAttributes();
+ AbstractSpan asyncSpan = (AbstractSpan) attributes.get(Constants.SW_JETTY_EXIT_SPAN_KEY);
+ assertNotNull(asyncSpan);
+
+ assertJettySpan();
+
+ Assert.assertEquals(true, SpanHelper.getErrorOccurred(asyncSpan));
+ SpanAssert.assertException(SpanHelper.getLogs(asyncSpan).get(0), RuntimeException.class);
+ }
+
+ private void assertJettySpan() {
+ assertThat(segmentStorage.getTraceSegments().size(), is(1));
+ TraceSegment traceSegment = segmentStorage.getTraceSegments().get(0);
+
+ Assert.assertEquals(1, SegmentHelper.getSpans(traceSegment).size());
+ AbstractTracingSpan finishedSpan = SegmentHelper.getSpans(traceSegment).get(0);
+ assertNotNull(finishedSpan);
+ assertComponent(finishedSpan, ComponentsDefine.JETTY_CLIENT);
+ MatcherAssert.assertThat(finishedSpan.isExit(), is(true));
+ SpanAssert.assertLayer(finishedSpan, SpanLayer.HTTP);
+
+ List tags = SpanHelper.getTags(finishedSpan);
+ assertThat(tags.size(), is(2));
+ assertThat(tags.get(0).getValue(), is("GET"));
+ assertThat(tags.get(1).getValue(), is(uri.toString()));
+ }
+
+ private class MockHttpRequest extends HttpRequest implements EnhancedInstance {
+ public MockHttpRequest(HttpClient httpClient, URI uri) {
+ super(httpClient, uri);
+ }
+
+ @Override
+ public String getMethod() {
+ return "GET";
+ }
+
+ @Override
+ public URI getURI() {
+ return uri;
+ }
+
+ @Override
+ public Object getSkyWalkingDynamicField() {
+ return null;
+ }
+
+ @Override
+ public void setSkyWalkingDynamicField(Object value) {
+
+ }
+ }
+
+ private class MockResponseNotifier extends ResponseNotifier implements EnhancedInstance {
+ public MockResponseNotifier(HttpClient client) {
+ super(client);
+ }
+
+ @Override
+ public Object getSkyWalkingDynamicField() {
+ return null;
+ }
+
+ @Override
+ public void setSkyWalkingDynamicField(Object value) {
+
+ }
+ }
+}
diff --git a/test/plugin/scenarios/jetty-scenario/config/expectedData.yaml b/test/plugin/scenarios/jetty-scenario/config/expectedData.yaml
index ea3039c06..99e3bf2e7 100644
--- a/test/plugin/scenarios/jetty-scenario/config/expectedData.yaml
+++ b/test/plugin/scenarios/jetty-scenario/config/expectedData.yaml
@@ -15,7 +15,7 @@
# limitations under the License.
segmentItems:
- serviceName: jettyserver-scenario
- segmentSize: 1
+ segmentSize: ge 3
segments:
- segmentId: not null
spans:
@@ -38,9 +38,100 @@ segmentItems:
refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not
null, parentService: jettyclient-scenario, traceId: not null}
skipAnalysis: 'false'
+ - segmentId: not null
+ spans:
+ - operationName: /jettyserver-case/case/receiveContext-1
+ parentSpanId: -1
+ spanId: 0
+ spanLayer: Http
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 19
+ isError: false
+ spanType: Entry
+ peer: ''
+ tags:
+ - {key: url, value: 'http://localhost:18080/jettyserver-case/case/receiveContext-1'}
+ - {key: http.method, value: GET}
+ - {key: http.status_code, value: '200'}
+ refs:
+ - {parentEndpoint: GET:/jettyclient-case/case/jettyclient-case, networkAddress: 'localhost:18080',
+ refType: CrossProcess, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not
+ null, parentService: jettyclient-scenario, traceId: not null}
+ skipAnalysis: 'false'
+ - segmentId: not null
+ spans:
+ - operationName: /jettyserver-case/case/receiveContext-0
+ parentSpanId: -1
+ spanId: 0
+ spanLayer: Http
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 19
+ isError: false
+ spanType: Entry
+ peer: ''
+ skipAnalysis: false
+ tags:
+ - { key: url, value: 'http://localhost:18080/jettyserver-case/case/receiveContext-0' }
+ - { key: http.method, value: GET }
+ - { key: http.status_code, value: '200' }
+ refs:
+ - { parentEndpoint: JettyClient9.x/CompleteListener/onComplete, networkAddress: 'localhost:18080',
+ refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not null,
+ parentService: jettyclient-scenario, traceId: not null }
- serviceName: jettyclient-scenario
- segmentSize: 2
+ segmentSize: ge 3
segments:
+ - segmentId: not null
+ spans:
+ - operationName: HEAD:/jettyclient-case/case/healthCheck
+ parentSpanId: -1
+ spanId: 0
+ spanLayer: Http
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 1
+ isError: false
+ spanType: Entry
+ peer: ''
+ skipAnalysis: false
+ tags:
+ - {key: url, value: 'http://localhost:8080/jettyclient-case/case/healthCheck'}
+ - {key: http.method, value: HEAD}
+ - {key: http.status_code, value: '200'}
+ - segmentId: not null
+ spans:
+ - operationName: /jettyserver-case/case/receiveContext-0
+ parentSpanId: 0
+ spanId: 1
+ spanLayer: Http
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 2
+ isError: false
+ spanType: Exit
+ peer: localhost:18080
+ skipAnalysis: false
+ tags:
+ - {key: url, value: 'http://localhost:18080/jettyserver-case/case/receiveContext-0'}
+ - {key: http.method, value: GET}
+ - {key: http.status_code, value: '200'}
+ - operationName: JettyClient9.x/CompleteListener/onComplete
+ parentSpanId: -1
+ spanId: 0
+ spanLayer: Http
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 18
+ isError: false
+ spanType: Local
+ peer: ''
+ skipAnalysis: false
+ refs:
+ - {parentEndpoint: 'GET:/jettyclient-case/case/jettyclient-case', networkAddress: '',
+ refType: CrossThread, parentSpanId: 2, parentTraceSegmentId: not null, parentServiceInstance: not null,
+ parentService: jettyclient-scenario, traceId: not null}
- segmentId: not null
spans:
- operationName: /jettyserver-case/case/receiveContext-0
@@ -53,10 +144,24 @@ segmentItems:
isError: false
spanType: Exit
peer: localhost:18080
+ skipAnalysis: false
tags:
- {key: http.method, value: GET}
- {key: url, value: 'http://localhost:18080/jettyserver-case/case/receiveContext-0'}
- skipAnalysis: 'false'
+ - operationName: /jettyserver-case/case/receiveContext-1
+ parentSpanId: 0
+ spanId: 2
+ spanLayer: Http
+ startTime: nq 0
+ endTime: nq 0
+ componentId: 18
+ isError: false
+ spanType: Exit
+ peer: localhost:18080
+ skipAnalysis: false
+ tags:
+ - {key: http.method, value: GET}
+ - {key: url, value: 'http://localhost:18080/jettyserver-case/case/receiveContext-1'}
- operationName: GET:/jettyclient-case/case/jettyclient-case
parentSpanId: -1
spanId: 0
@@ -66,8 +171,9 @@ segmentItems:
componentId: 1
isError: false
spanType: Entry
+ peer: ''
+ skipAnalysis: false
tags:
- {key: url, value: 'http://localhost:8080/jettyclient-case/case/jettyclient-case'}
- {key: http.method, value: GET}
- - {key: http.status_code, value: '200'}
- skipAnalysis: 'false'
+ - {key: http.status_code, value: '200'}
\ No newline at end of file
diff --git a/test/plugin/scenarios/jetty-scenario/jettyclient-scenario/pom.xml b/test/plugin/scenarios/jetty-scenario/jettyclient-scenario/pom.xml
index bfde57fb3..c48ad9315 100644
--- a/test/plugin/scenarios/jetty-scenario/jettyclient-scenario/pom.xml
+++ b/test/plugin/scenarios/jetty-scenario/jettyclient-scenario/pom.xml
@@ -38,6 +38,11 @@
jetty-client
${jettyclient.version}
+
+ org.apache.httpcomponents
+ httpclient
+ ${httpclient.version}
+
org.springframework.boot
diff --git a/test/plugin/scenarios/jetty-scenario/jettyclient-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyclient/controller/CaseController.java b/test/plugin/scenarios/jetty-scenario/jettyclient-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyclient/controller/CaseController.java
index 2b6977875..d45d8adfe 100644
--- a/test/plugin/scenarios/jetty-scenario/jettyclient-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyclient/controller/CaseController.java
+++ b/test/plugin/scenarios/jetty-scenario/jettyclient-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyclient/controller/CaseController.java
@@ -16,10 +16,16 @@
*
*/
-package org.apache.skywalking.apm.testcase.jettyclient.controller;
+package org.apache.skywalking.apm.testcase.jettyclient.contr;
import javax.annotation.PostConstruct;
+import java.io.IOException;
+
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.Response;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Controller;
@@ -45,6 +51,16 @@ public class CaseController {
@ResponseBody
public String jettyClientScenario() throws Exception {
client.newRequest("http://" + jettyServerHost + ":18080/jettyserver-case/case/receiveContext-0").send();
+ Response.CompleteListener listener = result -> {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ HttpGet httpget = new HttpGet("http://" + jettyServerHost + ":18080/jettyserver-case/case/receiveContext-0");
+ try {
+ httpclient.execute(httpget);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ };
+ client.newRequest("http://" + jettyServerHost + ":18080/jettyserver-case/case/receiveContext-1").send(listener);
return "Success";
}
diff --git a/test/plugin/scenarios/jetty-scenario/jettyserver-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyserver/Application.java b/test/plugin/scenarios/jetty-scenario/jettyserver-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyserver/Application.java
index 6af8c860c..08f465d4d 100644
--- a/test/plugin/scenarios/jetty-scenario/jettyserver-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyserver/Application.java
+++ b/test/plugin/scenarios/jetty-scenario/jettyserver-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyserver/Application.java
@@ -19,6 +19,7 @@
package org.apache.skywalking.apm.testcase.jettyserver;
import java.net.InetSocketAddress;
+import org.apache.skywalking.apm.testcase.jettyserver.servlet.AsyncCaseServlet;
import org.apache.skywalking.apm.testcase.jettyserver.servlet.CaseServlet;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
@@ -31,6 +32,7 @@ public class Application {
ServletContextHandler servletContextHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
servletContextHandler.setContextPath(contextPath);
servletContextHandler.addServlet(CaseServlet.class, CaseServlet.SERVLET_PATH);
+ servletContextHandler.addServlet(AsyncCaseServlet.class, AsyncCaseServlet.SERVLET_PATH);
jettyServer.setHandler(servletContextHandler);
jettyServer.start();
}
diff --git a/test/plugin/scenarios/jetty-scenario/jettyserver-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyserver/servlet/AsyncCaseServlet.java b/test/plugin/scenarios/jetty-scenario/jettyserver-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyserver/servlet/AsyncCaseServlet.java
new file mode 100644
index 000000000..667fdafd1
--- /dev/null
+++ b/test/plugin/scenarios/jetty-scenario/jettyserver-scenario/src/main/java/org/apache/skywalking/apm/testcase/jettyserver/servlet/AsyncCaseServlet.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ * http://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 org.apache.skywalking.apm.testcase.jettyserver.servlet;
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class AsyncCaseServlet extends HttpServlet {
+ public static String SERVLET_PATH = "/case/receiveContext-1";
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ }
+ }
+}
diff --git a/test/plugin/scenarios/jetty-scenario/pom.xml b/test/plugin/scenarios/jetty-scenario/pom.xml
index 9700a48f9..f28863c63 100644
--- a/test/plugin/scenarios/jetty-scenario/pom.xml
+++ b/test/plugin/scenarios/jetty-scenario/pom.xml
@@ -43,6 +43,8 @@
${test.framework.version}
${test.framework.version}
+ 4.3.5
+
2.6.2