Skip to content

Commit 4855a9b

Browse files
committed
feat: support org.asynchttpclient
1 parent b417f14 commit 4855a9b

File tree

21 files changed

+1465
-3
lines changed

21 files changed

+1465
-3
lines changed

arex-agent/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,11 @@
121121
<artifactId>arex-httpclient-okhttp-v3</artifactId>
122122
<version>${project.version}</version>
123123
</dependency>
124+
<dependency>
125+
<groupId>${project.groupId}</groupId>
126+
<artifactId>arex-httpclient-asynchttpclient</artifactId>
127+
<version>${project.version}</version>
128+
</dependency>
124129
<dependency>
125130
<groupId>${project.groupId}</groupId>
126131
<artifactId>arex-netty-v4</artifactId>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>arex-instrumentation-parent</artifactId>
7+
<groupId>io.arex</groupId>
8+
<version>${revision}</version>
9+
<relativePath>../../pom.xml</relativePath>
10+
</parent>
11+
<modelVersion>4.0.0</modelVersion>
12+
13+
<artifactId>arex-httpclient-asynchttpclient</artifactId>
14+
15+
<dependencies>
16+
<dependency>
17+
<groupId>${project.groupId}</groupId>
18+
<artifactId>arex-httpclient-common</artifactId>
19+
<version>${project.version}</version>
20+
<scope>compile</scope>
21+
</dependency>
22+
<dependency>
23+
<groupId>org.asynchttpclient</groupId>
24+
<artifactId>async-http-client</artifactId>
25+
<version>2.7.0</version>
26+
<scope>provided</scope>
27+
</dependency>
28+
</dependencies>
29+
30+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package io.arex.inst.httpclient.asynchttpclient;
2+
3+
import static io.arex.inst.httpclient.common.HttpClientExtractor.ALLOW_HTTP_METHOD_BODY_SETS;
4+
5+
import io.arex.agent.bootstrap.model.MockResult;
6+
import io.arex.agent.bootstrap.model.Mocker;
7+
import io.arex.inst.httpclient.asynchttpclient.wrapper.ResponseWrapper;
8+
import io.arex.inst.runtime.serializer.Serializer;
9+
import io.arex.inst.runtime.util.IgnoreUtils;
10+
import io.arex.inst.runtime.util.MockUtils;
11+
import io.arex.inst.runtime.util.TypeUtil;
12+
import java.nio.charset.StandardCharsets;
13+
import java.util.Base64;
14+
import java.util.HashMap;
15+
import java.util.Map;
16+
import org.asynchttpclient.Request;
17+
18+
public class AsyncHttpClientExtractor {
19+
private static final byte[] ZERO_BYTE = new byte[0];
20+
private final Request request;
21+
private final ResponseWrapper response;
22+
23+
public AsyncHttpClientExtractor(Request request, ResponseWrapper responseWrapper) {
24+
this.request = request;
25+
this.response = responseWrapper;
26+
}
27+
28+
29+
public void record() {
30+
Mocker mocker = makeMocker();
31+
mocker.getTargetResponse().setType(TypeUtil.getName(response));
32+
mocker.getTargetResponse().setBody(Serializer.serialize(response));
33+
MockUtils.recordMocker(mocker);
34+
}
35+
36+
protected Mocker makeMocker() {
37+
String httpMethod = request.getMethod();
38+
Mocker mocker = MockUtils.createHttpClient(request.getUri().getPath());
39+
Map<String, Object> attributes = new HashMap<>(3);
40+
41+
mocker.getTargetRequest().setAttributes(attributes);
42+
attributes.put("HttpMethod", httpMethod);
43+
attributes.put("QueryString", request.getUri().getQuery());
44+
attributes.put("ContentType", request.getHeaders().get("Content-Type"));
45+
46+
mocker.getTargetRequest().setBody(encodeRequest(httpMethod));
47+
return mocker;
48+
}
49+
50+
protected String encodeRequest(String httpMethod) {
51+
if (ALLOW_HTTP_METHOD_BODY_SETS.contains(httpMethod)) {
52+
byte[] bytes = getRequestBytes();
53+
if (bytes != null) {
54+
return Base64.getEncoder().encodeToString(bytes);
55+
}
56+
}
57+
return request.getUri().getQuery();
58+
}
59+
60+
private byte[] getRequestBytes() {
61+
if (request.getByteData() != null) {
62+
return request.getByteData();
63+
}
64+
65+
if (request.getCompositeByteData() != null) {
66+
return request.getCompositeByteData().toString().getBytes(StandardCharsets.UTF_8);
67+
}
68+
69+
if (request.getStringData() != null) {
70+
return request.getStringData().getBytes(StandardCharsets.UTF_8);
71+
}
72+
73+
if (request.getByteBufferData() != null) {
74+
return request.getByteBufferData().array();
75+
76+
}
77+
78+
return ZERO_BYTE;
79+
}
80+
81+
82+
public MockResult replay() {
83+
final boolean ignoreMockResult = IgnoreUtils.ignoreMockResult("http", request.getUri().getPath());
84+
final Object replayBody = MockUtils.replayBody(makeMocker());
85+
return MockResult.success(ignoreMockResult, replayBody);
86+
}
87+
88+
public void record(Throwable throwable) {
89+
Mocker mocker = makeMocker();
90+
mocker.getTargetResponse().setType(TypeUtil.getName(throwable));
91+
mocker.getTargetResponse().setBody(Serializer.serialize(throwable));
92+
MockUtils.recordMocker(mocker);
93+
}
94+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package io.arex.inst.httpclient.asynchttpclient;
2+
3+
import io.arex.agent.bootstrap.model.MockResult;
4+
import io.arex.inst.extension.MethodInstrumentation;
5+
import io.arex.inst.extension.TypeInstrumentation;
6+
import io.arex.inst.httpclient.asynchttpclient.listener.AsyncHttpClientConsumer;
7+
import io.arex.inst.httpclient.asynchttpclient.listener.AsyncHttpClientListenableFuture;
8+
import io.arex.inst.httpclient.asynchttpclient.wrapper.AsyncHandlerWrapper;
9+
import io.arex.inst.httpclient.asynchttpclient.wrapper.ResponseWrapper;
10+
import io.arex.inst.runtime.context.ContextManager;
11+
import io.arex.inst.runtime.context.RepeatedCollectManager;
12+
import io.arex.inst.runtime.util.IgnoreUtils;
13+
import java.util.Collections;
14+
import java.util.List;
15+
import net.bytebuddy.asm.Advice.Argument;
16+
import net.bytebuddy.asm.Advice.Local;
17+
import net.bytebuddy.asm.Advice.OnMethodEnter;
18+
import net.bytebuddy.asm.Advice.OnMethodExit;
19+
import net.bytebuddy.asm.Advice.OnNonDefaultValue;
20+
import net.bytebuddy.asm.Advice.Return;
21+
import net.bytebuddy.description.type.TypeDescription;
22+
import net.bytebuddy.implementation.bytecode.assign.Assigner.Typing;
23+
import net.bytebuddy.matcher.ElementMatcher;
24+
import org.asynchttpclient.AsyncHandler;
25+
import org.asynchttpclient.ListenableFuture;
26+
import org.asynchttpclient.Request;
27+
28+
import static net.bytebuddy.matcher.ElementMatchers.*;
29+
30+
public class AsyncHttpClientInstrumentation extends TypeInstrumentation {
31+
32+
@Override
33+
protected ElementMatcher<TypeDescription> typeMatcher() {
34+
return named("org.asynchttpclient.DefaultAsyncHttpClient");
35+
}
36+
37+
@Override
38+
public List<MethodInstrumentation> methodAdvices() {
39+
return Collections.singletonList(new MethodInstrumentation(named("execute").and(takesArguments(2))
40+
.and(takesArgument(0, named("org.asynchttpclient.Request"))), ExecuteAdvice.class.getName()));
41+
}
42+
43+
public static class ExecuteAdvice {
44+
@OnMethodEnter(skipOn = OnNonDefaultValue.class, suppress = Throwable.class)
45+
public static boolean onEnter(@Argument(0) Request request,
46+
@Argument(value = 1, readOnly = false) AsyncHandler<?> handler,
47+
@Local("extractor") AsyncHttpClientExtractor extractor,
48+
@Local("mockResult") MockResult mockResult) {
49+
if (IgnoreUtils.excludeOperation(request.getUri().getPath())) {
50+
return false;
51+
}
52+
53+
if (ContextManager.needRecord()) {
54+
RepeatedCollectManager.enter();
55+
}
56+
57+
if (ContextManager.needRecordOrReplay()) {
58+
ResponseWrapper response = new ResponseWrapper();
59+
extractor = new AsyncHttpClientExtractor(request, response);
60+
if (ContextManager.needReplay()) {
61+
mockResult = extractor.replay();
62+
return mockResult != null && mockResult.notIgnoreMockResult();
63+
}
64+
handler = new AsyncHandlerWrapper(handler, response);
65+
}
66+
return false;
67+
}
68+
69+
@OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
70+
public static void onExit(@Argument(1) AsyncHandler<?> handler,
71+
@Return(readOnly = false, typing = Typing.DYNAMIC) ListenableFuture future,
72+
@Local("extractor") AsyncHttpClientExtractor extractor,
73+
@Local("mockResult") MockResult mockResult) {
74+
if (extractor == null) {
75+
return;
76+
}
77+
78+
if (mockResult != null && mockResult.notIgnoreMockResult()) {
79+
if (mockResult.getThrowable() != null) {
80+
future = new AsyncHttpClientListenableFuture(null, mockResult.getThrowable(), handler);
81+
} else {
82+
future = new AsyncHttpClientListenableFuture(mockResult.getResult(), null, handler);
83+
}
84+
return;
85+
}
86+
87+
if (ContextManager.needRecord() && RepeatedCollectManager.exitAndValidate()) {
88+
future.toCompletableFuture().whenComplete(new AsyncHttpClientConsumer(extractor));
89+
}
90+
}
91+
92+
}
93+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.arex.inst.httpclient.asynchttpclient;
2+
3+
import com.google.auto.service.AutoService;
4+
import io.arex.agent.bootstrap.model.ComparableVersion;
5+
import io.arex.inst.extension.ModuleDescription;
6+
import io.arex.inst.extension.ModuleInstrumentation;
7+
import io.arex.inst.extension.TypeInstrumentation;
8+
import java.util.Collections;
9+
import java.util.List;
10+
11+
@AutoService(ModuleInstrumentation.class)
12+
public class AsyncHttpClientModuleInstrumentation extends ModuleInstrumentation {
13+
public AsyncHttpClientModuleInstrumentation() {
14+
super("org.asynchttpclient", ModuleDescription.builder()
15+
.name("Asynchronous Http Client").supportFrom(ComparableVersion.of("2.7")).build());
16+
}
17+
18+
@Override
19+
public List<TypeInstrumentation> instrumentationTypes() {
20+
return Collections.singletonList(new AsyncHttpClientInstrumentation());
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package io.arex.inst.httpclient.asynchttpclient.listener;
2+
3+
import io.arex.agent.bootstrap.ctx.TraceTransmitter;
4+
import io.arex.inst.httpclient.asynchttpclient.AsyncHttpClientExtractor;
5+
import java.util.function.BiConsumer;
6+
7+
public class AsyncHttpClientConsumer implements BiConsumer<Object, Throwable> {
8+
private final AsyncHttpClientExtractor extractor;
9+
private final TraceTransmitter traceTransmitter;
10+
11+
public AsyncHttpClientConsumer(AsyncHttpClientExtractor extractor) {
12+
this.traceTransmitter = TraceTransmitter.create();
13+
this.extractor = extractor;
14+
}
15+
16+
17+
@Override
18+
public void accept(Object o, Throwable throwable) {
19+
try (TraceTransmitter tm = traceTransmitter.transmit()) {
20+
if (throwable != null) {
21+
extractor.record(throwable);
22+
} else {
23+
extractor.record();
24+
}
25+
}
26+
}
27+
}

0 commit comments

Comments
 (0)