Support asynchronous invocation in jetty client 9.0 and 9.x plugin (#590)

This commit is contained in:
Yang-Yule 2023-08-08 12:02:48 +08:00 committed by GitHub
parent c6331df795
commit e7394a87da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1240 additions and 15 deletions

View File

@ -150,6 +150,7 @@ Callable {
* Fix witness class in springmvc-annotation-5.x-plugin to avoid falling into v3 use cases.
* Fix Jedis-2.x plugin bug and add test for Redis cluster scene
* Merge two instrumentation classes to avoid duplicate enhancements in MySQL plugins.
* Support asynchronous invocation in jetty client 9.0 and 9.x plugin
#### Documentation

View File

@ -0,0 +1,93 @@
/*
* 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.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 org.eclipse.jetty.http.HttpMethod;
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);
}
private String getHttpMethod(HttpRequest request) {
HttpMethod httpMethod = HttpMethod.GET;
/**
* The method is null if the client using GET method.
*
* @see org.eclipse.jetty.client.HttpRequest#GET(String uri)
* @see org.eclipse.jetty.client.HttpRequest( org.eclipse.jetty.client.HttpClient client, long conversation, java.net.URI uri)
*/
if (request.getMethod() != null) {
httpMethod = request.getMethod();
}
return httpMethod.name();
}
}

View File

@ -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.v90.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();
}
}

View File

@ -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.v90.client;
public class Constants {
public final static String SW_JETTY_EXIT_SPAN_KEY = "SW_JETTY_EXIT_SPAN";
public final static String PLUGIN_NAME = "JettyClient9.0";
}

View File

@ -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.v90.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);
}
}

View File

@ -31,15 +31,20 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
/**
* {@link HttpRequestInstrumentation} enhance the <code>send</code> method without argument in
* <code>org.eclipse.jetty.client.HttpRequest</code> by <code>org.apache.skywalking.apm.plugin.jetty.client.SyncHttpRequestSendInterceptor</code>
* and enhance the <code>send</code> with <code>org.eclipse.jetty.client.api.Response$CompleteListener</code> parameter
* by <code>org.apache.skywalking.apm.plugin.jetty.client.AsyncHttpRequestSendInterceptor</code>
* <code>org.eclipse.jetty.client.HttpRequest</code> by
* <code>org.apache.skywalking.apm.plugin.jetty.client.SyncHttpRequestSendInterceptor</code> and enhance the
* <code>send</code> with <code>org.eclipse.jetty.client.api.Response$CompleteListener</code> parameter by
* <code>org.apache.skywalking.apm.plugin.jetty.client.AsyncHttpRequestSendInterceptor</code>
*/
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.v90.client.SyncHttpRequestSendV90Interceptor";
public static final String SYNC_SEND_INTERCEPTOR =
"org.apache.skywalking.apm.plugin.jetty.v90.client.SyncHttpRequestSendV90Interceptor";
public static final String ASYNC_SEND_INTERCEPTOR =
"org.apache.skywalking.apm.plugin.jetty.v90.client.AsyncHttpRequestSendInterceptor";
@Override
public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
@ -61,6 +66,23 @@ public class HttpRequestInstrumentation extends ClassInstanceMethodsEnhancePlugi
return SYNC_SEND_INTERCEPTOR;
}
@Override
public boolean isOverrideArgs() {
return false;
}
},
new InstanceMethodsInterceptPoint() {
// async call interceptor point
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
return named(ENHANCE_CLASS_NAME).and(takesArguments(1));
}
@Override
public String getMethodsInterceptor() {
return ASYNC_SEND_INTERCEPTOR;
}
@Override
public boolean isOverrideArgs() {
return false;

View File

@ -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 <code>send</code> method in
* <code>org.eclipse.jetty.client.ResponseNotifier</code> by
* <code>org.apache.skywalking.apm.plugin.jetty.v9.client.ResponseNotifierInterceptor</code>
* <p>
* 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<MethodDescription> 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"};
}
}

View File

@ -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

View File

@ -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<String, Object> 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<String, Object> 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<TagValuePair> 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) {
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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";
}

View File

@ -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);
}
}

View File

@ -35,15 +35,20 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
/**
* {@link HttpRequestInstrumentation} enhance the <code>send</code> method without argument in
* <code>org.eclipse.jetty.client.HttpRequest</code> by <code>org.apache.skywalking.apm.plugin.jetty.client.SyncHttpRequestSendInterceptor</code>
* and enhance the <code>send</code> with <code>org.eclipse.jetty.client.api.Response$CompleteListener</code> parameter
* by <code>org.apache.skywalking.apm.plugin.jetty.client.AsyncHttpRequestSendInterceptor</code>
* <code>org.eclipse.jetty.client.HttpRequest</code> by
* <code>org.apache.skywalking.apm.plugin.jetty.client.SyncHttpRequestSendInterceptor</code> and enhance the
* <code>send</code> with <code>org.eclipse.jetty.client.api.Response$CompleteListener</code> parameter by
* <code>org.apache.skywalking.apm.plugin.jetty.client.AsyncHttpRequestSendInterceptor</code>
*/
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<MethodDescription> 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<MethodDescription> getMethodsMatcher() {
return named(ENHANCE_CLASS_NAME).and(takesArguments(1));
}
@Override
public String getMethodsInterceptor() {
return ASYNC_SEND_INTERCEPTOR;
}
@Override
public boolean isOverrideArgs() {
return true;
}
}
};
}

View File

@ -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 <code>send</code> method in
* <code>org.eclipse.jetty.client.ResponseNotifier</code> by
* <code>org.apache.skywalking.apm.plugin.jetty.v9.client.ResponseNotifierInterceptor</code>
* <p>
* 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<MethodDescription> 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<WitnessMethod> witnessMethods() {
return Collections.singletonList(new WitnessMethod(
"org.eclipse.jetty.http.HttpFields",
named("getQuality")
));
}
}

View File

@ -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

View File

@ -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<String, Object> 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<String, Object> 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<TagValuePair> 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) {
}
}
}

View File

@ -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'}

View File

@ -38,6 +38,11 @@
<artifactId>jetty-client</artifactId>
<version>${jettyclient.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -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";
}

View File

@ -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();
}

View File

@ -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) {
}
}
}

View File

@ -43,6 +43,8 @@
<jettyserver.version>${test.framework.version}</jettyserver.version>
<jettyclient.version>${test.framework.version}</jettyclient.version>
<httpclient.version>4.3.5</httpclient.version>
<log4j.version>2.6.2</log4j.version>
</properties>