Skip to content

Commit a6fff74

Browse files
committed
feat: support org.asynchttpclient
1 parent 0ec6f84 commit a6fff74

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,95 @@
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.model.ArexConstants;
9+
import io.arex.inst.runtime.serializer.Serializer;
10+
import io.arex.inst.runtime.util.IgnoreUtils;
11+
import io.arex.inst.runtime.util.MockUtils;
12+
import io.arex.inst.runtime.util.TypeUtil;
13+
import java.nio.charset.StandardCharsets;
14+
import java.util.Base64;
15+
import java.util.HashMap;
16+
import java.util.Map;
17+
import org.asynchttpclient.Request;
18+
19+
public class AsyncHttpClientExtractor {
20+
private static final byte[] ZERO_BYTE = new byte[0];
21+
private final Request request;
22+
private final ResponseWrapper response;
23+
24+
public AsyncHttpClientExtractor(Request request, ResponseWrapper responseWrapper) {
25+
this.request = request;
26+
this.response = responseWrapper;
27+
}
28+
29+
30+
public void record() {
31+
Mocker mocker = makeMocker();
32+
mocker.getTargetResponse().setType(TypeUtil.getName(response));
33+
mocker.getTargetResponse().setBody(Serializer.serialize(response));
34+
MockUtils.recordMocker(mocker);
35+
}
36+
37+
protected Mocker makeMocker() {
38+
String httpMethod = request.getMethod();
39+
Mocker mocker = MockUtils.createHttpClient(request.getUri().getPath());
40+
Map<String, Object> attributes = new HashMap<>(3);
41+
42+
mocker.getTargetRequest().setAttributes(attributes);
43+
attributes.put("HttpMethod", httpMethod);
44+
attributes.put("QueryString", request.getUri().getQuery());
45+
attributes.put("ContentType", request.getHeaders().get("Content-Type"));
46+
47+
mocker.getTargetRequest().setBody(encodeRequest(httpMethod));
48+
return mocker;
49+
}
50+
51+
protected String encodeRequest(String httpMethod) {
52+
if (ALLOW_HTTP_METHOD_BODY_SETS.contains(httpMethod)) {
53+
byte[] bytes = getRequestBytes();
54+
if (bytes != null) {
55+
return Base64.getEncoder().encodeToString(bytes);
56+
}
57+
}
58+
return request.getUri().getQuery();
59+
}
60+
61+
private byte[] getRequestBytes() {
62+
if (request.getByteData() != null) {
63+
return request.getByteData();
64+
}
65+
66+
if (request.getCompositeByteData() != null) {
67+
return request.getCompositeByteData().toString().getBytes(StandardCharsets.UTF_8);
68+
}
69+
70+
if (request.getStringData() != null) {
71+
return request.getStringData().getBytes(StandardCharsets.UTF_8);
72+
}
73+
74+
if (request.getByteBufferData() != null) {
75+
return request.getByteBufferData().array();
76+
77+
}
78+
79+
return ZERO_BYTE;
80+
}
81+
82+
83+
public MockResult replay() {
84+
final boolean ignoreMockResult = IgnoreUtils.ignoreMockResult("http", request.getUri().getPath());
85+
final Object replayBody = MockUtils.replayBody(makeMocker());
86+
return MockResult.success(ignoreMockResult, replayBody);
87+
}
88+
89+
public void record(Throwable throwable) {
90+
Mocker mocker = makeMocker();
91+
mocker.getTargetResponse().setType(TypeUtil.getName(throwable));
92+
mocker.getTargetResponse().setBody(Serializer.serialize(throwable));
93+
MockUtils.recordMocker(mocker);
94+
}
95+
}
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,21 @@
1+
package io.arex.inst.httpclient.asynchttpclient;
2+
3+
import com.google.auto.service.AutoService;
4+
import io.arex.inst.extension.ModuleDescription;
5+
import io.arex.inst.extension.ModuleInstrumentation;
6+
import io.arex.inst.extension.TypeInstrumentation;
7+
import java.util.Collections;
8+
import java.util.List;
9+
10+
@AutoService(ModuleInstrumentation.class)
11+
public class AsyncHttpClientModuleInstrumentation extends ModuleInstrumentation {
12+
public AsyncHttpClientModuleInstrumentation() {
13+
super("org.asynchttpclient", ModuleDescription.builder()
14+
.name("Asynchronous Http Client").supportFrom(2, 7).build());
15+
}
16+
17+
@Override
18+
public List<TypeInstrumentation> instrumentationTypes() {
19+
return Collections.singletonList(new AsyncHttpClientInstrumentation());
20+
}
21+
}
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)