This is an automated email from the ASF dual-hosted git repository. jamesnetherton pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
The following commit(s) were added to refs/heads/main by this push: new 75a4b1d Add Azure Core HTTP Client Vert.x extension 75a4b1d is described below commit 75a4b1d6d4132b12f610c774a20346cb930f8755 Author: James Netherton <jamesnether...@gmail.com> AuthorDate: Tue Mar 8 07:19:23 2022 +0000 Add Azure Core HTTP Client Vert.x extension Fixes #2196 --- extensions-jvm/azure-cosmosdb/deployment/pom.xml | 2 +- extensions-jvm/azure-cosmosdb/runtime/pom.xml | 8 +- extensions-jvm/azure-servicebus/deployment/pom.xml | 4 + extensions-jvm/azure-servicebus/runtime/pom.xml | 10 + .../azure-storage-datalake/deployment/pom.xml | 2 +- .../azure-storage-datalake/runtime/pom.xml | 8 +- .../deployment}/pom.xml | 72 +++-- .../vertx/AzureCoreHttpClientVertxProcessor.java | 31 ++ .../azure/core/http/vertx/DeadlockTests.java | 89 ++++++ .../http/vertx/VertxHttpClientBuilderTests.java | 181 ++++++++++++ .../http/vertx/VertxHttpClientHttpClientTests.java | 55 ++++ .../http/vertx/VertxHttpClientProviderTests.java | 113 ++++++++ .../vertx/VertxHttpClientResponseTransformer.java | 56 ++++ .../http/vertx/VertxHttpClientRestProxyTests.java | 63 ++++ ...VertxHttpClientRestProxyWithHttpProxyTests.java | 86 ++++++ .../http/vertx/VertxHttpClientTestResource.java | 73 +++++ .../core/http/vertx/VertxHttpClientTests.java | 317 +++++++++++++++++++++ .../deployment/src/test/resources/upload.txt | 1 + .../azure-core-http-client-vertx/pom.xml | 37 +++ .../runtime/pom.xml | 22 +- .../core/http/vertx/BufferedVertxHttpResponse.java | 72 +++++ .../core/http/vertx/VertxHttpAsyncResponse.java | 52 ++++ .../azure/core/http/vertx/VertxHttpClient.java | 133 +++++++++ .../core/http/vertx/VertxHttpClientBuilder.java | 250 ++++++++++++++++ .../core/http/vertx/VertxHttpClientProvider.java | 76 +++++ .../azure/core/http/vertx/VertxHttpRequest.java | 37 +++ .../azure/core/http/vertx/VertxHttpResponse.java | 73 +++++ .../core/http/vertx/VertxHttpResponseHandler.java | 59 ++++ .../main/resources/META-INF/quarkus-extension.yaml | 27 ++ .../com.azure.core.http.HttpClientProvider | 1 + extensions-support/azure-core/deployment/pom.xml | 6 +- extensions-support/azure-core/runtime/pom.xml | 12 +- extensions-support/pom.xml | 1 + extensions/azure-eventhubs/runtime/pom.xml | 6 + extensions/azure-storage-blob/deployment/pom.xml | 2 +- extensions/azure-storage-blob/runtime/pom.xml | 8 +- extensions/azure-storage-queue/deployment/pom.xml | 2 +- extensions/azure-storage-queue/runtime/pom.xml | 8 +- .../storage/blob/it/AzureStorageBlobTest.java | 39 +-- pom.xml | 2 + poms/bom-test/pom.xml | 20 ++ poms/bom/pom.xml | 10 + 42 files changed, 2062 insertions(+), 64 deletions(-) diff --git a/extensions-jvm/azure-cosmosdb/deployment/pom.xml b/extensions-jvm/azure-cosmosdb/deployment/pom.xml index 1c7501c..2af136b 100644 --- a/extensions-jvm/azure-cosmosdb/deployment/pom.xml +++ b/extensions-jvm/azure-cosmosdb/deployment/pom.xml @@ -36,7 +36,7 @@ </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> - <artifactId>camel-quarkus-support-azure-core-deployment</artifactId> + <artifactId>camel-quarkus-support-azure-core-http-client-vertx-deployment</artifactId> </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> diff --git a/extensions-jvm/azure-cosmosdb/runtime/pom.xml b/extensions-jvm/azure-cosmosdb/runtime/pom.xml index 37f26bf..6302c56 100644 --- a/extensions-jvm/azure-cosmosdb/runtime/pom.xml +++ b/extensions-jvm/azure-cosmosdb/runtime/pom.xml @@ -53,11 +53,17 @@ </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> - <artifactId>camel-quarkus-support-azure-core</artifactId> + <artifactId>camel-quarkus-support-azure-core-http-client-vertx</artifactId> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-azure-cosmosdb</artifactId> + <exclusions> + <exclusion> + <groupId>com.azure</groupId> + <artifactId>azure-core-http-netty</artifactId> + </exclusion> + </exclusions> </dependency> </dependencies> diff --git a/extensions-jvm/azure-servicebus/deployment/pom.xml b/extensions-jvm/azure-servicebus/deployment/pom.xml index fe1d73e..c33bc7f 100644 --- a/extensions-jvm/azure-servicebus/deployment/pom.xml +++ b/extensions-jvm/azure-servicebus/deployment/pom.xml @@ -38,6 +38,10 @@ </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> + <artifactId>camel-quarkus-support-azure-core-http-client-vertx-deployment</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel.quarkus</groupId> <artifactId>camel-quarkus-azure-servicebus</artifactId> </dependency> </dependencies> diff --git a/extensions-jvm/azure-servicebus/runtime/pom.xml b/extensions-jvm/azure-servicebus/runtime/pom.xml index 7ed8115..a4d0a9e 100644 --- a/extensions-jvm/azure-servicebus/runtime/pom.xml +++ b/extensions-jvm/azure-servicebus/runtime/pom.xml @@ -54,8 +54,18 @@ <artifactId>camel-quarkus-core</artifactId> </dependency> <dependency> + <groupId>org.apache.camel.quarkus</groupId> + <artifactId>camel-quarkus-support-azure-core-http-client-vertx</artifactId> + </dependency> + <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-azure-servicebus</artifactId> + <exclusions> + <exclusion> + <groupId>com.azure</groupId> + <artifactId>azure-core-http-netty</artifactId> + </exclusion> + </exclusions> </dependency> </dependencies> diff --git a/extensions-jvm/azure-storage-datalake/deployment/pom.xml b/extensions-jvm/azure-storage-datalake/deployment/pom.xml index 8758926..4628cc2 100644 --- a/extensions-jvm/azure-storage-datalake/deployment/pom.xml +++ b/extensions-jvm/azure-storage-datalake/deployment/pom.xml @@ -36,7 +36,7 @@ </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> - <artifactId>camel-quarkus-support-azure-core-deployment</artifactId> + <artifactId>camel-quarkus-support-azure-core-http-client-vertx-deployment</artifactId> </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> diff --git a/extensions-jvm/azure-storage-datalake/runtime/pom.xml b/extensions-jvm/azure-storage-datalake/runtime/pom.xml index ae64da2..3f75f0d 100644 --- a/extensions-jvm/azure-storage-datalake/runtime/pom.xml +++ b/extensions-jvm/azure-storage-datalake/runtime/pom.xml @@ -53,11 +53,17 @@ </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> - <artifactId>camel-quarkus-support-azure-core</artifactId> + <artifactId>camel-quarkus-support-azure-core-http-client-vertx</artifactId> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-azure-storage-datalake</artifactId> + <exclusions> + <exclusion> + <groupId>com.azure</groupId> + <artifactId>azure-core-http-netty</artifactId> + </exclusion> + </exclusions> </dependency> </dependencies> diff --git a/extensions-support/azure-core/runtime/pom.xml b/extensions-support/azure-core-http-client-vertx/deployment/pom.xml similarity index 55% copy from extensions-support/azure-core/runtime/pom.xml copy to extensions-support/azure-core-http-client-vertx/deployment/pom.xml index 701cd25..5e9d670 100644 --- a/extensions-support/azure-core/runtime/pom.xml +++ b/extensions-support/azure-core-http-client-vertx/deployment/pom.xml @@ -21,24 +21,19 @@ <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.apache.camel.quarkus</groupId> - <artifactId>camel-quarkus-support-azure-core-parent</artifactId> + <artifactId>camel-quarkus-support-azure-core-http-client-vertx-parent</artifactId> <version>2.8.0-SNAPSHOT</version> <relativePath>../pom.xml</relativePath> </parent> - <artifactId>camel-quarkus-support-azure-core</artifactId> - <name>Camel Quarkus :: Support :: Azure Core :: Runtime</name> - - <properties> - <camel.quarkus.jvmSince>1.7.0</camel.quarkus.jvmSince> - <camel.quarkus.nativeSince>1.7.0</camel.quarkus.nativeSince> - </properties> + <artifactId>camel-quarkus-support-azure-core-http-client-vertx-deployment</artifactId> + <name>Camel Quarkus :: Support :: Azure Core HTTP Client Vert.x :: Deployment</name> <dependencyManagement> <dependencies> <dependency> <groupId>org.apache.camel.quarkus</groupId> - <artifactId>camel-quarkus-bom</artifactId> + <artifactId>camel-quarkus-bom-test</artifactId> <version>${project.version}</version> <type>pom</type> <scope>import</scope> @@ -49,29 +44,73 @@ <dependencies> <dependency> <groupId>io.quarkus</groupId> - <artifactId>quarkus-core</artifactId> + <artifactId>quarkus-core-deployment</artifactId> + </dependency> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-vertx-deployment</artifactId> </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> - <artifactId>camel-quarkus-support-jackson-dataformat-xml</artifactId> + <artifactId>camel-quarkus-support-azure-core-deployment</artifactId> </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> - <artifactId>camel-quarkus-support-reactor-netty</artifactId> + <artifactId>camel-quarkus-support-azure-core-http-client-vertx</artifactId> + </dependency> + + + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-junit5-internal</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.testcontainers</groupId> + <artifactId>testcontainers</artifactId> + <scope>test</scope> </dependency> <dependency> + <groupId>org.apache.camel.quarkus</groupId> + <artifactId>camel-quarkus-integration-wiremock-support</artifactId> + <scope>test</scope> + <exclusions> + <exclusion> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest-core</artifactId> + </exclusion> + </exclusions> + </dependency> + + + <!-- Azure core test support --> + <dependency> <groupId>com.azure</groupId> <artifactId>azure-core</artifactId> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.azure</groupId> + <artifactId>azure-core-test</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.azure</groupId> + <artifactId>azure-core-test</artifactId> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.projectreactor</groupId> + <artifactId>reactor-test</artifactId> + <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-bootstrap-maven-plugin</artifactId> - </plugin> - <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> @@ -86,4 +125,5 @@ </plugin> </plugins> </build> + </project> diff --git a/extensions-support/azure-core-http-client-vertx/deployment/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/AzureCoreHttpClientVertxProcessor.java b/extensions-support/azure-core-http-client-vertx/deployment/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/AzureCoreHttpClientVertxProcessor.java new file mode 100644 index 0000000..db0972b --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/deployment/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/AzureCoreHttpClientVertxProcessor.java @@ -0,0 +1,31 @@ +/* + * 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.camel.quarkus.support.azure.core.http.vertx; + +import io.netty.handler.ssl.OpenSsl; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; + +public class AzureCoreHttpClientVertxProcessor { + + @BuildStep + void runtimeInitializedClasses(BuildProducer<RuntimeInitializedClassBuildItem> runtimeInitializedClasses) { + runtimeInitializedClasses.produce(new RuntimeInitializedClassBuildItem(OpenSsl.class.getName())); + runtimeInitializedClasses.produce(new RuntimeInitializedClassBuildItem("io.netty.internal.tcnative.SSL")); + } +} diff --git a/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/DeadlockTests.java b/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/DeadlockTests.java new file mode 100644 index 0000000..18237b4 --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/DeadlockTests.java @@ -0,0 +1,89 @@ +/* + * 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.camel.quarkus.support.azure.core.http.vertx; + +import java.security.SecureRandom; + +import com.azure.core.http.HttpClient; +import com.azure.core.http.HttpMethod; +import com.azure.core.http.HttpRequest; +import com.azure.core.util.FluxUtil; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import io.quarkus.test.QuarkusUnitTest; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +public class DeadlockTests { + + @RegisterExtension + static final QuarkusUnitTest CONFIG = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); + + private static final String GET_ENDPOINT = "/get"; + + private WireMockServer server; + private byte[] expectedGetBytes; + + @BeforeEach + public void configureWireMockServer() { + expectedGetBytes = new byte[10 * 1024 * 1024]; + new SecureRandom().nextBytes(expectedGetBytes); + + server = new WireMockServer(WireMockConfiguration.options() + .dynamicPort() + .disableRequestJournal() + .gzipDisabled(true)); + + server.stubFor(WireMock.get(GET_ENDPOINT).willReturn(WireMock.aResponse().withBody(expectedGetBytes))); + + server.start(); + } + + @AfterEach + public void shutdownWireMockServer() { + if (server != null) { + server.shutdown(); + } + } + + @Test + public void attemptToDeadlock() { + HttpClient httpClient = new VertxHttpClientProvider().createInstance(); + + String endpoint = server.baseUrl() + GET_ENDPOINT; + + for (int i = 0; i < 100; i++) { + StepVerifier.create(httpClient.send(new HttpRequest(HttpMethod.GET, endpoint)) + .flatMap(response -> FluxUtil.collectBytesInByteBufferStream(response.getBody()) + .zipWith(Mono.just(response.getStatusCode())))) + .assertNext(responseTuple -> { + Assertions.assertEquals(200, responseTuple.getT2()); + Assertions.assertArrayEquals(expectedGetBytes, responseTuple.getT1()); + }) + .verifyComplete(); + } + } +} diff --git a/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientBuilderTests.java b/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientBuilderTests.java new file mode 100644 index 0000000..03f6099 --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientBuilderTests.java @@ -0,0 +1,181 @@ +/* + * 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.camel.quarkus.support.azure.core.http.vertx; + +import java.net.InetSocketAddress; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import com.azure.core.http.HttpClient; +import com.azure.core.http.HttpMethod; +import com.azure.core.http.HttpRequest; +import com.azure.core.http.ProxyOptions; +import com.azure.core.util.Configuration; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import io.vertx.core.Vertx; +import io.vertx.ext.web.client.WebClientOptions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import reactor.test.StepVerifier; + +import static org.apache.camel.quarkus.support.azure.core.http.vertx.VertxHttpClientTestResource.PROXY_PASSWORD; +import static org.apache.camel.quarkus.support.azure.core.http.vertx.VertxHttpClientTestResource.PROXY_USER; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests {@link VertxHttpClientBuilder}. + */ +public class VertxHttpClientBuilderTests { + private static final String COOKIE_VALIDATOR_PATH = "/cookieValidator"; + private static final String DEFAULT_PATH = "/default"; + private static final String DISPATCHER_PATH = "/dispatcher"; + + private static final WireMockServer server = new WireMockServer( + WireMockConfiguration.options().dynamicPort().disableRequestJournal()); + private static final Vertx vertx = Vertx.vertx(); + + private static String defaultUrl; + + @BeforeAll + public static void setupWireMock() { + // Mocked endpoint to test building a client with a prebuilt client. + server.stubFor(WireMock.get(COOKIE_VALIDATOR_PATH).withCookie("test", WireMock.matching("success")) + .willReturn(WireMock.aResponse().withStatus(200))); + + // Mocked endpoint to test building a client with a timeout. + server.stubFor(WireMock.get(DEFAULT_PATH).willReturn(WireMock.aResponse().withStatus(200))); + + // Mocked endpoint to test building a client with a dispatcher and uses a delayed response. + server.stubFor(WireMock.get(DISPATCHER_PATH).willReturn(WireMock.aResponse().withStatus(200) + .withFixedDelay(5000))); + + server.start(); + + defaultUrl = "http://localhost:" + server.port() + DEFAULT_PATH; + } + + @AfterAll + public static void afterAll() throws InterruptedException { + if (server.isRunning()) { + server.shutdown(); + } + CountDownLatch latch = new CountDownLatch(1); + vertx.close(x -> latch.countDown()); + latch.await(); + } + + @Test + public void buildWithConfigurationNone() { + HttpClient client = new VertxHttpClientBuilder(vertx) + .configuration(Configuration.NONE) + .build(); + try { + StepVerifier.create(client.send(new HttpRequest(HttpMethod.GET, defaultUrl))) + .assertNext(response -> assertEquals(200, response.getStatusCode())) + .verifyComplete(); + } finally { + ((VertxHttpClient) client).close(); + } + } + + @Test + public void buildWithDefaultConnectionOptions() { + WebClientOptions options = new WebClientOptions(); + + HttpClient client = new VertxHttpClientBuilder(vertx) + .webClientOptions(options) + .build(); + + try { + StepVerifier.create(client.send(new HttpRequest(HttpMethod.GET, defaultUrl))) + .assertNext(response -> assertEquals(200, response.getStatusCode())) + .verifyComplete(); + + assertEquals(options.getConnectTimeout(), 10000); + assertEquals(options.getIdleTimeout(), 60); + assertEquals(options.getReadIdleTimeout(), 60); + assertEquals(options.getWriteIdleTimeout(), 60); + } finally { + ((VertxHttpClient) client).close(); + } + } + + @Test + public void buildWithConnectionOptions() { + WebClientOptions options = new WebClientOptions(); + + HttpClient client = new VertxHttpClientBuilder(vertx) + .webClientOptions(options) + .connectTimeout(Duration.ofSeconds(10)) + .idleTimeout(Duration.ofSeconds(20)) + .readIdleTimeout(Duration.ofSeconds(30)) + .writeIdleTimeout(Duration.ofSeconds(40)) + .build(); + + try { + StepVerifier.create(client.send(new HttpRequest(HttpMethod.GET, defaultUrl))) + .assertNext(response -> assertEquals(200, response.getStatusCode())) + .verifyComplete(); + + assertEquals(options.getConnectTimeout(), 10000); + assertEquals(options.getIdleTimeout(), 20); + assertEquals(options.getReadIdleTimeout(), 30); + assertEquals(options.getWriteIdleTimeout(), 40); + } finally { + ((VertxHttpClient) client).close(); + } + } + + @ParameterizedTest + @EnumSource(ProxyOptions.Type.class) + public void allProxyOptions(ProxyOptions.Type type) { + WebClientOptions options = new WebClientOptions(); + InetSocketAddress address = new InetSocketAddress("localhost", 8888); + ProxyOptions proxyOptions = new ProxyOptions(type, address); + proxyOptions.setCredentials(PROXY_USER, PROXY_PASSWORD); + proxyOptions.setNonProxyHosts("foo.*|*bar.com|microsoft.com"); + + HttpClient client = new VertxHttpClientBuilder(vertx) + .webClientOptions(options) + .proxy(proxyOptions) + .build(); + + try { + io.vertx.core.net.ProxyOptions vertxProxyOptions = options.getProxyOptions(); + assertEquals(vertxProxyOptions.getHost(), address.getHostName()); + assertEquals(vertxProxyOptions.getPort(), address.getPort()); + assertEquals(vertxProxyOptions.getType().name(), type.name()); + assertEquals(vertxProxyOptions.getUsername(), PROXY_USER); + assertEquals(vertxProxyOptions.getPassword(), PROXY_PASSWORD); + + List<String> proxyHosts = new ArrayList<>(); + proxyHosts.add("foo*"); + proxyHosts.add(".*bar.com"); + proxyHosts.add("microsoft.com"); + assertEquals(proxyHosts, options.getNonProxyHosts()); + } finally { + ((VertxHttpClient) client).close(); + } + } +} diff --git a/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientHttpClientTests.java b/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientHttpClientTests.java new file mode 100644 index 0000000..67dae66 --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientHttpClientTests.java @@ -0,0 +1,55 @@ +/* + * 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.camel.quarkus.support.azure.core.http.vertx; + +import java.util.concurrent.CountDownLatch; + +import com.azure.core.http.HttpClient; +import com.azure.core.test.HttpClientTestsWireMockServer; +import com.azure.core.test.http.HttpClientTests; +import com.github.tomakehurst.wiremock.WireMockServer; +import io.vertx.core.Vertx; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; + +public class VertxHttpClientHttpClientTests extends HttpClientTests { + private static final WireMockServer server = HttpClientTestsWireMockServer.getHttpClientTestsServer(); + private static final Vertx vertx = Vertx.vertx(); + + @BeforeAll + public static void getWireMockServer() { + server.start(); + } + + @AfterAll + public static void afterAll() throws InterruptedException { + server.shutdown(); + CountDownLatch latch = new CountDownLatch(1); + vertx.close(x -> latch.countDown()); + latch.await(); + } + + @Override + protected int getWireMockPort() { + return server.port(); + } + + @Override + protected HttpClient createHttpClient() { + return new VertxHttpClientBuilder(vertx).build(); + } +} diff --git a/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientProviderTests.java b/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientProviderTests.java new file mode 100644 index 0000000..402d154 --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientProviderTests.java @@ -0,0 +1,113 @@ +/* + * 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.camel.quarkus.support.azure.core.http.vertx; + +import java.net.InetSocketAddress; +import java.time.Duration; + +import com.azure.core.http.ProxyOptions; +import com.azure.core.util.Configuration; +import com.azure.core.util.HttpClientOptions; +import io.quarkus.test.QuarkusUnitTest; +import io.vertx.ext.web.client.WebClientOptions; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * Tests {@link VertxHttpClientProvider}. + */ + +public class VertxHttpClientProviderTests { + + @RegisterExtension + static final QuarkusUnitTest CONFIG = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); + + @Test + public void nullOptionsReturnsBaseClient() { + VertxHttpClient httpClient = (VertxHttpClient) new VertxHttpClientProvider() + .createInstance(null); + + ProxyOptions environmentProxy = ProxyOptions.fromConfiguration(Configuration.getGlobalConfiguration()); + WebClientOptions options = httpClient.getWebClientOptions(); + io.vertx.core.net.ProxyOptions proxyOptions = options.getProxyOptions(); + if (environmentProxy == null) { + assertNull(proxyOptions); + } else { + assertNotNull(proxyOptions); + assertEquals(environmentProxy.getAddress().getHostName(), proxyOptions.getHost()); + } + } + + @Test + public void defaultOptionsReturnsBaseClient() { + VertxHttpClient httpClient = (VertxHttpClient) new VertxHttpClientProvider() + .createInstance(new HttpClientOptions()); + + ProxyOptions environmentProxy = ProxyOptions.fromConfiguration(Configuration.getGlobalConfiguration()); + WebClientOptions options = httpClient.getWebClientOptions(); + io.vertx.core.net.ProxyOptions proxyOptions = options.getProxyOptions(); + if (environmentProxy == null) { + assertNull(proxyOptions); + } else { + assertNotNull(proxyOptions); + assertEquals(environmentProxy.getAddress().getHostName(), proxyOptions.getHost()); + } + } + + @Test + public void optionsWithAProxy() { + ProxyOptions proxyOptions = new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress("localhost", 8888)); + proxyOptions.setNonProxyHosts("foo.*|bar.*|cheese.com|wine.org"); + + HttpClientOptions clientOptions = new HttpClientOptions().setProxyOptions(proxyOptions); + + VertxHttpClient httpClient = (VertxHttpClient) new VertxHttpClientProvider() + .createInstance(clientOptions); + + WebClientOptions options = httpClient.getWebClientOptions(); + io.vertx.core.net.ProxyOptions vertxProxyOptions = options.getProxyOptions(); + assertNotNull(vertxProxyOptions); + assertEquals(proxyOptions.getAddress().getHostName(), vertxProxyOptions.getHost()); + assertEquals(proxyOptions.getAddress().getPort(), vertxProxyOptions.getPort()); + assertEquals(proxyOptions.getType().name(), vertxProxyOptions.getType().name()); + } + + @Test + public void optionsWithTimeouts() { + long expectedTimeout = 15000; + Duration timeout = Duration.ofMillis(expectedTimeout); + HttpClientOptions clientOptions = new HttpClientOptions() + .setWriteTimeout(timeout) + .setResponseTimeout(timeout) + .setReadTimeout(timeout); + + VertxHttpClient httpClient = (VertxHttpClient) new VertxHttpClientProvider() + .createInstance(clientOptions); + + WebClientOptions options = httpClient.getWebClientOptions(); + + assertEquals(timeout.getSeconds(), options.getWriteIdleTimeout()); + assertEquals(timeout.getSeconds(), options.getReadIdleTimeout()); + } +} diff --git a/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientResponseTransformer.java b/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientResponseTransformer.java new file mode 100644 index 0000000..fe7aa1c --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientResponseTransformer.java @@ -0,0 +1,56 @@ +/* + * 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.camel.quarkus.support.azure.core.http.vertx; + +import com.github.tomakehurst.wiremock.common.FileSource; +import com.github.tomakehurst.wiremock.extension.Parameters; +import com.github.tomakehurst.wiremock.extension.ResponseTransformer; +import com.github.tomakehurst.wiremock.http.Request; +import com.github.tomakehurst.wiremock.http.Response; + +import static org.apache.camel.quarkus.support.azure.core.http.vertx.VertxHttpClientTests.RETURN_HEADERS_AS_IS_PATH; + +/** + * Mock response transformer used to test {@link VertxHttpClient}. + */ +public class VertxHttpClientResponseTransformer extends ResponseTransformer { + public static final String NAME = "vertx-http-client-response-transformer"; + + @Override + public Response transform(Request request, Response response, FileSource fileSource, Parameters parameters) { + String url = request.getUrl(); + + if (RETURN_HEADERS_AS_IS_PATH.equalsIgnoreCase(url)) { + return Response.response() + .status(200) + .headers(request.getHeaders()) + .build(); + } + + return response; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public boolean applyGlobally() { + return false; + } +} diff --git a/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientRestProxyTests.java b/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientRestProxyTests.java new file mode 100644 index 0000000..153d840 --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientRestProxyTests.java @@ -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.camel.quarkus.support.azure.core.http.vertx; + +import javax.inject.Inject; + +import com.azure.core.http.HttpClient; +import com.azure.core.test.RestProxyTestsWireMockServer; +import com.azure.core.test.implementation.RestProxyTests; +import com.github.tomakehurst.wiremock.WireMockServer; +import io.quarkus.test.QuarkusUnitTest; +import io.vertx.core.Vertx; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class VertxHttpClientRestProxyTests extends RestProxyTests { + private final static WireMockServer server = RestProxyTestsWireMockServer.getRestProxyTestsServer(); + + @RegisterExtension + static final QuarkusUnitTest CONFIG = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("upload.txt", "upload.txt")); + + @Inject + Vertx vertx; + + @BeforeAll + public static void getWireMockServer() { + server.start(); + } + + @AfterAll + public static void shutdownWireMockServer() { + server.shutdown(); + } + + @Override + protected int getWireMockPort() { + return server.port(); + } + + @Override + protected HttpClient createHttpClient() { + return new VertxHttpClientBuilder(vertx).build(); + } +} diff --git a/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientRestProxyWithHttpProxyTests.java b/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientRestProxyWithHttpProxyTests.java new file mode 100644 index 0000000..748ede8 --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientRestProxyWithHttpProxyTests.java @@ -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.camel.quarkus.support.azure.core.http.vertx; + +import java.net.InetSocketAddress; + +import javax.inject.Inject; + +import com.azure.core.http.HttpClient; +import com.azure.core.http.ProxyOptions; +import com.azure.core.test.RestProxyTestsWireMockServer; +import com.azure.core.test.implementation.RestProxyTests; +import com.github.tomakehurst.wiremock.WireMockServer; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.test.common.QuarkusTestResource; +import io.vertx.core.Vertx; +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.RegisterExtension; + +import static org.apache.camel.quarkus.support.azure.core.http.vertx.VertxHttpClientTestResource.PROXY_PASSWORD; +import static org.apache.camel.quarkus.support.azure.core.http.vertx.VertxHttpClientTestResource.PROXY_USER; + +@QuarkusTestResource(VertxHttpClientTestResource.class) +public class VertxHttpClientRestProxyWithHttpProxyTests extends RestProxyTests { + private static WireMockServer server; + + @RegisterExtension + static final QuarkusUnitTest CONFIG = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("upload.txt", "upload.txt")); + + @Inject + Vertx vertx; + + @BeforeAll + public static void getWireMockServer() { + server = RestProxyTestsWireMockServer.getRestProxyTestsServer(); + server.start(); + } + + @AfterAll + public static void shutdownWireMockServer() { + if (server != null) { + server.shutdown(); + } + } + + @Override + protected int getWireMockPort() { + return server.port(); + } + + @Override + protected HttpClient createHttpClient() { + Config config = ConfigProvider.getConfig(); + String proxyHost = config.getValue("tiny.proxy.host", String.class); + int proxyPort = config.getValue("tiny.proxy.port", int.class); + + InetSocketAddress address = new InetSocketAddress(proxyHost, proxyPort); + ProxyOptions proxyOptions = new ProxyOptions(ProxyOptions.Type.HTTP, address); + proxyOptions.setCredentials(PROXY_USER, PROXY_PASSWORD); + + return new VertxHttpClientBuilder(vertx) + .proxy(proxyOptions) + .build(); + } +} diff --git a/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientTestResource.java b/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientTestResource.java new file mode 100644 index 0000000..2a54ab7 --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientTestResource.java @@ -0,0 +1,73 @@ +/* + * 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.camel.quarkus.support.azure.core.http.vertx; + +import java.util.HashMap; +import java.util.Map; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +import org.apache.commons.lang3.SystemUtils; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.DockerImageName; + +public class VertxHttpClientTestResource implements QuarkusTestResourceLifecycleManager { + + public static final String PROXY_USER = "admin"; + public static final String PROXY_PASSWORD = "p4ssw0rd"; + + private static final DockerImageName TINY_PROXY_IMAGE_NAME = DockerImageName.parse("monokal/tinyproxy"); + private static final Integer TINY_PROXY_PORT = 8888; + private GenericContainer container; + + @Override + public Map<String, String> start() { + String host; + int port; + + container = new GenericContainer(TINY_PROXY_IMAGE_NAME) + .withEnv("BASIC_AUTH_USER", PROXY_USER) + .withEnv("BASIC_AUTH_PASSWORD", PROXY_PASSWORD) + .withCommand("ANY") + .waitingFor(Wait.forListeningPort()); + + if (SystemUtils.IS_OS_LINUX) { + container.withNetworkMode("host"); + port = TINY_PROXY_PORT; + host = "localhost"; + } else { + container.withNetworkMode("bridge") + .withExposedPorts(TINY_PROXY_PORT); + port = container.getMappedPort(TINY_PROXY_PORT); + host = "host.docker.internal"; + } + + container.start(); + + Map<String, String> options = new HashMap<>(); + options.put("tiny.proxy.host", host); + options.put("tiny.proxy.port", String.valueOf(port)); + return options; + } + + @Override + public void stop() { + if (container != null) { + container.stop(); + } + } +} diff --git a/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientTests.java b/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientTests.java new file mode 100644 index 0000000..ba72040 --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/deployment/src/test/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientTests.java @@ -0,0 +1,317 @@ +/* + * 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.camel.quarkus.support.azure.core.http.vertx; + +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import javax.inject.Inject; + +import com.azure.core.http.HttpClient; +import com.azure.core.http.HttpHeader; +import com.azure.core.http.HttpHeaders; +import com.azure.core.http.HttpMethod; +import com.azure.core.http.HttpRequest; +import com.azure.core.http.HttpResponse; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import io.quarkus.test.QuarkusUnitTest; +import io.vertx.core.Vertx; +import io.vertx.core.VertxException; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; +import reactor.test.StepVerifier; +import reactor.test.StepVerifierOptions; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class VertxHttpClientTests { + static final String RETURN_HEADERS_AS_IS_PATH = "/returnHeadersAsIs"; + + private static final String SHORT_BODY = "hi there"; + private static final String LONG_BODY = createLongBody(); + + private static WireMockServer server; + + @Inject + Vertx vertx; + + @RegisterExtension + static final QuarkusUnitTest CONFIG = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(VertxHttpClientResponseTransformer.class)); + + @BeforeAll + public static void beforeClass() { + server = new WireMockServer(WireMockConfiguration.options() + .extensions(new VertxHttpClientResponseTransformer()) + .dynamicPort() + .disableRequestJournal() + .gzipDisabled(true)); + + server.stubFor(WireMock.get("/short").willReturn(WireMock.aResponse().withBody(SHORT_BODY))); + server.stubFor(WireMock.get("/long").willReturn(WireMock.aResponse().withBody(LONG_BODY))); + server.stubFor(WireMock.get("/error").willReturn(WireMock.aResponse().withBody("error").withStatus(500))); + server.stubFor(WireMock.post("/shortPost").willReturn(WireMock.aResponse().withBody(SHORT_BODY))); + server.stubFor(WireMock.get(RETURN_HEADERS_AS_IS_PATH).willReturn(WireMock.aResponse() + .withTransformers(VertxHttpClientResponseTransformer.NAME))); + + server.start(); + } + + @AfterAll + public static void afterClass() { + if (server != null) { + server.shutdown(); + } + } + + @Test + public void testFlowableResponseShortBodyAsByteArrayAsync() { + checkBodyReceived(SHORT_BODY, "/short"); + } + + @Test + public void testFlowableResponseLongBodyAsByteArrayAsync() { + checkBodyReceived(LONG_BODY, "/long"); + } + + @Test + public void testFlowableWhenServerReturnsBodyAndNoErrorsWhenHttp500Returned() { + HttpResponse response = getResponse("/error"); + assertEquals(500, response.getStatusCode()); + StepVerifier.create(response.getBodyAsString()) + .expectNext("error") + .expectComplete() + .verify(Duration.ofSeconds(20)); + } + + @Test + public void testFlowableBackpressure() { + HttpResponse response = getResponse("/long"); + + StepVerifierOptions stepVerifierOptions = StepVerifierOptions.create(); + stepVerifierOptions.initialRequest(0); + + StepVerifier.create(response.getBody(), stepVerifierOptions) + .expectNextCount(0) + .thenRequest(1) + .expectNextCount(1) + .thenRequest(3) + .expectNextCount(3) + .thenRequest(Long.MAX_VALUE) + .thenConsumeWhile(ByteBuffer::hasRemaining) + .verifyComplete(); + } + + @Test + public void testRequestBodyIsErrorShouldPropagateToResponse() { + HttpClient client = new VertxHttpClientProvider().createInstance(); + HttpRequest request = new HttpRequest(HttpMethod.POST, url(server, "/shortPost")) + .setHeader("Content-Length", "123") + .setBody(Flux.error(new RuntimeException("boo"))); + + StepVerifier.create(client.send(request)) + .expectErrorMessage("boo") + .verify(); + } + + @Test + public void testRequestBodyEndsInErrorShouldPropagateToResponse() { + HttpClient client = new VertxHttpClientProvider().createInstance(); + String contentChunk = "abcdefgh"; + int repetitions = 1000; + HttpRequest request = new HttpRequest(HttpMethod.POST, url(server, "/shortPost")) + .setHeader("Content-Length", String.valueOf(contentChunk.length() * (repetitions + 1))) + .setBody(Flux.just(contentChunk) + .repeat(repetitions) + .map(s -> ByteBuffer.wrap(s.getBytes(StandardCharsets.UTF_8))) + .concatWith(Flux.error(new RuntimeException("boo")))); + StepVerifier.create(client.send(request)) + .expectErrorMessage("boo") + .verify(Duration.ofSeconds(10)); + } + + @Test + public void testServerShutsDownSocketShouldPushErrorToContentFlowable() { + Assertions.assertTimeout(Duration.ofMillis(5000), () -> { + CountDownLatch latch = new CountDownLatch(1); + try (ServerSocket ss = new ServerSocket(0)) { + Mono.fromCallable(() -> { + latch.countDown(); + Socket socket = ss.accept(); + // give the client time to get request across + Thread.sleep(500); + // respond but don't send the complete response + byte[] bytes = new byte[1024]; + int n = socket.getInputStream().read(bytes); + System.out.println(new String(bytes, 0, n, StandardCharsets.UTF_8)); + String response = "HTTP/1.1 200 OK\r\n" // + + "Content-Type: text/plain\r\n" // + + "Content-Length: 10\r\n" // + + "\r\n" // + + "zi"; + OutputStream out = socket.getOutputStream(); + out.write(response.getBytes()); + out.flush(); + // kill the socket with HTTP response body incomplete + socket.close(); + return 1; + }).subscribeOn(Schedulers.boundedElastic()).subscribe(); + // + latch.await(); + HttpClient client = new VertxHttpClientBuilder(vertx).build(); + HttpRequest request = new HttpRequest(HttpMethod.GET, + new URL("http://localhost:" + ss.getLocalPort() + "/ioException")); + + StepVerifier.create(client.send(request)) + .verifyError(VertxException.class); + } + }); + } + + @Test + public void testConcurrentRequests() throws NoSuchAlgorithmException { + int numRequests = 100; // 100 = 1GB of data read + HttpClient client = new VertxHttpClientProvider().createInstance(); + byte[] expectedDigest = digest(LONG_BODY); + long expectedByteCount = (long) numRequests * LONG_BODY.getBytes(StandardCharsets.UTF_8).length; + + Mono<Long> numBytesMono = Flux.range(1, numRequests) + .parallel(10) + .runOn(Schedulers.boundedElastic()) + .flatMap(n -> Mono.fromCallable(() -> getResponse(client, "/long")).flatMapMany(response -> { + MessageDigest md = md5Digest(); + return response.getBody() + .doOnNext(buffer -> md.update(buffer.duplicate())) + .doOnComplete(() -> assertArrayEquals(expectedDigest, md.digest(), "wrong digest!")); + })) + .sequential() + .map(buffer -> (long) buffer.remaining()) + .reduce(Long::sum); + + StepVerifier.create(numBytesMono) + .expectNext(expectedByteCount) + .expectComplete() + .verify(Duration.ofSeconds(60)); + } + + @Test + public void validateHeadersReturnAsIs() { + HttpClient client = new VertxHttpClientProvider().createInstance(); + + final String singleValueHeaderName = "singleValue"; + final String singleValueHeaderValue = "value"; + + final String multiValueHeaderName = "Multi-value"; + final List<String> multiValueHeaderValue = Arrays.asList("value1", "value2"); + + HttpHeaders headers = new HttpHeaders() + .set(singleValueHeaderName, singleValueHeaderValue) + .set(multiValueHeaderName, multiValueHeaderValue); + + StepVerifier.create(client.send(new HttpRequest(HttpMethod.GET, url(server, RETURN_HEADERS_AS_IS_PATH), + headers, Flux.empty()))) + .assertNext(response -> { + Assertions.assertEquals(200, response.getStatusCode()); + + HttpHeaders responseHeaders = response.getHeaders(); + HttpHeader singleValueHeader = responseHeaders.get(singleValueHeaderName); + assertEquals(singleValueHeaderName, singleValueHeader.getName()); + assertEquals(singleValueHeaderValue, singleValueHeader.getValue()); + + HttpHeader multiValueHeader = responseHeaders.get("Multi-value"); + assertEquals(multiValueHeaderName, multiValueHeader.getName()); + }) + .expectComplete() + .verify(Duration.ofSeconds(10)); + } + + private static MessageDigest md5Digest() { + try { + return MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + private static byte[] digest(String s) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(s.getBytes(StandardCharsets.UTF_8)); + return md.digest(); + } + + private HttpResponse getResponse(String path) { + HttpClient client = new VertxHttpClientBuilder(vertx).build(); + return getResponse(client, path); + } + + private static HttpResponse getResponse(HttpClient client, String path) { + HttpRequest request = new HttpRequest(HttpMethod.GET, url(server, path)); + return client.send(request).block(); + } + + static URL url(WireMockServer server, String path) { + try { + return new URL("http://localhost:" + server.port() + path); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + private static String createLongBody() { + StringBuilder builder = new StringBuilder("abcdefghijk".length() * 1000000); + for (int i = 0; i < 1000000; i++) { + builder.append("abcdefghijk"); + } + + return builder.toString(); + } + + private void checkBodyReceived(String expectedBody, String path) { + HttpClient client = new VertxHttpClientBuilder(vertx).build(); + StepVerifier.create(doRequest(client, path).getBodyAsByteArray()) + .assertNext(bytes -> assertEquals(expectedBody, new String(bytes, StandardCharsets.UTF_8))) + .verifyComplete(); + } + + private HttpResponse doRequest(HttpClient client, String path) { + HttpRequest request = new HttpRequest(HttpMethod.GET, url(server, path)); + return client.send(request).block(); + } +} diff --git a/extensions-support/azure-core-http-client-vertx/deployment/src/test/resources/upload.txt b/extensions-support/azure-core-http-client-vertx/deployment/src/test/resources/upload.txt new file mode 100644 index 0000000..ff3bb63 --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/deployment/src/test/resources/upload.txt @@ -0,0 +1 @@ +The quick brown fox jumps over the lazy dog \ No newline at end of file diff --git a/extensions-support/azure-core-http-client-vertx/pom.xml b/extensions-support/azure-core-http-client-vertx/pom.xml new file mode 100644 index 0000000..82f4083 --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/pom.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + 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. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.camel.quarkus</groupId> + <artifactId>camel-quarkus-extensions-support</artifactId> + <version>2.8.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>camel-quarkus-support-azure-core-http-client-vertx-parent</artifactId> + <name>Camel Quarkus :: Support :: Azure Core HTTP Client Vert.x</name> + <packaging>pom</packaging> + + <modules> + <module>deployment</module> + <module>runtime</module> + </modules> +</project> diff --git a/extensions-support/azure-core/runtime/pom.xml b/extensions-support/azure-core-http-client-vertx/runtime/pom.xml similarity index 81% copy from extensions-support/azure-core/runtime/pom.xml copy to extensions-support/azure-core-http-client-vertx/runtime/pom.xml index 701cd25..6a86758 100644 --- a/extensions-support/azure-core/runtime/pom.xml +++ b/extensions-support/azure-core-http-client-vertx/runtime/pom.xml @@ -21,17 +21,17 @@ <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.apache.camel.quarkus</groupId> - <artifactId>camel-quarkus-support-azure-core-parent</artifactId> + <artifactId>camel-quarkus-support-azure-core-http-client-vertx-parent</artifactId> <version>2.8.0-SNAPSHOT</version> <relativePath>../pom.xml</relativePath> </parent> - <artifactId>camel-quarkus-support-azure-core</artifactId> - <name>Camel Quarkus :: Support :: Azure Core :: Runtime</name> + <artifactId>camel-quarkus-support-azure-core-http-client-vertx</artifactId> + <name>Camel Quarkus :: Support :: Azure Core HTTP Client Vert.x :: Runtime</name> <properties> - <camel.quarkus.jvmSince>1.7.0</camel.quarkus.jvmSince> - <camel.quarkus.nativeSince>1.7.0</camel.quarkus.nativeSince> + <camel.quarkus.jvmSince>2.8.0</camel.quarkus.jvmSince> + <camel.quarkus.nativeSince>2.8.0</camel.quarkus.nativeSince> </properties> <dependencyManagement> @@ -52,17 +52,21 @@ <artifactId>quarkus-core</artifactId> </dependency> <dependency> - <groupId>org.apache.camel.quarkus</groupId> - <artifactId>camel-quarkus-support-jackson-dataformat-xml</artifactId> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-vertx</artifactId> </dependency> <dependency> - <groupId>org.apache.camel.quarkus</groupId> - <artifactId>camel-quarkus-support-reactor-netty</artifactId> + <groupId>io.vertx</groupId> + <artifactId>vertx-web-client</artifactId> </dependency> <dependency> <groupId>com.azure</groupId> <artifactId>azure-core</artifactId> </dependency> + <dependency> + <groupId>org.apache.camel.quarkus</groupId> + <artifactId>camel-quarkus-support-azure-core</artifactId> + </dependency> </dependencies> <build> diff --git a/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/BufferedVertxHttpResponse.java b/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/BufferedVertxHttpResponse.java new file mode 100644 index 0000000..344251f --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/BufferedVertxHttpResponse.java @@ -0,0 +1,72 @@ +/* + * 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.camel.quarkus.support.azure.core.http.vertx; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.ByteBuffer; + +import com.azure.core.http.HttpRequest; +import com.azure.core.http.HttpResponse; +import io.vertx.core.buffer.Buffer; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public final class BufferedVertxHttpResponse extends VertxHttpAsyncResponse { + + private final Buffer body; + + BufferedVertxHttpResponse(HttpRequest request, io.vertx.ext.web.client.HttpResponse response, Buffer body) { + super(request, response); + this.body = body; + } + + @Override + public Flux<ByteBuffer> getBody() { + return Flux.defer(() -> { + if (this.body == null || this.body.length() == 0) { + return Flux.empty(); + } + return Flux.just(this.body.getByteBuf().nioBuffer()); + }); + } + + @Override + public Mono<byte[]> getBodyAsByteArray() { + return Mono.defer(() -> { + if (this.body == null || this.body.length() == 0) { + return Mono.empty(); + } + return Mono.just(this.body.getBytes()); + }); + } + + @Override + public Mono<InputStream> getBodyAsInputStream() { + return Mono.defer(() -> { + if (this.body == null || this.body.length() == 0) { + return Mono.empty(); + } + return Mono.just(new ByteArrayInputStream(this.body.getBytes())); + }); + } + + @Override + public HttpResponse buffer() { + return this; + } +} diff --git a/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpAsyncResponse.java b/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpAsyncResponse.java new file mode 100644 index 0000000..740c442 --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpAsyncResponse.java @@ -0,0 +1,52 @@ +/* + * 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.camel.quarkus.support.azure.core.http.vertx; + +import java.nio.ByteBuffer; + +import com.azure.core.http.HttpRequest; +import io.vertx.core.buffer.Buffer; +import io.vertx.ext.web.client.HttpResponse; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public class VertxHttpAsyncResponse extends VertxHttpResponse { + + VertxHttpAsyncResponse(HttpRequest request, HttpResponse response) { + super(request, response); + } + + @Override + public Flux<ByteBuffer> getBody() { + Buffer responseBody = getVertxHttpResponse().bodyAsBuffer(); + if (responseBody == null || responseBody.length() == 0) { + return Flux.empty(); + } + return Flux.just(responseBody.getByteBuf().nioBuffer()); + } + + @Override + public Mono<byte[]> getBodyAsByteArray() { + return Mono.fromCallable(() -> { + Buffer responseBody = getVertxHttpResponse().bodyAsBuffer(); + if (responseBody == null || responseBody.length() == 0) { + return null; + } + return responseBody.getBytes(); + }); + } +} diff --git a/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClient.java b/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClient.java new file mode 100644 index 0000000..5500a7d --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClient.java @@ -0,0 +1,133 @@ +/* + * 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.camel.quarkus.support.azure.core.http.vertx; + +import java.io.Closeable; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.ByteBuffer; +import java.util.Objects; + +import com.azure.core.http.HttpClient; +import com.azure.core.http.HttpMethod; +import com.azure.core.http.HttpRequest; +import com.azure.core.http.HttpResponse; +import com.azure.core.util.Context; +import io.vertx.core.buffer.Buffer; +import io.vertx.ext.web.client.WebClient; +import io.vertx.ext.web.client.WebClientOptions; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * {@link HttpClient} implementation for the Vert.x {@link WebClient}. + */ +public class VertxHttpClient implements HttpClient, Closeable { + + private final WebClient client; + private final WebClientOptions options; + + public VertxHttpClient(WebClient client, WebClientOptions options) { + Objects.requireNonNull(client, "client cannot be null"); + Objects.requireNonNull(client, "options cannot be null"); + this.client = client; + this.options = options; + } + + @Override + public Mono<HttpResponse> send(HttpRequest request) { + return send(request, Context.NONE); + } + + @Override + public Mono<HttpResponse> send(HttpRequest request, Context context) { + boolean eagerlyReadResponse = (boolean) context.getData("azure-eagerly-read-response").orElse(false); + return Mono.create(sink -> sink.onRequest(value -> { + toVertxHttpRequest(request).subscribe(vertxHttpRequest -> { + vertxHttpRequest.send(new VertxHttpResponseHandler(request, sink, eagerlyReadResponse)); + }, sink::error); + })); + } + + public void close() { + this.client.close(); + } + + // Exposed for testing + public WebClientOptions getWebClientOptions() { + return options; + } + + private Mono<VertxHttpRequest> toVertxHttpRequest(HttpRequest request) { + return Mono.from(convertBodyToBuffer(request)) + .map(buffer -> { + HttpMethod httpMethod = request.getHttpMethod(); + io.vertx.core.http.HttpMethod requestMethod = io.vertx.core.http.HttpMethod.valueOf(httpMethod.name()); + + URL url = request.getUrl(); + if (url.getPath().isEmpty()) { + try { + // Azure API documentation states: + // + // The URI must always include the forward slash (/) to separate the host name + // from the path and query portions of the URI. + // + url = new URL(url.getProtocol(), url.getHost(), url.getPort(), "/" + url.getFile()); + } catch (MalformedURLException e) { + throw new IllegalStateException(e); + } + } + + io.vertx.ext.web.client.HttpRequest<Buffer> delegate = client + .requestAbs(requestMethod, url.toString()); + + if (request.getHeaders() != null) { + request.getHeaders() + .stream() + .forEach(httpHeader -> delegate.putHeader(httpHeader.getName(), + httpHeader.getValuesList())); + } + + return new VertxHttpRequest(delegate, buffer); + }); + } + + private Mono<Buffer> convertBodyToBuffer(HttpRequest request) { + return Mono.using(() -> Buffer.buffer(), + buffer -> getBody(request).reduce(buffer, (b, byteBuffer) -> { + for (int i = 0; i < byteBuffer.limit(); i++) { + b.appendByte(byteBuffer.get(i)); + } + return b; + }), buffer -> buffer.getClass()); + } + + private Flux<ByteBuffer> getBody(HttpRequest request) { + long contentLength = 0; + String contentLengthHeader = request.getHeaders().getValue("content-length"); + if (contentLengthHeader != null) { + contentLength = Long.parseLong(contentLengthHeader); + } + + Flux<ByteBuffer> body = request.getBody(); + if (body == null || contentLength <= 0) { + body = Flux.just(Buffer.buffer().getByteBuf().nioBuffer()); + } + + return body; + } +} diff --git a/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientBuilder.java b/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientBuilder.java new file mode 100644 index 0000000..0cb5a14 --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientBuilder.java @@ -0,0 +1,250 @@ +/* + * 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.camel.quarkus.support.azure.core.http.vertx; + +import java.net.InetSocketAddress; +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import com.azure.core.http.HttpClient; +import com.azure.core.http.ProxyOptions; +import com.azure.core.util.Configuration; +import com.azure.core.util.logging.ClientLogger; +import io.vertx.core.Vertx; +import io.vertx.core.net.ProxyType; +import io.vertx.ext.web.client.WebClient; +import io.vertx.ext.web.client.WebClientOptions; + +import static com.azure.core.util.Configuration.PROPERTY_AZURE_REQUEST_CONNECT_TIMEOUT; +import static com.azure.core.util.Configuration.PROPERTY_AZURE_REQUEST_READ_TIMEOUT; +import static com.azure.core.util.Configuration.PROPERTY_AZURE_REQUEST_WRITE_TIMEOUT; +import static com.azure.core.util.CoreUtils.getDefaultTimeoutFromEnvironment; + +/** + * Builds a {@link VertxHttpClient}. + */ +public class VertxHttpClientBuilder { + + private static final long DEFAULT_CONNECT_TIMEOUT; + private static final long DEFAULT_WRITE_TIMEOUT; + private static final long DEFAULT_READ_TIMEOUT; + + static { + ClientLogger logger = new ClientLogger(VertxHttpClientBuilder.class); + Configuration configuration = Configuration.getGlobalConfiguration(); + DEFAULT_CONNECT_TIMEOUT = getDefaultTimeoutFromEnvironment(configuration, + PROPERTY_AZURE_REQUEST_CONNECT_TIMEOUT, Duration.ofSeconds(10), logger).toMillis(); + DEFAULT_WRITE_TIMEOUT = getDefaultTimeoutFromEnvironment(configuration, PROPERTY_AZURE_REQUEST_WRITE_TIMEOUT, + Duration.ofSeconds(60), logger).toSeconds(); + DEFAULT_READ_TIMEOUT = getDefaultTimeoutFromEnvironment(configuration, PROPERTY_AZURE_REQUEST_READ_TIMEOUT, + Duration.ofSeconds(60), logger).toSeconds(); + } + + private Duration readIdleTimeout; + private Duration writeIdleTimeout; + private Duration connectTimeout; + private Duration idleTimeout = Duration.ofSeconds(60); + private ProxyOptions proxyOptions; + private Configuration configuration; + private WebClientOptions webClientOptions; + private final Vertx vertx; + + /** + * Creates VertxAsyncHttpClientBuilder. + * + * @param vertx The {@link Vertx} instance to pass to the {@link WebClient}. + */ + public VertxHttpClientBuilder(Vertx vertx) { + Objects.requireNonNull(vertx, "vertx cannot be null"); + this.vertx = vertx; + } + + /** + * Sets the read idle timeout. + * + * The default read idle timeout is 60 seconds. + * + * @param readIdleTimeout the read idle timeout + * @return the updated VertxAsyncHttpClientBuilder object + */ + public VertxHttpClientBuilder readIdleTimeout(Duration readIdleTimeout) { + this.readIdleTimeout = readIdleTimeout; + return this; + } + + /** + * Sets the write idle timeout. + * + * The default read idle timeout is 60 seconds. + * + * @param writeIdleTimeout the write idle timeout + * @return the updated VertxAsyncHttpClientBuilder object + */ + public VertxHttpClientBuilder writeIdleTimeout(Duration writeIdleTimeout) { + this.writeIdleTimeout = writeIdleTimeout; + return this; + } + + /** + * Sets the connect timeout. + * + * The default connect timeout is 10 seconds. + * + * @param connectTimeout the connection timeout + * @return the updated VertxAsyncHttpClientBuilder object + */ + public VertxHttpClientBuilder connectTimeout(Duration connectTimeout) { + this.connectTimeout = connectTimeout; + return this; + } + + /** + * Sets the connection idle timeout. + * + * The default connect timeout is 60 seconds. + * + * @param idleTimeout the connection idle timeout + * @return the updated VertxAsyncHttpClientBuilder object + */ + public VertxHttpClientBuilder idleTimeout(Duration idleTimeout) { + this.idleTimeout = idleTimeout; + return this; + } + + /** + * Sets proxy configuration. + * + * @param proxyOptions The proxy configuration to use. + * @return The updated VertxAsyncHttpClientBuilder object. + */ + public VertxHttpClientBuilder proxy(ProxyOptions proxyOptions) { + this.proxyOptions = proxyOptions; + return this; + } + + /** + * Sets the configuration store that is used during construction of the HTTP client. + * <p> + * The default configuration store is a clone of the {@link Configuration#getGlobalConfiguration() global + * configuration store}, use {@link Configuration#NONE} to bypass using configuration settings during construction. + * + * @param configuration The configuration store. + * @return The updated VertxAsyncHttpClientBuilder object. + */ + public VertxHttpClientBuilder configuration(Configuration configuration) { + this.configuration = configuration; + return this; + } + + /** + * Sets custom {@link WebClientOptions} for the constructed {@link WebClient}. + * + * @param webClientOptions The options of the web client. + * @return The updated VertxAsyncHttpClientBuilder object + */ + public VertxHttpClientBuilder webClientOptions(WebClientOptions webClientOptions) { + this.webClientOptions = webClientOptions; + return this; + } + + /** + * Creates a new Vert.x {@link com.azure.core.http.HttpClient} instance on every call, using the + * configuration set in the builder at the time of the build method call. + * + * @return A new Vert.x backed {@link com.azure.core.http.HttpClient} instance. + */ + public HttpClient build() { + if (this.webClientOptions == null) { + this.webClientOptions = new WebClientOptions(); + } + + if (this.connectTimeout != null) { + this.webClientOptions.setConnectTimeout((int) this.connectTimeout.toMillis()); + } else { + this.webClientOptions.setConnectTimeout((int) DEFAULT_CONNECT_TIMEOUT); + } + + if (this.readIdleTimeout != null) { + this.webClientOptions.setReadIdleTimeout((int) this.readIdleTimeout.toSeconds()); + } else { + this.webClientOptions.setReadIdleTimeout((int) DEFAULT_READ_TIMEOUT); + } + + if (this.writeIdleTimeout != null) { + this.webClientOptions.setWriteIdleTimeout((int) this.writeIdleTimeout.toSeconds()); + } else { + this.webClientOptions.setWriteIdleTimeout((int) DEFAULT_WRITE_TIMEOUT); + } + + this.webClientOptions.setIdleTimeout((int) this.idleTimeout.toSeconds()); + + Configuration buildConfiguration = (configuration == null) + ? Configuration.getGlobalConfiguration() + : configuration; + + ProxyOptions buildProxyOptions = (this.proxyOptions == null && buildConfiguration != Configuration.NONE) + ? ProxyOptions.fromConfiguration(buildConfiguration, true) + : this.proxyOptions; + + if (buildProxyOptions != null) { + io.vertx.core.net.ProxyOptions vertxProxyOptions = new io.vertx.core.net.ProxyOptions(); + InetSocketAddress proxyAddress = buildProxyOptions.getAddress(); + + if (proxyAddress != null) { + vertxProxyOptions.setHost(proxyAddress.getHostName()); + vertxProxyOptions.setPort(proxyAddress.getPort()); + } + + String proxyUsername = buildProxyOptions.getUsername(); + String proxyPassword = buildProxyOptions.getPassword(); + if (proxyUsername != null && proxyPassword != null) { + vertxProxyOptions.setUsername(proxyUsername); + vertxProxyOptions.setPassword(proxyPassword); + } + + ProxyOptions.Type type = buildProxyOptions.getType(); + if (type != null) { + try { + ProxyType proxyType = ProxyType.valueOf(type.name()); + vertxProxyOptions.setType(proxyType); + } catch (IllegalArgumentException e) { + throw new IllegalStateException("Unknown Vert.x proxy type: " + type.name(), e); + } + } + + String nonProxyHostsString = proxyOptions.getNonProxyHosts(); + if (nonProxyHostsString != null) { + // Undo Azure ProxyOptions string sanitization since Vert.x has its own logic + List<String> nonProxyHosts = Arrays.asList(nonProxyHostsString.split("\\|")) + .stream() + .map(host -> host.replaceAll("\\\\E", "") + .replaceAll("\\\\Q", "") + .replaceAll("\\.\\.", "")) + .collect(Collectors.toList()); + webClientOptions.setNonProxyHosts(nonProxyHosts); + } + + webClientOptions.setProxyOptions(vertxProxyOptions); + } + + WebClient client = WebClient.create(this.vertx, this.webClientOptions); + return new VertxHttpClient(client, this.webClientOptions); + } +} diff --git a/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientProvider.java b/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientProvider.java new file mode 100644 index 0000000..73120d3 --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpClientProvider.java @@ -0,0 +1,76 @@ +/* + * 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.camel.quarkus.support.azure.core.http.vertx; + +import java.util.Set; + +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.CDI; + +import com.azure.core.http.HttpClient; +import com.azure.core.http.HttpClientProvider; +import com.azure.core.util.HttpClientOptions; +import io.vertx.core.Vertx; +import io.vertx.ext.web.client.WebClient; + +/** + * {@link HttpClientProvider} backed by the Vert.x {@link WebClient} + */ +public class VertxHttpClientProvider implements HttpClientProvider { + + @Override + public HttpClient createInstance() { + return createInstance(null); + } + + @Override + public HttpClient createInstance(HttpClientOptions clientOptions) { + VertxHttpClientBuilder builder = new VertxHttpClientBuilder(getVertx()); + if (clientOptions != null) { + builder = builder.proxy(clientOptions.getProxyOptions()) + .configuration(clientOptions.getConfiguration()) + .connectTimeout(clientOptions.getConnectTimeout()) + .idleTimeout(clientOptions.getConnectionIdleTimeout()) + .writeIdleTimeout(clientOptions.getWriteTimeout()) + .readIdleTimeout(clientOptions.getReadTimeout()); + } + return builder.build(); + } + + /** + * Obtains a reference to the Quarkus managed {@link Vertx} instance + * + * @return The Quarkus managed {@link Vertx} instance + */ + private static final Vertx getVertx() { + BeanManager beanManager = CDI.current().getBeanManager(); + Set<Bean<?>> beans = beanManager.getBeans(Vertx.class); + if (beans.isEmpty()) { + throw new IllegalStateException("Failed to discover Vert.x bean from the CDI bean manager"); + } + + if (beans.size() > 1) { + throw new IllegalStateException( + "Expected 1 Vert.x bean in the CDI bean manager but " + beans.size() + " were found"); + } + + Bean<?> bean = beanManager.resolve(beans); + Object reference = beanManager.getReference(bean, Vertx.class, beanManager.createCreationalContext(bean)); + return Vertx.class.cast(reference); + } +} diff --git a/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpRequest.java b/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpRequest.java new file mode 100644 index 0000000..e6f9e60 --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpRequest.java @@ -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.camel.quarkus.support.azure.core.http.vertx; + +import io.vertx.core.buffer.Buffer; +import io.vertx.ext.web.client.HttpRequest; + +/** + * Holds a Vert.x {@link HttpRequest} together with a body payload. + */ +class VertxHttpRequest { + private final Buffer body; + private final HttpRequest<Buffer> delegate; + + public VertxHttpRequest(HttpRequest<Buffer> delegate, Buffer body) { + this.delegate = delegate; + this.body = body; + } + + public void send(VertxHttpResponseHandler handler) { + delegate.sendBuffer(body, handler); + } +} diff --git a/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpResponse.java b/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpResponse.java new file mode 100644 index 0000000..7d71964 --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpResponse.java @@ -0,0 +1,73 @@ +/* + * 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.camel.quarkus.support.azure.core.http.vertx; + +import java.nio.charset.Charset; + +import com.azure.core.http.HttpHeaders; +import com.azure.core.http.HttpRequest; +import com.azure.core.http.HttpResponse; +import com.azure.core.util.CoreUtils; +import io.vertx.core.MultiMap; +import reactor.core.publisher.Mono; + +abstract class VertxHttpResponse extends HttpResponse { + + private final io.vertx.ext.web.client.HttpResponse response; + private final HttpHeaders headers; + + VertxHttpResponse(HttpRequest request, io.vertx.ext.web.client.HttpResponse response) { + super(request); + this.response = response; + this.headers = fromVertxHttpHeaders(response.headers()); + } + + private HttpHeaders fromVertxHttpHeaders(MultiMap headers) { + HttpHeaders azureHeaders = new HttpHeaders(); + headers.names().forEach(name -> azureHeaders.set(name, headers.getAll(name))); + return azureHeaders; + } + + protected io.vertx.ext.web.client.HttpResponse getVertxHttpResponse() { + return this.response; + } + + @Override + public int getStatusCode() { + return response.statusCode(); + } + + @Override + public String getHeaderValue(String name) { + return this.headers.getValue(name); + } + + @Override + public HttpHeaders getHeaders() { + return this.headers; + } + + @Override + public final Mono<String> getBodyAsString() { + return getBodyAsByteArray().map(bytes -> CoreUtils.bomAwareToString(bytes, getHeaderValue("Content-Type"))); + } + + @Override + public final Mono<String> getBodyAsString(Charset charset) { + return Mono.fromCallable(() -> this.response.bodyAsString(charset.toString())); + } +} diff --git a/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpResponseHandler.java b/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpResponseHandler.java new file mode 100644 index 0000000..9e33e0b --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/runtime/src/main/java/org/apache/camel/quarkus/support/azure/core/http/vertx/VertxHttpResponseHandler.java @@ -0,0 +1,59 @@ +/* + * 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.camel.quarkus.support.azure.core.http.vertx; + +import com.azure.core.http.HttpRequest; +import io.vertx.core.AsyncResult; +import io.vertx.core.Handler; +import io.vertx.core.buffer.Buffer; +import io.vertx.ext.web.client.HttpResponse; +import reactor.core.publisher.MonoSink; + +/** + * {@link Handler} for Azure HTTP responses. + */ +class VertxHttpResponseHandler implements Handler<AsyncResult<HttpResponse<Buffer>>> { + + private final HttpRequest request; + private final MonoSink<com.azure.core.http.HttpResponse> sink; + private final boolean eagerlyReadResponse; + + VertxHttpResponseHandler(HttpRequest request, MonoSink<com.azure.core.http.HttpResponse> sink, + boolean eagerlyReadResponse) { + this.request = request; + this.sink = sink; + this.eagerlyReadResponse = eagerlyReadResponse; + } + + @Override + public void handle(AsyncResult<HttpResponse<Buffer>> event) { + if (event.succeeded()) { + VertxHttpResponse response; + if (eagerlyReadResponse) { + io.vertx.ext.web.client.HttpResponse<Buffer> originalResponse = event.result(); + response = new BufferedVertxHttpResponse(request, originalResponse, originalResponse.body()); + } else { + response = new VertxHttpAsyncResponse(request, event.result()); + } + sink.success(response); + } else { + if (event.cause() != null) { + sink.error(event.cause()); + } + } + } +} diff --git a/extensions-support/azure-core-http-client-vertx/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions-support/azure-core-http-client-vertx/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000..5f875c0 --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,27 @@ +# +# 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. +# + +--- +name: "Camel Quarkus Support Azure Core HTTP Client Vert.x" +description: "Camel Quarkus Support Azure Core HTTP Client Vert.x" +metadata: + unlisted: true + keywords: + - "camel" + guide: "https://quarkus.io/guides/camel" + categories: + - "integration" \ No newline at end of file diff --git a/extensions-support/azure-core-http-client-vertx/runtime/src/main/resources/META-INF/services/com.azure.core.http.HttpClientProvider b/extensions-support/azure-core-http-client-vertx/runtime/src/main/resources/META-INF/services/com.azure.core.http.HttpClientProvider new file mode 100644 index 0000000..8487b59 --- /dev/null +++ b/extensions-support/azure-core-http-client-vertx/runtime/src/main/resources/META-INF/services/com.azure.core.http.HttpClientProvider @@ -0,0 +1 @@ +org.apache.camel.quarkus.support.azure.core.http.vertx.VertxHttpClientProvider \ No newline at end of file diff --git a/extensions-support/azure-core/deployment/pom.xml b/extensions-support/azure-core/deployment/pom.xml index 91a5018..b762e0a 100644 --- a/extensions-support/azure-core/deployment/pom.xml +++ b/extensions-support/azure-core/deployment/pom.xml @@ -35,12 +35,12 @@ <artifactId>quarkus-core-deployment</artifactId> </dependency> <dependency> - <groupId>org.apache.camel.quarkus</groupId> - <artifactId>camel-quarkus-support-jackson-dataformat-xml-deployment</artifactId> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-netty-deployment</artifactId> </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> - <artifactId>camel-quarkus-support-reactor-netty-deployment</artifactId> + <artifactId>camel-quarkus-support-jackson-dataformat-xml-deployment</artifactId> </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> diff --git a/extensions-support/azure-core/runtime/pom.xml b/extensions-support/azure-core/runtime/pom.xml index 701cd25..0319e50 100644 --- a/extensions-support/azure-core/runtime/pom.xml +++ b/extensions-support/azure-core/runtime/pom.xml @@ -52,16 +52,22 @@ <artifactId>quarkus-core</artifactId> </dependency> <dependency> - <groupId>org.apache.camel.quarkus</groupId> - <artifactId>camel-quarkus-support-jackson-dataformat-xml</artifactId> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-netty</artifactId> </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> - <artifactId>camel-quarkus-support-reactor-netty</artifactId> + <artifactId>camel-quarkus-support-jackson-dataformat-xml</artifactId> </dependency> <dependency> <groupId>com.azure</groupId> <artifactId>azure-core</artifactId> + <exclusions> + <exclusion> + <groupId>com.azure</groupId> + <artifactId>azure-core-http-netty</artifactId> + </exclusion> + </exclusions> </dependency> </dependencies> diff --git a/extensions-support/pom.xml b/extensions-support/pom.xml index 4a8cad3..5e6b35e 100644 --- a/extensions-support/pom.xml +++ b/extensions-support/pom.xml @@ -38,6 +38,7 @@ <module>aws</module> <module>aws2</module> <module>azure-core</module> + <module>azure-core-http-client-vertx</module> <module>bouncycastle</module> <module>commons-logging</module> <module>consul-client</module> diff --git a/extensions/azure-eventhubs/runtime/pom.xml b/extensions/azure-eventhubs/runtime/pom.xml index 3c79b20..5be7c8b 100644 --- a/extensions/azure-eventhubs/runtime/pom.xml +++ b/extensions/azure-eventhubs/runtime/pom.xml @@ -59,6 +59,12 @@ <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-azure-eventhubs</artifactId> + <exclusions> + <exclusion> + <groupId>com.azure</groupId> + <artifactId>azure-core-http-netty</artifactId> + </exclusion> + </exclusions> </dependency> </dependencies> diff --git a/extensions/azure-storage-blob/deployment/pom.xml b/extensions/azure-storage-blob/deployment/pom.xml index 49ff729..e341501 100644 --- a/extensions/azure-storage-blob/deployment/pom.xml +++ b/extensions/azure-storage-blob/deployment/pom.xml @@ -36,7 +36,7 @@ </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> - <artifactId>camel-quarkus-support-azure-core-deployment</artifactId> + <artifactId>camel-quarkus-support-azure-core-http-client-vertx-deployment</artifactId> </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> diff --git a/extensions/azure-storage-blob/runtime/pom.xml b/extensions/azure-storage-blob/runtime/pom.xml index d432e32..3e59347 100644 --- a/extensions/azure-storage-blob/runtime/pom.xml +++ b/extensions/azure-storage-blob/runtime/pom.xml @@ -54,11 +54,17 @@ </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> - <artifactId>camel-quarkus-support-azure-core</artifactId> + <artifactId>camel-quarkus-support-azure-core-http-client-vertx</artifactId> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-azure-storage-blob</artifactId> + <exclusions> + <exclusion> + <groupId>com.azure</groupId> + <artifactId>azure-core-http-netty</artifactId> + </exclusion> + </exclusions> </dependency> </dependencies> diff --git a/extensions/azure-storage-queue/deployment/pom.xml b/extensions/azure-storage-queue/deployment/pom.xml index 6819a3e..e6b3473 100644 --- a/extensions/azure-storage-queue/deployment/pom.xml +++ b/extensions/azure-storage-queue/deployment/pom.xml @@ -36,7 +36,7 @@ </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> - <artifactId>camel-quarkus-support-azure-core-deployment</artifactId> + <artifactId>camel-quarkus-support-azure-core-http-client-vertx-deployment</artifactId> </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> diff --git a/extensions/azure-storage-queue/runtime/pom.xml b/extensions/azure-storage-queue/runtime/pom.xml index ecb18ff..4afc269 100644 --- a/extensions/azure-storage-queue/runtime/pom.xml +++ b/extensions/azure-storage-queue/runtime/pom.xml @@ -54,11 +54,17 @@ </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> - <artifactId>camel-quarkus-support-azure-core</artifactId> + <artifactId>camel-quarkus-support-azure-core-http-client-vertx</artifactId> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-azure-storage-queue</artifactId> + <exclusions> + <exclusion> + <groupId>com.azure</groupId> + <artifactId>azure-core-http-netty</artifactId> + </exclusion> + </exclusions> </dependency> </dependencies> diff --git a/integration-test-groups/azure/azure-storage-blob/src/test/java/org/apache/camel/quarkus/component/azure/storage/blob/it/AzureStorageBlobTest.java b/integration-test-groups/azure/azure-storage-blob/src/test/java/org/apache/camel/quarkus/component/azure/storage/blob/it/AzureStorageBlobTest.java index 6594824..7a3e8c1 100644 --- a/integration-test-groups/azure/azure-storage-blob/src/test/java/org/apache/camel/quarkus/component/azure/storage/blob/it/AzureStorageBlobTest.java +++ b/integration-test-groups/azure/azure-storage-blob/src/test/java/org/apache/camel/quarkus/component/azure/storage/blob/it/AzureStorageBlobTest.java @@ -27,13 +27,7 @@ import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; -import com.azure.core.http.policy.HttpLogDetailLevel; -import com.azure.core.http.policy.HttpLogOptions; -import com.azure.storage.blob.BlobContainerClient; -import com.azure.storage.blob.BlobServiceClient; -import com.azure.storage.blob.BlobServiceClientBuilder; import com.azure.storage.blob.models.BlockListType; -import com.azure.storage.common.StorageSharedKeyCredential; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; @@ -65,31 +59,26 @@ class AzureStorageBlobTest { @BeforeAll static void beforeAll() { - getClient().create(); + final Config config = ConfigProvider.getConfig(); + String containerName = config.getValue("azure.blob.container.name", String.class); + int port = config.getValue("quarkus.http.test-port", int.class); + RestAssured.port = port; + RestAssured.given() + .queryParam("containerName", containerName) + .post("/azure-storage-blob/blob/container") + .then() + .statusCode(201); } @AfterAll static void afterAll() { - getClient().delete(); - } - - private static BlobContainerClient getClient() { final Config config = ConfigProvider.getConfig(); - final String azureStorageAccountName = config.getValue("azure.storage.account-name", - String.class); - final String azureStorageAccountKey = config - .getValue("azure.storage.account-key", String.class); - - StorageSharedKeyCredential credentials = new StorageSharedKeyCredential(azureStorageAccountName, - azureStorageAccountKey); - BlobServiceClient client = new BlobServiceClientBuilder() - .endpoint(config.getValue("azure.blob.service.url", String.class)) - .credential(credentials) - .httpLogOptions(new HttpLogOptions().setLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS).setPrettyPrintBody(true)) - .buildClient(); - String containerName = config.getValue("azure.blob.container.name", String.class); - return client.getBlobContainerClient(containerName); + RestAssured.given() + .queryParam("containerName", containerName) + .delete("/azure-storage-blob/blob/container") + .then() + .statusCode(204); } @Test diff --git a/pom.xml b/pom.xml index 5cbdeea..5c3b1f8 100644 --- a/pom.xml +++ b/pom.xml @@ -69,6 +69,8 @@ <awssdk.version>2.17.127</awssdk.version><!-- @sync io.quarkiverse.amazonservices:quarkus-amazon-services-parent:${quarkiverse-amazonservices.version} prop:awssdk.version --> <aws-java-sdk.version>1.11.714</aws-java-sdk.version> <azure-sdk-bom.version>1.0.5</azure-sdk-bom.version><!-- Keep in sync with camel-azure component versions --> + <azure-core.version>1.21.0</azure-core.version><!-- @sync com.azure:azure-sdk-bom:${azure-sdk-bom.version} dep:com.azure:azure-core --> + <azure-core-test.version>1.7.3</azure-core-test.version> <bouncycastle.version>1.70</bouncycastle.version><!-- @sync io.quarkus:quarkus-bom:${quarkus.version} dep:org.bouncycastle:bcprov-jdk15on --> <commons-beanutils.version>${commons-beanutils-version}</commons-beanutils.version> <commons-cli.version>1.4</commons-cli.version><!-- keep in sync with Quarkus, via quarkus-bootstrap-core --> diff --git a/poms/bom-test/pom.xml b/poms/bom-test/pom.xml index fced647..53b1e1c 100644 --- a/poms/bom-test/pom.xml +++ b/poms/bom-test/pom.xml @@ -301,6 +301,26 @@ <artifactId>aws-java-sdk-core</artifactId> <version>${aws-java-sdk.version}</version> </dependency> + <dependency> + <groupId>com.azure</groupId> + <artifactId>azure-core</artifactId> + <version>${azure-core.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.azure</groupId> + <artifactId>azure-core-test</artifactId> + <version>${azure-core-test.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.azure</groupId> + <artifactId>azure-core-test</artifactId> + <version>${azure-core-test.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> </dependencies> </dependencyManagement> diff --git a/poms/bom/pom.xml b/poms/bom/pom.xml index 2218c60..564e4a2 100644 --- a/poms/bom/pom.xml +++ b/poms/bom/pom.xml @@ -5556,6 +5556,16 @@ </dependency> <dependency> <groupId>org.apache.camel.quarkus</groupId> + <artifactId>camel-quarkus-support-azure-core-http-client-vertx</artifactId> + <version>${camel-quarkus.version}</version> + </dependency> + <dependency> + <groupId>org.apache.camel.quarkus</groupId> + <artifactId>camel-quarkus-support-azure-core-http-client-vertx-deployment</artifactId> + <version>${camel-quarkus.version}</version> + </dependency> + <dependency> + <groupId>org.apache.camel.quarkus</groupId> <artifactId>camel-quarkus-support-bouncycastle</artifactId> <version>${camel-quarkus.version}</version> </dependency>