diff --git a/.circleci/config.yml b/.circleci/config.yml index be0d2579f..462ce2dae 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,7 +9,7 @@ jobs: # a collection of steps # Configure the JVMto avoid OOM errors _JAVA_OPTIONS: "-Xmx3g" docker: # run the steps with Docker - - image: circleci/openjdk:11.0.3-jdk-stretch + - image: circleci/openjdk:17.0.1-jdk-buster steps: - add_ssh_keys: diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9a93bcbd0..24e39783d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,27 +1,22 @@ version: 2 updates: -- package-ecosystem: maven - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 - ignore: - - dependency-name: io.projectreactor.tools:blockhound - versions: - - 1.0.5.RELEASE - - dependency-name: org.springframework.boot:spring-boot-dependencies - versions: - - 2.4.2 - - 2.4.3 - - 2.4.4 - - dependency-name: org.openjdk.jmh:jmh-generator-annprocess - versions: - - "1.27" - - "1.28" - - dependency-name: org.openjdk.jmh:jmh-core - versions: - - "1.27" - - "1.28" - - dependency-name: org.springframework.cloud:spring-cloud-dependencies - versions: - - Hoxton.SR10 + - package-ecosystem: "maven" + directory: "/" + target-branch: "develop" + schedule: + interval: "weekly" + open-pull-requests-limit: 50 + ignore: + - dependency-name: "com.amazonaws:*" + update-types: ["version-update:semver-patch"] + - package-ecosystem: "github-actions" + directory: "/" + target-branch: "develop" + labels: + - "housekeeping" + schedule: + interval: "monthly" + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000..23f44c4eb --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,2 @@ +"documentation": + - /**/*.adoc \ No newline at end of file diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 000000000..c4810a761 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,34 @@ +name-template: $NEXT_PATCH_VERSION +tag-template: $NEXT_PATCH_VERSION + +template: | + # Changes + $CHANGES + +# -------- +# NOTE: When adding new labels please also update required-labels.yml workflow. +# -------- +categories: + - title: 💣️ Breaking changes + label: breaking-change + + - title: 🚀 Features & Enhancements + labels: + - feature + - enhancement + + - title: 🐞 Fixes + label: bug + + - title: 📁 Java Dependencies updates + label: dependencies + + - title: 📁 Docker images updates + label: docker-update-images + + - title: 📖 Documentation + label: documentation + + - title: 🏡 Housekeeping + label: housekeeping + diff --git a/.github/workflows/changelog-release-drafter.yml b/.github/workflows/changelog-release-drafter.yml new file mode 100644 index 000000000..8886f4775 --- /dev/null +++ b/.github/workflows/changelog-release-drafter.yml @@ -0,0 +1,14 @@ +name: Changelog Release Drafter + +on: + push: + branches: + - develop + +jobs: + update_release_draft: + runs-on: ubuntu-latest + steps: + - uses: release-drafter/release-drafter@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..40cc30dee --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,27 @@ +name: "Trivy" + +on: + schedule: + - cron: '24 10 * * 5' + +jobs: + build: + name: Trivy vulnerability scanner + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run Trivy vulnerability scanner in repo mode + uses: aquasecurity/trivy-action@master + with: + scan-type: 'fs' + ignore-unfixed: true + format: 'sarif' + output: 'trivy-results.sarif' + severity: 'CRITICAL' + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 000000000..3807d5aff --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,14 @@ +name: "Pull Request Auto Labeler" +on: + - pull_request_target + +jobs: + triage: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v4 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" \ No newline at end of file diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 000000000..ac55ccc2e --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,39 @@ +# This workflow will build a Java project with Maven +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Java CI with Maven + +on: + push: + branches: + - develop + pull_request: + branches: + - develop + +jobs: + build-jdk17: + runs-on: ubuntu-latest + name: Build project + concurrency: + # The commit SHA or the branch name of the pull request. See: https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions + group: ${{ github.event_name == 'pull_request' && github.head_ref || github.sha}} + cancel-in-progress: true + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Cache Maven packages + uses: actions/cache@v3 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Set up JDK + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'corretto' + - name: Build with Maven + run: ./mvnw -version && whoami && umask -S && umask a+rw && umask -S && ./mvnw clean verify -P docker-clean -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.http.retryHandler.count=3 --no-snapshot-updates --batch-mode --no-transfer-progress \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..58270df79 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,47 @@ +name: Publish to the Maven Central Repository + +on: + release: + types: [ published ] + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{github.event.release.target_commitish}} + token: ${{ secrets.RELEASE_PERSONAL_ACCESS_TOKEN }} + + - name: Set up JDK + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'corretto' + server-id: ossrh + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE + cache: 'maven' + + - name: Update version + if: ${{ success() }} + run: ./mvnw --batch-mode --no-transfer-progress versions:set -DnewVersion=${{github.event.release.tag_name}} versions:commit + + - name: Publish to the Maven Central Repository + if: ${{ success() }} + run: ./mvnw --batch-mode --no-transfer-progress -Dgib.disable=true -P ossrh -DskipTests deploy + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + MAVEN_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + + - name: Commit & Push changes + if: ${{ success() }} + uses: actions-js/push@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + message: 'Release ${{github.event.release.tag_name}}' + branch: ${{ github.event.release.target_commitish }} diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml new file mode 100644 index 000000000..62b9aabab --- /dev/null +++ b/.github/workflows/renovate.yml @@ -0,0 +1,39 @@ +name: Renovate for update docker images + +on: + workflow_dispatch: + inputs: + dryRun: + description: "Dry-Run" + default: false + required: false + type: boolean + logLevel: + description: "Log-Level" + required: false + default: 'debug' + type: choice + options: + - info + - warn + - debug + - error + - fatal + schedule: + - cron: '0 8 * * *' + +jobs: + renovate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Self-hosted Renovate + uses: renovatebot/github-action@v39.1.1 + with: + configurationFile: .github/renovate/renovate.json + token: ${{ secrets.RELEASE_PERSONAL_ACCESS_TOKEN }} + env: + DRY_RUN: ${{ inputs.dryRun || 'false' }} + LOG_LEVEL: ${{ inputs.logLevel || 'debug' }} \ No newline at end of file diff --git a/.github/workflows/required-labels.yml b/.github/workflows/required-labels.yml new file mode 100644 index 000000000..089af346b --- /dev/null +++ b/.github/workflows/required-labels.yml @@ -0,0 +1,16 @@ +# https://github.com/mheap/github-action-required-labels +name: Pull Request Required Labels +on: + pull_request: + types: [ opened, labeled, unlabeled, synchronize ] +jobs: + label: + if: github.event.pull_request.state == 'open' + runs-on: ubuntu-latest + name: Verify Pull Request has labels + steps: + - uses: mheap/github-action-required-labels@v5 + with: + mode: minimum + count: 1 + labels: "breaking-change, feature, enhancement, bug, dependencies, documentation, housekeeping" \ No newline at end of file diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 000000000..c32394f14 --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed 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. + */ +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.5"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 000000000..c1dd12f17 Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..4f3b9f5d6 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# 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. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.2/apache-maven-3.8.2-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar diff --git a/feign-reactor-benchmarks/pom.xml b/feign-reactor-benchmarks/pom.xml index 9efde2940..15bc7e079 100644 --- a/feign-reactor-benchmarks/pom.xml +++ b/feign-reactor-benchmarks/pom.xml @@ -6,14 +6,14 @@ com.playtika.reactivefeign feign-reactor-parent ../feign-reactor-parent - 3.2.6 + 4.0.3 feign-reactor-benchmarks 11 - 1.35 + 1.37 @@ -52,17 +52,17 @@ org.eclipse.jetty.http2 - http2-client + jetty-http2-client org.eclipse.jetty.http2 - http2-http-client-transport + jetty-http2-client-transport org.eclipse.jetty.http2 - http2-server + jetty-http2-server @@ -96,7 +96,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 + 3.5.1 package @@ -117,7 +117,7 @@ org.skife.maven really-executable-jar-maven-plugin - 1.5.0 + 2.1.1 benchmark diff --git a/feign-reactor-benchmarks/src/main/java/reactivefeign/benchmarks/RealRequestBenchmarks.java b/feign-reactor-benchmarks/src/main/java/reactivefeign/benchmarks/RealRequestBenchmarks.java index 265506bd3..cc79b9494 100644 --- a/feign-reactor-benchmarks/src/main/java/reactivefeign/benchmarks/RealRequestBenchmarks.java +++ b/feign-reactor-benchmarks/src/main/java/reactivefeign/benchmarks/RealRequestBenchmarks.java @@ -12,10 +12,17 @@ import io.reactivex.netty.protocol.http.server.RequestHandler; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.http2.client.HTTP2Client; -import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; +import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2; import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; -import org.eclipse.jetty.server.*; -import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.springframework.web.reactive.function.client.WebClient; import reactivefeign.java11.Java11ReactiveFeign; @@ -25,10 +32,8 @@ import reactivefeign.webclient.WebReactiveFeign; import reactor.core.publisher.Mono; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; import java.lang.reflect.ParameterizedType; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashMap; @@ -161,16 +166,17 @@ public rx.Observable handle(HttpServerRequest request, private Server jettyH2c(int port){ Server serverJetty = new Server(); - serverJetty.setHandler(new AbstractHandler(){ + serverJetty.setHandler(new Handler.Abstract(){ @Override - public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { - request.getInputStream().skip(Integer.MAX_VALUE); - if(target.equals(PATH_WITH_PAYLOAD)){ - response.addHeader("Content-Type", "application/json"); - response.getOutputStream().write(responseJson); - response.getOutputStream().flush(); + public boolean handle(Request request, Response response, Callback callback) throws Exception { + Content.Chunk chunk = request.read(); + chunk.skip(Integer.MAX_VALUE); + if (request.getHttpURI().getPath().equals(PATH_WITH_PAYLOAD)) { + response.getHeaders().add("Content-Type", "application/json"); + response.write(true, ByteBuffer.wrap(responseJson), callback); } - baseRequest.setHandled(true); + callback.succeeded(); + return true; } }); diff --git a/feign-reactor-bom/pom.xml b/feign-reactor-bom/pom.xml index 0fc636d1b..7e31855f1 100644 --- a/feign-reactor-bom/pom.xml +++ b/feign-reactor-bom/pom.xml @@ -7,7 +7,7 @@ com.playtika.reactivefeign feign-reactor - 3.2.6 + 4.0.3 pom diff --git a/feign-reactor-cloud/pom.xml b/feign-reactor-cloud/pom.xml index e06bb2d8c..92cb0391b 100644 --- a/feign-reactor-cloud/pom.xml +++ b/feign-reactor-cloud/pom.xml @@ -6,7 +6,7 @@ com.playtika.reactivefeign feign-reactor-parent ../feign-reactor-parent - 3.2.6 + 4.0.3 feign-reactor-cloud @@ -100,8 +100,8 @@ - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test diff --git a/feign-reactor-cloud/src/main/java/reactivefeign/cloud2/CloudReactiveFeign.java b/feign-reactor-cloud/src/main/java/reactivefeign/cloud2/CloudReactiveFeign.java index 6c912e743..9b3b60bcb 100644 --- a/feign-reactor-cloud/src/main/java/reactivefeign/cloud2/CloudReactiveFeign.java +++ b/feign-reactor-cloud/src/main/java/reactivefeign/cloud2/CloudReactiveFeign.java @@ -196,7 +196,7 @@ public PublisherHttpClient create(MethodMetadata methodMetadata) { publisherClient = new LoadBalancerPublisherClient( loadBalancerFactory.getInstance(target.name()), publisherClient); if(retryOnNextPolicy != null){ - publisherClient = retry(publisherClient, methodMetadata, retryOnNextPolicy.retry()); + publisherClient = retry(publisherClient, methodMetadata, retryOnNextPolicy); } return publisherClient; } else { diff --git a/feign-reactor-cloud/src/main/java/reactivefeign/cloud2/publisher/LoadBalancerPublisherClient.java b/feign-reactor-cloud/src/main/java/reactivefeign/cloud2/publisher/LoadBalancerPublisherClient.java index ebd47148a..dd38e7bff 100644 --- a/feign-reactor-cloud/src/main/java/reactivefeign/cloud2/publisher/LoadBalancerPublisherClient.java +++ b/feign-reactor-cloud/src/main/java/reactivefeign/cloud2/publisher/LoadBalancerPublisherClient.java @@ -5,7 +5,7 @@ import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; import reactivefeign.client.ReactiveHttpRequest; import reactivefeign.publisher.PublisherHttpClient; -import reactor.core.publisher.Mono; +import reactor.core.publisher.Flux; import java.net.URI; @@ -27,8 +27,8 @@ public LoadBalancerPublisherClient(ReactiveLoadBalancer reactiv @Override public Publisher executeRequest(ReactiveHttpRequest request) { - return Mono.from(reactiveLoadBalancer.choose()) - .flatMapMany(serviceInstanceResponse -> { + return Flux.from(reactiveLoadBalancer.choose()) + .flatMap(serviceInstanceResponse -> { URI lbUrl = reconstructURI(serviceInstanceResponse.getServer(), request.uri()); ReactiveHttpRequest lbRequest = new ReactiveHttpRequest(request, lbUrl); return publisherClient.executeRequest(lbRequest); diff --git a/feign-reactor-cloud/src/test/java/reactivefeign/cloud2/AllFeaturesTest.java b/feign-reactor-cloud/src/test/java/reactivefeign/cloud2/AllFeaturesTest.java index cee2e720a..2536a7998 100644 --- a/feign-reactor-cloud/src/test/java/reactivefeign/cloud2/AllFeaturesTest.java +++ b/feign-reactor-cloud/src/test/java/reactivefeign/cloud2/AllFeaturesTest.java @@ -16,6 +16,8 @@ package reactivefeign.cloud2; +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; +import io.github.resilience4j.timelimiter.TimeLimiterRegistry; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; @@ -54,7 +56,10 @@ public class AllFeaturesTest extends reactivefeign.allfeatures.AllFeaturesTest { @BeforeClass public static void setupServersList() { loadBalancerFactory = LoadBalancingReactiveHttpClientTest.loadBalancerFactory(serviceName, 8080); - circuitBreakerFactory = new ReactiveResilience4JCircuitBreakerFactory(); + circuitBreakerFactory = new ReactiveResilience4JCircuitBreakerFactory( + CircuitBreakerRegistry.ofDefaults(), + TimeLimiterRegistry.ofDefaults() + ); } @Override diff --git a/feign-reactor-cloud/src/test/java/reactivefeign/cloud2/CircuitBreakerFuncTest.java b/feign-reactor-cloud/src/test/java/reactivefeign/cloud2/CircuitBreakerFuncTest.java index 16a7834ee..a790f5705 100644 --- a/feign-reactor-cloud/src/test/java/reactivefeign/cloud2/CircuitBreakerFuncTest.java +++ b/feign-reactor-cloud/src/test/java/reactivefeign/cloud2/CircuitBreakerFuncTest.java @@ -5,7 +5,9 @@ import com.github.tomakehurst.wiremock.junit.WireMockRule; import feign.RequestLine; import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; import io.github.resilience4j.timelimiter.TimeLimiterConfig; +import io.github.resilience4j.timelimiter.TimeLimiterRegistry; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; @@ -49,7 +51,10 @@ public class CircuitBreakerFuncTest extends BaseReactorTest { @BeforeClass public static void setupCircuitBreakerFactory() { - circuitBreakerFactory = new ReactiveResilience4JCircuitBreakerFactory(); + circuitBreakerFactory = new ReactiveResilience4JCircuitBreakerFactory( + CircuitBreakerRegistry.ofDefaults(), + TimeLimiterRegistry.ofDefaults() + ); } @Test diff --git a/feign-reactor-cloud/src/test/java/reactivefeign/cloud2/CircuitBreakerReactiveHttpClientTest.java b/feign-reactor-cloud/src/test/java/reactivefeign/cloud2/CircuitBreakerReactiveHttpClientTest.java index 56f17b56f..67a6fbc5f 100644 --- a/feign-reactor-cloud/src/test/java/reactivefeign/cloud2/CircuitBreakerReactiveHttpClientTest.java +++ b/feign-reactor-cloud/src/test/java/reactivefeign/cloud2/CircuitBreakerReactiveHttpClientTest.java @@ -6,6 +6,7 @@ import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; import io.github.resilience4j.timelimiter.TimeLimiterConfig; +import io.github.resilience4j.timelimiter.TimeLimiterRegistry; import org.awaitility.Awaitility; import org.junit.Before; import org.junit.BeforeClass; @@ -51,7 +52,10 @@ public class CircuitBreakerReactiveHttpClientTest extends BaseReactorTest { private static CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults(); private static ReactiveResilience4JCircuitBreakerFactory circuitBreakerFactory - = new ReactiveResilience4JCircuitBreakerFactory(); + = new ReactiveResilience4JCircuitBreakerFactory( + CircuitBreakerRegistry.ofDefaults(), + TimeLimiterRegistry.ofDefaults() + ); @BeforeClass public static void setupServersList() { diff --git a/feign-reactor-cloud/src/test/java/reactivefeign/cloud2/RequestInterceptorTest.java b/feign-reactor-cloud/src/test/java/reactivefeign/cloud2/RequestInterceptorTest.java index c86db9313..3004c9f7a 100644 --- a/feign-reactor-cloud/src/test/java/reactivefeign/cloud2/RequestInterceptorTest.java +++ b/feign-reactor-cloud/src/test/java/reactivefeign/cloud2/RequestInterceptorTest.java @@ -13,6 +13,8 @@ */ package reactivefeign.cloud2; +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; +import io.github.resilience4j.timelimiter.TimeLimiterRegistry; import org.junit.Before; import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory; import org.springframework.cloud.client.ServiceInstance; @@ -30,7 +32,10 @@ public class RequestInterceptorTest extends reactivefeign.RequestInterceptorTest protected static String serviceName = "RequestInterceptorTest"; private ReactiveResilience4JCircuitBreakerFactory circuitBreakerFactory - = new ReactiveResilience4JCircuitBreakerFactory(); + = new ReactiveResilience4JCircuitBreakerFactory( + CircuitBreakerRegistry.ofDefaults(), + TimeLimiterRegistry.ofDefaults() + ); private ReactiveLoadBalancer.Factory lbFactory; diff --git a/feign-reactor-core/pom.xml b/feign-reactor-core/pom.xml index a84a75166..c0998d3cb 100644 --- a/feign-reactor-core/pom.xml +++ b/feign-reactor-core/pom.xml @@ -23,7 +23,7 @@ com.playtika.reactivefeign feign-reactor-parent ../feign-reactor-parent - 3.2.6 + 4.0.3 feign-reactor-core @@ -42,11 +42,6 @@ feign-core - - org.apache.httpcomponents - httpclient - - org.slf4j slf4j-api @@ -64,6 +59,12 @@ + + org.apache.httpcomponents.client5 + httpclient5 + test + + io.projectreactor reactor-test @@ -82,8 +83,8 @@ - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test @@ -125,7 +126,17 @@ org.apache.logging.log4j - log4j-slf4j-impl + log4j-api + test + + + org.apache.logging.log4j + log4j-core + test + + + org.apache.logging.log4j + log4j-slf4j2-impl test @@ -140,6 +151,7 @@ test + org.springframework.boot spring-boot-starter-test @@ -176,7 +188,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.2.2 + 3.3.0 diff --git a/feign-reactor-core/src/main/java/reactivefeign/ReactiveFeign.java b/feign-reactor-core/src/main/java/reactivefeign/ReactiveFeign.java index ca03c6c5c..2bc742106 100644 --- a/feign-reactor-core/src/main/java/reactivefeign/ReactiveFeign.java +++ b/feign-reactor-core/src/main/java/reactivefeign/ReactiveFeign.java @@ -13,9 +13,19 @@ */ package reactivefeign; -import feign.*; +import feign.Contract; +import feign.Feign; +import feign.InvocationHandlerFactory; +import feign.MethodMetadata; +import feign.Target; import org.reactivestreams.Publisher; -import reactivefeign.client.*; +import reactivefeign.client.ReactiveErrorMapper; +import reactivefeign.client.ReactiveHttpClient; +import reactivefeign.client.ReactiveHttpClientFactory; +import reactivefeign.client.ReactiveHttpExchangeFilterFunction; +import reactivefeign.client.ReactiveHttpRequestInterceptor; +import reactivefeign.client.ReactiveHttpResponseMapper; +import reactivefeign.client.ResponseMappers; import reactivefeign.client.log.DefaultReactiveLogger; import reactivefeign.client.log.ReactiveLoggerListener; import reactivefeign.client.statushandler.ReactiveStatusHandler; @@ -25,27 +35,37 @@ import reactivefeign.methodhandler.MethodHandlerFactory; import reactivefeign.methodhandler.ReactiveMethodHandlerFactory; import reactivefeign.methodhandler.fallback.FallbackMethodHandlerFactory; -import reactivefeign.publisher.*; +import reactivefeign.publisher.FluxPublisherHttpClient; +import reactivefeign.publisher.MonoPublisherHttpClient; +import reactivefeign.publisher.PublisherClientFactory; +import reactivefeign.publisher.PublisherHttpClient; +import reactivefeign.publisher.ResponsePublisherHttpClient; import reactivefeign.publisher.retry.FluxRetryPublisherHttpClient; import reactivefeign.publisher.retry.MonoRetryPublisherHttpClient; import reactivefeign.retry.ReactiveRetryPolicy; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import reactor.util.retry.Retry; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.Type; import java.time.Clock; -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import static feign.Util.checkNotNull; import static feign.Util.isDefault; -import static reactivefeign.client.ReactiveHttpExchangeFilterFunction.*; +import static reactivefeign.client.ReactiveHttpExchangeFilterFunction.ofErrorMapper; +import static reactivefeign.client.ReactiveHttpExchangeFilterFunction.ofRequestProcessor; +import static reactivefeign.client.ReactiveHttpExchangeFilterFunction.ofResponseProcessor; import static reactivefeign.client.StatusHandlerPostProcessor.handleStatus; import static reactivefeign.client.log.LoggerExchangeFilterFunction.log; import static reactivefeign.utils.FeignUtils.isResponsePublisher; @@ -275,7 +295,7 @@ public PublisherHttpClient create(MethodMetadata methodMetadata) { reactivefeign.publisher.PublisherHttpClient publisherClient = toPublisher(reactiveClient, methodMetadata); if (retryPolicy != null) { - publisherClient = retry(publisherClient, methodMetadata, retryPolicy.retry()); + publisherClient = retry(publisherClient, methodMetadata, retryPolicy); } return publisherClient; @@ -294,12 +314,12 @@ public MethodHandlerFactory buildReactiveMethodHandlerFactory(PublisherClientFac public static PublisherHttpClient retry( PublisherHttpClient publisherClient, MethodMetadata methodMetadata, - Retry retry) { + ReactiveRetryPolicy retryPolicy) { Type returnPublisherType = returnPublisherType(methodMetadata); if(returnPublisherType == Mono.class){ - return new MonoRetryPublisherHttpClient(publisherClient, methodMetadata, retry); + return new MonoRetryPublisherHttpClient(publisherClient, methodMetadata, retryPolicy); } else if(returnPublisherType == Flux.class) { - return new FluxRetryPublisherHttpClient(publisherClient, methodMetadata, retry); + return new FluxRetryPublisherHttpClient(publisherClient, methodMetadata, retryPolicy); } else { throw new IllegalArgumentException("Unknown returnPublisherType: " + returnPublisherType); } diff --git a/feign-reactor-core/src/main/java/reactivefeign/ReactiveOptions.java b/feign-reactor-core/src/main/java/reactivefeign/ReactiveOptions.java index 44ef6a919..6a08f671f 100644 --- a/feign-reactor-core/src/main/java/reactivefeign/ReactiveOptions.java +++ b/feign-reactor-core/src/main/java/reactivefeign/ReactiveOptions.java @@ -53,20 +53,12 @@ public ProxySettings getProxySettings() { return proxySettings; } - public boolean isEmpty() { - return useHttp2 == null - && connectTimeoutMillis == null - && acceptCompressed == null - && followRedirects == null - && proxySettings == null; - } - public static boolean useHttp2(ReactiveOptions options){ return options != null && options.getUseHttp2() != null && options.getUseHttp2(); } - abstract public static class Builder { + abstract public static class Builder > { protected Boolean useHttp2; protected Long connectTimeoutMillis; protected Boolean acceptCompressed; @@ -75,29 +67,29 @@ abstract public static class Builder { public Builder() {} - public Builder setUseHttp2(boolean useHttp2) { + public B setUseHttp2(boolean useHttp2) { this.useHttp2 = useHttp2; - return this; + return (B)this; } - public Builder setConnectTimeoutMillis(long connectTimeoutMillis) { + public B setConnectTimeoutMillis(long connectTimeoutMillis) { this.connectTimeoutMillis = connectTimeoutMillis; - return this; + return (B)this; } - public Builder setAcceptCompressed(boolean acceptCompressed) { + public B setAcceptCompressed(boolean acceptCompressed) { this.acceptCompressed = acceptCompressed; - return this; + return (B)this; } - public Builder setFollowRedirects(boolean followRedirects) { + public B setFollowRedirects(boolean followRedirects) { this.followRedirects = followRedirects; - return this; + return (B)this; } - public Builder setProxySettings(ProxySettings proxySettings) { + public B setProxySettings(ProxySettings proxySettings) { this.proxySettings = proxySettings; - return this; + return (B)this; } abstract public ReactiveOptions build(); diff --git a/feign-reactor-core/src/main/java/reactivefeign/client/ReactiveFeignException.java b/feign-reactor-core/src/main/java/reactivefeign/client/ReactiveFeignException.java index 5d04df7ec..5d4b1ca69 100644 --- a/feign-reactor-core/src/main/java/reactivefeign/client/ReactiveFeignException.java +++ b/feign-reactor-core/src/main/java/reactivefeign/client/ReactiveFeignException.java @@ -2,16 +2,17 @@ public class ReactiveFeignException extends RuntimeException { - public static final String MESSAGE_PATTERN = "Error while running request: %s"; + public static final String MESSAGE_PATTERN = "Error while running request: %s\n%s"; private final ReactiveHttpRequest request; public ReactiveFeignException(Throwable cause, ReactiveHttpRequest request) { - super(String.format(MESSAGE_PATTERN, request), cause); + super(String.format(MESSAGE_PATTERN, request, cause.getMessage()), cause); this.request = request; } public ReactiveHttpRequest getRequest() { return request; } + } diff --git a/feign-reactor-core/src/main/java/reactivefeign/client/ReactiveHttpRequest.java b/feign-reactor-core/src/main/java/reactivefeign/client/ReactiveHttpRequest.java index 7bb2f0b3e..24e13df03 100644 --- a/feign-reactor-core/src/main/java/reactivefeign/client/ReactiveHttpRequest.java +++ b/feign-reactor-core/src/main/java/reactivefeign/client/ReactiveHttpRequest.java @@ -93,7 +93,6 @@ public Target target() { public String toString() { return "ReactiveHttpRequest{" + ", uri=" + uri + - ", headers=" + headers + ", target=" + target + "}"; } diff --git a/feign-reactor-core/src/main/java/reactivefeign/client/log/DefaultReactiveLogger.java b/feign-reactor-core/src/main/java/reactivefeign/client/log/DefaultReactiveLogger.java index 561f73dfc..18c155d7e 100644 --- a/feign-reactor-core/src/main/java/reactivefeign/client/log/DefaultReactiveLogger.java +++ b/feign-reactor-core/src/main/java/reactivefeign/client/log/DefaultReactiveLogger.java @@ -7,6 +7,7 @@ import reactivefeign.client.ReactiveHttpRequest; import reactivefeign.client.ReactiveHttpResponse; import reactivefeign.utils.Pair; +import reactivefeign.utils.SerializedFormData; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -73,7 +74,10 @@ public void bodySent(Object body, LogContext logContext) { logger.trace("[{}] REQUEST BODY\n{}", logContext.feignMethodKey, body); } else if (requestBody instanceof Flux) { logger.trace("[{}] REQUEST BODY ELEMENT\n{}", logContext.feignMethodKey, body); - } else { + } else if(requestBody instanceof SerializedFormData){ + logger.trace("[{}] REQUEST BODY FORM DATA\n{}", logContext.feignMethodKey, body); + } + else { throw new IllegalArgumentException("Unsupported publisher type: " + requestBody.getClass()); } } diff --git a/feign-reactor-core/src/main/java/reactivefeign/client/log/LoggerExchangeFilterFunction.java b/feign-reactor-core/src/main/java/reactivefeign/client/log/LoggerExchangeFilterFunction.java index 03ed2f074..4aec2ee2d 100644 --- a/feign-reactor-core/src/main/java/reactivefeign/client/log/LoggerExchangeFilterFunction.java +++ b/feign-reactor-core/src/main/java/reactivefeign/client/log/LoggerExchangeFilterFunction.java @@ -16,7 +16,12 @@ import feign.MethodMetadata; import feign.Target; import org.reactivestreams.Publisher; -import reactivefeign.client.*; +import reactivefeign.client.DelegatingReactiveHttpResponse; +import reactivefeign.client.ReactiveHttpClient; +import reactivefeign.client.ReactiveHttpExchangeFilterFunction; +import reactivefeign.client.ReactiveHttpRequest; +import reactivefeign.client.ReactiveHttpResponse; +import reactivefeign.utils.SerializedFormData; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -87,6 +92,8 @@ private ReactiveHttpRequest logRequestBody(ReactiveHttpRequest request, Object l } else if (request.body() instanceof Flux) { bodyLogged = ((Flux) request.body()) .doOnNext(requestBodyLogger(logContext)); + } else if(request.body() instanceof SerializedFormData){ + bodyLogged = ((SerializedFormData) request.body()).logged(requestBodyLogger(logContext)); } else { throw new IllegalArgumentException("Unsupported publisher type: " + request.body().getClass()); } diff --git a/feign-reactor-core/src/main/java/reactivefeign/methodhandler/PublisherClientMethodHandler.java b/feign-reactor-core/src/main/java/reactivefeign/methodhandler/PublisherClientMethodHandler.java index 80988321d..7eb79a6d7 100644 --- a/feign-reactor-core/src/main/java/reactivefeign/methodhandler/PublisherClientMethodHandler.java +++ b/feign-reactor-core/src/main/java/reactivefeign/methodhandler/PublisherClientMethodHandler.java @@ -13,7 +13,12 @@ */ package reactivefeign.methodhandler; -import feign.*; +import feign.CollectionFormat; +import feign.MethodMetadata; +import feign.Param; +import feign.QueryMapEncoder; +import feign.RequestTemplate; +import feign.Target; import feign.querymap.FieldQueryMapEncoder; import feign.template.UriUtils; import org.reactivestreams.Publisher; @@ -21,13 +26,23 @@ import reactivefeign.client.ReactiveHttpClient; import reactivefeign.client.ReactiveHttpRequest; import reactivefeign.publisher.PublisherHttpClient; +import reactivefeign.utils.ContentType; import reactivefeign.utils.LinkedCaseInsensitiveMap; import reactivefeign.utils.Pair; +import reactivefeign.utils.SerializedFormData; import reactor.core.publisher.Mono; import java.lang.reflect.InvocationTargetException; import java.net.URI; -import java.util.*; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -38,10 +53,18 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.*; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; +import static reactivefeign.utils.FormUtils.serializeForm; import static reactivefeign.utils.HttpUtils.CONTENT_TYPE_HEADER; +import static reactivefeign.utils.HttpUtils.FORM_URL_ENCODED; import static reactivefeign.utils.HttpUtils.MULTIPART_MIME_TYPES; -import static reactivefeign.utils.MultiValueMapUtils.*; +import static reactivefeign.utils.MultiValueMapUtils.add; +import static reactivefeign.utils.MultiValueMapUtils.addAll; +import static reactivefeign.utils.MultiValueMapUtils.addAllOrdered; +import static reactivefeign.utils.MultiValueMapUtils.addOrdered; import static reactivefeign.utils.StringUtils.cutPrefix; import static reactivefeign.utils.StringUtils.cutTail; @@ -66,7 +89,10 @@ public class PublisherClientMethodHandler implements MethodHandler { private final URI staticUri; private final QueryMapEncoder queryMapEncoder = new FieldQueryMapEncoder(); + + private final Optional contentType; private final boolean isMultipart; + private final boolean isFormUrlEncoded; public PublisherClientMethodHandler(Target target, MethodMetadata methodMetadata, @@ -80,18 +106,20 @@ public PublisherClientMethodHandler(Target target, this.headerExpanders = buildExpanders(requestTemplate.headers()); this.queriesAll = new HashMap<>(requestTemplate.queries()); - this.isMultipart = isMultipartForm(requestTemplate.headers()); + this.contentType = getContentType(requestTemplate.headers()); + this.isMultipart = contentType.map(ct -> MULTIPART_MIME_TYPES.contains(ct.getMediaType())).orElse(false); if(isMultipart && methodMetadata.template().bodyTemplate() != null){ throw new IllegalArgumentException("isMultipart && methodMetadata.template().bodyTemplate() != null"); } - if(!isMultipart) { + this.isFormUrlEncoded = contentType.map(ct -> ct.getMediaType().equalsIgnoreCase(FORM_URL_ENCODED)).orElse(false); + if(!isMultipart && !isFormUrlEncoded) { methodMetadata.formParams() .forEach(param -> add(queriesAll, param, "{" + param + "}")); } this.queryExpanders = buildExpanders(queriesAll); //static template (POST & PUT) - if(pathExpander instanceof StaticPathExpander + if(pathExpander instanceof StaticExpander && queriesAll.isEmpty() && methodMetadata.queryMapIndex() == null){ staticUri = URI.create(target.url() + cutTail(requestTemplate.url(), "/")); @@ -161,8 +189,12 @@ private String queryLine(Map> queries) { Collection valuesEncoded = query.getValue().stream() .map(value -> UriUtils.encode(value, UTF_8)) .collect(toList()); - queryBuilder.append('&'); - queryBuilder.append(collectionFormat.join(query.getKey(), valuesEncoded, UTF_8)); + + CharSequence queryString = collectionFormat.join(query.getKey(), valuesEncoded, UTF_8); + if(queryString.length() > 0){ + queryBuilder.append('&'); + queryBuilder.append(queryString); + } } if(queryBuilder.length() > 0) { queryBuilder.deleteCharAt(0); @@ -236,21 +268,42 @@ protected Map> headers(Object[] argv, Substitutions substit protected Publisher body( Object[] argv, Substitutions substitutions) { + + if(isFormUrlEncoded){ + return serializeFormData(argv, substitutions); + } + if (methodMetadata.bodyIndex() != null) { - return body(argv[methodMetadata.bodyIndex()]); - } else if(isMultipart) { - Map> formVariables = new LinkedHashMap<>(); - for (Map.Entry entry : substitutions.placeholderToSubstitution.entrySet()) { - if (methodMetadata.formParams().contains(entry.getKey())) { - formVariables.put(entry.getKey(), singletonList(entry.getValue())); - } - } - return new MultipartMap(formVariables); + Object body = argv[methodMetadata.bodyIndex()]; + return body(body); + } else if(isMultipart) { //all arguments have Param annotation + return new MultipartMap(collectFormData(substitutions)); } else { return Mono.empty(); } } + private SerializedFormData serializeFormData(Object[] argv, Substitutions substitutions) { + Map formData; + if (methodMetadata.bodyIndex() != null) { + Object body = argv[methodMetadata.bodyIndex()]; + formData = (Map)body; + } else { + formData = collectFormData(substitutions); + } + return serializeForm(formData, contentType.get().getCharset()); + } + + private Map> collectFormData(Substitutions substitutions) { + Map> formVariables = new LinkedHashMap<>(); + for (Map.Entry entry : substitutions.placeholderToSubstitution.entrySet()) { + if (methodMetadata.formParams().contains(entry.getKey())) { + formVariables.put(entry.getKey(), singletonList(entry.getValue())); + } + } + return formVariables; + } + protected Publisher body(Object body) { if (body instanceof Publisher) { return (Publisher) body; @@ -266,7 +319,7 @@ private static Map>>> buildExp .map(v -> new Pair<>(e.getKey(), v))); return templatesFlattened.collect(groupingBy( entry -> entry.left, - mapping(entry -> buildExpandFunction(entry.right), toList()))); + mapping(entry -> buildMultiValueExpandFunction(entry.right), toList()))); } /** @@ -274,7 +327,7 @@ private static Map>>> buildExp * @param template * @return function that able to map substitutions map to actual value for specified template */ - private static Function> buildExpandFunction(String template) { + private static Function> buildMultiValueExpandFunction(String template) { Matcher matcher = SUBSTITUTION_PATTERN.matcher(template); if(matcher.matches()){ String placeholder = matcher.group(1); @@ -299,7 +352,8 @@ private static Function> buildExpandFunction(String } }; } else { - return substitutions -> singletonList(template); + Function expandFunction = buildExpandFunction(template); + return substitutions -> singletonList(expandFunction.apply(substitutions)); } } @@ -308,19 +362,18 @@ private static Function buildUrlExpandFunction( String requestUrl = getRequestUrl(requestTemplate); if(target instanceof Target.EmptyTarget){ - return expandUrlForEmptyTarget(buildUrlExpandFunction(requestUrl)); + return expandUrlForEmptyTarget(buildExpandFunction(requestUrl)); } else { String targetUrl = cutTail(target.url(), "/"); - return buildUrlExpandFunction(targetUrl+requestUrl); + return buildExpandFunction(targetUrl+requestUrl); } } private static String getRequestUrl(RequestTemplate requestTemplate) { String requestUrl = cutTail(requestTemplate.url(), requestTemplate.queryLine()); requestUrl = cutPrefix(requestUrl, "/"); - requestUrl = cutTail(requestUrl, "/"); if(!requestUrl.isEmpty()){ - requestUrl = "/"+requestUrl; + requestUrl = "/" + requestUrl; } return requestUrl; } @@ -335,7 +388,7 @@ private static Function expandUrlForEmptyTarget( * @param template * @return function that able to map substitutions map to actual value for specified template */ - private static Function buildUrlExpandFunction(String template) { + private static Function buildExpandFunction(String template) { List> chunks = new ArrayList<>(); Matcher matcher = SUBSTITUTION_PATTERN.matcher(template); int previousMatchEnd = 0; @@ -357,9 +410,9 @@ private static Function buildUrlExpandFunction(String tem previousMatchEnd = matcher.end(); } - //no substitutions in path + //no substitutions in template if(previousMatchEnd == 0){ - return new StaticPathExpander(template); + return new StaticExpander(template); } String textChunk = template.substring(previousMatchEnd); @@ -371,11 +424,11 @@ private static Function buildUrlExpandFunction(String tem .collect(Collectors.joining()); } - private static class StaticPathExpander implements Function{ + private static class StaticExpander implements Function{ private final String staticPath; - private StaticPathExpander(String staticPath) { + private StaticExpander(String staticPath) { this.staticPath = staticPath; } @@ -463,9 +516,10 @@ public void subscribe(Subscriber s) { } } - public boolean isMultipartForm(Map> headers){ + public Optional getContentType(Map> headers){ return headers.entrySet().stream() - .anyMatch(entry -> entry.getKey().equalsIgnoreCase(CONTENT_TYPE_HEADER) - && MULTIPART_MIME_TYPES.contains(entry.getValue().iterator().next().toLowerCase())); + .filter(entry -> entry.getKey().equalsIgnoreCase(CONTENT_TYPE_HEADER)) + .map(entry -> ContentType.parse(entry.getValue().iterator().next().toLowerCase())) + .findFirst(); } } diff --git a/feign-reactor-core/src/main/java/reactivefeign/publisher/retry/FluxRetryPublisherHttpClient.java b/feign-reactor-core/src/main/java/reactivefeign/publisher/retry/FluxRetryPublisherHttpClient.java index 182d3d371..ce8d0bf30 100644 --- a/feign-reactor-core/src/main/java/reactivefeign/publisher/retry/FluxRetryPublisherHttpClient.java +++ b/feign-reactor-core/src/main/java/reactivefeign/publisher/retry/FluxRetryPublisherHttpClient.java @@ -17,8 +17,8 @@ import org.reactivestreams.Publisher; import reactivefeign.client.ReactiveHttpRequest; import reactivefeign.publisher.PublisherHttpClient; +import reactivefeign.retry.ReactiveRetryPolicy; import reactor.core.publisher.Flux; -import reactor.util.retry.Retry; /** * Wraps {@link PublisherHttpClient} with retry logic provided by retryFunction @@ -30,8 +30,8 @@ public class FluxRetryPublisherHttpClient extends RetryPublisherHttpClient { public FluxRetryPublisherHttpClient( PublisherHttpClient publisherClient, MethodMetadata methodMetadata, - Retry retry) { - super(publisherClient, methodMetadata, retry); + ReactiveRetryPolicy retryPolicy) { + super(publisherClient, methodMetadata, retryPolicy); } @Override diff --git a/feign-reactor-core/src/main/java/reactivefeign/publisher/retry/MonoRetryPublisherHttpClient.java b/feign-reactor-core/src/main/java/reactivefeign/publisher/retry/MonoRetryPublisherHttpClient.java index ee39709cf..690e7670e 100644 --- a/feign-reactor-core/src/main/java/reactivefeign/publisher/retry/MonoRetryPublisherHttpClient.java +++ b/feign-reactor-core/src/main/java/reactivefeign/publisher/retry/MonoRetryPublisherHttpClient.java @@ -17,8 +17,8 @@ import org.reactivestreams.Publisher; import reactivefeign.client.ReactiveHttpRequest; import reactivefeign.publisher.PublisherHttpClient; +import reactivefeign.retry.ReactiveRetryPolicy; import reactor.core.publisher.Mono; -import reactor.util.retry.Retry; /** * Wraps {@link PublisherHttpClient} with retry logic provided by retryFunction @@ -30,8 +30,8 @@ public class MonoRetryPublisherHttpClient extends RetryPublisherHttpClient { public MonoRetryPublisherHttpClient( PublisherHttpClient publisherClient, MethodMetadata methodMetadata, - Retry retry) { - super(publisherClient, methodMetadata, retry); + ReactiveRetryPolicy retryPolicy) { + super(publisherClient, methodMetadata, retryPolicy); } @Override diff --git a/feign-reactor-core/src/main/java/reactivefeign/publisher/retry/RetryPublisherHttpClient.java b/feign-reactor-core/src/main/java/reactivefeign/publisher/retry/RetryPublisherHttpClient.java index 783835219..b27be7ea2 100644 --- a/feign-reactor-core/src/main/java/reactivefeign/publisher/retry/RetryPublisherHttpClient.java +++ b/feign-reactor-core/src/main/java/reactivefeign/publisher/retry/RetryPublisherHttpClient.java @@ -13,6 +13,7 @@ */ package reactivefeign.publisher.retry; +import feign.ExceptionPropagationPolicy; import feign.MethodMetadata; import org.reactivestreams.Publisher; import org.slf4j.Logger; @@ -20,6 +21,7 @@ import reactivefeign.client.ReactiveFeignException; import reactivefeign.client.ReactiveHttpRequest; import reactivefeign.publisher.PublisherHttpClient; +import reactivefeign.retry.ReactiveRetryPolicy; import reactor.core.Exceptions; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -38,15 +40,20 @@ abstract public class RetryPublisherHttpClient implements PublisherHttpClient { protected final String feignMethodKey; protected final PublisherHttpClient publisherClient; private final Retry retry; + private final ReactiveRetryPolicy retryPolicy; + + private final ExceptionPropagationPolicy exceptionPropagationPolicy; protected RetryPublisherHttpClient( PublisherHttpClient publisherClient, MethodMetadata methodMetadata, - Retry retry) { + ReactiveRetryPolicy retryPolicy) { this.publisherClient = publisherClient; this.feignMethodKey = methodMetadata.configKey(); - this.retry = wrapWithRetryLog(retry, feignMethodKey); + this.retry = wrapWithRetryLog(retryPolicy.retry(), feignMethodKey); + this.exceptionPropagationPolicy = retryPolicy.exceptionPropagationPolicy(); + this.retryPolicy = retryPolicy; } protected Retry getRetry(ReactiveHttpRequest request){ @@ -59,14 +66,17 @@ private Retry wrapWithOutOfRetriesLog(ReactiveHttpRequest request) { public Publisher generateCompanion(Flux retrySignals) { return Flux.from(retry.generateCompanion(retrySignals)) .onErrorResume(throwable -> Mono.just(new OutOfRetriesWrapper(throwable, request))) - .zipWith(Flux.range(1, Integer.MAX_VALUE), (object, index) -> { + .zipWith(Flux.range(1, retryPolicy.maxAllowedRetries() + 1), (object, index) -> { if(object instanceof OutOfRetriesWrapper){ OutOfRetriesWrapper wrapper = (OutOfRetriesWrapper) object; if(index == 1){ throw Exceptions.propagate(wrapper.getCause()); } else { logger.debug("[{}]---> USED ALL RETRIES", feignMethodKey, wrapper.getCause()); - throw Exceptions.propagate(new OutOfRetriesException(wrapper.getCause(), request)); + throw Exceptions.propagate( + exceptionPropagationPolicy == ExceptionPropagationPolicy.UNWRAP + ? wrapper.getCause() + : new OutOfRetriesException(wrapper.getCause(), request)); } } else { return object; diff --git a/feign-reactor-core/src/main/java/reactivefeign/retry/BasicReactiveRetryPolicy.java b/feign-reactor-core/src/main/java/reactivefeign/retry/BasicReactiveRetryPolicy.java index 85c4062e3..775db030e 100644 --- a/feign-reactor-core/src/main/java/reactivefeign/retry/BasicReactiveRetryPolicy.java +++ b/feign-reactor-core/src/main/java/reactivefeign/retry/BasicReactiveRetryPolicy.java @@ -13,6 +13,7 @@ */ package reactivefeign.retry; +import feign.ExceptionPropagationPolicy; import feign.RetryableException; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; @@ -29,8 +30,11 @@ public class BasicReactiveRetryPolicy extends SimpleReactiveRetryPolicy{ private final long periodInMs; private final Clock clock; - private BasicReactiveRetryPolicy(int maxRetries, long periodInMs, Clock clock, Scheduler scheduler){ - super(scheduler); + private BasicReactiveRetryPolicy( + int maxRetries, long periodInMs, + Clock clock, Scheduler scheduler, + ExceptionPropagationPolicy exceptionPropagationPolicy){ + super(scheduler, exceptionPropagationPolicy); this.maxRetries = maxRetries; this.periodInMs = periodInMs; this.clock = clock; @@ -53,12 +57,10 @@ public long retryDelay(Throwable error, int attemptNo) { if (attemptNo <= maxRetries) { if(periodInMs > 0) { long delay; - Date retryAfter; + Long retryAfter; // "Retry-After" header set - if (error instanceof RetryableException - && (retryAfter = ((RetryableException) error) - .retryAfter()) != null) { - delay = retryAfter.getTime() - clock.millis(); + if (error instanceof RetryableException re && (retryAfter = re.retryAfter()) != null) { + delay = retryAfter - clock.millis(); delay = Math.min(delay, periodInMs); delay = Math.max(delay, 0); } else { @@ -73,9 +75,15 @@ public long retryDelay(Throwable error, int attemptNo) { } } + @Override + public int maxAllowedRetries() { + return maxRetries; + } + public static class Builder implements ReactiveRetryPolicy.Builder{ private int maxRetries; private long backoffInMs = 0; + private ExceptionPropagationPolicy exceptionPropagationPolicy; private Scheduler scheduler = Schedulers.parallel(); private Clock clock = Clock.systemUTC(); @@ -89,6 +97,11 @@ public Builder setBackoffInMs(long backoffInMs) { return this; } + public Builder setExceptionPropagationPolicy(ExceptionPropagationPolicy exceptionPropagationPolicy) { + this.exceptionPropagationPolicy = exceptionPropagationPolicy; + return this; + } + Builder setClock(Clock clock) { this.clock = clock; return this; @@ -100,7 +113,7 @@ Builder setScheduler(Scheduler scheduler) { } public BasicReactiveRetryPolicy build(){ - return new BasicReactiveRetryPolicy(maxRetries, backoffInMs, clock, scheduler); + return new BasicReactiveRetryPolicy(maxRetries, backoffInMs, clock, scheduler, exceptionPropagationPolicy); } } } diff --git a/feign-reactor-core/src/main/java/reactivefeign/retry/FilteredReactiveRetryPolicy.java b/feign-reactor-core/src/main/java/reactivefeign/retry/FilteredReactiveRetryPolicy.java index 08bc50f3a..f81767bed 100644 --- a/feign-reactor-core/src/main/java/reactivefeign/retry/FilteredReactiveRetryPolicy.java +++ b/feign-reactor-core/src/main/java/reactivefeign/retry/FilteredReactiveRetryPolicy.java @@ -31,6 +31,11 @@ public Retry retry() { return filter(retryPolicy.retry(), toRetryOn); } + @Override + public int maxAllowedRetries() { + return retryPolicy.maxAllowedRetries(); + } + static Retry filter( Retry retry, Predicate toRetryOn){ diff --git a/feign-reactor-core/src/main/java/reactivefeign/retry/ReactiveRetryPolicy.java b/feign-reactor-core/src/main/java/reactivefeign/retry/ReactiveRetryPolicy.java index cb53e886f..e1e32e2bd 100644 --- a/feign-reactor-core/src/main/java/reactivefeign/retry/ReactiveRetryPolicy.java +++ b/feign-reactor-core/src/main/java/reactivefeign/retry/ReactiveRetryPolicy.java @@ -1,10 +1,8 @@ package reactivefeign.retry; -import reactor.core.publisher.Flux; +import feign.ExceptionPropagationPolicy; import reactor.util.retry.Retry; -import java.util.function.Function; - /** * @author Sergii Karpenko */ @@ -12,7 +10,13 @@ public interface ReactiveRetryPolicy { Retry retry(); + int maxAllowedRetries(); + interface Builder { ReactiveRetryPolicy build(); } + + default ExceptionPropagationPolicy exceptionPropagationPolicy(){ + return ExceptionPropagationPolicy.NONE; + } } diff --git a/feign-reactor-core/src/main/java/reactivefeign/retry/SimpleReactiveRetryPolicy.java b/feign-reactor-core/src/main/java/reactivefeign/retry/SimpleReactiveRetryPolicy.java index 96580948d..0bbc94879 100644 --- a/feign-reactor-core/src/main/java/reactivefeign/retry/SimpleReactiveRetryPolicy.java +++ b/feign-reactor-core/src/main/java/reactivefeign/retry/SimpleReactiveRetryPolicy.java @@ -13,6 +13,7 @@ */ package reactivefeign.retry; +import feign.ExceptionPropagationPolicy; import reactor.core.Exceptions; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -28,9 +29,20 @@ public abstract class SimpleReactiveRetryPolicy implements ReactiveRetryPolicy { private final Scheduler scheduler; + private final ExceptionPropagationPolicy exceptionPropagationPolicy; - protected SimpleReactiveRetryPolicy(Scheduler scheduler) { + protected SimpleReactiveRetryPolicy( + Scheduler scheduler, + ExceptionPropagationPolicy exceptionPropagationPolicy) { this.scheduler = scheduler; + this.exceptionPropagationPolicy = exceptionPropagationPolicy; + } + + @Override + public ExceptionPropagationPolicy exceptionPropagationPolicy(){ + return exceptionPropagationPolicy != null + ? exceptionPropagationPolicy + : ReactiveRetryPolicy.super.exceptionPropagationPolicy(); } /** @@ -43,7 +55,7 @@ protected SimpleReactiveRetryPolicy(Scheduler scheduler) { @Override public Retry retry() { return Retry.from(errors -> errors - .zipWith(Flux.range(1, Integer.MAX_VALUE), (signal, index) -> { + .zipWith(Flux.range(1, maxAllowedRetries() + 1), (signal, index) -> { long delay = retryDelay(signal.failure(), index); if (delay >= 0) { return Tuples.of(delay, signal); diff --git a/feign-reactor-core/src/main/java/reactivefeign/utils/CollectionUtils.java b/feign-reactor-core/src/main/java/reactivefeign/utils/CollectionUtils.java new file mode 100644 index 000000000..3d7fcb788 --- /dev/null +++ b/feign-reactor-core/src/main/java/reactivefeign/utils/CollectionUtils.java @@ -0,0 +1,10 @@ +package reactivefeign.utils; + +import java.util.Collection; + +public class CollectionUtils { + + public static boolean isEmpty(Collection c){ + return c == null || c.isEmpty(); + } +} diff --git a/feign-reactor-core/src/main/java/reactivefeign/utils/ContentType.java b/feign-reactor-core/src/main/java/reactivefeign/utils/ContentType.java new file mode 100644 index 000000000..759a9e66b --- /dev/null +++ b/feign-reactor-core/src/main/java/reactivefeign/utils/ContentType.java @@ -0,0 +1,55 @@ +package reactivefeign.utils; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ContentType { + + private static final Pattern charsetPattern = Pattern + .compile("(?i)\\bcharset=\\s*\"?([^\\s;\"/]*)/?>"); + + + private final String mediaType; + private final Charset charset; + + private ContentType(String mediaType, Charset charset) { + this.mediaType = mediaType; + this.charset = charset; + } + + public static ContentType parse(String contentType){ + int splitPos = contentType.indexOf(';'); + if(splitPos == -1){ + return new ContentType(contentType, StandardCharsets.UTF_8); + } + String mediaType = contentType.substring(0, splitPos).trim(); + Charset charset = getCharsetFromContentType(contentType.substring(splitPos + 1)); + return new ContentType(mediaType, charset); + } + + private static Charset getCharsetFromContentType(CharSequence contentType) { + if (contentType == null) + return null; + Matcher m = charsetPattern.matcher(contentType); + if (m.find()) { + String charset = m.group(1).trim(); + if (Charset.isSupported(charset)) + return Charset.forName(charset); + charset = charset.toUpperCase(Locale.ENGLISH); + if (Charset.isSupported(charset)) + return Charset.forName(charset); + } + return StandardCharsets.UTF_8; + } + + public String getMediaType() { + return mediaType; + } + + public Charset getCharset() { + return charset; + } +} diff --git a/feign-reactor-core/src/main/java/reactivefeign/utils/FormUtils.java b/feign-reactor-core/src/main/java/reactivefeign/utils/FormUtils.java new file mode 100644 index 000000000..77b9eee84 --- /dev/null +++ b/feign-reactor-core/src/main/java/reactivefeign/utils/FormUtils.java @@ -0,0 +1,37 @@ +package reactivefeign.utils; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.Collection; +import java.util.Map; + +import static java.util.Collections.singletonList; + +public class FormUtils { + + public static SerializedFormData serializeForm(Map formData, Charset charset) { + StringBuilder builder = new StringBuilder(); + formData.forEach((name, values) -> + (values instanceof Collection ? (Collection)values : singletonList(values)) + .forEach(value -> { + try { + if (builder.length() != 0) { + builder.append('&'); + } + builder.append(URLEncoder.encode(name, charset.name())); + if (value != null) { + builder.append('='); + builder.append(URLEncoder.encode(value.toString(), charset.name())); + } + } + catch (UnsupportedEncodingException ex) { + throw new IllegalStateException(ex); + } + })); + + String formDataString = builder.toString(); + return new SerializedFormData(formDataString, ByteBuffer.wrap(formDataString.getBytes(charset))); + } +} diff --git a/feign-reactor-core/src/main/java/reactivefeign/utils/HttpUtils.java b/feign-reactor-core/src/main/java/reactivefeign/utils/HttpUtils.java index 2b9095ca5..c7b67150f 100644 --- a/feign-reactor-core/src/main/java/reactivefeign/utils/HttpUtils.java +++ b/feign-reactor-core/src/main/java/reactivefeign/utils/HttpUtils.java @@ -18,7 +18,12 @@ import java.util.HashSet; import java.util.Set; -import static reactivefeign.utils.HttpUtils.StatusCodeFamily.*; +import static reactivefeign.utils.HttpUtils.StatusCodeFamily.CLIENT_ERROR; +import static reactivefeign.utils.HttpUtils.StatusCodeFamily.INFORMATIONAL; +import static reactivefeign.utils.HttpUtils.StatusCodeFamily.OTHER; +import static reactivefeign.utils.HttpUtils.StatusCodeFamily.REDIRECTION; +import static reactivefeign.utils.HttpUtils.StatusCodeFamily.SERVER_ERROR; +import static reactivefeign.utils.HttpUtils.StatusCodeFamily.SUCCESSFUL; public final class HttpUtils { @@ -39,6 +44,8 @@ private HttpUtils(){} public static final Set MULTIPART_MIME_TYPES = Collections.unmodifiableSet(new HashSet<>( Arrays.asList(MULTIPART_FORM_DATA, MULTIPART_MIXED, MULTIPART_RELATED))); + public static final String FORM_URL_ENCODED = "application/x-www-form-urlencoded"; + public static final byte[] NEWLINE_SEPARATOR = {'\n'}; public static final String CONTENT_TYPE_HEADER = "Content-Type"; public static final String CONTENT_ENCODING_HEADER = "Content-Encoding"; diff --git a/feign-reactor-core/src/main/java/reactivefeign/utils/SerializedFormData.java b/feign-reactor-core/src/main/java/reactivefeign/utils/SerializedFormData.java new file mode 100644 index 000000000..dbecc69cf --- /dev/null +++ b/feign-reactor-core/src/main/java/reactivefeign/utils/SerializedFormData.java @@ -0,0 +1,54 @@ +package reactivefeign.utils; + +import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; + +import java.nio.ByteBuffer; +import java.util.function.Consumer; + +public class SerializedFormData implements Publisher { + + private final String formDataString; + private final ByteBuffer formData; + + private final Consumer logger; + + public SerializedFormData(String formDataString, ByteBuffer formData) { + this(formDataString, formData, null); + } + + private SerializedFormData(String formDataString, ByteBuffer formData, Consumer logger) { + this.formDataString = formDataString; + this.formData = formData; + this.logger = logger; + } + + public ByteBuffer getFormData() { + if(logger != null) { + logger.accept(this); + } + return formData; + } + + public String getFormDataString() { + if(logger != null) { + logger.accept(this); + } + return formDataString; + } + + @Override + public void subscribe(Subscriber s) { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return formDataString; + } + + public SerializedFormData logged(Consumer logger){ + return new SerializedFormData(formDataString, formData, logger); + } +} + diff --git a/feign-reactor-core/src/test/java/reactivefeign/BaseReactorTest.java b/feign-reactor-core/src/test/java/reactivefeign/BaseReactorTest.java index 18ae9e2cb..2b0343010 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/BaseReactorTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/BaseReactorTest.java @@ -49,7 +49,7 @@ public static void installBlockHound() { builder.allowBlockingCallsInside("org.eclipse.jetty.util.BlockingArrayQueue", "offer"); builder.allowBlockingCallsInside("org.eclipse.jetty.util.BlockingArrayQueue", "peek"); //java.net.InMemoryCookieStore.get - builder.allowBlockingCallsInside("org.eclipse.jetty.client.HttpConnection", "normalizeRequest"); + builder.allowBlockingCallsInside("org.eclipse.jetty.client.transport.HttpConnection", "normalizeRequest"); builder.allowBlockingCallsInside("java.util.concurrent.FutureTask", "handlePossibleCancellationInterrupt"); builder.allowBlockingCallsInside("org.eclipse.jetty.http2.HTTP2Session$StreamsState", "reserveSlot"); builder.allowBlockingCallsInside("org.eclipse.jetty.http2.HTTP2Session$StreamsState", "flush"); @@ -57,21 +57,26 @@ public static void installBlockHound() { //jetty http2 server builder.allowBlockingCallsInside("org.eclipse.jetty.util.IteratingCallback", "processing"); builder.allowBlockingCallsInside("org.eclipse.jetty.util.IteratingCallback", "iterate"); + builder.allowBlockingCallsInside("org.eclipse.jetty.util.thread.AutoLock", "lock"); //java11 builder.allowBlockingCallsInside("jdk.internal.net.http.MultiExchange", "responseAsync"); + builder.allowBlockingCallsInside("jdk.internal.misc.Unsafe", "park"); builder.allowBlockingCallsInside("com.sun.jmx.mbeanserver.Repository", "remove"); builder.allowBlockingCallsInside("com.sun.jmx.mbeanserver.Repository", "contains"); builder.allowBlockingCallsInside("com.sun.jmx.mbeanserver.Repository", "retrieve"); builder.allowBlockingCallsInside("com.sun.jmx.mbeanserver.Repository", "addMBean"); + //Apache http client5 + builder.allowBlockingCallsInside("org.apache.hc.core5.pool.StrictConnPool", "lease"); + builder.allowBlockingCallsInside("org.apache.hc.core5.reactor.IOSessionImpl", "setEvent"); builder.install(); } } - //by default we want to detect blocking calls + //by default, we want to detect blocking calls protected Scheduler testScheduler() { return Schedulers.parallel(); } diff --git a/feign-reactor-core/src/test/java/reactivefeign/BasicFeaturesTest.java b/feign-reactor-core/src/test/java/reactivefeign/BasicFeaturesTest.java index a0a00e4de..21fdcadd8 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/BasicFeaturesTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/BasicFeaturesTest.java @@ -14,6 +14,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.github.tomakehurst.wiremock.junit.WireMockClassRule; +import feign.Headers; import feign.Param; import feign.QueryMap; import feign.RequestLine; @@ -32,13 +33,21 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.function.Predicate; -import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static java.util.Arrays.asList; -import static reactivefeign.TestUtils.*; +import static org.assertj.core.api.Assertions.assertThat; +import static reactivefeign.TestUtils.MAPPER; +import static reactivefeign.TestUtils.readJsonFromFile; +import static reactivefeign.TestUtils.toLowerCaseKeys; /** * @author Sergii Karpenko @@ -68,8 +77,8 @@ protected int wireMockPort(){ public void setUp() { String targetUrl = getTargetUrl(); client = this.builder() - .decode404() - .target(TestClient.class, targetUrl); + .decode404() + .target(TestClient.class, targetUrl); } public String getTargetUrl() { @@ -101,7 +110,7 @@ public void shouldDecode404ToEmptyMono() { Mono emptyMono = client.decode404ToEmptyMono(123); StepVerifier.create(emptyMono) - .verifyComplete(); + .verifyComplete(); } @Test @@ -142,12 +151,19 @@ public void shouldPassResponseAsIs() throws JsonProcessingException { wireMockRule.stubFor(get(urlEqualTo("/reactiveHttpResponse")) .willReturn(aResponse().withStatus(200) .withHeader("Content-Type", "application/json") + .withHeader("header1", "value1") + .withHeader("header1", "value2") .withBody(MAPPER.writeValueAsString(testObjects)))); Mono>> result = client.reactiveHttpResponse(); StepVerifier.create(result) - .expectNextMatches(response -> toLowerCaseKeys(response.headers()) - .containsKey("content-type")) + .expectNextMatches(response -> { + Map> headers = toLowerCaseKeys(response.headers()); + return headers.containsKey("content-type") + && headers.get("header1").containsAll( + new HashSet<>((asList("value1", "value2")))); + }) + .verifyComplete(); Flux flux = result.flatMapMany(ReactiveHttpResponse::body); @@ -171,7 +187,7 @@ public void shouldExpandUrlWithBaseUriForEmptyTarget() throws URISyntaxException .target(Target.EmptyTarget.create(EmptyTargetClient.class)); StepVerifier.create(testClient.expandUrl(new URI(getTargetUrl()), testObject) - .subscribeOn(testScheduler())) + .subscribeOn(testScheduler())) .expectNext(testObject) .verifyComplete(); } @@ -185,7 +201,7 @@ public void shouldExpandQueryMapToQueryParameters() { .willReturn(aResponse().withStatus(200))); StepVerifier.create(client.queryMap(new HashMap(){{put(queryParameter, value);}}) - .subscribeOn(testScheduler())) + .subscribeOn(testScheduler())) .verifyComplete(); } @@ -200,7 +216,7 @@ public void shouldPassQueryParameters() { .willReturn(aResponse().withStatus(200))); StepVerifier.create(client.queryParam(asList(value1, value2)) - .subscribeOn(testScheduler())) + .subscribeOn(testScheduler())) .verifyComplete(); } @@ -211,8 +227,40 @@ public void shouldExpandPojoToQueryParameters() { .willReturn(aResponse().withStatus(200))); StepVerifier.create(client.queryPojo(new TestObject(1)) - .subscribeOn(testScheduler())) + .subscribeOn(testScheduler())) + .verifyComplete(); + } + + @Test + public void shouldPassExplicitContentTypeHeader() { + + String body = "123"; + String contentTypeHeader = "Content-Type"; + wireMockRule.stubFor(post(urlEqualTo("/passExplicitContentType")) + .withRequestBody(equalTo(body)) + .withHeader(contentTypeHeader, equalTo("application/customContentType")) + .willReturn(aResponse().withStatus(200))); + + StepVerifier.create(client.passExplicitContentTypeHeader(body) + .subscribeOn(testScheduler())) + .verifyComplete(); + + assertThat(wireMockRule.getAllServeEvents().get(0).getRequest().header(contentTypeHeader).values()) + .containsExactly("application/customContentType"); + } + + @Test + public void shouldNotCutTrailingSlash() { + + wireMockRule.stubFor(get(urlEqualTo("/users/1/dogs/")) + .willReturn(aResponse().withStatus(200))); + + StepVerifier.create(client.keepTrailingSlash(1) + .subscribeOn(testScheduler())) .verifyComplete(); + + assertThat(wireMockRule.getAllServeEvents().get(0).getRequest().getUrl()) + .endsWith("/users/1/dogs/"); } public interface TestClient { @@ -239,6 +287,13 @@ public interface TestClient { @RequestLine("POST /queryPojo") Mono queryPojo(@QueryMap TestObject queryPojo); + + @Headers("Content-Type: application/customContentType") + @RequestLine("POST /passExplicitContentType") + Mono passExplicitContentTypeHeader(String body); + + @RequestLine("GET /users/{userId}/dogs/") + Mono keepTrailingSlash(@Param("userId") int userId); } public interface EmptyTargetClient { diff --git a/feign-reactor-core/src/test/java/reactivefeign/DefaultMethodHandlerTest.java b/feign-reactor-core/src/test/java/reactivefeign/DefaultMethodHandlerTest.java index 098948291..313c8cfaa 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/DefaultMethodHandlerTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/DefaultMethodHandlerTest.java @@ -6,6 +6,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class DefaultMethodHandlerTest extends BaseReactorTest { @@ -37,6 +38,7 @@ public void shouldCallNotDefaultMethodOnActualImplementation() throws Throwable = new DefaultMethodHandler(TestInterface.class.getMethod("defaultMethod")); TestInterface mockImplementation = mock(TestInterface.class); + when(mockImplementation.defaultMethod()).thenCallRealMethod(); defaultMethodHandler.bindTo(mockImplementation); diff --git a/feign-reactor-core/src/test/java/reactivefeign/MultiPartTest.java b/feign-reactor-core/src/test/java/reactivefeign/MultiPartTest.java index c4e936de0..a65c66f6a 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/MultiPartTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/MultiPartTest.java @@ -10,9 +10,9 @@ import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; -import org.springframework.boot.web.server.LocalServerPort; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; diff --git a/feign-reactor-core/src/test/java/reactivefeign/RequestInterceptorTest.java b/feign-reactor-core/src/test/java/reactivefeign/RequestInterceptorTest.java index c6944cb6f..2107ad18e 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/RequestInterceptorTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/RequestInterceptorTest.java @@ -101,8 +101,17 @@ public void shouldInterceptRequestAndSetAuthHeaderFromSubscriberContext() throws String authHeader = "Authorization"; IcecreamServiceApi clientWithAuth = target(builder() + .addRequestInterceptor(request -> { + return Mono.deferContextual(ctx -> { + addOrdered(request.headers(), authHeader, ctx.get(authHeader)); + request.headers().remove(UPPER_HEADER_TO_REMOVE.toLowerCase()); + return Mono.just(request); + }); + })); + + IcecreamServiceApi clientWithAuth2 = target(builder() .addRequestInterceptor(request -> Mono - .subscriberContext() + .deferContextual(Mono::just) .map(ctx -> { addOrdered(request.headers(), authHeader, ctx.get(authHeader)); request.headers().remove(UPPER_HEADER_TO_REMOVE.toLowerCase()); @@ -110,7 +119,7 @@ public void shouldInterceptRequestAndSetAuthHeaderFromSubscriberContext() throws }))); Mono firstOrder = clientWithAuth.findFirstOrder() - .subscriberContext(Context.of(authHeader, "Bearer mytoken123")) + .contextWrite(ctx -> ctx.put(authHeader, "Bearer mytoken123")) .subscribeOn(testScheduler()); StepVerifier.create(firstOrder) diff --git a/feign-reactor-core/src/test/java/reactivefeign/RetryingTest.java b/feign-reactor-core/src/test/java/reactivefeign/RetryingTest.java index f807b5968..a2dbd659f 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/RetryingTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/RetryingTest.java @@ -18,6 +18,7 @@ import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.github.tomakehurst.wiremock.junit.WireMockClassRule; import com.github.tomakehurst.wiremock.stubbing.ServeEvent; +import feign.ExceptionPropagationPolicy; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -32,7 +33,10 @@ import java.util.Arrays; import java.util.List; -import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; @@ -183,6 +187,33 @@ public void shouldFailAsNoMoreRetries() { assertThat(getEventsForPath(orderUrl).size()).isEqualTo(maxRetries + 1); } + @Test + public void shouldFailAsNoMoreRetriesWithUnwrap() { + + String orderUrl = "/icecream/orders/1"; + + wireMockRule.stubFor(get(urlEqualTo(orderUrl)) + .withHeader("Accept", equalTo("application/json")) + .willReturn(aResponse().withStatus(503).withHeader(RETRY_AFTER, "1"))); + + int maxRetries = 3; + IcecreamServiceApi client = builder() + .statusHandler(errorDecoder((methodKey, response) -> { + throw new BusinessException(); + })) + .retryWhen(new BasicReactiveRetryPolicy.Builder() + .setMaxRetries(maxRetries) + .setExceptionPropagationPolicy(ExceptionPropagationPolicy.UNWRAP) + .build()) + .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port()); + + StepVerifier.create(client.findOrder(1).subscribeOn(testScheduler())) + .expectErrorMatches(throwable -> throwable instanceof BusinessException) + .verify(); + + assertThat(getEventsForPath(orderUrl).size()).isEqualTo(maxRetries + 1); + } + @Test public void shouldFailAsNoMoreRetriesWithBackoff() { @@ -231,5 +262,8 @@ private List getEventsForPath(String path) { return wireMockRule.getAllServeEvents().stream().filter(serveEvent -> serveEvent.getRequest().getUrl().contains(path)).collect(toList()); } + static class BusinessException extends RuntimeException { + } + } diff --git a/feign-reactor-core/src/test/java/reactivefeign/StatusHandlerTest.java b/feign-reactor-core/src/test/java/reactivefeign/StatusHandlerTest.java index 761918881..8f776d86b 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/StatusHandlerTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/StatusHandlerTest.java @@ -139,7 +139,7 @@ public void shouldThrowOnStatusCode() { return new RetryableException( response.status(), "Should retry on next node", - httpMethod, null, request); + httpMethod, (Long) null, request); }), throwOnStatus( status -> status == SC_UNAUTHORIZED, diff --git a/feign-reactor-core/src/test/java/reactivefeign/allfeatures/AllFeaturesApi.java b/feign-reactor-core/src/test/java/reactivefeign/allfeatures/AllFeaturesApi.java index 2d0d5b3e1..ed857f9a4 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/allfeatures/AllFeaturesApi.java +++ b/feign-reactor-core/src/test/java/reactivefeign/allfeatures/AllFeaturesApi.java @@ -91,8 +91,16 @@ default Mono mirrorDefaultBody() { Mono expandPathParameter(long timestamp); + Mono expandPathParameterInRequestParameter(String companyName); + Mono expandDataTimeParameterWithCustomFormat(LocalDateTime dateTime); + Mono formDataMap(Map form); + + Mono formDataParameters( + String organizationName, + String organizationId); + class TestObject { public String payload; diff --git a/feign-reactor-core/src/test/java/reactivefeign/allfeatures/AllFeaturesController.java b/feign-reactor-core/src/test/java/reactivefeign/allfeatures/AllFeaturesController.java index f85bdecf5..2d944aa25 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/allfeatures/AllFeaturesController.java +++ b/feign-reactor-core/src/test/java/reactivefeign/allfeatures/AllFeaturesController.java @@ -19,6 +19,7 @@ import org.reactivestreams.Publisher; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -178,6 +179,23 @@ public Mono expandRequestParameter(String date){ return Mono.just(new TestObject(date)); } + @Override + public Mono expandPathParameterInRequestParameter(String companyName) { + return Mono.just(new TestObject(companyName)); + } + + @Override + public Mono formDataMap(ServerWebExchange serverWebExchange){ + return serverWebExchange.getFormData() + .map(formData -> new AllFeaturesApi.TestObject(formData.toString())); + } + + @Override + public Mono formDataParameters(ServerWebExchange serverWebExchange){ + return serverWebExchange.getFormData() + .map(formData -> new AllFeaturesApi.TestObject(formData.toString())); + } + @Override public Flux mirrorStreamingBinaryBodyReactive(Publisher body) { return Flux.from(body); diff --git a/feign-reactor-core/src/test/java/reactivefeign/allfeatures/AllFeaturesFeign.java b/feign-reactor-core/src/test/java/reactivefeign/allfeatures/AllFeaturesFeign.java index 0fa7adc5b..f39507d9c 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/allfeatures/AllFeaturesFeign.java +++ b/feign-reactor-core/src/test/java/reactivefeign/allfeatures/AllFeaturesFeign.java @@ -16,7 +16,12 @@ package reactivefeign.allfeatures; -import feign.*; +import feign.CollectionFormat; +import feign.HeaderMap; +import feign.Headers; +import feign.Param; +import feign.QueryMap; +import feign.RequestLine; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -28,7 +33,10 @@ import java.util.List; import java.util.Map; -import static org.springframework.http.MediaType.*; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE; +import static org.springframework.http.MediaType.APPLICATION_STREAM_JSON_VALUE; +import static org.springframework.http.MediaType.TEXT_EVENT_STREAM_VALUE; @Headers({ "Accept: application/json" }) public interface AllFeaturesFeign extends AllFeaturesApi{ @@ -163,11 +171,27 @@ default Mono mirrorDefaultBody() { @RequestLine("GET /expand/{timestamp}") Mono expandPathParameter(@Param(value = "timestamp", expander = TimestampToDateExpander.class) long timestamp); + @Override + @RequestLine("GET /Invoices?filter=Company:{companyName}") + Mono expandPathParameterInRequestParameter(@Param("companyName") String companyName); + @Override @RequestLine("GET /expand") Mono expandDataTimeParameterWithCustomFormat( @Param(value = "dateTime", expander = LocalDateTimeExpander.class) LocalDateTime dateTime); + @Override + @Headers({ "Content-Type: application/x-www-form-urlencoded" }) + @RequestLine("POST /formDataMap") + Mono formDataMap(Map form); + + @Headers({"Content-Type: application/x-www-form-urlencoded", + "Accept: " + APPLICATION_JSON_VALUE}) + @RequestLine("POST " + "/formDataParameters") + Mono formDataParameters( + @Param("key1") String organizationName, + @Param("key2") String organizationId); + class TimestampToDateExpander implements Param.Expander { @Override diff --git a/feign-reactor-core/src/test/java/reactivefeign/allfeatures/AllFeaturesMvc.java b/feign-reactor-core/src/test/java/reactivefeign/allfeatures/AllFeaturesMvc.java index 1e16e2ce1..f91981381 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/allfeatures/AllFeaturesMvc.java +++ b/feign-reactor-core/src/test/java/reactivefeign/allfeatures/AllFeaturesMvc.java @@ -17,8 +17,15 @@ package reactivefeign.allfeatures; import org.reactivestreams.Publisher; +import org.springframework.http.MediaType; import org.springframework.util.MultiValueMap; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -126,4 +133,16 @@ Flux mirrorStringBodyStream( @GetMapping(path = "/expand") Mono expandRequestParameter(@RequestParam("dateTime") String date); + + @GetMapping(path = "/Invoices") + Mono expandPathParameterInRequestParameter(@RequestParam("filter") String filter); + + @PostMapping(path = "/formDataMap", + consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE}) + Mono formDataMap(ServerWebExchange serverWebExchange); + + @PostMapping(path = "/formDataParameters", + consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE}) + Mono formDataParameters(ServerWebExchange serverWebExchange); + } diff --git a/feign-reactor-core/src/test/java/reactivefeign/allfeatures/AllFeaturesTest.java b/feign-reactor-core/src/test/java/reactivefeign/allfeatures/AllFeaturesTest.java index 9e62435dc..e9a18a11b 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/allfeatures/AllFeaturesTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/allfeatures/AllFeaturesTest.java @@ -17,15 +17,13 @@ package reactivefeign.allfeatures; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; -import org.springframework.boot.web.server.LocalServerPort; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @@ -40,7 +38,10 @@ import java.time.LocalDateTime; import java.time.Month; import java.time.format.DateTimeFormatter; -import java.util.*; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; @@ -50,6 +51,8 @@ import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.waitAtMost; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static reactivefeign.ReactivityTest.CALLS_NUMBER; import static reactivefeign.ReactivityTest.timeToCompleteReactively; import static reactivefeign.TestUtils.toLowerCaseKeys; @@ -84,9 +87,6 @@ protected List getActiveProfiles() { @LocalServerPort protected int port; - @Rule - public ExpectedException expectedException = ExpectedException.none(); - abstract protected AllFeaturesApi buildClient(String url); protected AllFeaturesApi buildClient(){ @@ -100,36 +100,41 @@ public void setUp() { @Test public void shouldReturnAllPassedParameters() { - Map paramMap = new HashMap() {{ - put("paramKey1", "paramValue1"); - put("paramKey2", "paramValue2"); - }}; - Map returned = client.mirrorParameters(555,"777", paramMap) - .subscribeOn(testScheduler()).block(); + Map paramMap = Map.of("paramKey1", "paramValue1", "paramKey2", "paramValue2"); - assertThat(returned).containsEntry("paramInPath", "555"); - assertThat(returned).containsEntry("paramInUrl", "777"); - assertThat(returned).containsAllEntriesOf(paramMap); + Mono> result = client.mirrorParameters(555,"777", paramMap) + .subscribeOn(testScheduler()); + + StepVerifier.create(result) + .consumeNextWith(returned -> { + assertThat(returned).containsEntry("paramInPath", "555"); + assertThat(returned).containsEntry("paramInUrl", "777"); + assertThat(returned).containsAllEntriesOf(paramMap); + }); } @Test public void shouldReturnEmptyPassedParameters() { - Map paramMap = new HashMap() { + Map paramMap = new HashMap<>() { { put("paramKey", ""); } }; - Map returned = client.mirrorParameters(555,"", paramMap) - .subscribeOn(testScheduler()).block(); + Mono> returned = client.mirrorParameters(555,"", paramMap) + .subscribeOn(testScheduler()); - assertThat(returned).containsEntry("paramKey", ""); - assertThat(returned).containsEntry("paramInUrl", ""); + StepVerifier.create(returned) + .consumeNextWith(map -> { + assertThat(map).containsEntry("paramKey", ""); + assertThat(map).containsEntry("paramInUrl", ""); + }) + .verifyComplete(); } @Test public void shouldReturnAllPassedParametersNew() { - Map paramMap = new HashMap() { + Map paramMap = new HashMap<>() { { put("paramKey", "paramValue"); } @@ -156,7 +161,7 @@ public void shouldPassEmptyParameterInUrl() { @Test public void shouldNotReturnNullPassedParametersNew() { - Map paramMap = new HashMap() { + Map paramMap = new HashMap<>() { { put("paramKey", "paramValue"); put("paramKeyNull", null); @@ -174,10 +179,12 @@ public void shouldNotReturnNullPassedParametersNew() { public void shouldReturnAllPassedListParametersNew() { List dynamicListParam = asList(1, 2, 3); - List returned = client.mirrorListParametersNew(dynamicListParam) - .subscribeOn(testScheduler()).block(); + Mono> result = client.mirrorListParametersNew(dynamicListParam) + .subscribeOn(testScheduler()); - assertThat(returned).containsAll(dynamicListParam); + StepVerifier.create(result) + .consumeNextWith(returned -> assertThat(returned).containsAll(dynamicListParam)) + .verifyComplete(); } @Test @@ -202,7 +209,7 @@ public void shouldReturnEmptyOnNullPassedListParametersNew() { @Test public void shouldReturnAllPassedMapParametersNew() { - Map> paramMap = new HashMap>() { + Map> paramMap = new HashMap<>() { { put("paramKey", asList("paramValue1", "paramValue2")); } @@ -225,7 +232,7 @@ public void shouldReturnEmptyOnNullPassedMapParametersNew() { @Test public void shouldReturnAllPassedHeaders() { - Map headersMap = new HashMap() { + Map headersMap = new HashMap<>() { { put("headerKey1", "headerValue1"); put("headerKey2", "headerValue2"); @@ -250,15 +257,19 @@ public void shouldReturnAllPassedListHeaders() { @Test public void shouldReturnAllPassedMultiMapHeaders() { - Map> headersMap = new HashMap>() { + Map> headersMap = new HashMap<>() { { - put("headerKey1", asList("headerValue1", "headerValue2")); + put("headerKey1", List.of("headerValue1, headerValue2")); } }; - Map> returned = client.mirrorMultiMapHeaders(headersMap) - .subscribeOn(testScheduler()).block(); + Mono>> result = client.mirrorMultiMapHeaders(headersMap) + .subscribeOn(testScheduler()); - assertThat(toLowerCaseKeys(returned)).containsAllEntriesOf(toLowerCaseKeys(headersMap)); + StepVerifier.create(result) + .consumeNextWith(returned -> { + assertNotNull(returned); + assertThat(toLowerCaseKeys(returned)).containsAllEntriesOf(toLowerCaseKeys(headersMap)); + }); } @Test @@ -279,7 +290,7 @@ public void shouldReturnBody() { @Test public void shouldReturnBodyMap() { - Map bodyMap = new HashMap() { + Map bodyMap = new HashMap<>() { { put("key1", "value1"); put("key2", "value2"); @@ -300,7 +311,7 @@ public void shouldReturnBodyReactive() { @Test public void shouldReturnBodyMapReactive() { - Map bodyMap = new HashMap() { + Map bodyMap = new HashMap<>() { { put("key1", "value1"); put("key2", "value2"); @@ -338,14 +349,17 @@ public void shouldReturnFirstResultBeforeSecondSent() throws InterruptedExceptio countDownLatch.countDown(); }).subscribe(); - countDownLatch.await(); + boolean await = countDownLatch.await(5, TimeUnit.SECONDS); + assertThat(await).isTrue(); } @Test public void shouldReturnEmpty() { - Optional returned = client.empty() - .subscribeOn(testScheduler()).blockOptional(); - assertThat(!returned.isPresent()); + Mono returned = client.empty() + .subscribeOn(testScheduler()); + + StepVerifier.create(returned) + .verifyComplete(); } @Test @@ -355,7 +369,6 @@ public void shouldReturnDefaultBody() { assertThat(returned).isEqualTo("default"); } - @Test public void shouldRunReactively() { @@ -412,7 +425,6 @@ public void shouldMirrorBinaryBody() { @Test public void shouldMirrorStreamingBinaryBodyReactive() throws InterruptedException { - CountDownLatch countDownLatch = new CountDownLatch(2); AtomicInteger sentCount = new AtomicInteger(); @@ -421,7 +433,7 @@ public void shouldMirrorStreamingBinaryBodyReactive() throws InterruptedExceptio CompletableFuture firstReceived = new CompletableFuture<>(); Flux returned = client.mirrorStreamingBinaryBodyReactive( - Flux.just(fromByteArray(new byte[]{1,2,3}), fromByteArray(new byte[]{4,5,6}))) + Flux.just(fromByteArray(new byte[]{1,2,3}), fromByteArray(new byte[]{4,5,6}))) .subscribeOn(testScheduler()) .delayUntil(testObject -> sentCount.get() == 1 ? fromFuture(firstReceived) : empty()) @@ -431,12 +443,13 @@ public void shouldMirrorStreamingBinaryBodyReactive() throws InterruptedExceptio byte[] dataReceived = new byte[received.limit()]; received.get(dataReceived); receivedAll.add(dataReceived); - assertThat(receivedAll.size()).isEqualTo(sentCount.get()); + assertThat(receivedAll).hasSize(sentCount.get()); firstReceived.complete(received); countDownLatch.countDown(); }).subscribe(); - countDownLatch.await(); + boolean await = countDownLatch.await(5, TimeUnit.SECONDS); + assertTrue(await); assertThat(receivedAll).containsExactly(new byte[]{1,2,3}, new byte[]{4,5,6}); } @@ -510,6 +523,37 @@ public void shouldExpandRequestParamWithCustomFormat() { .verifyComplete(); } + @Test + public void shouldExpandPathParamInRequestParameter() { + + String companyName = "XRTGFS"; + StepVerifier.create(client.expandPathParameterInRequestParameter(companyName) + .subscribeOn(testScheduler())) + .expectNextMatches(result -> result.payload.equals("Company:"+companyName)) + .verifyComplete(); + } + + @Test + public void shouldPassUrlEncodedFormMap() { + Map form = new HashMap<>(); + form.put("key1", "value1"); + form.put("key2", "value2"); + + StepVerifier.create(client.formDataMap(form) + .subscribeOn(testScheduler())) + .expectNextMatches(result -> result.payload.equals("{key1=[value1], key2=[value2]}")) + .verifyComplete(); + } + + @Test + public void shouldPassUrlEncodedFormParameters() { + + StepVerifier.create(client.formDataParameters("value1", "value2") + .subscribeOn(testScheduler())) + .expectNextMatches(result -> result.payload.equals("{key1=[value1], key2=[value2]}")) + .verifyComplete(); + } + private static ByteBuffer fromByteArray(byte[] data){ return ByteBuffer.wrap(data); } diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/BasicFeaturesTest.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/BasicFeaturesTest.java index 43133afd1..ea27819fd 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/BasicFeaturesTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/BasicFeaturesTest.java @@ -41,6 +41,6 @@ protected Predicate corruptedJsonError() { //to not detect blocking calls @Override protected Scheduler testScheduler(){ - return Schedulers.elastic(); + return Schedulers.boundedElastic(); } } diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/CompressionTest.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/CompressionTest.java index caea9a6c3..e5fb3d8c8 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/CompressionTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/CompressionTest.java @@ -34,6 +34,6 @@ protected ReactiveFeignBuilder builder(boolean tryUseCompres //to not detect blocking calls @Override protected Scheduler testScheduler(){ - return Schedulers.elastic(); + return Schedulers.boundedElastic(); } } diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/ConnectionTimeoutTest.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/ConnectionTimeoutTest.java index b3192cc06..d17b8fda0 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/ConnectionTimeoutTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/ConnectionTimeoutTest.java @@ -34,6 +34,6 @@ protected ReactiveFeignBuilder builder(long connectTimeoutIn //to not detect blocking calls @Override protected Scheduler testScheduler(){ - return Schedulers.elastic(); + return Schedulers.boundedElastic(); } } diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/DefaultMethodTest.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/DefaultMethodTest.java index 1fc32f236..1ac269c98 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/DefaultMethodTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/DefaultMethodTest.java @@ -46,6 +46,6 @@ protected ReactiveFeignBuilder builder(long connectTimeoutIn //to not detect blocking calls @Override protected Scheduler testScheduler(){ - return Schedulers.elastic(); + return Schedulers.boundedElastic(); } } diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/ErrorMapperTest.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/ErrorMapperTest.java index ba670d778..313055729 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/ErrorMapperTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/ErrorMapperTest.java @@ -32,6 +32,6 @@ protected ReactiveFeign.Builder builder() { //to not detect blocking calls @Override protected Scheduler testScheduler(){ - return Schedulers.elastic(); + return Schedulers.boundedElastic(); } } diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/FallbackTest.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/FallbackTest.java index 3ff592372..cbefbde6f 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/FallbackTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/FallbackTest.java @@ -31,6 +31,6 @@ protected ReactiveFeign.Builder builder() { //to not detect blocking calls @Override protected Scheduler testScheduler(){ - return Schedulers.elastic(); + return Schedulers.boundedElastic(); } } diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/LoggerTest.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/LoggerTest.java index 64285a97c..48e4b5a87 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/LoggerTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/LoggerTest.java @@ -53,6 +53,6 @@ interface IcecreamServiceApiRestTemplate extends IcecreamServiceApi{} //to not detect blocking calls @Override protected Scheduler testScheduler(){ - return Schedulers.elastic(); + return Schedulers.boundedElastic(); } } diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/MetricsTest.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/MetricsTest.java index 842714445..48a7282ab 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/MetricsTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/MetricsTest.java @@ -40,7 +40,7 @@ protected ReactiveFeignBuilder builder(long readTimeoutInMil //to not detect blocking calls @Override protected Scheduler testScheduler(){ - return Schedulers.elastic(); + return Schedulers.boundedElastic(); } } diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/NotFoundTest.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/NotFoundTest.java index aaab1e3ec..42cafede2 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/NotFoundTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/NotFoundTest.java @@ -32,6 +32,6 @@ protected ReactiveFeign.Builder builder() { //to not detect blocking calls @Override protected Scheduler testScheduler(){ - return Schedulers.elastic(); + return Schedulers.boundedElastic(); } } diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/ObjectMapperTest.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/ObjectMapperTest.java index e2880644c..b41ca38c4 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/ObjectMapperTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/ObjectMapperTest.java @@ -32,6 +32,6 @@ protected ReactiveFeign.Builder builder() { //to not detect blocking calls @Override protected Scheduler testScheduler(){ - return Schedulers.elastic(); + return Schedulers.boundedElastic(); } } diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/OptionsTest.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/OptionsTest.java index d03533230..085bc6aa4 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/OptionsTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/OptionsTest.java @@ -47,7 +47,7 @@ protected ReactiveFeignBuilder builder(ReactiveOptions.Proxy //to not detect blocking calls @Override protected Scheduler testScheduler(){ - return Schedulers.elastic(); + return Schedulers.boundedElastic(); } } diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/ReactiveOptionsTest.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/ReactiveOptionsTest.java index e284a3a6e..d26aeb4fd 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/ReactiveOptionsTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/ReactiveOptionsTest.java @@ -8,11 +8,6 @@ public class ReactiveOptionsTest { - @Test - public void shouldBeEmptyByDefault(){ - assertThat(new RestTemplateReactiveOptions.Builder().build().isEmpty()).isTrue(); - } - @Test public void shouldNotUseHttp2ByDefault(){ assertThat(useHttp2(new RestTemplateReactiveOptions.Builder().build())).isFalse(); diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/RequestInterceptorTest.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/RequestInterceptorTest.java index 32509983e..d89b6ea96 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/RequestInterceptorTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/RequestInterceptorTest.java @@ -32,6 +32,6 @@ protected ReactiveFeign.Builder builder() { //to not detect blocking calls @Override protected Scheduler testScheduler(){ - return Schedulers.elastic(); + return Schedulers.boundedElastic(); } } diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/ResponseMapperTest.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/ResponseMapperTest.java index 7dccb51b8..acb41e446 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/ResponseMapperTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/ResponseMapperTest.java @@ -32,6 +32,6 @@ protected ReactiveFeign.Builder builder() { //to not detect blocking calls @Override protected Scheduler testScheduler(){ - return Schedulers.elastic(); + return Schedulers.boundedElastic(); } } diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/RetryingTest.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/RetryingTest.java index 935c1d238..e7f62fb47 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/RetryingTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/RetryingTest.java @@ -32,6 +32,6 @@ protected ReactiveFeign.Builder builder() { //to not detect blocking calls @Override protected Scheduler testScheduler(){ - return Schedulers.elastic(); + return Schedulers.boundedElastic(); } } diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/SmokeTest.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/SmokeTest.java index c5c38803a..fbc185f8c 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/SmokeTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/SmokeTest.java @@ -32,6 +32,6 @@ protected ReactiveFeign.Builder builder() { //to not detect blocking calls @Override protected Scheduler testScheduler(){ - return Schedulers.elastic(); + return Schedulers.boundedElastic(); } } diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/StatusHandlerTest.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/StatusHandlerTest.java index c75dc12f9..59d5ec1ed 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/StatusHandlerTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/StatusHandlerTest.java @@ -32,6 +32,6 @@ protected ReactiveFeign.Builder builder() { //to not detect blocking calls @Override protected Scheduler testScheduler(){ - return Schedulers.elastic(); + return Schedulers.boundedElastic(); } } diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/allfeatures/AllFeaturesTest.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/allfeatures/AllFeaturesTest.java index 494991c76..bbbbefa64 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/allfeatures/AllFeaturesTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/allfeatures/AllFeaturesTest.java @@ -41,7 +41,7 @@ public class AllFeaturesTest extends AllFeaturesFeignTest { //to not detect blocking calls @Override protected Scheduler testScheduler(){ - return Schedulers.elastic(); + return Schedulers.boundedElastic(); } @Override diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/client/RestTemplateFakeReactiveFeign.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/client/RestTemplateFakeReactiveFeign.java index 0cc8fdb88..ccd202b7e 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/client/RestTemplateFakeReactiveFeign.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/client/RestTemplateFakeReactiveFeign.java @@ -14,6 +14,13 @@ package reactivefeign.resttemplate.client; import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.concurrent.TimeUnit; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.core5.http.io.SocketConfig; +import org.apache.hc.core5.util.Timeout; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; @@ -48,6 +55,7 @@ public static ReactiveFeign.Builder builder() { ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(mapper); restTemplate.getMessageConverters().add(0, converter); + restTemplate.getMessageConverters().add(new SerializedFormMessageConverter()); } @Override @@ -60,6 +68,7 @@ protected ReactiveHttpClientFactory clientFactory() { public ReactiveFeignBuilder objectMapper(ObjectMapper objectMapper) { MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(objectMapper); restTemplate.getMessageConverters().set(0, converter); + restTemplate.getMessageConverters().add(new SerializedFormMessageConverter()); return this; } @@ -103,21 +112,28 @@ protected void prepareConnection(HttpURLConnection connection, String httpMethod else { - HttpComponentsClientHttpRequestFactory requestFactory = - new HttpComponentsClientHttpRequestFactory(); - if (options.getConnectTimeoutMillis() != null) { - requestFactory.setConnectTimeout(options.getConnectTimeoutMillis().intValue()); - } + final HttpClientBuilder clientBuilder = HttpClients.custom(); + final PoolingHttpClientConnectionManagerBuilder httpClientConnectionManagerBuilder = PoolingHttpClientConnectionManagerBuilder.create(); - RestTemplateReactiveOptions restTemplateOptions = (RestTemplateReactiveOptions)options; + RestTemplateReactiveOptions restTemplateOptions = (RestTemplateReactiveOptions) options; if (restTemplateOptions.getReadTimeoutMillis() != null) { - requestFactory.setReadTimeout(restTemplateOptions.getReadTimeoutMillis().intValue()); + SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(restTemplateOptions.getReadTimeoutMillis().intValue(), TimeUnit.MILLISECONDS).build(); + httpClientConnectionManagerBuilder.setDefaultSocketConfig(socketConfig); } + if (options.getConnectTimeoutMillis() != null) { + clientBuilder.setDefaultRequestConfig( + RequestConfig.custom().setConnectTimeout(Timeout.ofMilliseconds(options.getConnectTimeoutMillis().intValue())).build() + ); + } + + clientBuilder.setConnectionManager(httpClientConnectionManagerBuilder.build()); + + HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(clientBuilder.build()); + this.restTemplate = new RestTemplate(requestFactory); this.acceptGzip = ofNullable(options.isTryUseCompression()).orElse(false); return this; - } } }; diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/client/RestTemplateFakeReactiveHttpClient.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/client/RestTemplateFakeReactiveHttpClient.java index 56e68913b..bb0ac1ef3 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/client/RestTemplateFakeReactiveHttpClient.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/client/RestTemplateFakeReactiveHttpClient.java @@ -24,7 +24,12 @@ import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.ResourceAccessException; import org.springframework.web.client.RestTemplate; -import reactivefeign.client.*; +import reactivefeign.client.ReactiveFeignException; +import reactivefeign.client.ReactiveHttpClient; +import reactivefeign.client.ReactiveHttpRequest; +import reactivefeign.client.ReactiveHttpResponse; +import reactivefeign.client.ReadTimeoutException; +import reactivefeign.utils.SerializedFormData; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -59,7 +64,10 @@ public class RestTemplateFakeReactiveHttpClient implements ReactiveHttpClient { public Mono executeRequest(ReactiveHttpRequest request) { Mono bodyMono; - if (request.body() instanceof Mono) { + if(request.body() instanceof SerializedFormData){ + bodyMono = Mono.just(((SerializedFormData) request.body()).getFormData()); + } + else if (request.body() instanceof Mono) { bodyMono = ((Mono) request.body()); } else if (request.body() instanceof Flux) { bodyMono = ((Flux) request.body()).collectList(); @@ -69,18 +77,18 @@ public Mono executeRequest(ReactiveHttpRequest request) { Mono bodyMonoFinal = bodyMono.switchIfEmpty(Mono.just(new byte[0])); return Mono.defer(() -> bodyMonoFinal).flatMap(body -> { - MultiValueMap headers = new LinkedMultiValueMap<>(request.headers()); - if (acceptGzip) { - headers.add("Accept-Encoding", "gzip"); - } - - return Mono.fromCallable(() -> { - ResponseEntity response = restTemplate.exchange( - request.uri(), HttpMethod.valueOf(request.method()), - new HttpEntity<>(body, headers), responseType()); - return new FakeReactiveHttpResponse(request, response, returnPublisherType); - }); - }) + MultiValueMap headers = new LinkedMultiValueMap<>(request.headers()); + if (acceptGzip) { + headers.add("Accept-Encoding", "gzip"); + } + + return Mono.fromCallable(() -> { + ResponseEntity response = restTemplate.exchange( + request.uri(), HttpMethod.valueOf(request.method()), + new HttpEntity<>(body, headers), responseType()); + return new FakeReactiveHttpResponse(request, response, returnPublisherType); + }); + }) .onErrorResume(HttpStatusCodeException.class, ex -> Mono.just(new ErrorReactiveHttpResponse(request, ex))) .onErrorMap(ex -> { diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/client/RestTemplateReactiveOptions.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/client/RestTemplateReactiveOptions.java index 9502a890d..2a1fd571e 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/client/RestTemplateReactiveOptions.java +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/client/RestTemplateReactiveOptions.java @@ -33,11 +33,7 @@ public Long getReadTimeoutMillis() { return readTimeoutMillis; } - public boolean isEmpty() { - return super.isEmpty() && readTimeoutMillis == null; - } - - public static class Builder extends ReactiveOptions.Builder{ + public static class Builder extends ReactiveOptions.Builder{ private Long readTimeoutMillis; public Builder() {} diff --git a/feign-reactor-core/src/test/java/reactivefeign/resttemplate/client/SerializedFormMessageConverter.java b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/client/SerializedFormMessageConverter.java new file mode 100644 index 000000000..87caa7877 --- /dev/null +++ b/feign-reactor-core/src/test/java/reactivefeign/resttemplate/client/SerializedFormMessageConverter.java @@ -0,0 +1,46 @@ +package reactivefeign.resttemplate.client; + +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.http.converter.HttpMessageNotWritableException; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; + +import static java.util.Collections.singletonList; + +public class SerializedFormMessageConverter implements HttpMessageConverter { + + private static final List MEDIA_TYPES = singletonList(MediaType.APPLICATION_FORM_URLENCODED); + + @Override + public boolean canRead(Class clazz, MediaType mediaType) { + return false; + } + + @Override + public boolean canWrite(Class clazz, MediaType mediaType) { + return ByteBuffer.class.isAssignableFrom(clazz); + } + + @Override + public List getSupportedMediaTypes() { + return MEDIA_TYPES; + } + + @Override + public ByteBuffer read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { + throw new UnsupportedOperationException(); + } + + @Override + public void write(ByteBuffer formData, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { + byte[] b = new byte[formData.remaining()]; + formData.get(b, 0, b.length); + outputMessage.getBody().write(b); + } +} diff --git a/feign-reactor-core/src/test/java/reactivefeign/retry/BasicReactiveRetryPolicyTest.java b/feign-reactor-core/src/test/java/reactivefeign/retry/BasicReactiveRetryPolicyTest.java index 9c621f210..75f81f8a9 100644 --- a/feign-reactor-core/src/test/java/reactivefeign/retry/BasicReactiveRetryPolicyTest.java +++ b/feign-reactor-core/src/test/java/reactivefeign/retry/BasicReactiveRetryPolicyTest.java @@ -68,7 +68,7 @@ public void shouldTakeIntoAccountRetryAfter(){ assertThat(retryDelay).isEqualTo(delay); retryDelay = retryPolicy.retryDelay(new RetryableException(-1, "error msg", Request.HttpMethod.GET, - null, request), 1); + (Long) null, request), 1); assertThat(retryDelay).isEqualTo(backoff); } diff --git a/feign-reactor-core/src/test/java/reactivefeign/utils/ContentTypeTest.java b/feign-reactor-core/src/test/java/reactivefeign/utils/ContentTypeTest.java new file mode 100644 index 000000000..fcbde2409 --- /dev/null +++ b/feign-reactor-core/src/test/java/reactivefeign/utils/ContentTypeTest.java @@ -0,0 +1,16 @@ +package reactivefeign.utils; + +import org.junit.Test; + +public class ContentTypeTest { + + @Test + public void shouldParseContentTypeWoCharset() { + //TODO + } + + @Test + public void shouldParseContentTypeWithCharset() { + //TODO + } +} diff --git a/feign-reactor-core/src/test/java/reactivefeign/utils/FormUtilsTest.java b/feign-reactor-core/src/test/java/reactivefeign/utils/FormUtilsTest.java new file mode 100644 index 000000000..ba438a29b --- /dev/null +++ b/feign-reactor-core/src/test/java/reactivefeign/utils/FormUtilsTest.java @@ -0,0 +1,6 @@ +package reactivefeign.utils; + +public class FormUtilsTest { + + //TODO +} diff --git a/feign-reactor-java11/pom.xml b/feign-reactor-java11/pom.xml index c580d0ad7..803115d6d 100644 --- a/feign-reactor-java11/pom.xml +++ b/feign-reactor-java11/pom.xml @@ -6,15 +6,11 @@ com.playtika.reactivefeign feign-reactor-parent ../feign-reactor-parent - 3.2.6 + 4.0.3 feign-reactor-java11 - - 11 - - com.playtika.reactivefeign @@ -63,8 +59,8 @@ - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test @@ -75,26 +71,36 @@ - org.eclipse.jetty - jetty-servlet + org.eclipse.jetty.ee10 + jetty-ee10-servlet test org.eclipse.jetty.http2 - http2-server + jetty-http2-server test - org.eclipse.jetty - jetty-webapp + org.eclipse.jetty.ee10 + jetty-ee10-webapp test org.apache.logging.log4j - log4j-slf4j-impl + log4j-api + test + + + org.apache.logging.log4j + log4j-core + test + + + org.apache.logging.log4j + log4j-slf4j2-impl test @@ -150,12 +156,6 @@ - - org.eclipse.jetty.http2 - http2-server - test - - org.springframework.boot spring-boot-starter-undertow @@ -181,4 +181,4 @@ - \ No newline at end of file + diff --git a/feign-reactor-java11/src/main/java/reactivefeign/java11/Java11ReactiveOptions.java b/feign-reactor-java11/src/main/java/reactivefeign/java11/Java11ReactiveOptions.java index faafd467c..66a7e7634 100644 --- a/feign-reactor-java11/src/main/java/reactivefeign/java11/Java11ReactiveOptions.java +++ b/feign-reactor-java11/src/main/java/reactivefeign/java11/Java11ReactiveOptions.java @@ -33,11 +33,7 @@ public Long getRequestTimeoutMillis() { return requestTimeoutMillis; } - public boolean isEmpty() { - return super.isEmpty() && requestTimeoutMillis == null; - } - - public static class Builder extends ReactiveOptions.Builder{ + public static class Builder extends ReactiveOptions.Builder{ private Long requestTimeoutMillis; public Builder() {} diff --git a/feign-reactor-java11/src/main/java/reactivefeign/java11/client/Java11ReactiveHttpClient.java b/feign-reactor-java11/src/main/java/reactivefeign/java11/client/Java11ReactiveHttpClient.java index 56a149267..656bdff35 100644 --- a/feign-reactor-java11/src/main/java/reactivefeign/java11/client/Java11ReactiveHttpClient.java +++ b/feign-reactor-java11/src/main/java/reactivefeign/java11/client/Java11ReactiveHttpClient.java @@ -23,7 +23,12 @@ import com.fasterxml.jackson.databind.ObjectWriter; import feign.MethodMetadata; import org.reactivestreams.Publisher; -import reactivefeign.client.*; +import reactivefeign.client.ReactiveFeignException; +import reactivefeign.client.ReactiveHttpClient; +import reactivefeign.client.ReactiveHttpRequest; +import reactivefeign.client.ReactiveHttpResponse; +import reactivefeign.client.ReadTimeoutException; +import reactivefeign.utils.SerializedFormData; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -40,8 +45,22 @@ import static java.net.http.HttpResponse.BodyHandlers.fromSubscriber; import static java.nio.charset.StandardCharsets.UTF_8; -import static reactivefeign.utils.FeignUtils.*; -import static reactivefeign.utils.HttpUtils.*; +import static reactivefeign.utils.CollectionUtils.isEmpty; +import static reactivefeign.utils.FeignUtils.getBodyActualType; +import static reactivefeign.utils.FeignUtils.returnActualType; +import static reactivefeign.utils.FeignUtils.returnPublisherType; +import static reactivefeign.utils.HttpUtils.ACCEPT_ENCODING_HEADER; +import static reactivefeign.utils.HttpUtils.ACCEPT_HEADER; +import static reactivefeign.utils.HttpUtils.APPLICATION_JSON; +import static reactivefeign.utils.HttpUtils.APPLICATION_JSON_UTF_8; +import static reactivefeign.utils.HttpUtils.APPLICATION_OCTET_STREAM; +import static reactivefeign.utils.HttpUtils.APPLICATION_STREAM_JSON; +import static reactivefeign.utils.HttpUtils.APPLICATION_STREAM_JSON_UTF_8; +import static reactivefeign.utils.HttpUtils.CONTENT_TYPE_HEADER; +import static reactivefeign.utils.HttpUtils.GZIP; +import static reactivefeign.utils.HttpUtils.NEWLINE_SEPARATOR; +import static reactivefeign.utils.HttpUtils.TEXT; +import static reactivefeign.utils.HttpUtils.TEXT_UTF_8; import static reactor.adapter.JdkFlowAdapter.publisherToFlowPublisher; /** @@ -50,7 +69,7 @@ */ public class Java11ReactiveHttpClient implements ReactiveHttpClient { - private final HttpClient httpClient; + private final HttpClient httpClient; private final Class bodyActualClass; private final Class returnPublisherClass; private final Class returnActualClass; @@ -112,25 +131,25 @@ public Mono executeRequest(ReactiveHttpRequest request) { requestBuilder = requestBuilder.setHeader(ACCEPT_ENCODING_HEADER, GZIP); } - Java11ReactiveHttpResponse.ReactiveBodySubscriber bodySubscriber = new Java11ReactiveHttpResponse.ReactiveBodySubscriber(); + Java11ReactiveHttpResponse.ReactiveBodySubscriber bodySubscriber = new Java11ReactiveHttpResponse.ReactiveBodySubscriber(); - CompletableFuture> response = httpClient.sendAsync( - requestBuilder.build(), fromSubscriber(bodySubscriber)); + CompletableFuture> response = httpClient.sendAsync( + requestBuilder.build(), fromSubscriber(bodySubscriber)); - return Mono.fromFuture(response) - .map(resp -> { - if(!resp.version().equals(httpClient.version())){ - throw new IllegalArgumentException("Incorrect response version:"+resp.version()); + return Mono.fromFuture(response) + .map(resp -> { + if(!resp.version().equals(httpClient.version())){ + throw new IllegalArgumentException("Incorrect response version:"+resp.version()); } - return new Java11ReactiveHttpResponse(request, resp, bodySubscriber.content(), + return new Java11ReactiveHttpResponse(request, resp, bodySubscriber.content(), returnPublisherClass, returnActualClass, jsonFactory, responseReader); }) - .onErrorMap(ex -> { - if(ex instanceof java.net.http.HttpTimeoutException){ - return new ReadTimeoutException(ex.getCause(), request); + .onErrorMap(ex -> { + if(ex instanceof java.net.http.HttpTimeoutException){ + return new ReadTimeoutException(ex, request); } else { - return new ReactiveFeignException(ex, request); + return new ReactiveFeignException(ex, request); } }); } @@ -138,84 +157,89 @@ public Mono executeRequest(ReactiveHttpRequest request) { protected void setUpHeaders(ReactiveHttpRequest request, HttpRequest.Builder requestBuilder) { request.headers().forEach((key, values) -> values.forEach(value -> requestBuilder.header(key, value))); - String contentTypeHeader = getContentTypeHeader(request); - if(contentTypeHeader != null) { - requestBuilder.header(CONTENT_TYPE_HEADER, contentTypeHeader); + if(isEmpty(request.headers().get(CONTENT_TYPE_HEADER))){ + String contentTypeHeader = getContentTypeHeader(request); + if(contentTypeHeader != null) { + requestBuilder.header(CONTENT_TYPE_HEADER, contentTypeHeader); + } + } + + if(isEmpty(request.headers().get(ACCEPT_HEADER))){ + requestBuilder.header(ACCEPT_HEADER, getAcceptHeader()); } - requestBuilder.header(ACCEPT_HEADER, getAcceptHeader()); + } - private String getAcceptHeader() { - String acceptHeader; - if(CharSequence.class.isAssignableFrom(returnActualClass) && returnPublisherClass == Mono.class){ - acceptHeader = TEXT; - } - else if(returnActualClass == ByteBuffer.class || returnActualClass == byte[].class){ - acceptHeader = APPLICATION_OCTET_STREAM; - } - else if(returnPublisherClass == Mono.class){ - acceptHeader = APPLICATION_JSON; - } - else { - acceptHeader = APPLICATION_STREAM_JSON; - } - return acceptHeader; - } - - private String getContentTypeHeader(ReactiveHttpRequest request) { - String contentType; - if(bodyActualClass == null){ - return null; + private String getAcceptHeader() { + String acceptHeader; + if(CharSequence.class.isAssignableFrom(returnActualClass) && returnPublisherClass == Mono.class){ + acceptHeader = TEXT; + } + else if(returnActualClass == ByteBuffer.class || returnActualClass == byte[].class){ + acceptHeader = APPLICATION_OCTET_STREAM; + } + else if(returnPublisherClass == Mono.class){ + acceptHeader = APPLICATION_JSON; } + else { + acceptHeader = APPLICATION_STREAM_JSON; + } + return acceptHeader; + } - if(request.body() instanceof Mono){ - if(bodyActualClass == ByteBuffer.class){ - contentType = APPLICATION_OCTET_STREAM; - } - else if (CharSequence.class.isAssignableFrom(bodyActualClass)){ - contentType = TEXT_UTF_8; - } - else { - contentType = APPLICATION_JSON_UTF_8; - } - } else { - if(bodyActualClass == ByteBuffer.class){ - contentType = APPLICATION_OCTET_STREAM; - } - else { - contentType = APPLICATION_STREAM_JSON_UTF_8; - } - } - return contentType; - } - - protected HttpRequest.BodyPublisher provideBody(ReactiveHttpRequest request) { + private String getContentTypeHeader(ReactiveHttpRequest request) { + String contentType; if(bodyActualClass == null){ - return HttpRequest.BodyPublishers.noBody(); + return null; } - Publisher bodyPublisher; if(request.body() instanceof Mono){ if(bodyActualClass == ByteBuffer.class){ - bodyPublisher = (Mono)request.body(); + contentType = APPLICATION_OCTET_STREAM; } else if (CharSequence.class.isAssignableFrom(bodyActualClass)){ - bodyPublisher = Flux.from(request.body()).map(this::toCharSequenceChunk); + contentType = TEXT_UTF_8; } else { - bodyPublisher = Flux.from(request.body()).map(data -> toJsonChunk(data, false)); + contentType = APPLICATION_JSON_UTF_8; } - } else { if(bodyActualClass == ByteBuffer.class){ - bodyPublisher = (Publisher)request.body(); + contentType = APPLICATION_OCTET_STREAM; } else { - bodyPublisher = Flux.from(request.body()).map(data -> toJsonChunk(data, true)); + contentType = APPLICATION_STREAM_JSON_UTF_8; } } + return contentType; + } + + protected HttpRequest.BodyPublisher provideBody(ReactiveHttpRequest request) { + if(bodyActualClass != null || request.body() instanceof SerializedFormData) { + Publisher bodyPublisher; + if (request.body() instanceof SerializedFormData) { + bodyPublisher = Mono.just(((SerializedFormData) request.body()).getFormData()); + } else if (request.body() instanceof Mono) { + if (bodyActualClass == ByteBuffer.class) { + bodyPublisher = (Mono) request.body(); + } else if (CharSequence.class.isAssignableFrom(bodyActualClass)) { + bodyPublisher = Flux.from(request.body()).map(this::toCharSequenceChunk); + } else { + bodyPublisher = Flux.from(request.body()).map(data -> toJsonChunk(data, false)); + } - return HttpRequest.BodyPublishers.fromPublisher(publisherToFlowPublisher(bodyPublisher)); + } else { + if (bodyActualClass == ByteBuffer.class) { + bodyPublisher = (Publisher) request.body(); + } else { + bodyPublisher = Flux.from(request.body()).map(data -> toJsonChunk(data, true)); + } + } + + return HttpRequest.BodyPublishers.fromPublisher(publisherToFlowPublisher(bodyPublisher)); + } else { + return HttpRequest.BodyPublishers.noBody(); + } } protected ByteBuffer toCharSequenceChunk(Object data){ diff --git a/feign-reactor-jetty/pom.xml b/feign-reactor-jetty/pom.xml index 92e4a1894..17f461103 100644 --- a/feign-reactor-jetty/pom.xml +++ b/feign-reactor-jetty/pom.xml @@ -6,7 +6,7 @@ com.playtika.reactivefeign feign-reactor-parent ../feign-reactor-parent - 3.2.6 + 4.0.3 feign-reactor-jetty @@ -17,15 +17,20 @@ feign-reactor-core + + org.eclipse.jetty + jetty-client + + org.eclipse.jetty.http2 - http2-client + jetty-http2-client true org.eclipse.jetty.http2 - http2-http-client-transport + jetty-http2-client-transport true @@ -72,8 +77,8 @@ - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test @@ -84,26 +89,36 @@ - org.eclipse.jetty - jetty-servlet + org.eclipse.jetty.ee10 + jetty-ee10-servlets test org.eclipse.jetty.http2 - http2-server + jetty-http2-server test - org.eclipse.jetty - jetty-webapp + org.eclipse.jetty.ee10 + jetty-ee10-webapp test org.apache.logging.log4j - log4j-slf4j-impl + log4j-api + test + + + org.apache.logging.log4j + log4j-core + test + + + org.apache.logging.log4j + log4j-slf4j2-impl test diff --git a/feign-reactor-jetty/src/main/java/reactivefeign/jetty/JettyReactiveFeign.java b/feign-reactor-jetty/src/main/java/reactivefeign/jetty/JettyReactiveFeign.java index 6fb234941..d3d3c327e 100644 --- a/feign-reactor-jetty/src/main/java/reactivefeign/jetty/JettyReactiveFeign.java +++ b/feign-reactor-jetty/src/main/java/reactivefeign/jetty/JettyReactiveFeign.java @@ -18,7 +18,7 @@ import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpProxy; import org.eclipse.jetty.http2.client.HTTP2Client; -import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; +import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2; import reactivefeign.ReactiveFeign; import reactivefeign.ReactiveFeignBuilder; import reactivefeign.ReactiveOptions; @@ -113,8 +113,8 @@ protected ReactiveHttpClientFactory clientFactory() { if(options != null && this.options.getProxySettings() != null){ ReactiveOptions.ProxySettings proxySettings = this.options.getProxySettings(); - httpClient.getProxyConfiguration().getProxies() - .add(new HttpProxy(proxySettings.getHost(), proxySettings.getPort())); + httpClient.getProxyConfiguration() + .addProxy(new HttpProxy(proxySettings.getHost(), proxySettings.getPort())); } return methodMetadata -> { diff --git a/feign-reactor-jetty/src/main/java/reactivefeign/jetty/JettyReactiveOptions.java b/feign-reactor-jetty/src/main/java/reactivefeign/jetty/JettyReactiveOptions.java index 619c5dfea..0b355dc04 100644 --- a/feign-reactor-jetty/src/main/java/reactivefeign/jetty/JettyReactiveOptions.java +++ b/feign-reactor-jetty/src/main/java/reactivefeign/jetty/JettyReactiveOptions.java @@ -33,11 +33,7 @@ public Long getRequestTimeoutMillis() { return requestTimeoutMillis; } - public boolean isEmpty() { - return super.isEmpty() && requestTimeoutMillis == null; - } - - public static class Builder extends ReactiveOptions.Builder{ + public static class Builder extends ReactiveOptions.Builder{ private Long requestTimeoutMillis; public Builder() {} diff --git a/feign-reactor-jetty/src/main/java/reactivefeign/jetty/client/JettyReactiveHttpClient.java b/feign-reactor-jetty/src/main/java/reactivefeign/jetty/client/JettyReactiveHttpClient.java index 29e138476..9a7bb22e5 100644 --- a/feign-reactor-jetty/src/main/java/reactivefeign/jetty/client/JettyReactiveHttpClient.java +++ b/feign-reactor-jetty/src/main/java/reactivefeign/jetty/client/JettyReactiveHttpClient.java @@ -23,15 +23,21 @@ import com.fasterxml.jackson.databind.ObjectWriter; import feign.MethodMetadata; import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.Request; import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.reactive.client.ContentChunk; +import org.eclipse.jetty.io.Content; import org.eclipse.jetty.reactive.client.ReactiveRequest; import org.reactivestreams.Publisher; -import reactivefeign.client.*; +import reactivefeign.client.ReactiveFeignException; +import reactivefeign.client.ReactiveHttpClient; +import reactivefeign.client.ReactiveHttpRequest; +import reactivefeign.client.ReactiveHttpResponse; +import reactivefeign.client.ReadTimeoutException; +import reactivefeign.utils.SerializedFormData; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.io.IOException; import java.io.UncheckedIOException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; @@ -43,184 +49,197 @@ import static java.util.Collections.singletonList; import static org.eclipse.jetty.http.HttpHeader.ACCEPT; import static org.eclipse.jetty.http.HttpHeader.ACCEPT_ENCODING; -import static reactivefeign.jetty.utils.ProxyPostProcessor.postProcess; -import static reactivefeign.utils.FeignUtils.*; -import static reactivefeign.utils.HttpUtils.*; +import static reactivefeign.utils.FeignUtils.getBodyActualType; +import static reactivefeign.utils.FeignUtils.returnActualType; +import static reactivefeign.utils.FeignUtils.returnPublisherType; +import static reactivefeign.utils.HttpUtils.APPLICATION_JSON; +import static reactivefeign.utils.HttpUtils.APPLICATION_JSON_UTF_8; +import static reactivefeign.utils.HttpUtils.APPLICATION_OCTET_STREAM; +import static reactivefeign.utils.HttpUtils.APPLICATION_STREAM_JSON; +import static reactivefeign.utils.HttpUtils.APPLICATION_STREAM_JSON_UTF_8; +import static reactivefeign.utils.HttpUtils.FORM_URL_ENCODED; +import static reactivefeign.utils.HttpUtils.GZIP; +import static reactivefeign.utils.HttpUtils.NEWLINE_SEPARATOR; +import static reactivefeign.utils.HttpUtils.TEXT; +import static reactivefeign.utils.HttpUtils.TEXT_UTF_8; /** * Uses reactive Jetty client to execute http requests + * * @author Sergii Karpenko */ public class JettyReactiveHttpClient implements ReactiveHttpClient { - private final HttpClient httpClient; - private final Class bodyActualClass; - private final Class returnPublisherClass; - private final Class returnActualClass; - private final JsonFactory jsonFactory; - private final ObjectWriter bodyWriter; - private final ObjectReader responseReader; - private long requestTimeout = -1; - private boolean tryUseCompression; - - public static JettyReactiveHttpClient jettyClient( - MethodMetadata methodMetadata, - HttpClient httpClient, - JsonFactory jsonFactory, ObjectMapper objectMapper) { - - Class returnPublisherType = returnPublisherType(methodMetadata); - Type returnActualType = returnActualType(methodMetadata); - Type bodyActualType = getBodyActualType(methodMetadata.bodyType()); - ObjectWriter bodyWriter = bodyActualType != null - ? objectMapper.writerFor(objectMapper.constructType(bodyActualType)) : null; - ObjectReader responseReader = objectMapper.readerFor(objectMapper.constructType(returnActualType)); - - return new JettyReactiveHttpClient(httpClient, - getClass(bodyActualType), returnPublisherType, getClass(returnActualType), - jsonFactory, bodyWriter, responseReader); - } - - public JettyReactiveHttpClient(HttpClient httpClient, - Class bodyActualClass, Class returnPublisherClass, Class returnActualClass, - JsonFactory jsonFactory, ObjectWriter bodyWriter, ObjectReader responseReader) { - this.httpClient = httpClient; - this.bodyActualClass = bodyActualClass; - this.returnPublisherClass = returnPublisherClass; - this.returnActualClass = returnActualClass; - this.jsonFactory = jsonFactory; - this.bodyWriter = bodyWriter; - this.responseReader = responseReader; - } - - public JettyReactiveHttpClient setRequestTimeout(long timeoutInMillis){ - this.requestTimeout = timeoutInMillis; - return this; - } - - public JettyReactiveHttpClient setTryUseCompression(boolean tryUseCompression){ - this.tryUseCompression = tryUseCompression; - return this; - } - - @Override - public Mono executeRequest(ReactiveHttpRequest request) { - Request jettyRequest = httpClient.newRequest(request.uri()).method(request.method()); - -// jettyRequest.headers(httpFields -> setUpHeaders(request, httpFields)); - setUpHeaders(request, jettyRequest.getHeaders()); - - if(requestTimeout > 0){ - jettyRequest.timeout(requestTimeout, TimeUnit.MILLISECONDS); - } - - ReactiveRequest.Builder requestBuilder = ReactiveRequest.newBuilder(jettyRequest); - if(bodyActualClass != null){ - requestBuilder.content(provideBody(request)); - } - - return Mono.from(requestBuilder.build().response((response, content) -> Mono.just( - new JettyReactiveHttpResponse(request, response.getResponse(), - postProcess(content, - (contentChunk, throwable) -> { - if(throwable != null){ - contentChunk.callback.failed(throwable); - } else { - contentChunk.callback.succeeded(); - } - }), - returnPublisherClass, returnActualClass, - jsonFactory, responseReader)))) - .onErrorMap(ex -> { - if(ex instanceof java.util.concurrent.TimeoutException){ - return new ReadTimeoutException(ex, request); - } else { - return new ReactiveFeignException(ex, request); - } - }); - } - - protected void setUpHeaders(ReactiveHttpRequest request, HttpFields httpHeaders) { - request.headers().forEach(httpHeaders::put); - - String acceptHeader; - if(CharSequence.class.isAssignableFrom(returnActualClass) && returnPublisherClass == Mono.class){ - acceptHeader = TEXT; - } - else if(returnActualClass == ByteBuffer.class || returnActualClass == byte[].class){ - acceptHeader = APPLICATION_OCTET_STREAM; - } - else if(returnPublisherClass == Mono.class){ - acceptHeader = APPLICATION_JSON; - } - else { - acceptHeader = APPLICATION_STREAM_JSON; - } - httpHeaders.put(ACCEPT.asString(), singletonList(acceptHeader)); - - if(tryUseCompression){ - httpHeaders.put(ACCEPT_ENCODING.asString(), singletonList(GZIP)); - } else { - httpHeaders.remove(ACCEPT_ENCODING.asString()); - } - } - - protected ReactiveRequest.Content provideBody(ReactiveHttpRequest request) { - Publisher bodyPublisher; - String contentType; - if(request.body() instanceof Mono){ - if(bodyActualClass == ByteBuffer.class){ - bodyPublisher = ((Mono)request.body()).map(this::toByteBufferChunk); - contentType = APPLICATION_OCTET_STREAM; - } - else if (CharSequence.class.isAssignableFrom(bodyActualClass)){ - bodyPublisher = Flux.from(request.body()).map(this::toCharSequenceChunk); - contentType = TEXT_UTF_8; - } - else { - bodyPublisher = Flux.from(request.body()).map(data -> toJsonChunk(data, false)); - contentType = APPLICATION_JSON_UTF_8; - } - - } else { - if(bodyActualClass == ByteBuffer.class){ - bodyPublisher = Flux.from(request.body()).map(this::toByteBufferChunk); - contentType = APPLICATION_OCTET_STREAM; - } - else { - bodyPublisher = Flux.from(request.body()).map(data -> toJsonChunk(data, true)); - contentType = APPLICATION_STREAM_JSON_UTF_8; - } - } - - return ReactiveRequest.Content.fromPublisher(bodyPublisher, contentType); - } - - protected ContentChunk toByteBufferChunk(Object data){ - return new ContentChunk((ByteBuffer)data); - } - - protected ContentChunk toCharSequenceChunk(Object data){ - CharBuffer charBuffer = CharBuffer.wrap((CharSequence) data); - ByteBuffer byteBuffer = UTF_8.encode(charBuffer); - return new ContentChunk(byteBuffer); - } - - protected ContentChunk toJsonChunk(Object data, boolean stream){ - try { - ByteArrayBuilder byteArrayBuilder = new ByteArrayBuilder(); - bodyWriter.writeValue(byteArrayBuilder, data); - if(stream) { - byteArrayBuilder.write(NEWLINE_SEPARATOR); - } - ByteBuffer buffer = ByteBuffer.wrap(byteArrayBuilder.toByteArray()); - return new ContentChunk(buffer); - } catch (java.io.IOException e) { - throw new UncheckedIOException(e); - } - } - - public static Class getClass(Type type){ - return (Class)(type instanceof ParameterizedType - ? ((ParameterizedType) type).getRawType() : type); - } + private final HttpClient httpClient; + private final Class bodyActualClass; + private final Class returnPublisherClass; + private final Class returnActualClass; + private final JsonFactory jsonFactory; + private final ObjectWriter bodyWriter; + private final ObjectReader responseReader; + private long requestTimeout = -1; + private boolean tryUseCompression; + + public static JettyReactiveHttpClient jettyClient( + MethodMetadata methodMetadata, + HttpClient httpClient, + JsonFactory jsonFactory, ObjectMapper objectMapper) { + + Class returnPublisherType = returnPublisherType(methodMetadata); + Type returnActualType = returnActualType(methodMetadata); + Type bodyActualType = getBodyActualType(methodMetadata.bodyType()); + ObjectWriter bodyWriter = bodyActualType != null + ? objectMapper.writerFor(objectMapper.constructType(bodyActualType)) : null; + ObjectReader responseReader = objectMapper.readerFor(objectMapper.constructType(returnActualType)); + + return new JettyReactiveHttpClient(httpClient, + getClass(bodyActualType), returnPublisherType, getClass(returnActualType), + jsonFactory, bodyWriter, responseReader); + } + + public JettyReactiveHttpClient(HttpClient httpClient, + Class bodyActualClass, Class returnPublisherClass, Class returnActualClass, + JsonFactory jsonFactory, ObjectWriter bodyWriter, ObjectReader responseReader) { + this.httpClient = httpClient; + this.bodyActualClass = bodyActualClass; + this.returnPublisherClass = returnPublisherClass; + this.returnActualClass = returnActualClass; + this.jsonFactory = jsonFactory; + this.bodyWriter = bodyWriter; + this.responseReader = responseReader; + } + + public JettyReactiveHttpClient setRequestTimeout(long timeoutInMillis) { + this.requestTimeout = timeoutInMillis; + return this; + } + + public JettyReactiveHttpClient setTryUseCompression(boolean tryUseCompression) { + this.tryUseCompression = tryUseCompression; + return this; + } + + @Override + public Mono executeRequest(ReactiveHttpRequest request) { + Request jettyRequest = httpClient + .newRequest(request.uri()) + .headers(httpFields -> setUpHeaders(request, httpFields)) + .method(request.method()); + + if (requestTimeout > 0) { + jettyRequest.timeout(requestTimeout, TimeUnit.MILLISECONDS); + } + + ReactiveRequest.Builder requestBuilder = ReactiveRequest.newBuilder(jettyRequest); + if (bodyActualClass != null + || request.body() instanceof SerializedFormData) { + requestBuilder.content(provideBody(request)); + } + + return Mono.from(requestBuilder.build().response((response, content) -> Mono.just( + new JettyReactiveHttpResponse( + request, + response.getResponse(), + content, + returnPublisherClass, + returnActualClass, + jsonFactory, + responseReader) + ))) + .onErrorMap(ex -> { + if (ex instanceof java.util.concurrent.TimeoutException) { + return new ReadTimeoutException(ex, request); + } else { + return new ReactiveFeignException(ex, request); + } + }); + } + + protected void setUpHeaders(ReactiveHttpRequest request, HttpFields.Mutable httpHeaders) { + request.headers().forEach(httpHeaders::put); + + String acceptHeader; + if (CharSequence.class.isAssignableFrom(returnActualClass) && returnPublisherClass == Mono.class) { + acceptHeader = TEXT; + } else if (returnActualClass == ByteBuffer.class || returnActualClass == byte[].class) { + acceptHeader = APPLICATION_OCTET_STREAM; + } else if (returnPublisherClass == Mono.class) { + acceptHeader = APPLICATION_JSON; + } else { + acceptHeader = APPLICATION_STREAM_JSON; + } + httpHeaders.put(ACCEPT.asString(), singletonList(acceptHeader)); + + if (tryUseCompression) { + httpHeaders.put(ACCEPT_ENCODING.asString(), singletonList(GZIP)); + } else { + httpHeaders.remove(ACCEPT_ENCODING.asString()); + } + } + + protected ReactiveRequest.Content provideBody(ReactiveHttpRequest request) { + Publisher bodyPublisher; + String contentType; + if (request.body() instanceof SerializedFormData) { + bodyPublisher = Mono.just(toByteBufferChunk(((SerializedFormData) request.body()).getFormData(), false)); + contentType = FORM_URL_ENCODED; + } else if (request.body() instanceof Mono) { + if (bodyActualClass == ByteBuffer.class) { + bodyPublisher = ((Mono) request.body()).map(data -> toByteBufferChunk(data, false)); + contentType = APPLICATION_OCTET_STREAM; + } else if (CharSequence.class.isAssignableFrom(bodyActualClass)) { + bodyPublisher = Flux.from(request.body()).map(data -> toCharSequenceChunk(data, false)); + contentType = TEXT_UTF_8; + } else { + bodyPublisher = Flux.from(request.body()).map(data -> toJsonChunk(data, false)); + contentType = APPLICATION_JSON_UTF_8; + } + } else { + if (bodyActualClass == ByteBuffer.class) { + bodyPublisher = Flux.concat( + Flux.from(request.body()).map(data -> toByteBufferChunk(data, true)), + Flux.just(Content.Chunk.EOF) + ); + contentType = APPLICATION_OCTET_STREAM; + } else { + bodyPublisher = Flux.concat( + Flux.from(request.body()).map(data -> toJsonChunk(data, true)), + Flux.just(Content.Chunk.EOF) + ); + contentType = APPLICATION_STREAM_JSON_UTF_8; + } + } + //TODO + //String originalContentType = request.headers().get(CONTENT_TYPE_HEADER).get(0); + + return ReactiveRequest.Content.fromPublisher(bodyPublisher, contentType); + } + + protected Content.Chunk toByteBufferChunk(Object data, boolean stream) { + return Content.Chunk.from((ByteBuffer) data, !stream); + } + + protected Content.Chunk toCharSequenceChunk(Object data, boolean stream) { + CharBuffer charBuffer = CharBuffer.wrap((CharSequence) data); + ByteBuffer byteBuffer = UTF_8.encode(charBuffer); + return Content.Chunk.from(byteBuffer, !stream); + } + + protected Content.Chunk toJsonChunk(Object data, boolean stream) { + try { + ByteArrayBuilder byteArrayBuilder = new ByteArrayBuilder(); + bodyWriter.writeValue(byteArrayBuilder, data); + if (stream) { + byteArrayBuilder.write(NEWLINE_SEPARATOR); + } + ByteBuffer buffer = ByteBuffer.wrap(byteArrayBuilder.toByteArray()); + return Content.Chunk.from(buffer, !stream); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + public static Class getClass(Type type) { + return (Class) (type instanceof ParameterizedType pt ? pt.getRawType() : type); + } } diff --git a/feign-reactor-jetty/src/main/java/reactivefeign/jetty/client/JettyReactiveHttpResponse.java b/feign-reactor-jetty/src/main/java/reactivefeign/jetty/client/JettyReactiveHttpResponse.java index 99814d344..0e976cd57 100644 --- a/feign-reactor-jetty/src/main/java/reactivefeign/jetty/client/JettyReactiveHttpResponse.java +++ b/feign-reactor-jetty/src/main/java/reactivefeign/jetty/client/JettyReactiveHttpResponse.java @@ -2,9 +2,10 @@ import com.fasterxml.jackson.core.async_.JsonFactory; import com.fasterxml.jackson.databind.ObjectReader; -import org.eclipse.jetty.client.api.Response; -import org.eclipse.jetty.http.HttpField; -import org.eclipse.jetty.reactive.client.ContentChunk; +import org.eclipse.jetty.client.Response; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.io.Retainable; import org.reactivestreams.Publisher; import reactivefeign.client.ReactiveHttpRequest; import reactivefeign.client.ReactiveHttpResponse; @@ -15,12 +16,13 @@ import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Arrays.asList; import static java.util.Optional.ofNullable; import static org.eclipse.jetty.http.HttpHeader.CONTENT_TYPE; @@ -29,13 +31,13 @@ class JettyReactiveHttpResponse implements ReactiveHttpResponse{ public static final String CHARSET_DELIMITER = ";charset="; private ReactiveHttpRequest request; private final Response clientResponse; - private final Publisher contentChunks; + private final Publisher contentChunks; private final Class returnPublisherType; private Class returnActualClass; private final ObjectReader objectReader; private final JsonFactory jsonFactory; - JettyReactiveHttpResponse(ReactiveHttpRequest request, Response clientResponse, Publisher contentChunks, + JettyReactiveHttpResponse(ReactiveHttpRequest request, Response clientResponse, Publisher contentChunks, Class returnPublisherType, Class returnActualClass, JsonFactory jsonFactory, ObjectReader objectReader) { this.request = request; @@ -59,8 +61,21 @@ public int status() { @Override public Map> headers() { - return clientResponse.getHeaders().stream() - .collect(Collectors.toMap(HttpField::getName, field -> asList(field.getValues()))); + HttpFields headers = clientResponse.getHeaders(); + Map> headersMap = new HashMap<>(headers.size()); + headers.forEach(httpField -> + headersMap.compute(httpField.getName(), (oldName, oldValues) -> { + List values; + if(oldValues == null){ + values = Arrays.asList(httpField.getValues()); + } else { + values = new ArrayList<>(oldValues.size() + httpField.getValues().length); + values.addAll(oldValues); + values.addAll(Arrays.asList(httpField.getValues())); + } + return values; + })); + return headersMap; } @Override @@ -88,7 +103,9 @@ public Publisher body() { @Override public Mono releaseBody() { - return Flux.from(contentChunks).then(); + return Flux.from(contentChunks) + .doOnNext(Retainable::release) + .then(); } private Charset getCharset() { @@ -106,7 +123,8 @@ private Charset getCharset() { } private Flux directContent() { - return Flux.from(contentChunks).map(contentChunk -> contentChunk.buffer.slice()); + return Flux.from(contentChunks) + .map(contentChunk -> contentChunk.getByteBuffer().slice()); } @Override diff --git a/feign-reactor-jetty/src/test/java/reactivefeign/jetty/h1/DefaultMethodTest.java b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/h1/DefaultMethodTest.java index 37cea36f8..4069fbcf6 100644 --- a/feign-reactor-jetty/src/test/java/reactivefeign/jetty/h1/DefaultMethodTest.java +++ b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/h1/DefaultMethodTest.java @@ -15,10 +15,10 @@ import reactivefeign.ReactiveFeign; import reactivefeign.jetty.JettyReactiveFeign; -import reactivefeign.jetty.JettyReactiveOptions; import reactivefeign.testcase.IcecreamServiceApi; import static reactivefeign.jetty.h1.TestUtils.builderHttp; +import static reactivefeign.jetty.h1.TestUtils.builderHttpWithConnectTimeout; /** * @author Sergii Karpenko @@ -37,8 +37,6 @@ protected ReactiveFeign.Builder builder(Class apiClass) { @Override protected ReactiveFeign.Builder builder(long connectTimeoutInMillis) { - return JettyReactiveFeign.builder().options( - new JettyReactiveOptions.Builder().setConnectTimeoutMillis(connectTimeoutInMillis).build() - ); + return builderHttpWithConnectTimeout(connectTimeoutInMillis); } } diff --git a/feign-reactor-jetty/src/test/java/reactivefeign/jetty/h2c/AllFeaturesTest.java b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/h2c/AllFeaturesTest.java index 6d1259c0c..79bb9a8fd 100644 --- a/feign-reactor-jetty/src/test/java/reactivefeign/jetty/h2c/AllFeaturesTest.java +++ b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/h2c/AllFeaturesTest.java @@ -40,7 +40,7 @@ */ @EnableAutoConfiguration(exclude = {ReactiveSecurityAutoConfiguration.class, ReactiveUserDetailsServiceAutoConfiguration.class}) @ContextConfiguration(classes={TestServerConfigurations.class}) -@ActiveProfiles(JETTY_H2C) +@ActiveProfiles(UNDERTOW_H2C) public class AllFeaturesTest extends AllFeaturesFeignTest { @Override diff --git a/feign-reactor-parent/pom.xml b/feign-reactor-parent/pom.xml index b697a99a8..76fe422d4 100644 --- a/feign-reactor-parent/pom.xml +++ b/feign-reactor-parent/pom.xml @@ -5,7 +5,7 @@ com.playtika.reactivefeign feign-reactor - 3.2.6 + 4.0.3 feign-reactor-parent @@ -13,61 +13,49 @@ - 1.8 + 17 UTF-8 UTF-8 - 2021.0.1 - 2.6.5 + 2023.0.2 + 3.2.7 - - - - - - - 0.0.4 + 1.3.8 + 2.2.21 4.13.2 - 2.32.0 + 3.5.4 4.2.0 - 1.0.6.RELEASE + 1.0.9.RELEASE 3.4.4 - 3.1.1 - 5.13.7 + 5.16.0 - 0.8.7 + 0.8.11 4.3.0 - 3.10.1 - 2.22.2 + 3.13.0 + 3.2.5 + + 3.5.0 + 3.3.0 - 3.3.2 - 3.2.1 + + + 3EEF24C7 + false + true + never - - - - - - - - - - - - - org.springframework.boot @@ -86,6 +74,16 @@ + + io.reactivex + rxjava + ${rxjava.version} + + + io.reactivex.rxjava2 + rxjava + ${rxjava2.version} + com.playtika.reactivefeign feign-reactor-core @@ -106,6 +104,11 @@ feign-reactor-webclient-jetty ${project.version} + + com.playtika.reactivefeign + feign-reactor-webclient-apache-client5 + ${project.version} + com.playtika.reactivefeign feign-reactor-jetty @@ -172,8 +175,8 @@ - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone ${wiremock.version} test @@ -194,12 +197,6 @@ test - - org.springframework.cloud - spring-cloud-starter-sleuth - ${spring-cloud-starter-sleuth.version} - test - io.zipkin.brave brave-tests @@ -233,6 +230,9 @@ org.apache.maven.plugins maven-surefire-plugin ${maven-surefire-plugin.version} + + -XX:+AllowRedefinitionToAddDeleteMethods + diff --git a/feign-reactor-rx2/pom.xml b/feign-reactor-rx2/pom.xml index ad7c777e5..76ebc39fa 100644 --- a/feign-reactor-rx2/pom.xml +++ b/feign-reactor-rx2/pom.xml @@ -6,7 +6,7 @@ com.playtika.reactivefeign feign-reactor-parent ../feign-reactor-parent - 3.2.6 + 4.0.3 feign-reactor-rx2 @@ -37,6 +37,7 @@ org.springframework.boot + test @@ -74,8 +75,8 @@ - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test @@ -87,7 +88,17 @@ org.apache.logging.log4j - log4j-slf4j-impl + log4j-api + test + + + org.apache.logging.log4j + log4j-core + test + + + org.apache.logging.log4j + log4j-slf4j2-impl test diff --git a/feign-reactor-rx2/src/test/java/reactivefeign/rx2/StatusHandlerTest.java b/feign-reactor-rx2/src/test/java/reactivefeign/rx2/StatusHandlerTest.java index ec8f88148..62d3cb6b0 100644 --- a/feign-reactor-rx2/src/test/java/reactivefeign/rx2/StatusHandlerTest.java +++ b/feign-reactor-rx2/src/test/java/reactivefeign/rx2/StatusHandlerTest.java @@ -65,7 +65,7 @@ public void shouldThrowRetryException() throws InterruptedException { return new RetryableException( response.status(), "Should retry on next node", - httpMethod, null, request); + httpMethod, (Long) null, request); })) .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port()); diff --git a/feign-reactor-spring-cloud-starter/pom.xml b/feign-reactor-spring-cloud-starter/pom.xml index 3d826f228..7746bcae8 100644 --- a/feign-reactor-spring-cloud-starter/pom.xml +++ b/feign-reactor-spring-cloud-starter/pom.xml @@ -8,7 +8,7 @@ com.playtika.reactivefeign feign-reactor-parent ../feign-reactor-parent - 3.2.6 + 4.0.3 pom diff --git a/feign-reactor-spring-configuration/pom.xml b/feign-reactor-spring-configuration/pom.xml index bb02564a5..4d085f5ee 100644 --- a/feign-reactor-spring-configuration/pom.xml +++ b/feign-reactor-spring-configuration/pom.xml @@ -6,7 +6,7 @@ com.playtika.reactivefeign feign-reactor-parent ../feign-reactor-parent - 3.2.6 + 4.0.3 feign-reactor-spring-configuration diff --git a/feign-reactor-spring-configuration/src/main/java/reactivefeign/spring/config/ReactiveFeignAutoConfiguration.java b/feign-reactor-spring-configuration/src/main/java/reactivefeign/spring/config/ReactiveFeignAutoConfiguration.java index d77431691..98f2680b9 100644 --- a/feign-reactor-spring-configuration/src/main/java/reactivefeign/spring/config/ReactiveFeignAutoConfiguration.java +++ b/feign-reactor-spring-configuration/src/main/java/reactivefeign/spring/config/ReactiveFeignAutoConfiguration.java @@ -1,7 +1,7 @@ package reactivefeign.spring.config; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -20,9 +20,8 @@ import java.util.ArrayList; import java.util.List; -@Configuration +@AutoConfiguration(after = LoadBalancerAutoConfiguration.class) @ConditionalOnClass(ReactiveFeign.class) -@AutoConfigureAfter(LoadBalancerAutoConfiguration.class) public class ReactiveFeignAutoConfiguration { @Autowired(required = false) @@ -42,7 +41,7 @@ public ReactiveFeignNamedContextFactory reactiveFeignContext() { @Configuration @ConditionalOnClass(WebReactiveFeign.class) - public class WebClientReactiveFeignClientPropertiesAutoConfiguration { + public static class WebClientReactiveFeignClientPropertiesAutoConfiguration { @Bean @ConditionalOnMissingBean @@ -55,7 +54,7 @@ public ReactiveFeignClientsProperties webClientReact @Configuration @ConditionalOnClass(Java11ReactiveFeign.class) - public class Java11ReactiveFeignClientPropertiesAutoConfiguration { + public static class Java11ReactiveFeignClientPropertiesAutoConfiguration { @Bean @ConditionalOnMissingBean @@ -68,7 +67,7 @@ public ReactiveFeignClientsProperties java11React @Configuration @ConditionalOnClass(JettyReactiveFeign.class) - public class JettyReactiveFeignClientPropertiesAutoConfiguration { + public static class JettyReactiveFeignClientPropertiesAutoConfiguration { @Bean @ConditionalOnMissingBean diff --git a/feign-reactor-spring-configuration/src/main/java/reactivefeign/spring/config/ReactiveFeignBasicConfigurator.java b/feign-reactor-spring-configuration/src/main/java/reactivefeign/spring/config/ReactiveFeignBasicConfigurator.java index 3406b3b1f..bc73e9b9c 100644 --- a/feign-reactor-spring-configuration/src/main/java/reactivefeign/spring/config/ReactiveFeignBasicConfigurator.java +++ b/feign-reactor-spring-configuration/src/main/java/reactivefeign/spring/config/ReactiveFeignBasicConfigurator.java @@ -33,6 +33,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.stream.Collectors; @@ -48,7 +49,7 @@ public ReactiveFeignBuilder configure( ReactiveFeignBuilder builder, ReactiveFeignNamedContext namedContext) { - if (namedContext.getProperties().isDefaultToProperties()) { + if (isDefaultToProperties(namedContext)) { builder = configureUsingConfiguration(builder, namedContext); for(ReactiveFeignClientsProperties.ReactiveFeignClientProperties config : namedContext.getConfigsReverted()){ builder = configureUsingProperties(builder, namedContext, config); @@ -62,6 +63,13 @@ public ReactiveFeignBuilder configure( return builder; } + private boolean isDefaultToProperties(ReactiveFeignNamedContext namedContext){ + Map> config = namedContext.getProperties().getConfig(); + return Optional.ofNullable(config.get(namedContext.getClientName())) + .map(ReactiveFeignClientsProperties.ReactiveFeignClientProperties::getDefaultToProperties) + .orElse(namedContext.getProperties().isDefaultToProperties()); + } + private ReactiveFeignBuilder configureUsingConfiguration(ReactiveFeignBuilder builder, ReactiveFeignNamedContext namedContext) { ReactiveFeignBuilder resultBuilder = builder; diff --git a/feign-reactor-spring-configuration/src/main/java/reactivefeign/spring/config/ReactiveFeignClientsProperties.java b/feign-reactor-spring-configuration/src/main/java/reactivefeign/spring/config/ReactiveFeignClientsProperties.java index 5c93d9f54..29568c5a7 100644 --- a/feign-reactor-spring-configuration/src/main/java/reactivefeign/spring/config/ReactiveFeignClientsProperties.java +++ b/feign-reactor-spring-configuration/src/main/java/reactivefeign/spring/config/ReactiveFeignClientsProperties.java @@ -10,7 +10,6 @@ import reactivefeign.client.statushandler.ReactiveStatusHandler; import reactivefeign.retry.ReactiveRetryPolicy; -import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -65,6 +64,8 @@ public int hashCode() { public static class ReactiveFeignClientProperties { + private Boolean defaultToProperties; + private O options; //used for no cloud configuration @@ -92,6 +93,14 @@ public static class ReactiveFeignClientProperties> defaultQueryParameters; + public Boolean getDefaultToProperties() { + return defaultToProperties; + } + + public void setDefaultToProperties(Boolean defaultToProperties) { + this.defaultToProperties = defaultToProperties; + } + public O getOptions() { return options; } @@ -193,7 +202,8 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ReactiveFeignClientProperties that = (ReactiveFeignClientProperties) o; - return Objects.equals(options, that.options) && + return Objects.equals(defaultToProperties, that.defaultToProperties) && + Objects.equals(options, that.options) && Objects.equals(retryOnSame, that.retryOnSame) && Objects.equals(retryOnNext, that.retryOnNext) && Objects.equals(statusHandler, that.statusHandler) && diff --git a/feign-reactor-spring-configuration/src/main/java/reactivefeign/spring/config/ReactiveFeignClientsRegistrar.java b/feign-reactor-spring-configuration/src/main/java/reactivefeign/spring/config/ReactiveFeignClientsRegistrar.java index c06fd46bd..94ccb8196 100644 --- a/feign-reactor-spring-configuration/src/main/java/reactivefeign/spring/config/ReactiveFeignClientsRegistrar.java +++ b/feign-reactor-spring-configuration/src/main/java/reactivefeign/spring/config/ReactiveFeignClientsRegistrar.java @@ -40,7 +40,6 @@ import org.springframework.core.type.filter.TypeFilter; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; import java.io.IOException; import java.net.MalformedURLException; @@ -53,6 +52,7 @@ import java.util.Map; import java.util.Set; +import static org.springframework.util.StringUtils.hasText; import static reactivefeign.utils.StringUtils.cutTail; /** @@ -148,9 +148,11 @@ protected boolean match(ClassMetadata metadata) { .getAnnotationAttributes( ReactiveFeignClient.class.getCanonicalName()); - String name = getClientName(attributes); - registerClientConfiguration(registry, name, - attributes.get("configuration")); + String name = getQualifier(attributes); + if(!hasText(name)){ + name = getClientName(attributes); + } + registerClientConfiguration(registry, name, attributes.get("configuration")); registerReactiveFeignClient(registry, annotationMetadata, attributes); } @@ -176,14 +178,15 @@ private void registerReactiveFeignClient(BeanDefinitionRegistry registry, String alias = name + "ReactiveFeignClient"; AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); - beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className); + Class type = ClassUtils.resolveClassName(className, null); + beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, type); boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null beanDefinition.setPrimary(primary); String qualifier = getQualifier(attributes); - if (StringUtils.hasText(qualifier)) { + if (hasText(qualifier)) { alias = qualifier; } @@ -215,10 +218,10 @@ static void validateFallbackFactory(final Class clazz) { /* for testing */ String getName(Map attributes) { String name = (String) attributes.get("serviceId"); - if (!StringUtils.hasText(name)) { + if (!hasText(name)) { name = (String) attributes.get("name"); } - if (!StringUtils.hasText(name)) { + if (!hasText(name)) { name = (String) attributes.get("value"); } name = resolve(name); @@ -226,7 +229,7 @@ static void validateFallbackFactory(final Class clazz) { } static String getName(String name) { - if (!StringUtils.hasText(name)) { + if (!hasText(name)) { return ""; } @@ -248,7 +251,7 @@ static String getName(String name) { } private String resolve(String value) { - if (StringUtils.hasText(value)) { + if (hasText(value)) { return this.environment.resolvePlaceholders(value); } return value; @@ -260,7 +263,7 @@ private String getUrl(Map attributes) { } static String getUrl(String url) { - if (StringUtils.hasText(url) && !(url.startsWith("#{") && url.contains("}"))) { + if (hasText(url) && !(url.startsWith("#{") && url.contains("}"))) { if (!url.contains("://")) { url = "http://" + url; } @@ -280,7 +283,7 @@ private String getPath(Map attributes) { } static String getPath(String path) { - if (StringUtils.hasText(path)) { + if (hasText(path)) { path = path.trim(); if (!path.startsWith("/")) { path = "/" + path; @@ -311,12 +314,12 @@ protected Set getBasePackages(AnnotationMetadata importingClassMetadata) Set basePackages = new HashSet<>(); for (String pkg : (String[]) attributes.get("value")) { - if (StringUtils.hasText(pkg)) { + if (hasText(pkg)) { basePackages.add(pkg); } } for (String pkg : (String[]) attributes.get("basePackages")) { - if (StringUtils.hasText(pkg)) { + if (hasText(pkg)) { basePackages.add(pkg); } } @@ -336,7 +339,7 @@ private String getQualifier(Map client) { return null; } String qualifier = (String) client.get("qualifier"); - if (StringUtils.hasText(qualifier)) { + if (hasText(qualifier)) { return qualifier; } return null; @@ -347,13 +350,13 @@ private String getClientName(Map client) { return null; } String value = (String) client.get("value"); - if (!StringUtils.hasText(value)) { + if (!hasText(value)) { value = (String) client.get("name"); } - if (!StringUtils.hasText(value)) { + if (!hasText(value)) { value = (String) client.get("serviceId"); } - if (StringUtils.hasText(value)) { + if (hasText(value)) { return value; } diff --git a/feign-reactor-spring-configuration/src/main/resources/META-INF/spring.factories b/feign-reactor-spring-configuration/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 1f28cf7ca..000000000 --- a/feign-reactor-spring-configuration/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -reactivefeign.spring.config.ReactiveFeignAutoConfiguration diff --git a/feign-reactor-spring-configuration/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/feign-reactor-spring-configuration/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 000000000..1e9706ec3 --- /dev/null +++ b/feign-reactor-spring-configuration/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +reactivefeign.spring.config.ReactiveFeignAutoConfiguration \ No newline at end of file diff --git a/feign-reactor-test/feign-reactor-server-configurations/pom.xml b/feign-reactor-test/feign-reactor-server-configurations/pom.xml index 6d9c70211..4313e23e3 100644 --- a/feign-reactor-test/feign-reactor-server-configurations/pom.xml +++ b/feign-reactor-test/feign-reactor-server-configurations/pom.xml @@ -5,7 +5,7 @@ com.playtika.reactivefeign feign-reactor-test - 3.2.6 + 4.0.3 feign-reactor-server-configurations @@ -41,7 +41,7 @@ org.eclipse.jetty.http2 - http2-server + jetty-http2-server test @@ -52,8 +52,8 @@ - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test @@ -64,7 +64,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.2.2 + 3.3.0 diff --git a/feign-reactor-test/feign-reactor-server-configurations/src/test/java/reactivefeign/spring/server/config/TestServerConfigurations.java b/feign-reactor-test/feign-reactor-server-configurations/src/test/java/reactivefeign/spring/server/config/TestServerConfigurations.java index 8a5699bdb..e2111ce2d 100644 --- a/feign-reactor-test/feign-reactor-server-configurations/src/test/java/reactivefeign/spring/server/config/TestServerConfigurations.java +++ b/feign-reactor-test/feign-reactor-server-configurations/src/test/java/reactivefeign/spring/server/config/TestServerConfigurations.java @@ -12,6 +12,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; +import java.util.Arrays; + import static java.util.Collections.singleton; @Configuration @@ -30,14 +32,20 @@ public ReactiveWebServerFactory reactiveWebServerFactory(){ Http2 http2 = new Http2(); http2.setEnabled(true); jettyReactiveWebServerFactory.setHttp2(http2); - jettyReactiveWebServerFactory.setServerCustomizers(singleton(server -> { - ServerConnector sc = (ServerConnector) server.getConnectors()[0]; + jettyReactiveWebServerFactory.addServerCustomizers(server -> { HttpConfiguration httpConfig = new HttpConfiguration(); httpConfig.setIdleTimeout(0); - HTTP2CServerConnectionFactory http2CFactory = new HTTP2CServerConnectionFactory(httpConfig); + HTTP2CServerConnectionFactory http2CFactory = new HTTP2CServerConnectionFactory(httpConfig, "h2c"); http2CFactory.setMaxConcurrentStreams(1000); - sc.addConnectionFactory(http2CFactory); - })); + Arrays.stream(server.getConnectors()) + .filter(ServerConnector.class::isInstance) + .map(ServerConnector.class::cast) + .findFirst() + .ifPresentOrElse(sc -> sc.addConnectionFactory(http2CFactory), () -> { + ServerConnector sc = new ServerConnector(server, http2CFactory); + server.addConnector(sc); + }); + }); return jettyReactiveWebServerFactory; } } diff --git a/feign-reactor-test/feign-reactor-server-configurations/src/test/java/reactivefeign/wiremock/WireMockServerConfigurations.java b/feign-reactor-test/feign-reactor-server-configurations/src/test/java/reactivefeign/wiremock/WireMockServerConfigurations.java index 3ecd3218d..1cea75b80 100644 --- a/feign-reactor-test/feign-reactor-server-configurations/src/test/java/reactivefeign/wiremock/WireMockServerConfigurations.java +++ b/feign-reactor-test/feign-reactor-server-configurations/src/test/java/reactivefeign/wiremock/WireMockServerConfigurations.java @@ -2,7 +2,7 @@ import com.github.tomakehurst.wiremock.common.JettySettings; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; -import com.github.tomakehurst.wiremock.jetty9.JettyHttpServer; +import com.github.tomakehurst.wiremock.jetty11.Jetty11HttpServer; import wiremock.org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; import wiremock.org.eclipse.jetty.io.NetworkTrafficListener; import wiremock.org.eclipse.jetty.server.ConnectionFactory; @@ -10,6 +10,9 @@ import wiremock.org.eclipse.jetty.server.HttpConnectionFactory; import wiremock.org.eclipse.jetty.server.ServerConnector; +import static com.github.tomakehurst.wiremock.jetty11.Jetty11Utils.createHttpConfig; +import static com.github.tomakehurst.wiremock.jetty11.Jetty11Utils.createServerConnector; + public class WireMockServerConfigurations { public static WireMockConfiguration h2cConfig(){ @@ -21,7 +24,7 @@ public static WireMockConfiguration h2cConfig(boolean includeH1, int maxConcurre .dynamicPort() .asynchronousResponseEnabled(true) .httpServerFactory((options, adminRequestHandler, stubRequestHandler) -> - new JettyHttpServer(options, adminRequestHandler, stubRequestHandler) { + new Jetty11HttpServer(options, adminRequestHandler, stubRequestHandler) { @Override protected ServerConnector createHttpConnector( String bindAddress, int port, JettySettings jettySettings, NetworkTrafficListener listener) { @@ -32,6 +35,7 @@ protected ServerConnector createHttpConnector( http2CFactory.setMaxConcurrentStreams(maxConcurrentCalls); return createServerConnector( + jettyServer, bindAddress, jettySettings, port, diff --git a/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/pom.xml b/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/pom.xml index d07f323de..aa3f48cd9 100644 --- a/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/pom.xml +++ b/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/pom.xml @@ -5,7 +5,7 @@ com.playtika.reactivefeign feign-reactor-test - 3.2.6 + 4.0.3 feign-reactor-spring-configuration-cloud2-test @@ -47,11 +47,6 @@ test - - org.springframework.cloud - spring-cloud-starter-sleuth - test - io.zipkin.brave brave-tests @@ -110,14 +105,24 @@ test - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test org.apache.logging.log4j - log4j-slf4j-impl + log4j-api + test + + + org.apache.logging.log4j + log4j-core + test + + + org.apache.logging.log4j + log4j-slf4j2-impl test diff --git a/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/java/reactivefeign/spring/config/cloud2/LoadBalancerEnabledCircuitBreakerDisabledUsingPropertiesTest.java b/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/java/reactivefeign/spring/config/cloud2/LoadBalancerEnabledCircuitBreakerDisabledUsingPropertiesTest.java index 4550b666b..6c9b6b90d 100644 --- a/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/java/reactivefeign/spring/config/cloud2/LoadBalancerEnabledCircuitBreakerDisabledUsingPropertiesTest.java +++ b/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/java/reactivefeign/spring/config/cloud2/LoadBalancerEnabledCircuitBreakerDisabledUsingPropertiesTest.java @@ -26,6 +26,8 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpEntity; +import org.springframework.http.ResponseEntity; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; @@ -38,10 +40,15 @@ import java.util.stream.Stream; -import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.assertj.core.api.Assertions.assertThat; -import static reactivefeign.spring.config.cloud2.LoadBalancerEnabledCircuitBreakerDisabledUsingPropertiesTest.*; +import static reactivefeign.spring.config.cloud2.LoadBalancerEnabledCircuitBreakerDisabledUsingPropertiesTest.FEIGN_CLIENT_TEST_LB; +import static reactivefeign.spring.config.cloud2.LoadBalancerEnabledCircuitBreakerDisabledUsingPropertiesTest.MOCK_SERVER_1_PORT_PROPERTY; +import static reactivefeign.spring.config.cloud2.LoadBalancerEnabledCircuitBreakerDisabledUsingPropertiesTest.MOCK_SERVER_2_PORT_PROPERTY; +import static reactivefeign.spring.config.cloud2.LoadBalancerEnabledCircuitBreakerDisabledUsingPropertiesTest.TestConfiguration; /** * @author Sergii Karpenko @@ -57,7 +64,7 @@ "spring.cloud.discovery.client.simple.instances."+ FEIGN_CLIENT_TEST_LB +"[1].uri=http://localhost:${"+MOCK_SERVER_2_PORT_PROPERTY+"}", }) @TestPropertySource(locations = { - "classpath:lb-enabled-cb-disabled.properties", + "classpath:lb-enabled-cb-disabled.properties", "classpath:common.properties"}) @DirtiesContext public class LoadBalancerEnabledCircuitBreakerDisabledUsingPropertiesTest { @@ -69,21 +76,20 @@ public class LoadBalancerEnabledCircuitBreakerDisabledUsingPropertiesTest { private static final String TEST_URL = "/testUrl"; private static final String BODY_TEXT = "test"; - private static WireMockServer mockHttpServer1 = new WireMockServer(wireMockConfig().dynamicPort()); - private static WireMockServer mockHttpServer2 = new WireMockServer(wireMockConfig().dynamicPort()); + private static final WireMockServer mockHttpServer1 = new WireMockServer(wireMockConfig().dynamicPort()); + private static final WireMockServer mockHttpServer2 = new WireMockServer(wireMockConfig().dynamicPort()); @Autowired TestReactiveFeignClient feignClient; @Test public void shouldRetryAndNotFailOnDefaultCircuitBreakerTimeout() { - Stream.of(mockHttpServer1, mockHttpServer2).forEach(wireMockServer -> { - wireMockServer.stubFor(get(urlPathMatching(TEST_URL)) - .willReturn(aResponse() - .withFixedDelay(700) - .withBody(BODY_TEXT) - .withStatus(200))); - }); + Stream.of(mockHttpServer1, mockHttpServer2).forEach(wireMockServer -> + wireMockServer.stubFor(get(urlPathMatching(TEST_URL)) + .willReturn(aResponse() + .withFixedDelay(700) + .withBody(BODY_TEXT) + .withStatus(200)))); Mono result = feignClient.testMethod(); @@ -98,6 +104,27 @@ public void shouldRetryAndNotFailOnDefaultCircuitBreakerTimeout() { assertThat(mockHttpServer2.getAllServeEvents().size()).isEqualTo(1); } + @Test + public void shouldNotFailWithLoadBalancingAndResponseEntity() { + Stream.of(mockHttpServer1, mockHttpServer2).forEach(wireMockServer -> + wireMockServer.stubFor(get(urlPathMatching(TEST_URL)) + .willReturn(aResponse() + .withHeader("header1", "headerValue") + .withBody(BODY_TEXT) + .withStatus(200)))); + + Mono>> result = feignClient.testMethodResponseEntity(); + + StepVerifier.create(result + .doOnNext(response -> assertThat(response.getHeaders().containsKey("header1")).isTrue()) + .flatMapMany(HttpEntity::getBody)) + .expectNext(BODY_TEXT) + .verifyComplete(); + + assertThat(mockHttpServer1.getAllServeEvents().size() + mockHttpServer2.getAllServeEvents().size()) + .isEqualTo(1); + } + @BeforeClass public static void setUp() { mockHttpServer1.start(); @@ -125,6 +152,9 @@ public interface TestReactiveFeignClient { @GetMapping(path = TEST_URL) Mono testMethod(); + @GetMapping(path = TEST_URL) + Mono>> testMethodResponseEntity(); + } @EnableReactiveFeignClients(clients = TestReactiveFeignClient.class) diff --git a/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/java/reactivefeign/spring/config/cloud2/QualifierTest.java b/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/java/reactivefeign/spring/config/cloud2/QualifierTest.java new file mode 100644 index 000000000..dcc9e46de --- /dev/null +++ b/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/java/reactivefeign/spring/config/cloud2/QualifierTest.java @@ -0,0 +1,108 @@ +package reactivefeign.spring.config.cloud2; + +import com.github.tomakehurst.wiremock.WireMockServer; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import reactivefeign.spring.config.EnableReactiveFeignClients; +import reactivefeign.spring.config.ReactiveFeignClient; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static java.util.Arrays.asList; +import static reactivefeign.spring.config.cloud2.BasicAutoconfigurationTest.MOCK_SERVER_PORT_PROPERTY; +import static reactivefeign.spring.config.cloud2.QualifierTest.RFGN_PROPS; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = QualifierTest.TestConfiguration.class, webEnvironment = SpringBootTest.WebEnvironment.NONE, + properties = { + "spring.cloud.discovery.client.simple.instances."+RFGN_PROPS+"[0].uri=http://localhost:${"+ MOCK_SERVER_PORT_PROPERTY+"}", + "reactive.feign.client.config.default.options.readTimeoutMillis=100", + "reactive.feign.client.config.rfgn-proper.options.readTimeoutMillis=500" + }) +@DirtiesContext +public class QualifierTest extends BasicAutoconfigurationTest{ + + public static final String RFGN_PROPS = "rfgn-proper"; + + private static final WireMockServer mockHttpServer = new WireMockServer(wireMockConfig().dynamicPort()); + + @Autowired + private PropsSampleClient propsSampleClient; + + @Autowired + private PropsSampleClientWithOtherQualifier propsSampleClientWithOtherQualifier; + + + @Test + public void shouldRunClientsWithSameNameAndDifferentQualifiers() { + mockHttpServer.stubFor(get(urlPathMatching("/sampleUrl")) + .willReturn(aResponse() + .withFixedDelay(300) + .withBody("OK"))); + + asList(propsSampleClient, propsSampleClientWithOtherQualifier).forEach(feignClient -> { + StepVerifier.create(feignClient.sampleMethod()) + .expectNext("OK") + .verifyComplete(); + }); + } + + @ReactiveFeignClient(name = RFGN_PROPS, qualifier = "FeignClient1") + protected interface PropsSampleClient extends SampleClient { + + @RequestMapping(method = RequestMethod.GET, value = "/sampleUrl") + Mono sampleMethod(); + } + + @ReactiveFeignClient(name = RFGN_PROPS, qualifier = "FeignClient2") + protected interface PropsSampleClientWithOtherQualifier extends SampleClient { + + @RequestMapping(method = RequestMethod.GET, value = "/sampleUrl") + Mono sampleMethod(); + } + + protected interface SampleClient{ + Mono sampleMethod(); + } + + @Configuration + @EnableAutoConfiguration + @EnableReactiveFeignClients( + clients = {PropsSampleClient.class, + PropsSampleClientWithOtherQualifier.class}) + protected static class TestConfiguration { + } + + @BeforeClass + public static void setupStubs() { + mockHttpServer.start(); + + System.setProperty(MOCK_SERVER_PORT_PROPERTY, Integer.toString(mockHttpServer.port())); + } + + @Before + public void reset() throws InterruptedException { + //to close circuit breaker + mockHttpServer.resetAll(); + } + + @AfterClass + public static void teardown() { + mockHttpServer.stop(); + } +} diff --git a/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/java/reactivefeign/spring/config/cloud2/SampleConfigurationsTest.java b/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/java/reactivefeign/spring/config/cloud2/SampleConfigurationsTest.java index 4d74aba9a..2359ff4e2 100644 --- a/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/java/reactivefeign/spring/config/cloud2/SampleConfigurationsTest.java +++ b/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/java/reactivefeign/spring/config/cloud2/SampleConfigurationsTest.java @@ -20,6 +20,7 @@ import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; import io.github.resilience4j.timelimiter.TimeLimiterConfig; +import io.github.resilience4j.timelimiter.TimeLimiterRegistry; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; @@ -90,7 +91,7 @@ public class SampleConfigurationsTest extends BasicAutoconfigurationTest{ public static final int UPDATE_INTERVAL = 5; public static final int SLEEP_WINDOW = 1000; - private static WireMockServer mockHttpServer = new WireMockServer(wireMockConfig().dynamicPort()); + private static final WireMockServer mockHttpServer = new WireMockServer(wireMockConfig().dynamicPort()); //configured via properties file @Autowired @@ -106,7 +107,7 @@ public class SampleConfigurationsTest extends BasicAutoconfigurationTest{ @Autowired private ErrorDecoderSampleClient errorDecoderSampleClient; - private static CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults(); + private static final CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults(); //this test checks that default readTimeoutMillis is overridden for each client // (one in via properties file and other via configuration class) @@ -118,7 +119,7 @@ public void shouldRetryAndFailOnRibbon() { .withBody("OK"))); asList(propertiesSampleClient, configsSampleClient).forEach(feignClient -> { - StepVerifier.create(propertiesSampleClient.sampleMethod()) + StepVerifier.create(feignClient.sampleMethod()) .expectErrorMatches(throwable -> throwable instanceof RuntimeException && throwable.getCause() instanceof OutOfRetriesException @@ -203,7 +204,7 @@ public void shouldOpenCircuitBreakerButNotWrapException() throws InterruptedExce } @ReactiveFeignClient(name = RFGN_PROPER) - protected interface PropertiesSampleClient { + protected interface PropertiesSampleClient extends SampleClient{ @RequestMapping(method = RequestMethod.GET, value = "/sampleUrl") Mono sampleMethod(); @@ -211,26 +212,29 @@ protected interface PropertiesSampleClient { @ReactiveFeignClient(name = RFGN_CONFIGS, configuration = ReactiveFeignSampleConfiguration.class) - protected interface ConfigsSampleClient { + protected interface ConfigsSampleClient extends SampleClient{ @RequestMapping(method = RequestMethod.GET, value = "/sampleUrl") Mono sampleMethod(); } - @ReactiveFeignClient(name = RFGN_FALLBACK, fallback = ReactiveFeignFallbackConfiguration.Fallback.class, configuration = ReactiveFeignFallbackConfiguration.class) - protected interface FallbackSampleClient { + protected interface FallbackSampleClient extends SampleClient{ @RequestMapping(method = RequestMethod.GET, value = "/sampleUrl") Mono sampleMethod(); } @ReactiveFeignClient(name = RFGN_ERRORDECODER, fallbackFactory = ErrorDecoderSkipFallbackFactory.class) - protected interface ErrorDecoderSampleClient { + protected interface ErrorDecoderSampleClient extends SampleClient{ @RequestMapping(method = RequestMethod.GET, value = "/sampleUrl") Mono sampleMethod(); } + protected interface SampleClient{ + Mono sampleMethod(); + } + protected static class ErrorDecoderSkipFallbackFactory implements FallbackFactory { @Override @@ -256,7 +260,10 @@ protected static class TestConfiguration { @Bean public ReactiveResilience4JCircuitBreakerFactory circuitBreakerFactory(){ - ReactiveResilience4JCircuitBreakerFactory circuitBreakerFactory = new ReactiveResilience4JCircuitBreakerFactory(); + ReactiveResilience4JCircuitBreakerFactory circuitBreakerFactory = new ReactiveResilience4JCircuitBreakerFactory( + CircuitBreakerRegistry.ofDefaults(), + TimeLimiterRegistry.ofDefaults() + ); circuitBreakerFactory.configureDefault(id -> new Resilience4JConfigBuilder(id) .circuitBreakerConfig(CircuitBreakerConfig.custom() .minimumNumberOfCalls(VOLUME_THRESHOLD) @@ -293,7 +300,7 @@ protected static class ReactiveFeignSampleConfiguration { @Bean public ReactiveOptions reactiveOptions(){ - return new WebReactiveOptions.Builder().setReadTimeoutMillis(500).build(); + return new WebReactiveOptions.Builder().setReadTimeoutMillis(300).build(); } @Bean diff --git a/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/java/reactivefeign/spring/config/cloud2/SleuthTest.java b/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/java/reactivefeign/spring/config/cloud2/SleuthTest.java deleted file mode 100644 index 0f4bded07..000000000 --- a/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/java/reactivefeign/spring/config/cloud2/SleuthTest.java +++ /dev/null @@ -1,130 +0,0 @@ -package reactivefeign.spring.config.cloud2; - - -import brave.Span; -import brave.Tracer; -import brave.handler.SpanHandler; -import brave.sampler.Sampler; -import brave.test.TestSpanHandler; -import org.junit.After; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.sleuth.CurrentTraceContext; -import org.springframework.cloud.sleuth.TraceContext; -import org.springframework.cloud.sleuth.brave.bridge.BraveTraceContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpHeaders; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; -import reactivefeign.spring.config.EnableReactiveFeignClients; -import reactivefeign.spring.config.ReactiveFeignClient; -import reactor.core.publisher.Mono; - -import java.util.HashMap; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static reactivefeign.spring.config.cloud2.SleuthTest.FEIGN_CLIENT_TEST_SLEUTH; - -@RunWith(SpringRunner.class) -@SpringBootTest(classes = {SleuthTest.TestConfiguration.class, SleuthTest.TestController.class}, - webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) -@TestPropertySource(properties = { - "spring.sleuth.enabled=true", - "spring.cloud.discovery.client.simple.instances."+ FEIGN_CLIENT_TEST_SLEUTH +"[0].uri=http://localhost:8080"}, - locations = "classpath:common.properties") -public class SleuthTest { - - static final String TRACE_ID_NAME = "X-B3-TraceId"; - static final String SPAN_ID_NAME = "X-B3-SpanId"; - static final String PARENT_SPAN_ID_NAME = "X-B3-ParentSpanId"; - - static final String FEIGN_CLIENT_TEST_SLEUTH = "feign-client-test-sleuth"; - - @Autowired - TestFeignInterface feignClient; - - @Autowired - TestSpanHandler spans; - - @Autowired - Tracer tracer; - - @Autowired - CurrentTraceContext currentTraceContext; - - @After - public void reset() { - this.spans.clear(); - } - - @Test - public void shouldKeepOriginalTraceId() { - Span span = this.tracer.nextSpan().name("foo").start(); - - try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { - - Map response = feignClient.headers() - .contextWrite(context -> context.put(TraceContext.class, new BraveTraceContext(span.context()))) - .block(); - - String currentTraceId = tracer.currentSpan().context().traceIdString(); - String currentSpanId = tracer.currentSpan().context().spanIdString(); - - assertThat(response.get(TRACE_ID_NAME)).isEqualTo(currentTraceId); - assertThat(response.get(PARENT_SPAN_ID_NAME)).isEqualTo(currentSpanId); - } - finally { - span.finish(); - } - - assertThat(this.tracer.currentSpan()).isNull(); - assertThat(this.spans.spans()).isNotEmpty(); - } - - @ReactiveFeignClient(name = FEIGN_CLIENT_TEST_SLEUTH) - public interface TestFeignInterface { - - @RequestMapping(method = RequestMethod.GET, value = "/") - Mono> headers(); - - } - - @Configuration - @EnableAutoConfiguration - @EnableReactiveFeignClients(clients = TestFeignInterface.class) - public static class TestConfiguration { - @Bean - Sampler sampler() { - return Sampler.ALWAYS_SAMPLE; - } - - @Bean - SpanHandler testSpanHandler() { - return new TestSpanHandler(); - } - } - - @RestController - public static class TestController { - - @RequestMapping("/") - public Map headers(@RequestHeader HttpHeaders headers) { - Map map = new HashMap<>(); - for (String key : headers.keySet()) { - map.put(key, headers.getFirst(key)); - } - return map; - } - - } -} \ No newline at end of file diff --git a/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/resources/common.properties b/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/resources/common.properties index a61aaa3a9..18d6df7fc 100644 --- a/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/resources/common.properties +++ b/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/resources/common.properties @@ -1,2 +1 @@ -eureka.client.enabled=false -spring.sleuth.enabled=false \ No newline at end of file +eureka.client.enabled=false \ No newline at end of file diff --git a/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/resources/error-decoder.properties b/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/resources/error-decoder.properties index 57ad1a912..5feb86866 100644 --- a/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/resources/error-decoder.properties +++ b/feign-reactor-test/feign-reactor-spring-configuration-cloud2-test/src/test/resources/error-decoder.properties @@ -5,4 +5,6 @@ reactive.feign.client.config.rfgn-proper.retryOnSame.builder=reactivefeign.retry reactive.feign.client.config.rfgn-proper.retryOnSame.args.maxRetries=1 reactive.feign.client.config.rfgn-proper.retryOnSame.args.backoffInMs=10 +reactive.feign.client.config.rfgn-configs.defaultToProperties=false + reactive.feign.client.config.rfgn-errordecoder.errorDecoder=reactivefeign.spring.config.cloud2.ErrorDecoder \ No newline at end of file diff --git a/feign-reactor-test/feign-reactor-spring-configuration-test/pom.xml b/feign-reactor-test/feign-reactor-spring-configuration-test/pom.xml index d638e49bf..7e4aede9d 100644 --- a/feign-reactor-test/feign-reactor-spring-configuration-test/pom.xml +++ b/feign-reactor-test/feign-reactor-spring-configuration-test/pom.xml @@ -5,7 +5,7 @@ com.playtika.reactivefeign feign-reactor-test - 3.2.6 + 4.0.3 feign-reactor-spring-configuration-test @@ -83,14 +83,24 @@ test - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test org.apache.logging.log4j - log4j-slf4j-impl + log4j-api + test + + + org.apache.logging.log4j + log4j-core + test + + + org.apache.logging.log4j + log4j-slf4j2-impl test @@ -105,6 +115,12 @@ awaitility test + + + io.micrometer + micrometer-core + test + \ No newline at end of file diff --git a/feign-reactor-test/feign-reactor-spring-configuration-test/src/test/java/reactivefeign/spring/config/ReactiveFeignClientUsingPropertiesTests.java b/feign-reactor-test/feign-reactor-spring-configuration-test/src/test/java/reactivefeign/spring/config/ReactiveFeignClientUsingPropertiesTests.java index ab3342f3c..7d81179db 100644 --- a/feign-reactor-test/feign-reactor-spring-configuration-test/src/test/java/reactivefeign/spring/config/ReactiveFeignClientUsingPropertiesTests.java +++ b/feign-reactor-test/feign-reactor-spring-configuration-test/src/test/java/reactivefeign/spring/config/ReactiveFeignClientUsingPropertiesTests.java @@ -17,6 +17,12 @@ package reactivefeign.spring.config; import com.github.tomakehurst.wiremock.WireMockServer; +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.After; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; @@ -37,11 +43,16 @@ import java.util.Collections; -import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static reactivefeign.spring.config.WebClientCustomizerTest.MOCK_SERVER_PORT_PROPERTY; +import static reactor.netty.Metrics.ACTIVE_CONNECTIONS; +import static reactor.netty.Metrics.CONNECTION_PROVIDER_PREFIX; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = ReactiveFeignClientUsingPropertiesTests.Application.class, webEnvironment = WebEnvironment.NONE) @@ -69,6 +80,8 @@ public class ReactiveFeignClientUsingPropertiesTests { @Autowired private MultipleDefaultQueryClient multipleSingleDefaultQueryClient; + private MeterRegistry meterRegistry; + @BeforeClass public static void setupStubs() { @@ -82,6 +95,10 @@ public static void setupStubs() { .withFixedDelay(1000) .withBody("OK"))); + mockHttpServer.stubFor(get(urlEqualTo("/barMetered")) + .willReturn(aResponse() + .withBody("OK"))); + mockHttpServer.stubFor(get(urlEqualTo("/header")) .withHeader("header", equalTo("value")) .willReturn(aResponse().withBody("OK"))); @@ -102,6 +119,19 @@ public static void setupStubs() { System.setProperty(MOCK_SERVER_PORT_PROPERTY, Integer.toString(mockHttpServer.port())); } + @Before + public void setUp(){ + meterRegistry = new SimpleMeterRegistry(); + Metrics.addRegistry(meterRegistry); + } + + @After + public void tearDown() { + Metrics.removeRegistry(meterRegistry); + meterRegistry.clear(); + meterRegistry.close(); + } + @Test public void testFoo() { String response = fooClient.foo().block(); @@ -138,6 +168,21 @@ public void testBar() { fail("it should timeout"); } + @Test + public void testBarMetered() { + + String response = barClient.barMetered() + .doOnNext(s -> { + Metrics.globalRegistry.forEachMeter(meter -> { + Gauge activeConnections = meterRegistry.find(CONNECTION_PROVIDER_PREFIX + ACTIVE_CONNECTIONS).gauge(); + assertEquals(1., activeConnections.value(), 0.); + }); + }) + .block(); + + assertEquals("OK", response); + } + @ReactiveFeignClient(name = "foo", url = "http://localhost:${" + MOCK_SERVER_PORT_PROPERTY+"}") protected interface FooClient { @@ -150,6 +195,9 @@ protected interface BarClient { @RequestMapping(method = RequestMethod.GET, value = "/bar") Mono bar(); + + @RequestMapping(method = RequestMethod.GET, value = "/barMetered") + Mono barMetered(); } @ReactiveFeignClient(name = "header", url = "http://localhost:${" + MOCK_SERVER_PORT_PROPERTY+"}") diff --git a/feign-reactor-test/feign-reactor-spring-configuration-test/src/test/resources/reactive-feign-properties.properties b/feign-reactor-test/feign-reactor-spring-configuration-test/src/test/resources/reactive-feign-properties.properties index af05814d7..1b1e3a430 100644 --- a/feign-reactor-test/feign-reactor-spring-configuration-test/src/test/resources/reactive-feign-properties.properties +++ b/feign-reactor-test/feign-reactor-spring-configuration-test/src/test/resources/reactive-feign-properties.properties @@ -20,4 +20,6 @@ reactive.feign.client.config.queries.defaultQueryParameters[query2]=value2 reactive.feign.client.config.bar.options.connectTimeoutMillis=500 -reactive.feign.client.config.bar.options.readTimeoutMillis=500 \ No newline at end of file +reactive.feign.client.config.bar.options.readTimeoutMillis=500 +reactive.feign.client.config.bar.options.connectionMetricsEnabled=true +reactive.feign.client.config.bar.options.metricsEnabled=true \ No newline at end of file diff --git a/feign-reactor-test/feign-reactor-spring-mvc-test/pom.xml b/feign-reactor-test/feign-reactor-spring-mvc-test/pom.xml index 83d44b983..495169240 100644 --- a/feign-reactor-test/feign-reactor-spring-mvc-test/pom.xml +++ b/feign-reactor-test/feign-reactor-spring-mvc-test/pom.xml @@ -5,7 +5,7 @@ com.playtika.reactivefeign feign-reactor-test - 3.2.6 + 4.0.3 feign-reactor-spring-mvc-test @@ -111,14 +111,24 @@ test - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test org.apache.logging.log4j - log4j-slf4j-impl + log4j-api + test + + + org.apache.logging.log4j + log4j-core + test + + + org.apache.logging.log4j + log4j-slf4j2-impl test diff --git a/feign-reactor-test/feign-reactor-spring-mvc-test/src/test/java/reactivefeign/spring/mvc/MultiPartTest.java b/feign-reactor-test/feign-reactor-spring-mvc-test/src/test/java/reactivefeign/spring/mvc/MultiPartTest.java index a5564f6b0..cc1ca6b29 100644 --- a/feign-reactor-test/feign-reactor-spring-mvc-test/src/test/java/reactivefeign/spring/mvc/MultiPartTest.java +++ b/feign-reactor-test/feign-reactor-spring-mvc-test/src/test/java/reactivefeign/spring/mvc/MultiPartTest.java @@ -7,9 +7,9 @@ import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; -import org.springframework.boot.web.server.LocalServerPort; import org.springframework.cloud.openfeign.support.SpringMvcContract; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/feign-reactor-test/feign-reactor-spring-mvc-test/src/test/java/reactivefeign/spring/mvc/allfeatures/AllFeaturesMvc.java b/feign-reactor-test/feign-reactor-spring-mvc-test/src/test/java/reactivefeign/spring/mvc/allfeatures/AllFeaturesMvc.java index d638586f7..50ac7d140 100644 --- a/feign-reactor-test/feign-reactor-spring-mvc-test/src/test/java/reactivefeign/spring/mvc/allfeatures/AllFeaturesMvc.java +++ b/feign-reactor-test/feign-reactor-spring-mvc-test/src/test/java/reactivefeign/spring/mvc/allfeatures/AllFeaturesMvc.java @@ -20,7 +20,14 @@ import org.springframework.cloud.openfeign.CollectionFormat; import org.springframework.cloud.openfeign.SpringQueryMap; import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.web.bind.annotation.*; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -158,9 +165,24 @@ Flux mirrorStringBodyStream( @GetMapping(path = "/expand/{timestamp}") Mono expandPathParameter(@PathVariable("timestamp") long timestamp); + @Override + @GetMapping(path = "/Invoices?filter=Company:{companyName}") + Mono expandPathParameterInRequestParameter(@RequestParam("companyName") String companyName); + @GetMapping(path = "/expand") Mono expandDataTimeParameterWithCustomFormat( @DateTimeFormat(pattern = DATE_TIME_FORMAT) @RequestParam("dateTime") LocalDateTime dateTime); + + @PostMapping(path = "/formDataMap", + consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE}) + Mono formDataMap(Map form); + + @PostMapping(path = "/formDataParameters", + consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE}) + Mono formDataParameters( + @RequestPart("key1") String organizationName, + @RequestPart("key2") String organizationId); + } diff --git a/feign-reactor-test/pom.xml b/feign-reactor-test/pom.xml index a7bd87611..24e8241d1 100644 --- a/feign-reactor-test/pom.xml +++ b/feign-reactor-test/pom.xml @@ -6,7 +6,7 @@ com.playtika.reactivefeign feign-reactor-parent ../feign-reactor-parent - 3.2.6 + 4.0.3 feign-reactor-test diff --git a/feign-reactor-webclient-apache-client5/pom.xml b/feign-reactor-webclient-apache-client5/pom.xml new file mode 100644 index 000000000..9b0f6dade --- /dev/null +++ b/feign-reactor-webclient-apache-client5/pom.xml @@ -0,0 +1,178 @@ + + + 4.0.0 + + + com.playtika.reactivefeign + feign-reactor-parent + ../feign-reactor-parent + 4.0.3 + + + feign-reactor-webclient-apache-client5 + WebClient with Apache Http Client 5 HttpComponentsClientHttpConnector + + + + com.playtika.reactivefeign + feign-reactor-webclient-core + + + + org.springframework + spring-webflux + + + + org.springframework + spring-web + + + + + org.apache.httpcomponents.client5 + httpclient5 + + + org.apache.httpcomponents.core5 + httpcore5 + + + org.apache.httpcomponents.core5 + httpcore5-h2 + + + org.apache.httpcomponents.core5 + httpcore5-reactive + + + + + com.playtika.reactivefeign + feign-reactor-core + test-jar + test + + + com.playtika.reactivefeign + feign-reactor-webclient-core + test-jar + test + + + com.playtika.reactivefeign + feign-reactor-server-configurations + test-jar + test + + + org.springframework.boot + spring-boot-starter-webflux + + + spring-boot-starter-logging + org.springframework.boot + + + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.hamcrest + hamcrest-core + + + + + io.projectreactor + reactor-test + test + + + org.wiremock + wiremock-standalone + test + + + + org.eclipse.jetty + jetty-server + test + + + + org.eclipse.jetty.ee10 + jetty-ee10-servlets + test + + + + org.eclipse.jetty.http2 + jetty-http2-server + test + + + + org.eclipse.jetty.ee10 + jetty-ee10-webapp + test + + + + org.apache.logging.log4j + log4j-api + test + + + org.apache.logging.log4j + log4j-core + test + + + org.apache.logging.log4j + log4j-slf4j2-impl + test + + + + junit + junit + test + + + + org.awaitility + awaitility + test + + + + io.projectreactor.tools + blockhound + test + + + + com.lmax + disruptor + test + + + + io.micrometer + micrometer-core + test + + + + + \ No newline at end of file diff --git a/feign-reactor-webclient-apache-client5/src/main/java/reactivefeign/webclient/client5/HttpClient5HttpConnectorBuilder.java b/feign-reactor-webclient-apache-client5/src/main/java/reactivefeign/webclient/client5/HttpClient5HttpConnectorBuilder.java new file mode 100644 index 000000000..563c03bde --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/main/java/reactivefeign/webclient/client5/HttpClient5HttpConnectorBuilder.java @@ -0,0 +1,101 @@ +package reactivefeign.webclient.client5; + +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.H2AsyncClientBuilder; +import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; +import org.apache.hc.client5.http.impl.async.HttpAsyncClients; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager; +import org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner; +import org.apache.hc.core5.http.HttpHost; +import org.springframework.http.client.reactive.ClientHttpConnector; +import org.springframework.http.client.reactive.HttpComponentsClientHttpConnector; +import reactivefeign.ReactiveOptions; + +import java.util.concurrent.TimeUnit; + +class HttpClient5HttpConnectorBuilder { + + public static ClientHttpConnector buildHttp5ClientHttpConnector(HttpClient5ReactiveOptions options) { + + RequestConfig requestConfig = buildRequestConfig(options); + + CloseableHttpAsyncClient client; + if(ReactiveOptions.useHttp2(options)){ + H2AsyncClientBuilder clientBuilder = HttpAsyncClients.customHttp2() + .setDefaultRequestConfig(requestConfig) + .disableAutomaticRetries(); + + ReactiveOptions.ProxySettings proxySettings = options.getProxySettings(); + if (proxySettings != null) { + clientBuilder = clientBuilder.setRoutePlanner(new DefaultProxyRoutePlanner( + new HttpHost(proxySettings.getHost(), proxySettings.getPort()))); + } + + client = clientBuilder.build(); + } else { + HttpAsyncClientBuilder clientBuilder = HttpAsyncClients.custom() + .setDefaultRequestConfig(requestConfig) + .disableAutomaticRetries(); + + clientBuilder = clientBuilder.setConnectionManager(buildConnectionManager(options)); + + if(options != null) { + ReactiveOptions.ProxySettings proxySettings = options.getProxySettings(); + if (proxySettings != null) { + clientBuilder = clientBuilder.setRoutePlanner(new DefaultProxyRoutePlanner( + new HttpHost(proxySettings.getHost(), proxySettings.getPort()))); + } + } + + client = clientBuilder.build(); + } + + return new HttpComponentsClientHttpConnector(client); + } + + private static PoolingAsyncClientConnectionManager buildConnectionManager(HttpClient5ReactiveOptions options) { + + PoolingAsyncClientConnectionManager connectionManager = new PoolingAsyncClientConnectionManager(); + if(options != null){ + if(options.getConnectionsDefaultMaxPerRoute() != null){ + connectionManager.setDefaultMaxPerRoute(options.getConnectionsDefaultMaxPerRoute()); + } + if(options.getConnectionsMaxTotal() != null){ + connectionManager.setMaxTotal(options.getConnectionsMaxTotal()); + } + } + + return connectionManager; + } + + private static RequestConfig buildRequestConfig(HttpClient5ReactiveOptions options) { + RequestConfig.Builder configBuilder = options != null && options.getRequestConfig() != null + ? RequestConfig.copy(options.getRequestConfig()) : RequestConfig.custom(); + + if(options != null) { + if (options.getConnectTimeoutMillis() != null) { + configBuilder = configBuilder.setConnectTimeout(options.getConnectTimeoutMillis(), TimeUnit.MILLISECONDS); + } + if(options.getSocketTimeoutMillis() != null){ + configBuilder = configBuilder.setResponseTimeout(options.getSocketTimeoutMillis(), TimeUnit.MILLISECONDS); + } + + + if (options.isFollowRedirects() != null) { + configBuilder = configBuilder.setRedirectsEnabled(options.isFollowRedirects()); + } + +// if (options.getProxySettings() != null) { +// ReactiveOptions.ProxySettings proxySettings = options.getProxySettings(); +// configBuilder = configBuilder.setProxy(new HttpHost(proxySettings.getHost(), proxySettings.getPort())); +// } + + if (options.isTryUseCompression() != null) { + configBuilder = configBuilder.setContentCompressionEnabled(options.isTryUseCompression()); + } + } + + return configBuilder.build(); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/main/java/reactivefeign/webclient/client5/HttpClient5ReactiveOptions.java b/feign-reactor-webclient-apache-client5/src/main/java/reactivefeign/webclient/client5/HttpClient5ReactiveOptions.java new file mode 100644 index 000000000..8be1bfc17 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/main/java/reactivefeign/webclient/client5/HttpClient5ReactiveOptions.java @@ -0,0 +1,99 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5; + +import org.apache.hc.client5.http.config.RequestConfig; +import reactivefeign.ReactiveOptions; + +/** + * @author Sergii Karpenko + */ +public class HttpClient5ReactiveOptions extends ReactiveOptions { + + private final RequestConfig requestConfig; + + private final Integer socketTimeoutMillis; + private final Integer connectionsDefaultMaxPerRoute; + private final Integer connectionsMaxTotal; + + private HttpClient5ReactiveOptions(Boolean useHttp2, Long connectTimeoutMillis, + Boolean tryUseCompression, Boolean followRedirects, + ProxySettings proxySettings, + RequestConfig requestConfig, + Integer socketTimeoutMillis, + Integer connectionsDefaultMaxPerRoute, Integer connectionsMaxTotal) { + super(useHttp2, connectTimeoutMillis, tryUseCompression, followRedirects, proxySettings); + + this.requestConfig = requestConfig; + this.socketTimeoutMillis = socketTimeoutMillis; + this.connectionsDefaultMaxPerRoute = connectionsDefaultMaxPerRoute; + this.connectionsMaxTotal = connectionsMaxTotal; + } + + public RequestConfig getRequestConfig() { + return requestConfig; + } + + public Integer getSocketTimeoutMillis() { + return socketTimeoutMillis; + } + + public Integer getConnectionsDefaultMaxPerRoute() { + return connectionsDefaultMaxPerRoute; + } + + public Integer getConnectionsMaxTotal() { + return connectionsMaxTotal; + } + + public static class Builder extends ReactiveOptions.Builder{ + private RequestConfig requestConfig; + + private Integer socketTimeoutMillis; + + private Integer connectionsDefaultMaxPerRoute; + private Integer connectionsMaxTotal; + + public Builder() {} + + public Builder setRequestConfig(RequestConfig requestConfig) { + this.requestConfig = requestConfig; + return this; + } + + public Builder setSocketTimeout(Integer socketTimeoutMillis) { + this.socketTimeoutMillis = socketTimeoutMillis; + return this; + } + + public Builder setConnectionsDefaultMaxPerRoute(Integer connectionsDefaultMaxPerRoute) { + this.connectionsDefaultMaxPerRoute = connectionsDefaultMaxPerRoute; + return this; + } + + public Builder setConnectionsMaxTotal(Integer connectionsMaxTotal) { + this.connectionsMaxTotal = connectionsMaxTotal; + return this; + } + + public HttpClient5ReactiveOptions build() { + return new HttpClient5ReactiveOptions(useHttp2, connectTimeoutMillis, + acceptCompressed, followRedirects, proxySettings, + requestConfig, + socketTimeoutMillis, + connectionsDefaultMaxPerRoute, connectionsMaxTotal); + } + } + +} diff --git a/feign-reactor-webclient-apache-client5/src/main/java/reactivefeign/webclient/client5/HttpClient5WebReactiveFeign.java b/feign-reactor-webclient-apache-client5/src/main/java/reactivefeign/webclient/client5/HttpClient5WebReactiveFeign.java new file mode 100644 index 000000000..4ebd3bc44 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/main/java/reactivefeign/webclient/client5/HttpClient5WebReactiveFeign.java @@ -0,0 +1,89 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5; + +import org.springframework.http.client.reactive.ClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientRequestException; +import reactivefeign.ReactiveOptions; +import reactivefeign.client.ReactiveHttpRequest; +import reactivefeign.client.ReadTimeoutException; +import reactivefeign.webclient.CoreWebBuilder; +import reactivefeign.webclient.WebClientFeignCustomizer; + +import java.util.function.BiFunction; + +import static reactivefeign.webclient.client5.HttpClient5HttpConnectorBuilder.buildHttp5ClientHttpConnector; + + +/** + * {@link WebClient} based implementation of reactive Feign + * + * @author Sergii Karpenko + */ +public class HttpClient5WebReactiveFeign { + + public static Builder builder() { + return builder(WebClient.builder()); + } + + public static Builder builder(WebClient.Builder webClientBuilder) { + return new Builder<>(webClientBuilder); + } + + public static Builder builder(WebClient.Builder webClientBuilder, + WebClientFeignCustomizer webClientCustomizer) { + return new Builder<>(webClientBuilder, webClientCustomizer); + } + + public static class Builder extends CoreWebBuilder { + + private HttpClient5ReactiveOptions reactiveOptions; + + protected Builder(WebClient.Builder webClientBuilder) { + super(webClientBuilder); + } + + protected Builder(WebClient.Builder webClientBuilder, WebClientFeignCustomizer webClientCustomizer) { + super(webClientBuilder, webClientCustomizer); + } + + @Override + public Builder options(ReactiveOptions options) { + this.reactiveOptions = (HttpClient5ReactiveOptions)options; + return this; + } + + @Override + public BiFunction errorMapper(){ + return (request, throwable) -> { + if(throwable instanceof WebClientRequestException + && throwable.getCause() instanceof java.util.concurrent.TimeoutException){ + return new ReadTimeoutException(throwable, request); + } + return null; + }; + } + + @Override + protected ClientHttpConnector clientConnector() { + + return buildHttp5ClientHttpConnector(reactiveOptions); + } + + } + +} + + diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/AllFeaturesTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/AllFeaturesTest.java new file mode 100644 index 000000000..77db8ec28 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/AllFeaturesTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed 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 reactivefeign.webclient.client5.h1; + +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; +import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; +import org.springframework.test.context.ActiveProfiles; +import reactivefeign.ReactiveFeign; +import reactivefeign.allfeatures.AllFeaturesFeign; +import reactivefeign.allfeatures.AllFeaturesFeignTest; +import reactivefeign.webclient.client5.HttpClient5WebReactiveFeign; + +/** + * @author Sergii Karpenko + * + * Tests ReactiveFeign in conjunction with WebFlux rest controller. + */ +@EnableAutoConfiguration(exclude = {ReactiveSecurityAutoConfiguration.class, ReactiveUserDetailsServiceAutoConfiguration.class}) +@ActiveProfiles("netty") +public class AllFeaturesTest extends AllFeaturesFeignTest { + + @Override + protected ReactiveFeign.Builder builder() { + return HttpClient5WebReactiveFeign.builder(); + } + + //Need specific client options configuration + /*.setConnectionsMaxTotal(1000) + .setConnectionsDefaultMaxPerRoute(1000)*/ + @Ignore + @Override + @Test + public void shouldRunReactively() { + } + + @Ignore + @Override + @Test + public void shouldMirrorStreamingBinaryBodyReactive() { + } + + //Apache's WebClient is not able to do this trick + @Ignore + @Test + @Override + public void shouldReturnFirstResultBeforeSecondSent() { + } + + @Ignore + @Test + @Override + public void shouldMirrorStringStreamBody() { + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/BasicFeaturesTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/BasicFeaturesTest.java new file mode 100644 index 000000000..93bf83d93 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/BasicFeaturesTest.java @@ -0,0 +1,39 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h1; + +import com.fasterxml.jackson.core.io.JsonEOFException; +import org.springframework.core.codec.DecodingException; +import reactivefeign.ReactiveFeign; + +import java.util.function.Predicate; + +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttp; + +/** + * @author Sergii Karpenko + */ +public class BasicFeaturesTest extends reactivefeign.BasicFeaturesTest { + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp(); + } + + @Override + protected Predicate corruptedJsonError() { + return throwable -> throwable instanceof DecodingException + && throwable.getCause() instanceof JsonEOFException; + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/CompressionTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/CompressionTest.java new file mode 100644 index 000000000..5913b5ed0 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/CompressionTest.java @@ -0,0 +1,33 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h1; + +import org.junit.Ignore; +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttpWithAcceptCompressed; + +/** + * @author Sergii Karpenko + */ +//TODO Investigate why not working +@Ignore +public class CompressionTest extends reactivefeign.CompressionTest { + + @Override + protected ReactiveFeign.Builder builder(boolean tryUseCompression) { + return builderHttpWithAcceptCompressed(tryUseCompression); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/ConnectionTimeoutTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/ConnectionTimeoutTest.java new file mode 100644 index 000000000..9e86226c2 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/ConnectionTimeoutTest.java @@ -0,0 +1,30 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h1; + +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttpWithConnectTimeout; + +/** + * @author Sergii Karpenko + */ +public class ConnectionTimeoutTest extends reactivefeign.ConnectionTimeoutTest { + + @Override + protected ReactiveFeign.Builder builder(long connectTimeoutInMillis) { + return builderHttpWithConnectTimeout(connectTimeoutInMillis); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/ContractTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/ContractTest.java new file mode 100644 index 000000000..91859db65 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/ContractTest.java @@ -0,0 +1,29 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h1; + +import reactivefeign.ReactiveFeign; + +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttp; + +/** + * @author Sergii Karpenko + */ +public class ContractTest extends reactivefeign.ContractTest { + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp(); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/DefaultMethodTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/DefaultMethodTest.java new file mode 100644 index 000000000..e70a95b6f --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/DefaultMethodTest.java @@ -0,0 +1,42 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h1; + +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; +import reactivefeign.webclient.client5.HttpClient5WebReactiveFeign; + +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttp; +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttpWithConnectTimeout; + +/** + * @author Sergii Karpenko + */ +public class DefaultMethodTest extends reactivefeign.DefaultMethodTest { + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp(); + } + + @Override + protected ReactiveFeign.Builder builder(Class apiClass) { + return HttpClient5WebReactiveFeign.builder(); + } + + @Override + protected ReactiveFeign.Builder builder(long connectTimeoutInMillis) { + return builderHttpWithConnectTimeout(connectTimeoutInMillis); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/ErrorMapperTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/ErrorMapperTest.java new file mode 100644 index 000000000..4bb609c21 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/ErrorMapperTest.java @@ -0,0 +1,30 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h1; + +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttp; + +/** + * @author Sergii Karpenko + */ +public class ErrorMapperTest extends reactivefeign.ErrorMapperTest { + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp(); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/FallbackTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/FallbackTest.java new file mode 100644 index 000000000..c56c64339 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/FallbackTest.java @@ -0,0 +1,29 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h1; + +import reactivefeign.ReactiveFeign; + +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttp; + +/** + * @author Sergii Karpenko + */ +public class FallbackTest extends reactivefeign.FallbackTest { + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp(); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/LoggerTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/LoggerTest.java new file mode 100644 index 000000000..4575de018 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/LoggerTest.java @@ -0,0 +1,57 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h1; + +import org.junit.Ignore; +import org.junit.Test; +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttp; +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttpWithSocketTimeout; + +/** + * @author Sergii Karpenko + */ +public class LoggerTest extends reactivefeign.LoggerTest { + + //TODO investigate why socket timeout doesn't work + @Ignore + @Override + @Test + public void shouldLogTimeout() { + } + + @Override + protected String appenderPrefix(){ + return "h1_"; + } + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp(); + } + + @Override + protected ReactiveFeign.Builder builder(long readTimeoutInMillis) { + return builderHttpWithSocketTimeout(readTimeoutInMillis); + } + + @Override + protected Class target(){ + return IcecreamServiceApiJettyH1.class; + } + + interface IcecreamServiceApiJettyH1 extends IcecreamServiceApi{} +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/MetricsTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/MetricsTest.java new file mode 100644 index 000000000..b6d85c496 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/MetricsTest.java @@ -0,0 +1,46 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h1; + +import org.junit.Ignore; +import org.junit.Test; +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttp; +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttpWithSocketTimeout; + +/** + * @author Sergii Karpenko + */ +public class MetricsTest extends reactivefeign.MetricsTest { + + //TODO investigate why socket timeout doesn't work + @Ignore + @Override + @Test + public void shouldLogTimeout() { + } + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp(); + } + + @Override + protected ReactiveFeign.Builder builder(long readTimeoutInMillis) { + return builderHttpWithSocketTimeout(readTimeoutInMillis); + } + +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/MultiPartTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/MultiPartTest.java new file mode 100644 index 000000000..fc65f54e4 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/MultiPartTest.java @@ -0,0 +1,14 @@ +package reactivefeign.webclient.client5.h1; + +import reactivefeign.ReactiveFeignBuilder; + +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttp; + +public class MultiPartTest extends reactivefeign.MultiPartTest { + + @Override + protected ReactiveFeignBuilder builder() { + return builderHttp(); + } + +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/NotFoundTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/NotFoundTest.java new file mode 100644 index 000000000..c56e505bd --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/NotFoundTest.java @@ -0,0 +1,31 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h1; + +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttp; + +/** + * @author Sergii Karpenko + */ +public class NotFoundTest extends reactivefeign.NotFoundTest { + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp(); + } + +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/ObjectMapperTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/ObjectMapperTest.java new file mode 100644 index 000000000..49b7d6027 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/ObjectMapperTest.java @@ -0,0 +1,30 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h1; + +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttp; + +/** + * @author Sergii Karpenko + */ +public class ObjectMapperTest extends reactivefeign.ObjectMapperTest { + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp(); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/OptionsTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/OptionsTest.java new file mode 100644 index 000000000..df36ea9e5 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/OptionsTest.java @@ -0,0 +1,53 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h1; + +import org.junit.Ignore; +import org.junit.Test; +import reactivefeign.ReactiveFeign; +import reactivefeign.ReactiveFeignBuilder; +import reactivefeign.ReactiveOptions; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttpFollowRedirects; +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttpProxy; +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttpWithSocketTimeout; + +/** + * @author Sergii Karpenko + */ +public class OptionsTest extends reactivefeign.OptionsTest { + + //TODO investigate why socket timeout doesn't work + @Ignore + @Override + @Test + public void shouldFailOnReadTimeout() { + } + + @Override + protected ReactiveFeign.Builder builder(long readTimeoutInMillis) { + return builderHttpWithSocketTimeout(readTimeoutInMillis); + } + + @Override + protected ReactiveFeignBuilder builder(boolean followRedirects) { + return builderHttpFollowRedirects(followRedirects); + } + + @Override + protected ReactiveFeignBuilder builder(ReactiveOptions.ProxySettings proxySettings) { + return builderHttpProxy(proxySettings); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/ReactivityTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/ReactivityTest.java new file mode 100644 index 000000000..bf2baf0f6 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/ReactivityTest.java @@ -0,0 +1,37 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h1; + +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; +import reactivefeign.webclient.client5.HttpClient5ReactiveOptions; +import reactivefeign.webclient.client5.HttpClient5WebReactiveFeign; + +public class ReactivityTest extends reactivefeign.ReactivityTest { + + @Override + protected ReactiveFeign.Builder builder() { + + int socketTimeout = timeToCompleteReactively(); + + return HttpClient5WebReactiveFeign.builder().options( + new HttpClient5ReactiveOptions.Builder() + .setSocketTimeout(socketTimeout) + .setConnectTimeoutMillis(socketTimeout / 2) + .setConnectionsMaxTotal(1000) + .setConnectionsDefaultMaxPerRoute(1000) + .build() + ); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/RequestInterceptorTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/RequestInterceptorTest.java new file mode 100644 index 000000000..60ba406f2 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/RequestInterceptorTest.java @@ -0,0 +1,31 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h1; + +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttp; + +/** + * @author Sergii Karpenko + */ +public class RequestInterceptorTest extends reactivefeign.RequestInterceptorTest { + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp(); + } + +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/ResponseMapperTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/ResponseMapperTest.java new file mode 100644 index 000000000..3166ceb76 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/ResponseMapperTest.java @@ -0,0 +1,30 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h1; + +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttp; + +/** + * @author Sergii Karpenko + */ +public class ResponseMapperTest extends reactivefeign.ResponseMapperTest { + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp(); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/RetryingTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/RetryingTest.java new file mode 100644 index 000000000..d0cde72e9 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/RetryingTest.java @@ -0,0 +1,31 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h1; + +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttp; + +/** + * @author Sergii Karpenko + */ +public class RetryingTest extends reactivefeign.RetryingTest { + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp(); + } + +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/SmokeTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/SmokeTest.java new file mode 100644 index 000000000..35b2ce614 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/SmokeTest.java @@ -0,0 +1,31 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h1; + +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttp; + +/** + * @author Sergii Karpenko + */ +public class SmokeTest extends reactivefeign.SmokeTest { + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp(); + } + +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/StatusHandlerTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/StatusHandlerTest.java new file mode 100644 index 000000000..ceeb72c78 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/StatusHandlerTest.java @@ -0,0 +1,30 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h1; + +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h1.TestUtils.builderHttp; + +/** + * @author Sergii Karpenko + */ +public class StatusHandlerTest extends reactivefeign.StatusHandlerTest { + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp(); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/TestUtils.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/TestUtils.java new file mode 100644 index 000000000..32ad69ce1 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h1/TestUtils.java @@ -0,0 +1,55 @@ +package reactivefeign.webclient.client5.h1; + +import reactivefeign.ReactiveFeign; +import reactivefeign.ReactiveOptions; +import reactivefeign.webclient.client5.HttpClient5ReactiveOptions; +import reactivefeign.webclient.client5.HttpClient5WebReactiveFeign; + +public class TestUtils { + + public static ReactiveFeign.Builder builderHttp() { + return HttpClient5WebReactiveFeign.builder(); + } + + static ReactiveFeign.Builder builderHttpWithAcceptCompressed(boolean acceptCompressed) { + return HttpClient5WebReactiveFeign.builder().options( + new HttpClient5ReactiveOptions.Builder() + .setAcceptCompressed(acceptCompressed) + .build() + ); + } + + static ReactiveFeign.Builder builderHttpWithConnectTimeout(long connectTimeout) { + return HttpClient5WebReactiveFeign.builder().options( + new HttpClient5ReactiveOptions.Builder() + .setConnectTimeoutMillis(connectTimeout) + .build() + ); + } + + static ReactiveFeign.Builder builderHttpWithSocketTimeout(long socketTimeout) { + return HttpClient5WebReactiveFeign.builder().options( + new HttpClient5ReactiveOptions.Builder() + .setSocketTimeout((int)socketTimeout) + .setConnectTimeoutMillis(socketTimeout / 2) + .build() + ); + } + + static ReactiveFeign.Builder builderHttpFollowRedirects(boolean followRedirects) { + return HttpClient5WebReactiveFeign.builder().options( + new HttpClient5ReactiveOptions.Builder() + .setFollowRedirects(followRedirects) + .build() + ); + } + + static ReactiveFeign.Builder builderHttpProxy(ReactiveOptions.ProxySettings proxySettings) { + return HttpClient5WebReactiveFeign.builder().options( + new HttpClient5ReactiveOptions.Builder() + .setProxySettings(proxySettings) + .build() + ); + } + +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/AllFeaturesTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/AllFeaturesTest.java new file mode 100644 index 000000000..d49caa408 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/AllFeaturesTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed 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 reactivefeign.webclient.client5.h2c; + +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; +import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import reactivefeign.ReactiveFeign; +import reactivefeign.allfeatures.AllFeaturesFeign; +import reactivefeign.allfeatures.AllFeaturesFeignTest; +import reactivefeign.spring.server.config.TestServerConfigurations; + +import static reactivefeign.ReactivityTest.timeToCompleteReactively; +import static reactivefeign.spring.server.config.TestServerConfigurations.JETTY_H2C; +import static reactivefeign.spring.server.config.TestServerConfigurations.UNDERTOW_H2C; +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2WithRequestTimeout; + +/** + * @author Sergii Karpenko + * + * Tests ReactiveFeign in conjunction with WebFlux rest controller. + */ +@EnableAutoConfiguration(exclude = {ReactiveSecurityAutoConfiguration.class, ReactiveUserDetailsServiceAutoConfiguration.class}) +@ContextConfiguration(classes={TestServerConfigurations.class}) +@ActiveProfiles(JETTY_H2C) +public class AllFeaturesTest extends AllFeaturesFeignTest { + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp2WithRequestTimeout(timeToCompleteReactively()); + } + + @Test + @Override + public void shouldMirrorStreamingBinaryBodyReactive() throws InterruptedException { + if(getActiveProfiles().contains(UNDERTOW_H2C)){ + return; + } + super.shouldMirrorStreamingBinaryBodyReactive(); + } + + //Apache's WebClient is not able to do this trick + @Ignore + @Test + @Override + public void shouldReturnFirstResultBeforeSecondSent() { + } + + @Ignore + @Test + @Override + public void shouldMirrorStringStreamBody() { + } + + //TODO Check later + @Ignore + @Test + @Override + public void shouldEncodePathParamWithReservedChars() { + } + +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/BasicFeaturesTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/BasicFeaturesTest.java new file mode 100644 index 000000000..a8f38a528 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/BasicFeaturesTest.java @@ -0,0 +1,46 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h2c; + +import com.fasterxml.jackson.core.io.JsonEOFException; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import org.springframework.core.codec.DecodingException; +import reactivefeign.ReactiveFeign; + +import java.util.function.Predicate; + +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2; +import static reactivefeign.wiremock.WireMockServerConfigurations.h2cConfig; + +/** + * @author Sergii Karpenko + */ +public class BasicFeaturesTest extends reactivefeign.BasicFeaturesTest { + + @Override + protected WireMockConfiguration wireMockConfig(){ + return h2cConfig(); + } + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp2(); + } + + @Override + protected Predicate corruptedJsonError() { + return throwable -> throwable instanceof DecodingException + && throwable.getCause() instanceof JsonEOFException; + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/CompressionTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/CompressionTest.java new file mode 100644 index 000000000..3b35210be --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/CompressionTest.java @@ -0,0 +1,40 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h2c; + +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import org.junit.Ignore; +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2WithAcceptCompressed; +import static reactivefeign.wiremock.WireMockServerConfigurations.h2cConfig; + +/** + * @author Sergii Karpenko + */ +//TODO Investigate why not working +@Ignore +public class CompressionTest extends reactivefeign.CompressionTest { + + @Override + protected WireMockConfiguration wireMockConfig(){ + return h2cConfig(); + } + + @Override + protected ReactiveFeign.Builder builder(boolean tryUseCompression) { + return builderHttp2WithAcceptCompressed(true); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/ConnectionTimeoutTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/ConnectionTimeoutTest.java new file mode 100644 index 000000000..70dc927f3 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/ConnectionTimeoutTest.java @@ -0,0 +1,30 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h2c; + +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2WithConnectTimeout; + +/** + * @author Sergii Karpenko + */ +public class ConnectionTimeoutTest extends reactivefeign.ConnectionTimeoutTest { + + @Override + protected ReactiveFeign.Builder builder(long connectTimeoutInMillis) { + return builderHttp2WithConnectTimeout(connectTimeoutInMillis); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/ContractTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/ContractTest.java new file mode 100644 index 000000000..9d30c4378 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/ContractTest.java @@ -0,0 +1,29 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h2c; + +import reactivefeign.ReactiveFeign; + +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2; + +/** + * @author Sergii Karpenko + */ +public class ContractTest extends reactivefeign.ContractTest { + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp2(); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/DefaultMethodTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/DefaultMethodTest.java new file mode 100644 index 000000000..d91a757e9 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/DefaultMethodTest.java @@ -0,0 +1,48 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h2c; + +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2; +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2WithConnectTimeout; +import static reactivefeign.wiremock.WireMockServerConfigurations.h2cConfig; + +/** + * @author Sergii Karpenko + */ +public class DefaultMethodTest extends reactivefeign.DefaultMethodTest { + + @Override + protected WireMockConfiguration wireMockConfig(){ + return h2cConfig(); + } + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp2(); + } + + @Override + protected ReactiveFeign.Builder builder(Class apiClass) { + return builderHttp2(); + } + + @Override + protected ReactiveFeign.Builder builder(long connectTimeoutInMillis) { + return builderHttp2WithConnectTimeout(connectTimeoutInMillis); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/ErrorMapperTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/ErrorMapperTest.java new file mode 100644 index 000000000..7fcf890d8 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/ErrorMapperTest.java @@ -0,0 +1,37 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h2c; + +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2; +import static reactivefeign.wiremock.WireMockServerConfigurations.h2cConfig; + +/** + * @author Sergii Karpenko + */ +public class ErrorMapperTest extends reactivefeign.StatusHandlerTest { + + @Override + protected WireMockConfiguration wireMockConfig(){ + return h2cConfig(); + } + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp2(); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/FallbackTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/FallbackTest.java new file mode 100644 index 000000000..b9ab4ee62 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/FallbackTest.java @@ -0,0 +1,29 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h2c; + +import reactivefeign.ReactiveFeign; + +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2; + +/** + * @author Sergii Karpenko + */ +public class FallbackTest extends reactivefeign.FallbackTest { + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp2(); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/LoggerTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/LoggerTest.java new file mode 100644 index 000000000..ff4c17271 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/LoggerTest.java @@ -0,0 +1,64 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h2c; + +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import org.junit.Ignore; +import org.junit.Test; +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2; +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2WithRequestTimeout; +import static reactivefeign.wiremock.WireMockServerConfigurations.h2cConfig; + +/** + * @author Sergii Karpenko + */ +public class LoggerTest extends reactivefeign.LoggerTest { + + //TODO investigate why socket timeout doesn't work + @Ignore + @Override + @Test + public void shouldLogTimeout() { + } + + @Override + protected String appenderPrefix(){ + return "h2c_"; + } + + @Override + protected WireMockConfiguration wireMockConfig(){ + return h2cConfig(); + } + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp2(); + } + + @Override + protected ReactiveFeign.Builder builder(long readTimeoutInMillis) { + return builderHttp2WithRequestTimeout(readTimeoutInMillis); + } + + @Override + protected Class target(){ + return IcecreamServiceApiJettyH2.class; + } + + interface IcecreamServiceApiJettyH2 extends IcecreamServiceApi{} +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/NotFoundTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/NotFoundTest.java new file mode 100644 index 000000000..c334b8e6c --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/NotFoundTest.java @@ -0,0 +1,38 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h2c; + +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2; +import static reactivefeign.wiremock.WireMockServerConfigurations.h2cConfig; + +/** + * @author Sergii Karpenko + */ +public class NotFoundTest extends reactivefeign.NotFoundTest { + + @Override + protected WireMockConfiguration wireMockConfig(){ + return h2cConfig(); + } + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp2(); + } + +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/ObjectMapperTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/ObjectMapperTest.java new file mode 100644 index 000000000..38463b60d --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/ObjectMapperTest.java @@ -0,0 +1,37 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h2c; + +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2; +import static reactivefeign.wiremock.WireMockServerConfigurations.h2cConfig; + +/** + * @author Sergii Karpenko + */ +public class ObjectMapperTest extends reactivefeign.ObjectMapperTest { + + @Override + protected WireMockConfiguration wireMockConfig(){ + return h2cConfig(); + } + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp2(); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/OptionsTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/OptionsTest.java new file mode 100644 index 000000000..165c1524e --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/OptionsTest.java @@ -0,0 +1,65 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h2c; + +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import org.junit.Ignore; +import org.junit.Test; +import reactivefeign.ReactiveFeign; +import reactivefeign.ReactiveFeignBuilder; +import reactivefeign.ReactiveOptions; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2FollowRedirects; +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2WithRequestTimeout; +import static reactivefeign.wiremock.WireMockServerConfigurations.h2cConfig; + +/** + * @author Sergii Karpenko + */ +public class OptionsTest extends reactivefeign.OptionsTest { + + //TODO investigate why socket timeout doesn't work + @Ignore + @Override + @Test + public void shouldFailOnReadTimeout() { + } + + @Override + protected WireMockConfiguration wireMockConfig(){ + return h2cConfig(); + } + + @Override + protected ReactiveFeign.Builder builder(long readTimeoutInMillis) { + return builderHttp2WithRequestTimeout(readTimeoutInMillis); + } + + @Override + protected ReactiveFeignBuilder builder(boolean followRedirects) { + return builderHttp2FollowRedirects(followRedirects); + } + + @Override + protected ReactiveFeignBuilder builder(ReactiveOptions.ProxySettings proxySettings) { + throw new IllegalArgumentException(); + } + + @Ignore + @Override + @Test + public void shouldUseProxy() {} + +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/ReactivityTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/ReactivityTest.java new file mode 100644 index 000000000..b56c1f4f8 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/ReactivityTest.java @@ -0,0 +1,27 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h2c; + +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2WithRequestTimeout; + +public class ReactivityTest extends reactivefeign.ReactivityTest { + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp2WithRequestTimeout(timeToCompleteReactively()); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/RequestInterceptorTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/RequestInterceptorTest.java new file mode 100644 index 000000000..9bcd0795f --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/RequestInterceptorTest.java @@ -0,0 +1,38 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h2c; + +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2; +import static reactivefeign.wiremock.WireMockServerConfigurations.h2cConfig; + +/** + * @author Sergii Karpenko + */ +public class RequestInterceptorTest extends reactivefeign.RequestInterceptorTest { + + @Override + protected WireMockConfiguration wireMockConfig(){ + return h2cConfig(); + } + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp2(); + } + +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/ResponseMapperTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/ResponseMapperTest.java new file mode 100644 index 000000000..b9bb52aef --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/ResponseMapperTest.java @@ -0,0 +1,37 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h2c; + +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2; +import static reactivefeign.wiremock.WireMockServerConfigurations.h2cConfig; + +/** + * @author Sergii Karpenko + */ +public class ResponseMapperTest extends reactivefeign.ResponseMapperTest { + + @Override + protected WireMockConfiguration wireMockConfig(){ + return h2cConfig(); + } + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp2(); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/RetryingTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/RetryingTest.java new file mode 100644 index 000000000..08a964e59 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/RetryingTest.java @@ -0,0 +1,38 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h2c; + +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2; +import static reactivefeign.wiremock.WireMockServerConfigurations.h2cConfig; + +/** + * @author Sergii Karpenko + */ +public class RetryingTest extends reactivefeign.RetryingTest { + + @Override + protected WireMockConfiguration wireMockConfig(){ + return h2cConfig(); + } + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp2(); + } + +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/SmokeTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/SmokeTest.java new file mode 100644 index 000000000..40d54d432 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/SmokeTest.java @@ -0,0 +1,38 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h2c; + +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2; +import static reactivefeign.wiremock.WireMockServerConfigurations.h2cConfig; + +/** + * @author Sergii Karpenko + */ +public class SmokeTest extends reactivefeign.SmokeTest { + + @Override + protected WireMockConfiguration wireMockConfig(){ + return h2cConfig(); + } + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp2(); + } + +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/StatusHandlerTest.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/StatusHandlerTest.java new file mode 100644 index 000000000..9ee30b228 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/StatusHandlerTest.java @@ -0,0 +1,37 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed 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 reactivefeign.webclient.client5.h2c; + +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +import static reactivefeign.webclient.client5.h2c.TestUtils.builderHttp2; +import static reactivefeign.wiremock.WireMockServerConfigurations.h2cConfig; + +/** + * @author Sergii Karpenko + */ +public class StatusHandlerTest extends reactivefeign.StatusHandlerTest { + + @Override + protected WireMockConfiguration wireMockConfig(){ + return h2cConfig(); + } + + @Override + protected ReactiveFeign.Builder builder() { + return builderHttp2(); + } +} diff --git a/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/TestUtils.java b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/TestUtils.java new file mode 100644 index 000000000..1cfbdcb41 --- /dev/null +++ b/feign-reactor-webclient-apache-client5/src/test/java/reactivefeign/webclient/client5/h2c/TestUtils.java @@ -0,0 +1,50 @@ +package reactivefeign.webclient.client5.h2c; + +import reactivefeign.ReactiveFeign; +import reactivefeign.webclient.client5.HttpClient5ReactiveOptions; +import reactivefeign.webclient.client5.HttpClient5WebReactiveFeign; + +public class TestUtils { + + public static final int DEFAULT_REQUEST_TIMEOUT = 1000; + + public static ReactiveFeign.Builder builderHttp2() { + return HttpClient5WebReactiveFeign.builder().options( + new HttpClient5ReactiveOptions.Builder() + .setSocketTimeout(DEFAULT_REQUEST_TIMEOUT) + .setUseHttp2(true).build()); + } + + public static ReactiveFeign.Builder builderHttp2WithRequestTimeout(long requestTimeout) { + return HttpClient5WebReactiveFeign.builder().options( + new HttpClient5ReactiveOptions.Builder() + .setSocketTimeout((int)requestTimeout) + .setUseHttp2(true).build()); + } + + static ReactiveFeign.Builder builderHttp2WithAcceptCompressed(boolean acceptCompressed) { + return HttpClient5WebReactiveFeign.builder().options( + new HttpClient5ReactiveOptions.Builder() + .setAcceptCompressed(acceptCompressed) + .setUseHttp2(true) + .build() + ); + } + + static ReactiveFeign.Builder builderHttp2WithConnectTimeout(long connectTimeout) { + return HttpClient5WebReactiveFeign.builder().options( + new HttpClient5ReactiveOptions.Builder() + .setSocketTimeout(DEFAULT_REQUEST_TIMEOUT) + .setConnectTimeoutMillis(connectTimeout) + .setUseHttp2(true).build()); + } + + static ReactiveFeign.Builder builderHttp2FollowRedirects(boolean follow) { + return HttpClient5WebReactiveFeign.builder().options( + new HttpClient5ReactiveOptions.Builder() + .setSocketTimeout(DEFAULT_REQUEST_TIMEOUT) + .setFollowRedirects(follow) + .setUseHttp2(true).build()); + } + +} diff --git a/feign-reactor-webclient-core/pom.xml b/feign-reactor-webclient-core/pom.xml index 7bcd5472b..53ed9c293 100644 --- a/feign-reactor-webclient-core/pom.xml +++ b/feign-reactor-webclient-core/pom.xml @@ -6,7 +6,7 @@ com.playtika.reactivefeign feign-reactor-parent ../feign-reactor-parent - 3.2.6 + 4.0.3 feign-reactor-webclient-core @@ -46,8 +46,14 @@ - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone + test + + + + org.assertj + assertj-core test @@ -58,7 +64,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.2.2 + 3.3.0 diff --git a/feign-reactor-webclient-core/src/main/java/reactivefeign/webclient/client/WebReactiveHttpClient.java b/feign-reactor-webclient-core/src/main/java/reactivefeign/webclient/client/WebReactiveHttpClient.java index 555982895..914e63c55 100644 --- a/feign-reactor-webclient-core/src/main/java/reactivefeign/webclient/client/WebReactiveHttpClient.java +++ b/feign-reactor-webclient-core/src/main/java/reactivefeign/webclient/client/WebReactiveHttpClient.java @@ -33,6 +33,7 @@ import reactivefeign.client.ReactiveHttpRequest; import reactivefeign.client.ReactiveHttpResponse; import reactivefeign.methodhandler.PublisherClientMethodHandler; +import reactivefeign.utils.SerializedFormData; import reactor.core.publisher.Mono; import java.lang.reflect.ParameterizedType; @@ -43,7 +44,11 @@ import static java.util.Optional.ofNullable; import static org.springframework.core.ParameterizedTypeReference.forType; import static reactivefeign.ReactiveContract.isReactorType; -import static reactivefeign.utils.FeignUtils.*; +import static reactivefeign.methodhandler.PublisherClientMethodHandler.MultipartMap; +import static reactivefeign.utils.FeignUtils.getBodyActualType; +import static reactivefeign.utils.FeignUtils.returnActualType; +import static reactivefeign.utils.FeignUtils.returnPublisherType; + /** * Uses {@link WebClient} to execute http requests @@ -127,11 +132,14 @@ protected ReactiveHttpResponse

toReactiveHttpResponse(ReactiveHttpRequest req } protected BodyInserter provideBody(ReactiveHttpRequest request) { - if(bodyActualType != null){ - return BodyInserters.fromPublisher(request.body(), bodyActualType); - } else if(request.body() instanceof PublisherClientMethodHandler.MultipartMap){ + if(request.body() instanceof SerializedFormData){ + return BodyInserters.fromValue(((SerializedFormData)request.body()).getFormData()); + } else if(request.body() instanceof MultipartMap){ return BodyInserters.fromMultipartData(new MultiValueMapAdapter<>( ((PublisherClientMethodHandler.MultipartMap) request.body()).getMap())); + } + else if(bodyActualType != null){ + return BodyInserters.fromPublisher(request.body(), bodyActualType); } else { return BodyInserters.empty(); } diff --git a/feign-reactor-webclient-core/src/test/java/reactivefeign/webclient/core/ResponseEntityTest.java b/feign-reactor-webclient-core/src/test/java/reactivefeign/webclient/core/ResponseEntityTest.java index 9252f7f47..ab34c9018 100644 --- a/feign-reactor-webclient-core/src/test/java/reactivefeign/webclient/core/ResponseEntityTest.java +++ b/feign-reactor-webclient-core/src/test/java/reactivefeign/webclient/core/ResponseEntityTest.java @@ -19,6 +19,7 @@ import java.util.Map; import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; abstract public class ResponseEntityTest { @@ -40,14 +41,15 @@ public void shouldPassResponseAsResponseEntity() { .target(TestCaller.class, "http://localhost:" + wireMockRule.port()); Mono>> result = client.call(); - StepVerifier.create(result) - .expectNextMatches(response -> toLowerCaseKeys(response.getHeaders()) - .containsKey("content-type")) - .verifyComplete(); - StepVerifier.create(result.flatMapMany(HttpEntity::getBody)) + StepVerifier.create(result + .doOnNext(response -> assertThat(toLowerCaseKeys(response.getHeaders()) + .containsKey("content-type")).isTrue()) + .flatMapMany(HttpEntity::getBody)) .expectNextSequence(asList(1, 2)) .verifyComplete(); + + assertThat(wireMockRule.getAllServeEvents().size()).isEqualTo(1); } @Test @@ -63,12 +65,10 @@ public void shouldPassResponseAsRawResponseEntity() { Mono>> resultRaw = client.callRaw(); - StepVerifier.create(resultRaw) - .expectNextMatches(response -> toLowerCaseKeys(response.getHeaders()) - .containsKey("content-type")) - .verifyComplete(); - - StepVerifier.create(resultRaw.flatMapMany(HttpEntity::getBody)) + StepVerifier.create(resultRaw + .doOnNext(response -> assertThat(toLowerCaseKeys(response.getHeaders()) + .containsKey("content-type")).isTrue()) + .flatMapMany(HttpEntity::getBody)) .expectNextMatches(bytes -> Arrays.equals("[1, 2]".getBytes(), bytes)) .verifyComplete(); } diff --git a/feign-reactor-webclient-jetty/pom.xml b/feign-reactor-webclient-jetty/pom.xml index bb9866115..e3601d3d0 100644 --- a/feign-reactor-webclient-jetty/pom.xml +++ b/feign-reactor-webclient-jetty/pom.xml @@ -6,7 +6,7 @@ com.playtika.reactivefeign feign-reactor-parent ../feign-reactor-parent - 3.2.6 + 4.0.3 feign-reactor-webclient-jetty @@ -36,16 +36,21 @@ org.eclipse.jetty.http2 - http2-client + jetty-http2-client true org.eclipse.jetty.http2 - http2-http-client-transport + jetty-http2-client-transport true + + org.eclipse.jetty + jetty-client + + com.playtika.reactivefeign @@ -92,14 +97,24 @@ test - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test org.apache.logging.log4j - log4j-slf4j-impl + log4j-api + test + + + org.apache.logging.log4j + log4j-core + test + + + org.apache.logging.log4j + log4j-slf4j2-impl test diff --git a/feign-reactor-webclient-jetty/src/main/java/reactivefeign/webclient/jetty/JettyClientHttpConnectorBuilder.java b/feign-reactor-webclient-jetty/src/main/java/reactivefeign/webclient/jetty/JettyClientHttpConnectorBuilder.java index 3892ca547..797ae8bea 100644 --- a/feign-reactor-webclient-jetty/src/main/java/reactivefeign/webclient/jetty/JettyClientHttpConnectorBuilder.java +++ b/feign-reactor-webclient-jetty/src/main/java/reactivefeign/webclient/jetty/JettyClientHttpConnectorBuilder.java @@ -3,7 +3,7 @@ import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpProxy; import org.eclipse.jetty.http2.client.HTTP2Client; -import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2; +import org.eclipse.jetty.http2.client.transport.HttpClientTransportOverHTTP2; import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.http.client.reactive.JettyClientHttpConnector; import reactivefeign.ReactiveOptions; @@ -39,8 +39,8 @@ public static ClientHttpConnector buildJettyClientHttpConnector(JettyReactiveOpt if(options.getProxySettings() != null){ ReactiveOptions.ProxySettings proxySettings = options.getProxySettings(); - httpClient.getProxyConfiguration().getProxies() - .add(new HttpProxy(proxySettings.getHost(), proxySettings.getPort())); + httpClient.getProxyConfiguration() + .addProxy(new HttpProxy(proxySettings.getHost(), proxySettings.getPort())); } return new JettyClientHttpConnector(httpClient); diff --git a/feign-reactor-webclient-jetty/src/main/java/reactivefeign/webclient/jetty/JettyReactiveOptions.java b/feign-reactor-webclient-jetty/src/main/java/reactivefeign/webclient/jetty/JettyReactiveOptions.java index c28a8952f..8f1667f2c 100644 --- a/feign-reactor-webclient-jetty/src/main/java/reactivefeign/webclient/jetty/JettyReactiveOptions.java +++ b/feign-reactor-webclient-jetty/src/main/java/reactivefeign/webclient/jetty/JettyReactiveOptions.java @@ -39,11 +39,7 @@ public Long getRequestTimeoutMillis() { return requestTimeoutMillis; } - public boolean isEmpty() { - return super.isEmpty() /*&& requestTimeoutMillis == null*/; - } - - public static class Builder extends ReactiveOptions.Builder{ + public static class Builder extends ReactiveOptions.Builder{ private Long requestTimeoutMillis; public Builder() {} diff --git a/feign-reactor-webclient-jetty/src/main/java/reactivefeign/webclient/jetty/JettyWebReactiveFeign.java b/feign-reactor-webclient-jetty/src/main/java/reactivefeign/webclient/jetty/JettyWebReactiveFeign.java index 81ee86d5f..753deba9d 100644 --- a/feign-reactor-webclient-jetty/src/main/java/reactivefeign/webclient/jetty/JettyWebReactiveFeign.java +++ b/feign-reactor-webclient-jetty/src/main/java/reactivefeign/webclient/jetty/JettyWebReactiveFeign.java @@ -13,7 +13,7 @@ */ package reactivefeign.webclient.jetty; -import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.Request; import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.client.WebClient; diff --git a/feign-reactor-webclient-jetty/src/test/java/reactivefeign/webclient/jetty/allfeatures/WebClientFeaturesTest.java b/feign-reactor-webclient-jetty/src/test/java/reactivefeign/webclient/jetty/allfeatures/WebClientFeaturesTest.java index f9bec6adf..61f27afe8 100644 --- a/feign-reactor-webclient-jetty/src/test/java/reactivefeign/webclient/jetty/allfeatures/WebClientFeaturesTest.java +++ b/feign-reactor-webclient-jetty/src/test/java/reactivefeign/webclient/jetty/allfeatures/WebClientFeaturesTest.java @@ -9,7 +9,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; diff --git a/feign-reactor-webclient/pom.xml b/feign-reactor-webclient/pom.xml index ceff5e217..3e0e01155 100644 --- a/feign-reactor-webclient/pom.xml +++ b/feign-reactor-webclient/pom.xml @@ -6,7 +6,7 @@ com.playtika.reactivefeign feign-reactor-parent ../feign-reactor-parent - 3.2.6 + 4.0.3 feign-reactor-webclient @@ -80,14 +80,24 @@ test - com.github.tomakehurst - wiremock-jre8-standalone + org.wiremock + wiremock-standalone test org.apache.logging.log4j - log4j-slf4j-impl + log4j-api + test + + + org.apache.logging.log4j + log4j-core + test + + + org.apache.logging.log4j + log4j-slf4j2-impl test diff --git a/feign-reactor-webclient/src/main/java/reactivefeign/webclient/NettyClientHttpConnectorBuilder.java b/feign-reactor-webclient/src/main/java/reactivefeign/webclient/NettyClientHttpConnectorBuilder.java index 54f8991a2..f2038a69e 100644 --- a/feign-reactor-webclient/src/main/java/reactivefeign/webclient/NettyClientHttpConnectorBuilder.java +++ b/feign-reactor-webclient/src/main/java/reactivefeign/webclient/NettyClientHttpConnectorBuilder.java @@ -20,7 +20,9 @@ import java.time.Duration; import java.util.Objects; import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import static java.lang.Boolean.TRUE; import static reactor.netty.resources.LoopResources.DEFAULT_NATIVE; class NettyClientHttpConnectorBuilder { @@ -33,30 +35,45 @@ private NettyClientHttpConnectorBuilder() { public static ClientHttpConnector buildNettyClientHttpConnector(HttpClient httpClient, WebReactiveOptions webOptions) { if (httpClient == null) { - ConnectionProvider connectionProvider = TcpResources.get(); - - Integer maxConnections = webOptions.getMaxConnections(); - Integer pendingAcquireMaxCount = webOptions.getPendingAcquireMaxCount(); - Long pendingAcquireTimeoutMillis = webOptions.getPendingAcquireTimeoutMillis(); - if (maxConnections != null || pendingAcquireMaxCount != null || pendingAcquireTimeoutMillis != null) { - ConnectionProvider.Builder connectionProviderBuilder = connectionProvider.mutate(); - if (maxConnections != null) { - connectionProviderBuilder = connectionProviderBuilder.maxConnections(maxConnections); - } - if (pendingAcquireMaxCount != null) { - connectionProviderBuilder = connectionProviderBuilder.pendingAcquireMaxCount(pendingAcquireMaxCount); - } - if (pendingAcquireTimeoutMillis != null) { - Duration pendingAcquireTimeout = Duration.ofMillis(pendingAcquireTimeoutMillis); - connectionProviderBuilder = connectionProviderBuilder.pendingAcquireTimeout(pendingAcquireTimeout); - } - connectionProvider = connectionProviderBuilder.build(); + + ConnectionProvider connectionProvider = webOptions.getConnectionProvider(); + if(connectionProvider == null){ + connectionProvider = TcpResources.get(); } + ConnectionProvider.Builder connectionProviderBuilder = connectionProvider.mutate(); + if(webOptions.getConnectionMetricsEnabled() != null){ + connectionProviderBuilder = connectionProviderBuilder.metrics(webOptions.getConnectionMetricsEnabled()); + } + if (webOptions.getMaxConnections() != null) { + connectionProviderBuilder = connectionProviderBuilder.maxConnections(webOptions.getMaxConnections()); + } + if(webOptions.getConnectionMaxIdleTimeMillis() != null){ + connectionProviderBuilder = connectionProviderBuilder.maxIdleTime( + Duration.ofMillis(webOptions.getConnectionMaxIdleTimeMillis())); + } + if(webOptions.getConnectionMaxLifeTimeMillis() != null){ + connectionProviderBuilder = connectionProviderBuilder.maxLifeTime( + Duration.ofMillis(webOptions.getConnectionMaxLifeTimeMillis())); + } + if (webOptions.getPendingAcquireMaxCount() != null) { + connectionProviderBuilder = connectionProviderBuilder.pendingAcquireMaxCount(webOptions.getPendingAcquireMaxCount()); + } + if (webOptions.getPendingAcquireTimeoutMillis() != null) { + connectionProviderBuilder = connectionProviderBuilder.pendingAcquireTimeout( + Duration.ofMillis(webOptions.getPendingAcquireTimeoutMillis())); + } + + connectionProvider = connectionProviderBuilder.build(); + httpClient = HttpClient.create(connectionProvider) .runOn(HttpResources.get(), DEFAULT_NATIVE); } + if(webOptions.getMetricsEnabled() != null){ + httpClient = httpClient.metrics(webOptions.getMetricsEnabled(), Function.identity()); + } + if (webOptions.getConnectTimeoutMillis() != null) { httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, webOptions.getConnectTimeoutMillis().intValue()); @@ -99,7 +116,10 @@ public static ClientHttpConnector buildNettyClientHttpConnector(HttpClient httpC httpClient = httpClient.followRedirect(webOptions.isFollowRedirects()); } - if (Objects.equals(Boolean.TRUE, webOptions.isDisableSslValidation())) { + if(webOptions.getSslContext() != null){ + httpClient = httpClient.secure(sslProviderBuilder -> sslProviderBuilder.sslContext(webOptions.getSslContext())); + } + else if (Objects.equals(TRUE, webOptions.isDisableSslValidation())) { try { SslContext sslContext = SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) diff --git a/feign-reactor-webclient/src/main/java/reactivefeign/webclient/WebReactiveOptions.java b/feign-reactor-webclient/src/main/java/reactivefeign/webclient/WebReactiveOptions.java index c4b5d0424..e329400f9 100644 --- a/feign-reactor-webclient/src/main/java/reactivefeign/webclient/WebReactiveOptions.java +++ b/feign-reactor-webclient/src/main/java/reactivefeign/webclient/WebReactiveOptions.java @@ -13,7 +13,9 @@ */ package reactivefeign.webclient; +import io.netty.handler.ssl.SslContext; import reactivefeign.ReactiveOptions; +import reactor.netty.resources.ConnectionProvider; /** * @author Sergii Karpenko @@ -30,23 +32,41 @@ public class WebReactiveOptions extends ReactiveOptions { private final Long readTimeoutMillis; private final Long writeTimeoutMillis; private final Long responseTimeoutMillis; + private final SslContext sslContext; private final Boolean disableSslValidation; + private final Boolean metricsEnabled; + + private final ConnectionProvider connectionProvider; private final Integer maxConnections; + private final Boolean connectionMetricsEnabled; + private final Long connectionMaxIdleTimeMillis; + private final Long connectionMaxLifeTimeMillis; private final Integer pendingAcquireMaxCount; private final Long pendingAcquireTimeoutMillis; private WebReactiveOptions(Boolean useHttp2, Long connectTimeoutMillis, Long readTimeoutMillis, Long writeTimeoutMillis, Long responseTimeoutMillis, Boolean tryUseCompression, Boolean followRedirects, - ProxySettings proxySettings, Boolean disableSslValidation, - Integer maxConnections, Integer pendingAcquireMaxCount, Long pendingAcquireTimeoutMillis) { + ProxySettings proxySettings, + SslContext sslContext, Boolean disableSslValidation, + Boolean metricsEnabled, + ConnectionProvider connectionProvider, + Integer maxConnections, Boolean connectionMetricsEnabled, + Long connectionMaxIdleTimeMillis, Long connectionMaxLifeTimeMillis, + Integer pendingAcquireMaxCount, Long pendingAcquireTimeoutMillis) { super(useHttp2, connectTimeoutMillis, tryUseCompression, followRedirects, proxySettings); this.readTimeoutMillis = readTimeoutMillis; this.writeTimeoutMillis = writeTimeoutMillis; this.responseTimeoutMillis = responseTimeoutMillis; + this.sslContext = sslContext; this.disableSslValidation = disableSslValidation; + this.metricsEnabled = metricsEnabled; + this.connectionProvider = connectionProvider; this.maxConnections = maxConnections; + this.connectionMetricsEnabled = connectionMetricsEnabled; + this.connectionMaxIdleTimeMillis = connectionMaxIdleTimeMillis; + this.connectionMaxLifeTimeMillis = connectionMaxLifeTimeMillis; this.pendingAcquireMaxCount = pendingAcquireMaxCount; this.pendingAcquireTimeoutMillis = pendingAcquireTimeoutMillis; } @@ -63,18 +83,38 @@ public Long getResponseTimeoutMillis() { return responseTimeoutMillis; } - public boolean isEmpty() { - return super.isEmpty() && readTimeoutMillis == null && writeTimeoutMillis == null; - } - public Boolean isDisableSslValidation() { return disableSslValidation; } + public SslContext getSslContext() { + return sslContext; + } + + public Boolean getMetricsEnabled() { + return metricsEnabled; + } + + public ConnectionProvider getConnectionProvider() { + return connectionProvider; + } + public Integer getMaxConnections() { return maxConnections; } + public Boolean getConnectionMetricsEnabled() { + return connectionMetricsEnabled; + } + + public Long getConnectionMaxIdleTimeMillis() { + return connectionMaxIdleTimeMillis; + } + + public Long getConnectionMaxLifeTimeMillis() { + return connectionMaxLifeTimeMillis; + } + public Integer getPendingAcquireMaxCount() { return pendingAcquireMaxCount; } @@ -83,12 +123,18 @@ public Long getPendingAcquireTimeoutMillis() { return pendingAcquireTimeoutMillis; } - public static class Builder extends ReactiveOptions.Builder { + public static class Builder extends ReactiveOptions.Builder { private Long readTimeoutMillis; private Long writeTimeoutMillis; private Long responseTimeoutMillis; private Boolean disableSslValidation; + private SslContext sslContext; + private Boolean metricsEnabled; + private ConnectionProvider connectionProvider; private Integer maxConnections; + private Boolean connectionMetricsEnabled; + private Long connectionMaxIdleTimeMillis; + private Long connectionMaxLifeTimeMillis; private Integer pendingAcquireMaxCount; private Long pendingAcquireTimeoutMillis; @@ -114,11 +160,41 @@ public Builder setDisableSslValidation(boolean disableSslValidation) { return this; } + public Builder setSslContext(SslContext sslContext) { + this.sslContext = sslContext; + return this; + } + + public Builder setMetricsEnabled(Boolean metricsEnabled) { + this.metricsEnabled = metricsEnabled; + return this; + } + + public Builder setConnectionProvider(ConnectionProvider connectionProvider) { + this.connectionProvider = connectionProvider; + return this; + } + + public Builder setConnectionMetricsEnabled(Boolean connectionMetricsEnabled) { + this.connectionMetricsEnabled = connectionMetricsEnabled; + return this; + } + public Builder setMaxConnections(Integer maxConnections) { this.maxConnections = maxConnections; return this; } + public Builder setConnectionMaxIdleTimeMillis(Long connectionMaxIdleTimeMillis) { + this.connectionMaxIdleTimeMillis = connectionMaxIdleTimeMillis; + return this; + } + + public Builder setConnectionMaxLifeTimeMillis(Long connectionMaxLifeTimeMillis) { + this.connectionMaxLifeTimeMillis = connectionMaxLifeTimeMillis; + return this; + } + public Builder setPendingAcquireMaxCount(Integer pendingAcquireMaxCount) { this.pendingAcquireMaxCount = pendingAcquireMaxCount; return this; @@ -132,8 +208,13 @@ public Builder setPendingAcquireTimeoutMillis(Long pendingAcquireTimeoutMillis) public WebReactiveOptions build() { return new WebReactiveOptions(useHttp2, connectTimeoutMillis, readTimeoutMillis, writeTimeoutMillis, responseTimeoutMillis, - acceptCompressed, followRedirects, proxySettings, disableSslValidation, - maxConnections, pendingAcquireMaxCount, pendingAcquireTimeoutMillis); + acceptCompressed, followRedirects, proxySettings, + sslContext, disableSslValidation, + metricsEnabled, + connectionProvider, + maxConnections, connectionMetricsEnabled, + connectionMaxIdleTimeMillis, connectionMaxLifeTimeMillis, + pendingAcquireMaxCount, pendingAcquireTimeoutMillis); } } @@ -171,17 +252,17 @@ public static class WebProxySettingsBuilder extends ProxySettingsBuilder { private Long timeout; - public ReactiveOptions.ProxySettingsBuilder username(String username) { + public WebProxySettingsBuilder username(String username) { this.username = username; return this; } - public ReactiveOptions.ProxySettingsBuilder password(String password) { + public WebProxySettingsBuilder password(String password) { this.password = password; return this; } - public ReactiveOptions.ProxySettingsBuilder timeout(Long timeout) { + public WebProxySettingsBuilder timeout(Long timeout) { this.timeout = timeout; return this; } diff --git a/feign-reactor-webclient/src/test/java/reactivefeign/webclient/BasicFeaturesTest.java b/feign-reactor-webclient/src/test/java/reactivefeign/webclient/BasicFeaturesTest.java index 31259827c..d9208a763 100644 --- a/feign-reactor-webclient/src/test/java/reactivefeign/webclient/BasicFeaturesTest.java +++ b/feign-reactor-webclient/src/test/java/reactivefeign/webclient/BasicFeaturesTest.java @@ -13,6 +13,7 @@ */ package reactivefeign.webclient; +import com.fasterxml.jackson.core.io.JsonEOFException; import feign.FeignException; import org.junit.Test; import org.springframework.core.codec.DecodingException; @@ -38,7 +39,8 @@ protected ReactiveFeign.Builder builder() { @Override protected Predicate corruptedJsonError() { - return throwable -> throwable instanceof DecodingException; + return throwable -> throwable instanceof DecodingException + && throwable.getCause() instanceof JsonEOFException; } @Test diff --git a/feign-reactor-webclient/src/test/java/reactivefeign/webclient/SslVerificationTest.java b/feign-reactor-webclient/src/test/java/reactivefeign/webclient/SslVerificationTest.java index 9b855d163..c1d5efe50 100644 --- a/feign-reactor-webclient/src/test/java/reactivefeign/webclient/SslVerificationTest.java +++ b/feign-reactor-webclient/src/test/java/reactivefeign/webclient/SslVerificationTest.java @@ -17,6 +17,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.github.tomakehurst.wiremock.junit.WireMockClassRule; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import org.junit.Rule; import org.junit.Test; import reactivefeign.ReactiveFeign; @@ -28,6 +30,8 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import javax.net.ssl.SSLException; + import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.post; @@ -56,11 +60,10 @@ private IcecreamServiceApi client(boolean disableSslValidation) { private ReactiveFeign.Builder builder(boolean disableSslValidation) { - return (ReactiveFeign.Builder) WebReactiveFeign.builder() + return WebReactiveFeign.builder() .options(new WebReactiveOptions.Builder() .setDisableSslValidation(disableSslValidation) - .build() - ); + .build()); } @@ -84,6 +87,32 @@ public void givenDisabledSslValidation_shouldPass() throws JsonProcessingExcepti .verifyComplete(); } + @Test + public void givenDisabledSslValidationContext_shouldPass() throws JsonProcessingException, SSLException { + + IceCreamOrder order = new OrderGenerator().generate(20); + Bill billExpected = Bill.makeBill(order); + + wireMockRule.stubFor(post(urlEqualTo("/icecream/orders")) + .withRequestBody(equalTo(TestUtils.MAPPER.writeValueAsString(order))) + .willReturn(aResponse().withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(TestUtils.MAPPER.writeValueAsString(billExpected)))); + + IcecreamServiceApi client = WebReactiveFeign.builder() + .options(new WebReactiveOptions.Builder() + .setSslContext(SslContextBuilder.forClient() + .trustManager(InsecureTrustManagerFactory.INSTANCE) + .build()) + .build()) + .target(IcecreamServiceApi.class, "https://localhost:" + wireMockRule.httpsPort()); + Mono bill = client.makeOrder(order); + + StepVerifier.create(bill.subscribeOn(testScheduler())) + .expectNextMatches(equalsComparingFieldByFieldRecursively(billExpected)) + .verifyComplete(); + } + @Test public void givenEnabledSslValidation_shouldFail() throws JsonProcessingException { diff --git a/feign-reactor-webclient/src/test/java/reactivefeign/webclient/allfeatures/WebClientFeaturesApi.java b/feign-reactor-webclient/src/test/java/reactivefeign/webclient/allfeatures/WebClientFeaturesApi.java index 7493f90ea..edae741d5 100644 --- a/feign-reactor-webclient/src/test/java/reactivefeign/webclient/allfeatures/WebClientFeaturesApi.java +++ b/feign-reactor-webclient/src/test/java/reactivefeign/webclient/allfeatures/WebClientFeaturesApi.java @@ -5,17 +5,25 @@ import org.reactivestreams.Publisher; import org.springframework.core.io.Resource; import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.util.MultiValueMap; +import reactivefeign.allfeatures.AllFeaturesApi; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE; import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE; public interface WebClientFeaturesApi { - @RequestLine("POST " + "/mirrorStreamingBinaryBodyReactive") @Headers({ "Content-Type: "+APPLICATION_OCTET_STREAM_VALUE }) + @RequestLine("POST " + "/mirrorStreamingBinaryBodyReactive") Flux mirrorStreamingBinaryBodyReactive(Publisher body); - @RequestLine("POST " + "/mirrorResourceReactiveWithZeroCopying") @Headers({ "Content-Type: "+APPLICATION_OCTET_STREAM_VALUE }) + @RequestLine("POST " + "/mirrorResourceReactiveWithZeroCopying") Flux mirrorResourceReactiveWithZeroCopying(Resource resource); + + @Headers({ "Content-Type: "+APPLICATION_FORM_URLENCODED_VALUE }) + @RequestLine("POST /passUrlEncodedForm") + Mono passUrlEncodedForm(MultiValueMap form); } diff --git a/feign-reactor-webclient/src/test/java/reactivefeign/webclient/allfeatures/WebClientFeaturesController.java b/feign-reactor-webclient/src/test/java/reactivefeign/webclient/allfeatures/WebClientFeaturesController.java index aff36b5aa..bbfc56481 100644 --- a/feign-reactor-webclient/src/test/java/reactivefeign/webclient/allfeatures/WebClientFeaturesController.java +++ b/feign-reactor-webclient/src/test/java/reactivefeign/webclient/allfeatures/WebClientFeaturesController.java @@ -5,23 +5,32 @@ import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ServerWebExchange; +import reactivefeign.allfeatures.AllFeaturesApi; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; @RestController -public class WebClientFeaturesController implements WebClientFeaturesApi{ +public class WebClientFeaturesController { @PostMapping(path = "/mirrorStreamingBinaryBodyReactive") - @Override public Flux mirrorStreamingBinaryBodyReactive(@RequestBody Publisher body) { return Flux.from(body); } @PostMapping(path = "/mirrorResourceReactiveWithZeroCopying") - @Override public Flux mirrorResourceReactiveWithZeroCopying(@RequestBody Resource resource) { return DataBufferUtils.read(resource, new DefaultDataBufferFactory(), 3); } + + @PostMapping(path = "/passUrlEncodedForm", + consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE}) + public Mono passUrlEncodedForm(ServerWebExchange serverWebExchange){ + return serverWebExchange.getFormData() + .map(formData -> new AllFeaturesApi.TestObject(formData.toString())); + } } diff --git a/feign-reactor-webclient/src/test/java/reactivefeign/webclient/allfeatures/WebClientFeaturesTest.java b/feign-reactor-webclient/src/test/java/reactivefeign/webclient/allfeatures/WebClientFeaturesTest.java index 89e55f9da..cdb877023 100644 --- a/feign-reactor-webclient/src/test/java/reactivefeign/webclient/allfeatures/WebClientFeaturesTest.java +++ b/feign-reactor-webclient/src/test/java/reactivefeign/webclient/allfeatures/WebClientFeaturesTest.java @@ -9,17 +9,23 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.MultiValueMapAdapter; import reactivefeign.webclient.WebReactiveFeign; import reactor.core.publisher.Flux; import reactor.test.StepVerifier; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import static java.nio.ByteBuffer.wrap; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; @RunWith(SpringRunner.class) @@ -35,9 +41,6 @@ public class WebClientFeaturesTest { @LocalServerPort private int port; - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @Before public void setUp() { client = WebReactiveFeign.builder() @@ -71,5 +74,16 @@ public void shouldMirrorResourceReactiveWithZeroCopying(){ assertThat(DataBufferUtils.join(returned).block().asByteBuffer()).isEqualTo(wrap(data)); } + @Test + public void shouldPassUrlEncodedForm() { + Map> form = new HashMap<>(); + form.put("key1", singletonList("value1")); + form.put("key2", singletonList("value2")); + + StepVerifier.create(client.passUrlEncodedForm(new MultiValueMapAdapter<>(form))) + .expectNextMatches(result -> result.payload.equals("{key1=[value1], key2=[value2]}")) + .verifyComplete(); + } + } diff --git a/mvnw b/mvnw new file mode 100755 index 000000000..5643201c7 --- /dev/null +++ b/mvnw @@ -0,0 +1,316 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`\\unset -f command; \\command -v java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 000000000..8a15b7f31 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,188 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml index 4c5c608d5..96fbe1198 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.playtika.reactivefeign feign-reactor - 3.2.6 + 4.0.3 pom @@ -13,6 +13,7 @@ feign-reactor-webclient-core feign-reactor-webclient feign-reactor-webclient-jetty + feign-reactor-webclient-apache-client5 feign-reactor-cloud feign-reactor-rx2 feign-reactor-jetty @@ -27,34 +28,26 @@ feign-reactive Use Feign client on WebClient - https://github.com/Playtika/feign-reactive + https://github.com/PlaytikaOSS/feign-reactive The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt + https://www.apache.org/licenses/LICENSE-2.0.txt repo - https://svn.apache.org/viewvc/maven - scm:git:git://github.com/Playtika/feign-reactive.git - scm:git:git@github.com:Playtika/feign-reactive.git + https://github.com/PlaytikaOSS/feign-reactive + scm:git:git://github.com/PlaytikaOSS/feign-reactive.git + scm:git:git@github.com:PlaytikaOSS/feign-reactive.git HEAD - 1.6.12 - 3.0.1 - 2.5.3 - 2.3 - - - 3EEF24C7 - false - true - never + 1.6.13 + 3.1.0 @@ -65,20 +58,16 @@ - - - - - - - - - - - - - - + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + +