diff --git a/arex-agent/pom.xml b/arex-agent/pom.xml
index a3eb4d433..f0b451d02 100644
--- a/arex-agent/pom.xml
+++ b/arex-agent/pom.xml
@@ -141,6 +141,11 @@
arex-netty-v3
${project.version}
+
+ ${project.groupId}
+ arex-httpclient-asynchttpclient
+ ${project.version}
+
${project.groupId}
arex-netty-v4
diff --git a/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/pom.xml b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/pom.xml
new file mode 100644
index 000000000..7dc2e1e11
--- /dev/null
+++ b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/pom.xml
@@ -0,0 +1,30 @@
+
+
+
+ arex-instrumentation-parent
+ io.arex
+ ${revision}
+ ../../pom.xml
+
+ 4.0.0
+
+ arex-httpclient-asynchttpclient
+
+
+
+ ${project.groupId}
+ arex-httpclient-common
+ ${project.version}
+ compile
+
+
+ org.asynchttpclient
+ async-http-client
+ 2.7.0
+ provided
+
+
+
+
diff --git a/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/AsyncHttpClientExtractor.java b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/AsyncHttpClientExtractor.java
new file mode 100644
index 000000000..7ccccd3ba
--- /dev/null
+++ b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/AsyncHttpClientExtractor.java
@@ -0,0 +1,94 @@
+package io.arex.inst.httpclient.asynchttpclient;
+
+import static io.arex.inst.httpclient.common.HttpClientExtractor.ALLOW_HTTP_METHOD_BODY_SETS;
+
+import io.arex.agent.bootstrap.model.MockResult;
+import io.arex.agent.bootstrap.model.Mocker;
+import io.arex.inst.httpclient.asynchttpclient.wrapper.ResponseWrapper;
+import io.arex.inst.runtime.serializer.Serializer;
+import io.arex.inst.runtime.util.IgnoreUtils;
+import io.arex.inst.runtime.util.MockUtils;
+import io.arex.inst.runtime.util.TypeUtil;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import org.asynchttpclient.Request;
+
+public class AsyncHttpClientExtractor {
+ private static final byte[] ZERO_BYTE = new byte[0];
+ private final Request request;
+ private final ResponseWrapper response;
+
+ public AsyncHttpClientExtractor(Request request, ResponseWrapper responseWrapper) {
+ this.request = request;
+ this.response = responseWrapper;
+ }
+
+
+ public void record() {
+ Mocker mocker = makeMocker();
+ mocker.getTargetResponse().setType(TypeUtil.getName(response));
+ mocker.getTargetResponse().setBody(Serializer.serialize(response));
+ MockUtils.recordMocker(mocker);
+ }
+
+ protected Mocker makeMocker() {
+ String httpMethod = request.getMethod();
+ Mocker mocker = MockUtils.createHttpClient(request.getUri().getPath());
+ Map attributes = new HashMap<>(3);
+
+ mocker.getTargetRequest().setAttributes(attributes);
+ attributes.put("HttpMethod", httpMethod);
+ attributes.put("QueryString", request.getUri().getQuery());
+ attributes.put("ContentType", request.getHeaders().get("Content-Type"));
+
+ mocker.getTargetRequest().setBody(encodeRequest(httpMethod));
+ return mocker;
+ }
+
+ protected String encodeRequest(String httpMethod) {
+ if (ALLOW_HTTP_METHOD_BODY_SETS.contains(httpMethod)) {
+ byte[] bytes = getRequestBytes();
+ if (bytes != null) {
+ return Base64.getEncoder().encodeToString(bytes);
+ }
+ }
+ return request.getUri().getQuery();
+ }
+
+ private byte[] getRequestBytes() {
+ if (request.getByteData() != null) {
+ return request.getByteData();
+ }
+
+ if (request.getCompositeByteData() != null) {
+ return request.getCompositeByteData().toString().getBytes(StandardCharsets.UTF_8);
+ }
+
+ if (request.getStringData() != null) {
+ return request.getStringData().getBytes(StandardCharsets.UTF_8);
+ }
+
+ if (request.getByteBufferData() != null) {
+ return request.getByteBufferData().array();
+
+ }
+
+ return ZERO_BYTE;
+ }
+
+
+ public MockResult replay() {
+ final boolean ignoreMockResult = IgnoreUtils.ignoreMockResult("http", request.getUri().getPath());
+ final Object replayBody = MockUtils.replayBody(makeMocker());
+ return MockResult.success(ignoreMockResult, replayBody);
+ }
+
+ public void record(Throwable throwable) {
+ Mocker mocker = makeMocker();
+ mocker.getTargetResponse().setType(TypeUtil.getName(throwable));
+ mocker.getTargetResponse().setBody(Serializer.serialize(throwable));
+ MockUtils.recordMocker(mocker);
+ }
+}
diff --git a/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/AsyncHttpClientInstrumentation.java b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/AsyncHttpClientInstrumentation.java
new file mode 100644
index 000000000..cd2e0afbd
--- /dev/null
+++ b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/AsyncHttpClientInstrumentation.java
@@ -0,0 +1,93 @@
+package io.arex.inst.httpclient.asynchttpclient;
+
+import io.arex.agent.bootstrap.model.MockResult;
+import io.arex.inst.extension.MethodInstrumentation;
+import io.arex.inst.extension.TypeInstrumentation;
+import io.arex.inst.httpclient.asynchttpclient.listener.AsyncHttpClientConsumer;
+import io.arex.inst.httpclient.asynchttpclient.listener.AsyncHttpClientListenableFuture;
+import io.arex.inst.httpclient.asynchttpclient.wrapper.AsyncHandlerWrapper;
+import io.arex.inst.httpclient.asynchttpclient.wrapper.ResponseWrapper;
+import io.arex.inst.runtime.context.ContextManager;
+import io.arex.inst.runtime.context.RepeatedCollectManager;
+import io.arex.inst.runtime.util.IgnoreUtils;
+import java.util.Collections;
+import java.util.List;
+import net.bytebuddy.asm.Advice.Argument;
+import net.bytebuddy.asm.Advice.Local;
+import net.bytebuddy.asm.Advice.OnMethodEnter;
+import net.bytebuddy.asm.Advice.OnMethodExit;
+import net.bytebuddy.asm.Advice.OnNonDefaultValue;
+import net.bytebuddy.asm.Advice.Return;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.bytecode.assign.Assigner.Typing;
+import net.bytebuddy.matcher.ElementMatcher;
+import org.asynchttpclient.AsyncHandler;
+import org.asynchttpclient.ListenableFuture;
+import org.asynchttpclient.Request;
+
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+public class AsyncHttpClientInstrumentation extends TypeInstrumentation {
+
+ @Override
+ protected ElementMatcher typeMatcher() {
+ return named("org.asynchttpclient.DefaultAsyncHttpClient");
+ }
+
+ @Override
+ public List methodAdvices() {
+ return Collections.singletonList(new MethodInstrumentation(named("execute").and(takesArguments(2))
+ .and(takesArgument(0, named("org.asynchttpclient.Request"))), ExecuteAdvice.class.getName()));
+ }
+
+ public static class ExecuteAdvice {
+ @OnMethodEnter(skipOn = OnNonDefaultValue.class, suppress = Throwable.class)
+ public static boolean onEnter(@Argument(0) Request request,
+ @Argument(value = 1, readOnly = false) AsyncHandler> handler,
+ @Local("extractor") AsyncHttpClientExtractor extractor,
+ @Local("mockResult") MockResult mockResult) {
+ if (IgnoreUtils.excludeOperation(request.getUri().getPath())) {
+ return false;
+ }
+
+ if (ContextManager.needRecord()) {
+ RepeatedCollectManager.enter();
+ }
+
+ if (ContextManager.needRecordOrReplay()) {
+ ResponseWrapper response = new ResponseWrapper();
+ extractor = new AsyncHttpClientExtractor(request, response);
+ if (ContextManager.needReplay()) {
+ mockResult = extractor.replay();
+ return mockResult != null && mockResult.notIgnoreMockResult();
+ }
+ handler = new AsyncHandlerWrapper(handler, response);
+ }
+ return false;
+ }
+
+ @OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
+ public static void onExit(@Argument(1) AsyncHandler> handler,
+ @Return(readOnly = false, typing = Typing.DYNAMIC) ListenableFuture future,
+ @Local("extractor") AsyncHttpClientExtractor extractor,
+ @Local("mockResult") MockResult mockResult) {
+ if (extractor == null) {
+ return;
+ }
+
+ if (mockResult != null && mockResult.notIgnoreMockResult()) {
+ if (mockResult.getThrowable() != null) {
+ future = new AsyncHttpClientListenableFuture(null, mockResult.getThrowable(), handler);
+ } else {
+ future = new AsyncHttpClientListenableFuture(mockResult.getResult(), null, handler);
+ }
+ return;
+ }
+
+ if (ContextManager.needRecord() && RepeatedCollectManager.exitAndValidate()) {
+ future.toCompletableFuture().whenComplete(new AsyncHttpClientConsumer(extractor));
+ }
+ }
+
+ }
+}
diff --git a/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/AsyncHttpClientModuleInstrumentation.java b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/AsyncHttpClientModuleInstrumentation.java
new file mode 100644
index 000000000..c4b845cea
--- /dev/null
+++ b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/AsyncHttpClientModuleInstrumentation.java
@@ -0,0 +1,22 @@
+package io.arex.inst.httpclient.asynchttpclient;
+
+import com.google.auto.service.AutoService;
+import io.arex.agent.bootstrap.model.ComparableVersion;
+import io.arex.inst.extension.ModuleDescription;
+import io.arex.inst.extension.ModuleInstrumentation;
+import io.arex.inst.extension.TypeInstrumentation;
+import java.util.Collections;
+import java.util.List;
+
+@AutoService(ModuleInstrumentation.class)
+public class AsyncHttpClientModuleInstrumentation extends ModuleInstrumentation {
+ public AsyncHttpClientModuleInstrumentation() {
+ super("org.asynchttpclient", ModuleDescription.builder()
+ .name("Asynchronous Http Client").supportFrom(ComparableVersion.of("2.7")).build());
+ }
+
+ @Override
+ public List instrumentationTypes() {
+ return Collections.singletonList(new AsyncHttpClientInstrumentation());
+ }
+}
diff --git a/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/listener/AsyncHttpClientConsumer.java b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/listener/AsyncHttpClientConsumer.java
new file mode 100644
index 000000000..4d85a919f
--- /dev/null
+++ b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/listener/AsyncHttpClientConsumer.java
@@ -0,0 +1,27 @@
+package io.arex.inst.httpclient.asynchttpclient.listener;
+
+import io.arex.agent.bootstrap.ctx.TraceTransmitter;
+import io.arex.inst.httpclient.asynchttpclient.AsyncHttpClientExtractor;
+import java.util.function.BiConsumer;
+
+public class AsyncHttpClientConsumer implements BiConsumer