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 { + private final AsyncHttpClientExtractor extractor; + private final TraceTransmitter traceTransmitter; + + public AsyncHttpClientConsumer(AsyncHttpClientExtractor extractor) { + this.traceTransmitter = TraceTransmitter.create(); + this.extractor = extractor; + } + + + @Override + public void accept(Object o, Throwable throwable) { + try (TraceTransmitter tm = traceTransmitter.transmit()) { + if (throwable != null) { + extractor.record(throwable); + } else { + extractor.record(); + } + } + } +} diff --git a/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/listener/AsyncHttpClientListenableFuture.java b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/listener/AsyncHttpClientListenableFuture.java new file mode 100644 index 000000000..b7c26dad2 --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/listener/AsyncHttpClientListenableFuture.java @@ -0,0 +1,135 @@ +package io.arex.inst.httpclient.asynchttpclient.listener; + +import io.arex.agent.bootstrap.util.MapUtils; +import io.arex.inst.httpclient.asynchttpclient.wrapper.ResponseWrapper; +import io.arex.inst.httpclient.asynchttpclient.wrapper.ResponseStatusWrapper; +import io.arex.inst.runtime.log.LogManager; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpHeaders; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.ListenableFuture; +import org.asynchttpclient.netty.EagerResponseBodyPart; +import org.asynchttpclient.uri.Uri; + +public class AsyncHttpClientListenableFuture implements ListenableFuture { + private final CompletableFuture future = new CompletableFuture<>(); + private Object response; + private Throwable throwable; + private AsyncHandler handler; + + public AsyncHttpClientListenableFuture() { + } + public AsyncHttpClientListenableFuture(Object response, Throwable throwable, AsyncHandler handler) { + this.response = response; + this.throwable = throwable; + this.handler = handler; + done(); + } + + @Override + public void done() { + if (throwable != null) { + future.completeExceptionally(throwable); + handler.onThrowable(throwable); + return; + } + try { + if (response instanceof ResponseWrapper) { + ResponseWrapper responseWrapper = (ResponseWrapper) response; + final ResponseStatusWrapper responseStatusWrapper = new ResponseStatusWrapper(Uri.create(responseWrapper.getUri()), + responseWrapper); + handler.onStatusReceived(responseStatusWrapper); + HttpHeaders httpHeaders = buildHttpHeaders(responseWrapper); + handler.onHeadersReceived(httpHeaders); + HttpResponseBodyPart bodyPart = buildBodyPart(responseWrapper); + handler.onBodyPartReceived(bodyPart); + } + future.complete(handler.onCompleted()); + } catch (Exception e) { + LogManager.warn("AsyncHttpClient.done", e); + future.completeExceptionally(e); + } + + } + + private HttpResponseBodyPart buildBodyPart(ResponseWrapper responseWrapper) { + final byte[] content = responseWrapper.getContent(); + if (content == null || content.length == 0) { + return new EagerResponseBodyPart(Unpooled.EMPTY_BUFFER, true); + } + ByteBuf byteBuf = Unpooled.wrappedBuffer(ByteBuffer.wrap(content)); + return new EagerResponseBodyPart(byteBuf, true); + } + + private HttpHeaders buildHttpHeaders(ResponseWrapper responseWrapper) { + HttpHeaders httpHeaders = new DefaultHttpHeaders(); + final Map> headers = responseWrapper.getHeaders(); + if (MapUtils.isEmpty(headers)) { + return httpHeaders; + } + for (Map.Entry> entry : headers.entrySet()) { + httpHeaders.add(entry.getKey(), entry.getValue()); + } + return httpHeaders; + } + + @Override + public void abort(Throwable t) { + handler.onThrowable(t); + } + + @Override + public void touch() { + // do nothing + } + + @Override + public ListenableFuture addListener(Runnable listener, Executor exec) { + if (exec == null) { + exec = Runnable::run; + } + future.whenCompleteAsync((r, v) -> listener.run(), exec); + return this; + } + + @Override + public CompletableFuture toCompletableFuture() { + return future; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return future.cancel(mayInterruptIfRunning); + } + + @Override + public boolean isCancelled() { + return future.isCancelled(); + } + + @Override + public boolean isDone() { + return future.isDone(); + } + + @Override + public T get() throws InterruptedException, ExecutionException { + return future.get(); + } + + @Override + public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return future.get(timeout, unit); + } +} diff --git a/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/wrapper/AsyncHandlerWrapper.java b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/wrapper/AsyncHandlerWrapper.java new file mode 100644 index 000000000..fe043ec8f --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/wrapper/AsyncHandlerWrapper.java @@ -0,0 +1,139 @@ +package io.arex.inst.httpclient.asynchttpclient.wrapper; + +import io.arex.inst.runtime.log.LogManager; +import io.netty.channel.Channel; +import io.netty.handler.codec.http.HttpHeaders; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import javax.net.ssl.SSLSession; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.netty.request.NettyRequest; + +public class AsyncHandlerWrapper implements AsyncHandler { + private final AsyncHandler handler; + private HttpResponseStatus responseStatus; + private HttpHeaders httpHeaders; + private final ResponseWrapper responseWrapper; + private final List bodyParts = new ArrayList<>(1); + + public AsyncHandlerWrapper(AsyncHandler handler, ResponseWrapper responseWrapper) { + this.handler = handler; + this.responseWrapper = responseWrapper; + } + + @Override + public State onStatusReceived(HttpResponseStatus responseStatus) throws Exception { + this.responseStatus = responseStatus; + return handler.onStatusReceived(responseStatus); + } + + @Override + public State onHeadersReceived(HttpHeaders headers) throws Exception { + this.httpHeaders = this.httpHeaders == null ? headers : this.httpHeaders.add(headers); + return handler.onHeadersReceived(headers); + } + + @Override + public State onBodyPartReceived(HttpResponseBodyPart bodyPart) throws Exception { + if (bodyPart != null && bodyPart.length() > 1) { + bodyParts.add(bodyPart); + } + return handler.onBodyPartReceived(bodyPart); + } + + @Override + public void onThrowable(Throwable t) { + handler.onThrowable(t); + } + + @Override + public T onCompleted() throws Exception { + try { + responseWrapper.setHttpHeaders(httpHeaders); + responseWrapper.setHttpStatus(responseStatus); + responseWrapper.setHttpResponseBody(bodyParts); + } catch (Throwable ex) { + LogManager.warn("AsyncHandlerWrapper.onCompleted", ex); + } + return handler.onCompleted(); + } + + @Override + public State onTrailingHeadersReceived(HttpHeaders headers) throws Exception { + this.httpHeaders = this.httpHeaders == null ? headers : this.httpHeaders.add(headers); + return handler.onTrailingHeadersReceived(headers); + } + + @Override + public void onHostnameResolutionAttempt(String name) { + handler.onHostnameResolutionAttempt(name); + } + + @Override + public void onHostnameResolutionSuccess(String name, List list) { + handler.onHostnameResolutionSuccess(name, list); + } + + @Override + public void onHostnameResolutionFailure(String name, Throwable cause) { + handler.onHostnameResolutionFailure(name, cause); + } + + @Override + public void onTcpConnectAttempt(InetSocketAddress remoteAddress) { + handler.onTcpConnectAttempt(remoteAddress); + } + + @Override + public void onTcpConnectSuccess(InetSocketAddress remoteAddress, Channel connection) { + handler.onTcpConnectSuccess(remoteAddress, connection); + } + + @Override + public void onTcpConnectFailure(InetSocketAddress remoteAddress, Throwable cause) { + handler.onTcpConnectFailure(remoteAddress, cause); + } + + @Override + public void onTlsHandshakeAttempt() { + handler.onTlsHandshakeAttempt(); + } + + @Override + public void onTlsHandshakeSuccess(SSLSession sslSession) { + handler.onTlsHandshakeSuccess(sslSession); + } + + @Override + public void onTlsHandshakeFailure(Throwable cause) { + handler.onTlsHandshakeFailure(cause); + } + + @Override + public void onConnectionPoolAttempt() { + handler.onConnectionPoolAttempt(); + } + + @Override + public void onConnectionPooled(Channel connection) { + handler.onConnectionPooled(connection); + } + + @Override + public void onConnectionOffer(Channel connection) { + handler.onConnectionOffer(connection); + } + + @Override + public void onRequestSend(NettyRequest request) { + handler.onRequestSend(request); + } + + @Override + public void onRetry() { + handler.onRetry(); + } +} diff --git a/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/wrapper/ResponseStatusWrapper.java b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/wrapper/ResponseStatusWrapper.java new file mode 100644 index 000000000..425ba495d --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/wrapper/ResponseStatusWrapper.java @@ -0,0 +1,68 @@ +package io.arex.inst.httpclient.asynchttpclient.wrapper; + +import java.net.SocketAddress; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.uri.Uri; + +public class ResponseStatusWrapper extends HttpResponseStatus { + private final int statusCode; + private final String statusText; + private final String localAddress; + private final String remoteAddress; + private final int protocolMajorVersion; + private final int protocolMinorVersion; + private final String protocolName; + private final String protocolText; + + public ResponseStatusWrapper(Uri uri, ResponseWrapper responseWrapper) { + super(uri); + this.statusCode = responseWrapper.getStatusCode(); + this.statusText = responseWrapper.getStatusText(); + this.localAddress = responseWrapper.getLocalAddress(); + this.remoteAddress = responseWrapper.getRemoteAddress(); + this.protocolMajorVersion = responseWrapper.getProtocolMajorVersion(); + this.protocolMinorVersion = responseWrapper.getProtocolMinorVersion(); + this.protocolName = responseWrapper.getProtocolName(); + this.protocolText = responseWrapper.getProtocolText(); + } + + @Override + public int getStatusCode() { + return statusCode; + } + + @Override + public String getStatusText() { + return statusText; + } + + @Override + public String getProtocolName() { + return protocolName; + } + + @Override + public int getProtocolMajorVersion() { + return protocolMajorVersion; + } + + @Override + public int getProtocolMinorVersion() { + return protocolMinorVersion; + } + + @Override + public String getProtocolText() { + return protocolText; + } + + @Override + public SocketAddress getRemoteAddress() { + return new SocketAddressWrapper(remoteAddress); + } + + @Override + public SocketAddress getLocalAddress() { + return new SocketAddressWrapper(localAddress); + } +} diff --git a/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/wrapper/ResponseWrapper.java b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/wrapper/ResponseWrapper.java new file mode 100644 index 000000000..d311675c2 --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/wrapper/ResponseWrapper.java @@ -0,0 +1,164 @@ +package io.arex.inst.httpclient.asynchttpclient.wrapper; + +import io.arex.agent.bootstrap.util.CollectionUtil; +import io.netty.handler.codec.http.HttpHeaders; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.HttpResponseStatus; + +public class ResponseWrapper { + private byte[] content; + private int statusCode; + private String statusText; + private Map> headers; + private String localAddress; + private String remoteAddress; + private String uri; + private int protocolMajorVersion; + private int protocolMinorVersion; + private String protocolName; + private String protocolText; + + public ResponseWrapper() { + } + + public void setHttpStatus(HttpResponseStatus responseStatus) { + if (responseStatus != null) { + this.statusCode = responseStatus.getStatusCode(); + this.statusText = responseStatus.getStatusText(); + this.localAddress = responseStatus.getLocalAddress().toString(); + this.remoteAddress = responseStatus.getRemoteAddress().toString(); + this.uri = responseStatus.getUri().toString(); + this.protocolMajorVersion = responseStatus.getProtocolMajorVersion(); + this.protocolMinorVersion = responseStatus.getProtocolMinorVersion(); + this.protocolName = responseStatus.getProtocolName(); + this.protocolText = responseStatus.getProtocolText(); + } + } + + public void setHttpHeaders(HttpHeaders headers) { + if (headers != null && !headers.isEmpty()) { + this.headers = encodeHeaders(headers); + } + } + + public void setHttpResponseBody(List bodyParts) { + if (CollectionUtil.isEmpty(bodyParts)) { + this.content = new byte[0]; + return; + } + int length = 0; + for (HttpResponseBodyPart part : bodyParts) { + length += part.length(); + } + + ByteBuffer target = ByteBuffer.wrap(new byte[length]); + for (HttpResponseBodyPart part : bodyParts) { + target.put(part.getBodyPartBytes()); + } + + target.flip(); + this.content = target.array(); + } + + private Map> encodeHeaders(HttpHeaders headers) { + Map> result = new HashMap<>(); + for (Map.Entry entry : headers.entries()) { + String key = entry.getKey(); + List value = headers.getAll(key); + result.put(key, value); + } + return result; + } + + public byte[] getContent() { + return content; + } + + public int getStatusCode() { + return statusCode; + } + + public String getStatusText() { + return statusText; + } + + public Map> getHeaders() { + return headers; + } + + public String getLocalAddress() { + return localAddress; + } + + public String getRemoteAddress() { + return remoteAddress; + } + + public String getUri() { + return uri; + } + + public void setContent(byte[] content) { + this.content = content; + } + + public void setStatusCode(int statusCode) { + this.statusCode = statusCode; + } + + public void setStatusText(String statusText) { + this.statusText = statusText; + } + + public void setHeaders(Map> headers) { + this.headers = headers; + } + + public void setLocalAddress(String localAddress) { + this.localAddress = localAddress; + } + + public void setRemoteAddress(String remoteAddress) { + this.remoteAddress = remoteAddress; + } + + public void setUri(String uri) { + this.uri = uri; + } + + public int getProtocolMajorVersion() { + return protocolMajorVersion; + } + + public void setProtocolMajorVersion(int protocolMajorVersion) { + this.protocolMajorVersion = protocolMajorVersion; + } + + public int getProtocolMinorVersion() { + return protocolMinorVersion; + } + + public void setProtocolMinorVersion(int protocolMinorVersion) { + this.protocolMinorVersion = protocolMinorVersion; + } + + public String getProtocolName() { + return protocolName; + } + + public void setProtocolName(String protocolName) { + this.protocolName = protocolName; + } + + public String getProtocolText() { + return protocolText; + } + + public void setProtocolText(String protocolText) { + this.protocolText = protocolText; + } +} diff --git a/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/wrapper/SocketAddressWrapper.java b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/wrapper/SocketAddressWrapper.java new file mode 100644 index 000000000..9add7a506 --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/main/java/io/arex/inst/httpclient/asynchttpclient/wrapper/SocketAddressWrapper.java @@ -0,0 +1,16 @@ +package io.arex.inst.httpclient.asynchttpclient.wrapper; + +import java.net.SocketAddress; + +public class SocketAddressWrapper extends SocketAddress { + private String string; + + public SocketAddressWrapper(String string) { + this.string = string; + } + + @Override + public String toString() { + return string; + } +} diff --git a/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/AsyncHttpClientExtractorTest.java b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/AsyncHttpClientExtractorTest.java new file mode 100644 index 000000000..1fa9a866c --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/AsyncHttpClientExtractorTest.java @@ -0,0 +1,141 @@ +package io.arex.inst.httpclient.asynchttpclient; + +import static org.junit.jupiter.api.Assertions.*; + +import io.arex.agent.bootstrap.model.ArexMocker; +import io.arex.agent.bootstrap.model.MockResult; +import io.arex.agent.bootstrap.model.Mocker.Target; +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.netty.handler.codec.http.DefaultHttpHeaders; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import org.asynchttpclient.Request; +import org.asynchttpclient.uri.Uri; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class AsyncHttpClientExtractorTest { + private static AsyncHttpClientExtractor extractor; + private static Request request; + private static ResponseWrapper responseWrapper; + private static MockedStatic serializerMockedStatic = null; + private static MockedStatic mockUtilsMockedStatic = null; + + @BeforeAll + static void setUp() { + Mockito.mockStatic(IgnoreUtils.class); + serializerMockedStatic = Mockito.mockStatic(Serializer.class); + mockUtilsMockedStatic = Mockito.mockStatic(MockUtils.class); + + final Uri uri = Mockito.mock(Uri.class); + Mockito.when(uri.getPath()).thenReturn("mockPath"); + + final ArexMocker mocker = new ArexMocker(); + mocker.setTargetRequest(new Target()); + mocker.setTargetResponse(new Target()); + mockUtilsMockedStatic.when(() -> MockUtils.createHttpClient("mockPath")).thenReturn(mocker); + + request = Mockito.mock(Request.class); + Mockito.when(request.getUri()).thenReturn(uri); + Mockito.when(request.getHeaders()).thenReturn(new DefaultHttpHeaders()); + + responseWrapper = Mockito.mock(ResponseWrapper.class); + extractor = new AsyncHttpClientExtractor(request, responseWrapper); + } + + @AfterAll + static void tearDown() { + extractor = null; + request = null; + responseWrapper = null; + Mockito.clearAllCaches(); + } + + @Order(1) + @Test + void record() { + extractor.record(); + mockUtilsMockedStatic.verify(() -> MockUtils.recordMocker(Mockito.any()), Mockito.times(1)); + } + + @Order(2) + @Test + void recordThrowable() { + extractor.record(new RuntimeException()); + mockUtilsMockedStatic.verify(() -> MockUtils.recordMocker(Mockito.any()), Mockito.times(2)); + } + + @Order(3) + @Test + void replay() { + final MockResult replay = extractor.replay(); + assertNotNull(replay); + } + + @Order(3) + @Test + void testEncodeRequest() { + // get + Mockito.when(request.getUri().getQuery()).thenReturn("mockQuery"); + final String encodeRequest = extractor.encodeRequest("GET"); + assertEquals("mockQuery", encodeRequest); + + + final byte[] expectedBytes = "mockData".getBytes(); + // byteBuff + final ByteBuffer expectedByteBuffer = ByteBuffer.wrap(expectedBytes); + Mockito.when(request.getCompositeByteData()).thenReturn(null); + Mockito.when(request.getByteData()).thenReturn(null); + Mockito.when(request.getStringData()).thenReturn(null); + Mockito.when(request.getByteBufferData()).thenReturn(expectedByteBuffer); + final String actualByteBuff = extractor.encodeRequest("POST"); + assertEquals(Base64.getEncoder().encodeToString(request.getByteBufferData().array()), actualByteBuff); + + // string + final String expectedString = "mockString"; + Mockito.when(request.getByteBufferData()).thenReturn(null); + Mockito.when(request.getByteData()).thenReturn(null); + Mockito.when(request.getCompositeByteData()).thenReturn(null); + Mockito.when(request.getStringData()).thenReturn(expectedString); + final String actualString = extractor.encodeRequest("POST"); + assertEquals(Base64.getEncoder().encodeToString(expectedString.getBytes(StandardCharsets.UTF_8)), actualString); + + // composite data + final List expectedCompositeData = Arrays.asList(expectedBytes); + Mockito.when(request.getByteBufferData()).thenReturn(null); + Mockito.when(request.getByteData()).thenReturn(null); + Mockito.when(request.getStringData()).thenReturn(null); + Mockito.when(request.getCompositeByteData()).thenReturn(expectedCompositeData); + final String actualCompositeData = extractor.encodeRequest("POST"); + assertEquals(Base64.getEncoder().encodeToString( + expectedCompositeData.toString().getBytes(StandardCharsets.UTF_8)), actualCompositeData); + + // byte[] + Mockito.when(request.getByteData()).thenReturn(expectedBytes); + Mockito.when(request.getByteBufferData()).thenReturn(null); + Mockito.when(request.getStringData()).thenReturn(null); + Mockito.when(request.getCompositeByteData()).thenReturn(null); + final String actualBytes = extractor.encodeRequest("POST"); + assertEquals(Base64.getEncoder().encodeToString(expectedBytes), actualBytes); + + // empty byte + Mockito.when(request.getByteData()).thenReturn(null); + Mockito.when(request.getByteBufferData()).thenReturn(null); + Mockito.when(request.getStringData()).thenReturn(null); + Mockito.when(request.getCompositeByteData()).thenReturn(null); + final String actualEmptyBytes = extractor.encodeRequest("POST"); + assertEquals(Base64.getEncoder().encodeToString(new byte[0]), actualEmptyBytes); + } +} \ No newline at end of file diff --git a/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/listener/AsyncHttpClientConsumerTest.java b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/listener/AsyncHttpClientConsumerTest.java new file mode 100644 index 000000000..91d9b9a30 --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/listener/AsyncHttpClientConsumerTest.java @@ -0,0 +1,31 @@ +package io.arex.inst.httpclient.asynchttpclient.listener; + +import static org.junit.jupiter.api.Assertions.*; + +import io.arex.inst.httpclient.asynchttpclient.AsyncHttpClientExtractor; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class AsyncHttpClientConsumerTest { + private static AsyncHttpClientExtractor extractor; + private static AsyncHttpClientConsumer consumer; + + @BeforeAll + static void setUp() { + extractor = Mockito.mock(AsyncHttpClientExtractor.class); + consumer = new AsyncHttpClientConsumer(extractor); + } + + @Test + void accept() { + // exception + final RuntimeException runtimeException = new RuntimeException(); + consumer.accept(null, runtimeException); + Mockito.verify(extractor, Mockito.times(1)).record(runtimeException); + // no exception + String o = "test"; + consumer.accept(o, null); + Mockito.verify(extractor, Mockito.times(1)).record(); + } +} \ No newline at end of file diff --git a/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/listener/AsyncHttpClientListenableFutureTest.java b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/listener/AsyncHttpClientListenableFutureTest.java new file mode 100644 index 000000000..a48c747cd --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/listener/AsyncHttpClientListenableFutureTest.java @@ -0,0 +1,131 @@ +package io.arex.inst.httpclient.asynchttpclient.listener; + +import static org.junit.jupiter.api.Assertions.*; + +import io.arex.inst.httpclient.asynchttpclient.wrapper.ResponseWrapper; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.asynchttpclient.AsyncHandler; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.mockito.Mock; +import org.mockito.Mockito; + +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class AsyncHttpClientListenableFutureTest { + private static AsyncHttpClientListenableFuture future; + private static AsyncHttpClientListenableFuture futureException; + private static AsyncHandler handler; + private static ResponseWrapper responseWrapper; + + @BeforeAll + static void setUp() throws Exception { + handler = Mockito.mock(AsyncHandler.class); + responseWrapper = Mockito.mock(ResponseWrapper.class); + Mockito.when(responseWrapper.getUri()).thenReturn("http://test"); + Mockito.when(responseWrapper.getProtocolMajorVersion()).thenReturn(1); + Mockito.when(responseWrapper.getProtocolMinorVersion()).thenReturn(1); + Mockito.when(responseWrapper.getProtocolName()).thenReturn("http"); + Mockito.when(responseWrapper.getProtocolText()).thenReturn("http"); + Mockito.when(responseWrapper.getStatusCode()).thenReturn(200); + Mockito.when(responseWrapper.getStatusText()).thenReturn("OK"); + Mockito.when(responseWrapper.getLocalAddress()).thenReturn("localAddress"); + Mockito.when(responseWrapper.getRemoteAddress()).thenReturn("remoteAddress"); + + Mockito.when(handler.onCompleted()).thenReturn(responseWrapper); + + future = new AsyncHttpClientListenableFuture(responseWrapper, null, handler); + futureException = new AsyncHttpClientListenableFuture(null, new RuntimeException(), handler); + } + + @AfterAll + static void tearDown() { + future = null; + futureException = null; + handler = null; + responseWrapper = null; + Mockito.clearAllCaches(); + } + + @Order(1) + @Test + void done() throws Exception { + // beforeAll will call onCompleted once + Mockito.verify(handler, Mockito.times(1)).onCompleted(); + // with exception + Mockito.verify(handler, Mockito.times(1)).onThrowable(Mockito.any(RuntimeException.class)); + + // responseWrapper null bodyPart hava header + Map> headers = new HashMap<>(); + headers.put("content-type", Arrays.asList("application/json")); + headers.put("content-length", Arrays.asList("100")); + headers.put("connection", Arrays.asList("keep-alive")); + Mockito.when(responseWrapper.getHeaders()).thenReturn(headers); + future.done(); + Mockito.verify(handler, Mockito.times(2)).onCompleted(); + + // responseWrapper null header hava bodyPart + Mockito.when(responseWrapper.getHeaders()).thenReturn(null); + Mockito.when(responseWrapper.getContent()).thenReturn("test".getBytes()); + future.done(); + Mockito.verify(handler, Mockito.times(3)).onCompleted(); + } + + @Test + void abort() { + final RuntimeException runtimeException = new RuntimeException(); + future.abort(runtimeException); + Mockito.verify(handler, Mockito.times(1)).onThrowable(runtimeException); + } + + @Test + void touch() { + future.touch(); + } + + @Test + void addListener() { + future.addListener(() -> System.out.println("test"), null); + future.addListener(() -> System.out.println("test"), Mockito.mock(Executor.class)); + } + + @Test + void toCompletableFuture() throws Exception { + assertEquals(future.toCompletableFuture().get(), responseWrapper); + } + + @Test + void cancel() { + assertFalse(future.cancel(true)); + } + + @Test + void isCancelled() { + assertFalse(future.isCancelled()); + } + + @Test + void isDone() { + assertTrue(future.isDone()); + } + + @Test + void get() throws Exception { + assertEquals(future.get(), responseWrapper); + } + + @Test + void testGet() throws Exception { + assertEquals(future.get(1, TimeUnit.MINUTES), responseWrapper); + } +} \ No newline at end of file diff --git a/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/wrapper/AsyncHandlerWrapperTest.java b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/wrapper/AsyncHandlerWrapperTest.java new file mode 100644 index 000000000..0c0274ee0 --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/wrapper/AsyncHandlerWrapperTest.java @@ -0,0 +1,162 @@ +package io.arex.inst.httpclient.asynchttpclient.wrapper; + +import static org.mockito.ArgumentMatchers.any; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import java.nio.ByteBuffer; +import org.asynchttpclient.AsyncHandler; +import org.asynchttpclient.netty.EagerResponseBodyPart; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class AsyncHandlerWrapperTest { + private static AsyncHandler handler; + private static ResponseWrapper responseWrapper; + private static AsyncHandlerWrapper asyncHandlerWrapper; + + @BeforeAll + static void setUp() { + handler = Mockito.mock(AsyncHandler.class); + responseWrapper = Mockito.mock(ResponseWrapper.class); + asyncHandlerWrapper = new AsyncHandlerWrapper(handler, responseWrapper); + } + + @AfterAll + static void tearDown() { + handler = null; + responseWrapper = null; + asyncHandlerWrapper = null; + Mockito.clearAllCaches(); + } + + + @Test + void onStatusReceived() throws Exception { + asyncHandlerWrapper.onStatusReceived(null); + Mockito.verify(handler, Mockito.times(1)).onStatusReceived(null); + } + + @Test + void onHeadersReceived() throws Exception { + asyncHandlerWrapper.onHeadersReceived(null); + Mockito.verify(handler, Mockito.times(1)).onHeadersReceived(null); + } + + @Test + void onBodyPartReceived() throws Exception { + // null bodyPart + asyncHandlerWrapper.onBodyPartReceived(null); + Mockito.verify(handler, Mockito.times(1)).onBodyPartReceived(null); + // bodyPart + byte[] content = "test".getBytes(); + ByteBuf byteBuf = Unpooled.wrappedBuffer(ByteBuffer.wrap(content)); + final EagerResponseBodyPart bodyPart = new EagerResponseBodyPart(byteBuf, true); + asyncHandlerWrapper.onBodyPartReceived(bodyPart); + Mockito.verify(handler, Mockito.times(1)).onBodyPartReceived(bodyPart); + } + + @Test + void onThrowable() { + asyncHandlerWrapper.onThrowable(null); + Mockito.verify(handler, Mockito.times(1)).onThrowable(null); + } + + @Test + void onCompleted() throws Exception { + asyncHandlerWrapper.onCompleted(); + Mockito.verify(handler, Mockito.times(1)).onCompleted(); + } + + @Test + void onTrailingHeadersReceived() throws Exception { + asyncHandlerWrapper.onTrailingHeadersReceived(null); + Mockito.verify(handler, Mockito.times(1)).onTrailingHeadersReceived(null); + } + + @Test + void onHostnameResolutionAttempt() { + asyncHandlerWrapper.onHostnameResolutionAttempt(null); + Mockito.verify(handler, Mockito.times(1)).onHostnameResolutionAttempt(null); + } + + @Test + void onHostnameResolutionSuccess() { + asyncHandlerWrapper.onHostnameResolutionSuccess(null, null); + Mockito.verify(handler, Mockito.times(1)).onHostnameResolutionSuccess(null, null); + } + + @Test + void onHostnameResolutionFailure() { + asyncHandlerWrapper.onHostnameResolutionFailure(null, null); + Mockito.verify(handler, Mockito.times(1)).onHostnameResolutionFailure(null, null); + } + + @Test + void onTcpConnectAttempt() { + asyncHandlerWrapper.onTcpConnectAttempt(null); + Mockito.verify(handler, Mockito.times(1)).onTcpConnectAttempt(null); + } + + @Test + void onTcpConnectSuccess() { + asyncHandlerWrapper.onTcpConnectSuccess(null, null); + Mockito.verify(handler, Mockito.times(1)).onTcpConnectSuccess(null, null); + } + + @Test + void onTcpConnectFailure() { + asyncHandlerWrapper.onTcpConnectFailure(null, null); + Mockito.verify(handler, Mockito.times(1)).onTcpConnectFailure(null, null); + } + + @Test + void onTlsHandshakeAttempt() { + asyncHandlerWrapper.onTlsHandshakeAttempt(); + Mockito.verify(handler, Mockito.times(1)).onTlsHandshakeAttempt(); + } + + @Test + void onTlsHandshakeSuccess() { + asyncHandlerWrapper.onTlsHandshakeSuccess(null); + Mockito.verify(handler, Mockito.times(1)).onTlsHandshakeSuccess(null); + } + + @Test + void onTlsHandshakeFailure() { + asyncHandlerWrapper.onTlsHandshakeFailure(null); + Mockito.verify(handler, Mockito.times(1)).onTlsHandshakeFailure(null); + } + + @Test + void onConnectionPoolAttempt() { + asyncHandlerWrapper.onConnectionPoolAttempt(); + Mockito.verify(handler, Mockito.times(1)).onConnectionPoolAttempt(); + } + + @Test + void onConnectionPooled() { + asyncHandlerWrapper.onConnectionPooled(null); + Mockito.verify(handler, Mockito.times(1)).onConnectionPooled(null); + } + + @Test + void onConnectionOffer() { + asyncHandlerWrapper.onConnectionOffer(null); + Mockito.verify(handler, Mockito.times(1)).onConnectionOffer(null); + } + + @Test + void onRequestSend() { + asyncHandlerWrapper.onRequestSend(null); + Mockito.verify(handler, Mockito.times(1)).onRequestSend(null); + } + + @Test + void onRetry() { + asyncHandlerWrapper.onRetry(); + Mockito.verify(handler, Mockito.times(1)).onRetry(); + } +} \ No newline at end of file diff --git a/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/wrapper/ResponseStatusWrapperTest.java b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/wrapper/ResponseStatusWrapperTest.java new file mode 100644 index 000000000..836293f16 --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/wrapper/ResponseStatusWrapperTest.java @@ -0,0 +1,78 @@ +package io.arex.inst.httpclient.asynchttpclient.wrapper; + +import static org.junit.jupiter.api.Assertions.*; + +import org.asynchttpclient.uri.Uri; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class ResponseStatusWrapperTest { + private static ResponseWrapper responseWrapper; + private static ResponseStatusWrapper responseStatusWrapper; + private static Uri uri; + + @BeforeAll + static void setUp() { + uri = Mockito.mock(Uri.class); + responseWrapper = Mockito.mock(ResponseWrapper.class); + Mockito.when(responseWrapper.getStatusCode()).thenReturn(200); + Mockito.when(responseWrapper.getStatusText()).thenReturn("OK"); + Mockito.when(responseWrapper.getProtocolName()).thenReturn("HTTP"); + Mockito.when(responseWrapper.getProtocolMajorVersion()).thenReturn(1); + Mockito.when(responseWrapper.getProtocolMinorVersion()).thenReturn(0); + Mockito.when(responseWrapper.getProtocolText()).thenReturn("HTTP/1.1"); + Mockito.when(responseWrapper.getRemoteAddress()).thenReturn("remoteAddress"); + Mockito.when(responseWrapper.getLocalAddress()).thenReturn("localAddress"); + responseStatusWrapper = new ResponseStatusWrapper(uri, responseWrapper); + } + + @AfterAll + static void tearDown() { + uri = null; + responseWrapper = null; + responseStatusWrapper = null; + Mockito.clearAllCaches(); + } + + @Test + void getStatusCode() { + assertEquals(200, responseStatusWrapper.getStatusCode()); + } + + @Test + void getStatusText() { + assertEquals("OK", responseStatusWrapper.getStatusText()); + } + + @Test + void getProtocolName() { + assertEquals("HTTP", responseStatusWrapper.getProtocolName()); + } + + @Test + void getProtocolMajorVersion() { + assertEquals(1, responseStatusWrapper.getProtocolMajorVersion()); + } + + @Test + void getProtocolMinorVersion() { + assertEquals(0, responseStatusWrapper.getProtocolMinorVersion()); + } + + @Test + void getProtocolText() { + assertEquals("HTTP/1.1", responseStatusWrapper.getProtocolText()); + } + + @Test + void getRemoteAddress() { + assertEquals("remoteAddress", responseStatusWrapper.getRemoteAddress().toString()); + } + + @Test + void getLocalAddress() { + assertEquals("localAddress", responseStatusWrapper.getLocalAddress().toString()); + } +} \ No newline at end of file diff --git a/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/wrapper/ResponseWrapperTest.java b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/wrapper/ResponseWrapperTest.java new file mode 100644 index 000000000..b1b179bbf --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/wrapper/ResponseWrapperTest.java @@ -0,0 +1,110 @@ +package io.arex.inst.httpclient.asynchttpclient.wrapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpHeaders; +import java.nio.ByteBuffer; +import java.util.Arrays; +import org.asynchttpclient.HttpResponseBodyPart; +import org.asynchttpclient.HttpResponseStatus; +import org.asynchttpclient.netty.EagerResponseBodyPart; +import org.asynchttpclient.uri.Uri; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; + +class ResponseWrapperTest { + + private static ResponseWrapper responseWrapper; + private static HttpResponseStatus responseStatus; + private static HttpHeaders httpHeaders; + private static HttpResponseBodyPart bodyPart; + + @BeforeAll + static void setUp() { + responseStatus = Mockito.mock(HttpResponseStatus.class); + final Uri uri = Mockito.mock(Uri.class); + Mockito.when(uri.toString()).thenReturn("http://localhost/"); + Mockito.when(responseStatus.getStatusCode()).thenReturn(200); + Mockito.when(responseStatus.getStatusText()).thenReturn("OK"); + Mockito.when(responseStatus.getProtocolName()).thenReturn("HTTP"); + Mockito.when(responseStatus.getProtocolMajorVersion()).thenReturn(1); + Mockito.when(responseStatus.getProtocolMinorVersion()).thenReturn(0); + Mockito.when(responseStatus.getProtocolText()).thenReturn("HTTP/1.1"); + Mockito.when(responseStatus.getRemoteAddress()).thenReturn(new SocketAddressWrapper("remoteAddress")); + Mockito.when(responseStatus.getLocalAddress()).thenReturn(new SocketAddressWrapper("localAddress")); + Mockito.when(responseStatus.getUri()).thenReturn(uri); + + httpHeaders = new DefaultHttpHeaders(); + httpHeaders.add("Content-Type", "application/json"); + httpHeaders.add("Content-Length", 100); + httpHeaders.add("test", Arrays.asList("test1", "test2")); + + byte[] content = "test".getBytes(); + ByteBuf byteBuf = Unpooled.wrappedBuffer(ByteBuffer.wrap(content)); + final EagerResponseBodyPart bodyPart = new EagerResponseBodyPart(byteBuf, true); + responseWrapper = new ResponseWrapper(); + responseWrapper.setHttpStatus(responseStatus); + responseWrapper.setHttpHeaders(httpHeaders); + responseWrapper.setHttpResponseBody(Arrays.asList(bodyPart)); + } + + @Test + void getContent() { + assertEquals("test", new String(responseWrapper.getContent())); + } + + @Test + void getStatusCode() { + assertEquals(200, responseWrapper.getStatusCode()); + } + + @Test + void getStatusText() { + assertEquals("OK", responseWrapper.getStatusText()); + } + + @Test + void getHeaders() { + assertEquals(3, responseWrapper.getHeaders().size()); + } + + @Test + void getLocalAddress() { + assertEquals("localAddress", responseWrapper.getLocalAddress()); + } + + @Test + void getRemoteAddress() { + assertEquals("remoteAddress", responseWrapper.getRemoteAddress()); + } + + @Test + void getUri() { + assertEquals("http://localhost/", responseWrapper.getUri()); + } + + @Test + void getProtocolMajorVersion() { + assertEquals(1, responseWrapper.getProtocolMajorVersion()); + } + + @Test + void getProtocolMinorVersion() { + assertEquals(0, responseWrapper.getProtocolMinorVersion()); + } + + @Test + void getProtocolName() { + assertEquals("HTTP", responseWrapper.getProtocolName()); + } + + @Test + void getProtocolText() { + assertEquals("HTTP/1.1", responseWrapper.getProtocolText()); + } +} \ No newline at end of file diff --git a/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/wrapper/SocketAddressWrapperTest.java b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/wrapper/SocketAddressWrapperTest.java new file mode 100644 index 000000000..301bbb6a7 --- /dev/null +++ b/arex-instrumentation/httpclient/arex-httpclient-asynchttpclient/src/test/java/io/arex/inst/httpclient/asynchttpclient/wrapper/SocketAddressWrapperTest.java @@ -0,0 +1,14 @@ +package io.arex.inst.httpclient.asynchttpclient.wrapper; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class SocketAddressWrapperTest { + + @Test + void testToString() { + SocketAddressWrapper socketAddressWrapper = new SocketAddressWrapper("test"); + assertEquals("test", socketAddressWrapper.toString()); + } +} \ No newline at end of file diff --git a/arex-instrumentation/httpclient/arex-httpclient-common/src/main/java/io/arex/inst/httpclient/common/HttpClientExtractor.java b/arex-instrumentation/httpclient/arex-httpclient-common/src/main/java/io/arex/inst/httpclient/common/HttpClientExtractor.java index 976a60a30..960a9f511 100644 --- a/arex-instrumentation/httpclient/arex-httpclient-common/src/main/java/io/arex/inst/httpclient/common/HttpClientExtractor.java +++ b/arex-instrumentation/httpclient/arex-httpclient-common/src/main/java/io/arex/inst/httpclient/common/HttpClientExtractor.java @@ -18,6 +18,7 @@ public class HttpClientExtractor { private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientExtractor.class); + private static final String HTTP_RESPONSE_WRAPPER_TYPE = HttpResponseWrapper.class.getName(); private final HttpClientAdapter adapter; @@ -38,7 +39,7 @@ public void record(TResponse response) { } Mocker mocker = makeMocker(); - mocker.getTargetResponse().setType(HttpResponseWrapper.class.getName()); + mocker.getTargetResponse().setType(HTTP_RESPONSE_WRAPPER_TYPE); mocker.getTargetResponse().setBody(Serializer.serialize(wrapped)); MockUtils.recordMocker(mocker); } @@ -87,7 +88,7 @@ private String encodeRequest(String httpMethod) { return adapter.getUri().getQuery(); } - private static final List ALLOW_HTTP_METHOD_BODY_SETS; + public static final List ALLOW_HTTP_METHOD_BODY_SETS; static { ALLOW_HTTP_METHOD_BODY_SETS = new ArrayList<>(4); diff --git a/arex-instrumentation/pom.xml b/arex-instrumentation/pom.xml index 3ab821708..661bb502f 100644 --- a/arex-instrumentation/pom.xml +++ b/arex-instrumentation/pom.xml @@ -44,6 +44,7 @@ httpclient/arex-httpclient-resttemplate httpclient/arex-httpclient-feign netty/arex-netty-v3 + httpclient/arex-httpclient-asynchttpclient netty/arex-netty-v4 dubbo/arex-dubbo-apache-v2 dubbo/arex-dubbo-apache-v3