Compare commits
49
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
161789e33a | ||
|
|
650b14ac22 | ||
|
|
17d0161faf | ||
|
|
d6783a6f1e | ||
|
|
1275875ddb | ||
|
|
c4b9492a9b | ||
|
|
3acf5f9e92 | ||
|
|
e9e38d0274 | ||
|
|
28b50401c5 | ||
|
|
6479db0a54 | ||
|
|
f7e490c466 | ||
|
|
d60cda824d | ||
|
|
190f5a9083 | ||
|
|
1005749eb5 | ||
|
|
df1f982046 | ||
|
|
baf7cf95f2 | ||
|
|
00ea88c67f | ||
|
|
d897b18e16 | ||
|
|
8c08204c78 | ||
|
|
2f43715f6f | ||
|
|
421df91a80 | ||
|
|
ec1f2ad5bb | ||
|
|
5baa8c8ed5 | ||
|
|
0684ee4bc2 | ||
|
|
0b34d89adc | ||
|
|
c33bc490cd | ||
|
|
d738b3b224 | ||
|
|
0c1d281970 | ||
|
|
0bf0426f60 | ||
|
|
9f9ce39789 | ||
|
|
4d0caec7ee | ||
|
|
92f7ba9901 | ||
|
|
b2c208ba5f | ||
|
|
cdfd3695c2 | ||
|
|
59216f2f82 | ||
|
|
4d827623f0 | ||
|
|
5e494aa196 | ||
|
|
e3439f38bc | ||
|
|
2a23b6d304 | ||
|
|
6b436c4133 | ||
|
|
b4fb3fe7b4 | ||
|
|
b8129f52b7 | ||
|
|
a2b798c31a | ||
|
|
11fd7dc43e | ||
|
|
cf20710cad | ||
|
|
c92849e28e | ||
|
|
a205afb88d | ||
|
|
37e1c2fd11 | ||
|
|
ce7a55a484 |
@@ -1,38 +0,0 @@
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: springcloud/pipeline-base
|
||||
environment:
|
||||
_JAVA_OPTIONS: "-Xms1024m -Xmx2048m"
|
||||
TERM: dumb
|
||||
branches:
|
||||
ignore:
|
||||
- gh-pages # list of branches to ignore
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: sc-openfeign-{{ .Branch }}
|
||||
- run:
|
||||
name: "Download dependencies"
|
||||
command: ./mvnw -s .settings.xml -U --fail-never dependency:go-offline || true
|
||||
- save_cache:
|
||||
key: sc-openfeign-{{ .Branch }}
|
||||
paths:
|
||||
- ~/.m2
|
||||
- run:
|
||||
name: "Running build"
|
||||
command: ./mvnw -s .settings.xml clean org.jacoco:jacoco-maven-plugin:prepare-agent install -U -P sonar -nsu --batch-mode -Dmaven.test.redirectTestOutputToFile=true -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn
|
||||
- run:
|
||||
name: "Aggregate test results"
|
||||
when: always
|
||||
command: |
|
||||
mkdir -p $CIRCLE_TEST_REPORTS/junit/
|
||||
find . -type f -regex ".*/target/.*-reports/.*" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \;
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
- store_artifacts:
|
||||
path: /junit/
|
||||
destination: artifacts
|
||||
- store_test_results:
|
||||
path: /junit/
|
||||
destination: testartifacts
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
Spring Cloud is released under the non-restrictive Apache 2.0 license,
|
||||
and follows a very standard Github development process, using Github
|
||||
tracker for issues and merging pull requests into master. If you want
|
||||
tracker for issues and merging pull requests into main. If you want
|
||||
to contribute even something trivial please do not hesitate, but
|
||||
follow the guidelines below.
|
||||
|
||||
@@ -17,7 +17,7 @@ given the ability to merge pull requests.
|
||||
|
||||
## Code of Conduct
|
||||
This project adheres to the Contributor Covenant [code of
|
||||
conduct](https://github.com/spring-cloud/spring-cloud-build/blob/master/docs/src/main/asciidoc/code-of-conduct.adoc). By participating, you are expected to uphold this code. Please report
|
||||
conduct](https://github.com/spring-cloud/spring-cloud-build/blob/main/docs/src/main/asciidoc/code-of-conduct.adoc). By participating, you are expected to uphold this code. Please report
|
||||
unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
|
||||
## Code Conventions and Housekeeping
|
||||
@@ -27,7 +27,7 @@ added after the original pull request but before a merge.
|
||||
* Use the Spring Framework code format conventions. If you use Eclipse
|
||||
you can import formatter settings using the
|
||||
`eclipse-code-formatter.xml` file from the
|
||||
[Spring Cloud Build](https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/spring-cloud-dependencies-parent/eclipse-code-formatter.xml) project. If using IntelliJ, you can use the
|
||||
[Spring Cloud Build](https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/spring-cloud-dependencies-parent/eclipse-code-formatter.xml) project. If using IntelliJ, you can use the
|
||||
[Eclipse Code Formatter Plugin](https://plugins.jetbrains.com/plugin/6546) to import the same file.
|
||||
* Make sure all new `.java` files to have a simple Javadoc class comment with at least an
|
||||
`@author` tag identifying you, and preferably at least a paragraph on what the class is
|
||||
@@ -38,7 +38,7 @@ added after the original pull request but before a merge.
|
||||
than cosmetic changes).
|
||||
* Add some Javadocs and, if you change the namespace, some XSD doc elements.
|
||||
* A few unit tests would help a lot as well -- someone has to do it.
|
||||
* If no-one else is using your branch, please rebase it against the current master (or
|
||||
* If no-one else is using your branch, please rebase it against the current main (or
|
||||
other target branch in the main project).
|
||||
* When writing a commit message please follow [these conventions](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html),
|
||||
if you are fixing an existing issue please add `Fixes gh-XXXX` at the end of the commit
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# 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: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ 2.2.x ]
|
||||
pull_request:
|
||||
branches: [ 2.2.x ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
java: ["8", "11", "16"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Cache local Maven repository
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-maven-
|
||||
- name: Build with Maven
|
||||
run: ./mvnw clean install -B -U
|
||||
+5
-19
@@ -5,9 +5,9 @@ Edit the files in the src/main/asciidoc/ directory instead.
|
||||
////
|
||||
|
||||
|
||||
image:https://circleci.com/gh/spring-cloud/spring-cloud-openfeign.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-openfeign"]
|
||||
image::https://github.com/spring-cloud/spring-cloud-openfeign/workflows/Build/badge.svg?branch=2.2.x&style=svg["Build",link="https://github.com/spring-cloud/spring-cloud-openfeign/actions"]
|
||||
|
||||
image:https://codecov.io/gh/spring-cloud/spring-cloud-openfeign/branch/master/graph/badge.svg["Codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-openfeign"]
|
||||
image:https://codecov.io/gh/spring-cloud/spring-cloud-openfeign/branch/main/graph/badge.svg["Codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-openfeign"]
|
||||
|
||||
image:https://api.codacy.com/project/badge/Grade/97b04c4e609c4b4f86b415e4437a6484["Codacy code quality", link="https://www.codacy.com/app/Spring-Cloud/spring-cloud-openfeign?utm_source=github.com&utm_medium=referral&utm_content=spring-cloud/spring-cloud-openfeign&utm_campaign=Badge_Grade"]
|
||||
|
||||
@@ -66,23 +66,9 @@ the `.mvn` configuration, so if you find you have to do it to make a
|
||||
build succeed, please raise a ticket to get the settings added to
|
||||
source control.
|
||||
|
||||
For hints on how to build the project look in `.travis.yml` if there
|
||||
is one. There should be a "script" and maybe "install" command. Also
|
||||
look at the "services" section to see if any services need to be
|
||||
running locally (e.g. mongo or rabbit). Ignore the git-related bits
|
||||
that you might find in "before_install" since they're related to setting git
|
||||
credentials and you already have those.
|
||||
The projects that require middleware (i.e. Redis) for testing generally
|
||||
require that a local instance of [Docker](https://www.docker.com/get-started) is installed and running.
|
||||
|
||||
The projects that require middleware generally include a
|
||||
`docker-compose.yml`, so consider using
|
||||
https://docs.docker.com/compose/[Docker Compose] to run the middeware servers
|
||||
in Docker containers. See the README in the
|
||||
https://github.com/spring-cloud-samples/scripts[scripts demo
|
||||
repository] for specific instructions about the common cases of mongo,
|
||||
rabbit and redis.
|
||||
|
||||
NOTE: If all else fails, build with the command from `.travis.yml` (usually
|
||||
`./mvnw install`).
|
||||
|
||||
=== Documentation
|
||||
|
||||
@@ -319,4 +305,4 @@ IMPORTANT: Remember to set the `Scan Scope` to `All sources` since we apply chec
|
||||
|
||||
== License
|
||||
|
||||
The project license file is available https://raw.githubusercontent.com/spring-cloud/spring-cloud-openfeign/master/LICENSE.txt[here].
|
||||
The project license file is available https://raw.githubusercontent.com/spring-cloud/spring-cloud-openfeign/main/LICENSE.txt[here].
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>2.2.7.RELEASE</version>
|
||||
<version>2.2.11.BUILD-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-openfeign-docs</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
image:https://circleci.com/gh/spring-cloud/spring-cloud-openfeign.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-openfeign"]
|
||||
image::https://github.com/spring-cloud/spring-cloud-openfeign/workflows/Build/badge.svg?branch=2.2.x&style=svg["Build",link="https://github.com/spring-cloud/spring-cloud-openfeign/actions"]
|
||||
|
||||
image:https://codecov.io/gh/spring-cloud/spring-cloud-openfeign/branch/master/graph/badge.svg["Codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-openfeign"]
|
||||
image:https://codecov.io/gh/spring-cloud/spring-cloud-openfeign/branch/main/graph/badge.svg["Codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-openfeign"]
|
||||
|
||||
image:https://api.codacy.com/project/badge/Grade/97b04c4e609c4b4f86b415e4437a6484["Codacy code quality", link="https://www.codacy.com/app/Spring-Cloud/spring-cloud-openfeign?utm_source=github.com&utm_medium=referral&utm_content=spring-cloud/spring-cloud-openfeign&utm_campaign=Badge_Grade"]
|
||||
|
||||
@@ -14,12 +14,12 @@ include::intro.adoc[]
|
||||
|
||||
== Building
|
||||
|
||||
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/building-jdk8.adoc[]
|
||||
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/docs/src/main/asciidoc/building-jdk8.adoc[]
|
||||
|
||||
== Contributing
|
||||
|
||||
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/docs/src/main/asciidoc/contributing.adoc[]
|
||||
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/docs/src/main/asciidoc/contributing.adoc[]
|
||||
|
||||
== License
|
||||
|
||||
The project license file is available https://raw.githubusercontent.com/spring-cloud/spring-cloud-openfeign/master/LICENSE.txt[here].
|
||||
The project license file is available https://raw.githubusercontent.com/spring-cloud/spring-cloud-openfeign/main/LICENSE.txt[here].
|
||||
|
||||
@@ -12,11 +12,17 @@
|
||||
|feign.compression.request.min-request-size | `2048` | The minimum threshold content size.
|
||||
|feign.compression.response.enabled | `false` | Enables the response from Feign to be compressed.
|
||||
|feign.compression.response.useGzipDecoder | `false` | Enables the default gzip decoder to be used.
|
||||
|feign.encoder.charset-from-content-type | `false` | Indicates whether the charset should be derived from the {@code Content-Type} header.
|
||||
|feign.httpclient.connection-timeout | `2000` |
|
||||
|feign.httpclient.connection-timer-repeat | `3000` |
|
||||
|feign.httpclient.disable-ssl-validation | `false` |
|
||||
|feign.httpclient.enabled | `true` | Enables the use of the Apache HTTP Client by Feign.
|
||||
|feign.httpclient.follow-redirects | `true` |
|
||||
|feign.httpclient.hc5.enabled | `false` | Enables the use of the Apache HTTP Client 5 by Feign.
|
||||
|feign.httpclient.hc5.pool-concurrency-policy | | Pool concurrency policies.
|
||||
|feign.httpclient.hc5.pool-reuse-policy | | Pool connection re-use policies.
|
||||
|feign.httpclient.hc5.socket-timeout | `5` | Default value for socket timeout.
|
||||
|feign.httpclient.hc5.socket-timeout-unit | | Default value for socket timeout unit.
|
||||
|feign.httpclient.max-connections | `200` |
|
||||
|feign.httpclient.max-connections-per-route | `50` |
|
||||
|feign.httpclient.time-to-live | `900` |
|
||||
|
||||
@@ -39,7 +39,7 @@ function check_if_anything_to_sync() {
|
||||
}
|
||||
|
||||
function retrieve_current_branch() {
|
||||
# Code getting the name of the current branch. For master we want to publish as we did until now
|
||||
# Code getting the name of the current branch. For main we want to publish as we did until now
|
||||
# https://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch
|
||||
# If there is a branch already passed will reuse it - otherwise will try to find it
|
||||
CURRENT_BRANCH=${BRANCH}
|
||||
@@ -134,8 +134,8 @@ function add_docs_from_target() {
|
||||
|
||||
# Copies the docs by using the retrieved properties from Maven build
|
||||
function copy_docs_for_current_version() {
|
||||
if [[ "${CURRENT_BRANCH}" == "master" ]] ; then
|
||||
echo -e "Current branch is master - will copy the current docs only to the root folder"
|
||||
if [[ "${CURRENT_BRANCH}" == "main" ]] ; then
|
||||
echo -e "Current branch is main - will copy the current docs only to the root folder"
|
||||
for f in docs/target/generated-docs/*; do
|
||||
file=${f#docs/target/generated-docs/*}
|
||||
if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then
|
||||
@@ -216,7 +216,7 @@ function commit_changes_if_applicable() {
|
||||
git commit -a -m "Sync docs from ${CURRENT_BRANCH} to gh-pages" && COMMIT_SUCCESSFUL="yes" || echo "Failed to commit changes"
|
||||
|
||||
# Uncomment the following push if you want to auto push to
|
||||
# the gh-pages branch whenever you commit to master locally.
|
||||
# the gh-pages branch whenever you commit to main locally.
|
||||
# This is a little extreme. Use with care!
|
||||
###################################################################
|
||||
if [[ "${COMMIT_SUCCESSFUL}" == "yes" ]] ; then
|
||||
@@ -249,7 +249,7 @@ The idea of this script is to update gh-pages branch with the generated docs. Wi
|
||||
the script will work in the following manner:
|
||||
|
||||
- if there's no gh-pages / target for docs module then the script ends
|
||||
- for master branch the generated docs are copied to the root of gh-pages branch
|
||||
- for main branch the generated docs are copied to the root of gh-pages branch
|
||||
- for any other branch (if that branch is whitelisted) a subfolder with branch name is created
|
||||
and docs are copied there
|
||||
- if the version switch is passed (-v) then a tag with (v) prefix will be retrieved and a folder
|
||||
@@ -327,4 +327,4 @@ build_docs_if_applicable
|
||||
retrieve_doc_properties
|
||||
stash_changes
|
||||
add_docs_from_target
|
||||
checkout_previous_branch
|
||||
checkout_previous_branch
|
||||
|
||||
@@ -27,7 +27,7 @@ public class WebApplication {
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions. You can read more on how to contribute to the project https://github.com/spring-cloud/spring-cloud-openfeign/blob/master/README.adoc#3-contributing[here].
|
||||
We welcome contributions. You can read more on how to contribute to the project https://github.com/spring-cloud/spring-cloud-openfeign/blob/main/README.adoc#3-contributing[here].
|
||||
|
||||
## Community Support
|
||||
|
||||
|
||||
@@ -55,11 +55,11 @@ public interface StoreClient {
|
||||
}
|
||||
----
|
||||
|
||||
In the `@FeignClient` annotation the String value ("stores" above) is an arbitrary client name, which is used to create either a https://github.com/Netflix/ribbon[Ribbon] load-balancer (see <<spring-cloud-ribbon,below for details of Ribbon support>> and <<spring-cloud-circuitbreaker,below for details of Spring Cloud CircuitBreaker support>>) or https://github.com/spring-cloud/spring-cloud-commons/blob/master/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancer.java[Spring Cloud LoadBalancer].
|
||||
In the `@FeignClient` annotation the String value ("stores" above) is an arbitrary client name, which is used to create either a https://github.com/Netflix/ribbon[Ribbon] load-balancer (see <<spring-cloud-ribbon,below for details of Ribbon support>> and <<spring-cloud-circuitbreaker,below for details of Spring Cloud CircuitBreaker support>>) or https://github.com/spring-cloud/spring-cloud-commons/blob/main/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancer.java[Spring Cloud LoadBalancer].
|
||||
You can also specify a URL using the `url` attribute
|
||||
(absolute value or just a hostname). The name of the bean in the
|
||||
application context is the fully qualified name of the interface.
|
||||
To specify your own alias value you can use the `qualifier` value
|
||||
To specify your own alias value you can use the `qualifiers` value
|
||||
of the `@FeignClient` annotation.
|
||||
|
||||
The load-balancer client above will want to discover the physical addresses
|
||||
@@ -125,8 +125,8 @@ If none of them is in the classpath, the default feign client is used.
|
||||
|
||||
NOTE: `spring-cloud-starter-openfeign` supports both `spring-cloud-starter-netflix-ribbon` and `spring-cloud-starter-loadbalancer`. However, as they are optional dependencies, you need to make sure the one you want to use has been added to your project.
|
||||
|
||||
The OkHttpClient and ApacheHttpClient feign clients can be used by setting `feign.okhttp.enabled` or `feign.httpclient.enabled` to `true`, respectively, and having them on the classpath.
|
||||
You can customize the HTTP client used by providing a bean of either `org.apache.http.impl.client.CloseableHttpClient` when using Apache or `okhttp3.OkHttpClient` when using OK HTTP.
|
||||
The OkHttpClient and ApacheHttpClient and ApacheHC5 feign clients can be used by setting `feign.okhttp.enabled` or `feign.httpclient.enabled` or `feign.httpclient.hc5.enabled` to `true`, respectively, and having them on the classpath.
|
||||
You can customize the HTTP client used by providing a bean of either `org.apache.http.impl.client.CloseableHttpClient` when using Apache or `okhttp3.OkHttpClient` when using OK HTTP or `org.apache.hc.client5.http.impl.classic.CloseableHttpClient` when using Apache HC5.
|
||||
|
||||
Spring Cloud OpenFeign _does not_ provide the following beans by default for feign, but still looks up beans of these types from the application context to create the feign client:
|
||||
|
||||
@@ -277,6 +277,12 @@ public FeignClientConfigurer feignClientConfigurer() {
|
||||
|
||||
TIP: By default, Feign clients do not encode slash `/` characters. You can change this behaviour, by setting the value of `feign.client.decodeSlash` to `false`.
|
||||
|
||||
==== `SpringEncoder` configuration
|
||||
|
||||
In the `SpringEncoder` that we provide, we set `null` charset for binary content types and `UTF-8` for all the other ones.
|
||||
|
||||
You can modify this behaviour to derive the charset from the `Content-Type` header charset instead by setting the value of `feign.encoder.charset-from-content-type` to `true`.
|
||||
|
||||
[[timeout-handling]]
|
||||
=== Timeout Handling
|
||||
|
||||
@@ -387,7 +393,7 @@ favor for an opt-in approach.
|
||||
[[spring-cloud-feign-hystrix-fallback]]
|
||||
=== Feign Hystrix Fallbacks
|
||||
|
||||
Hystrix supports the notion of a fallback: a default code path that is executed when they circuit is open or there is an error. To enable fallbacks for a given `@FeignClient` set the `fallback` attribute to the class name that implements the fallback. You also need to declare your implementation as a Spring bean.
|
||||
Hystrix supports the notion of a fallback: a default code path that is executed when the circuit is open or there is an error. To enable fallbacks for a given `@FeignClient` set the `fallback` attribute to the class name that implements the fallback. You also need to declare your implementation as a Spring bean.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@@ -450,12 +456,12 @@ public class FooConfiguration {
|
||||
}
|
||||
----
|
||||
|
||||
The circuit breaker name follows this pattern `<feignClientName>_<calledMethod>`. When calling a `@FeignClient` with name `foo` and the called interface method is `bar` then the circuit breaker name will be `foo_bar`.
|
||||
The circuit breaker name follows this pattern `<feignClientName>#<calledMethod>`. When calling a `@FeignClient` with name `foo` and the called interface method is `bar` then the circuit breaker name will be `foo_bar`.
|
||||
|
||||
[[spring-cloud-feign-circuitbreaker-fallback]]
|
||||
=== Feign Spring Cloud CircuitBreaker Fallbacks
|
||||
|
||||
Spring Cloud CircuitBreaker supports the notion of a fallback: a default code path that is executed when they circuit is open or there is an error. To enable fallbacks for a given `@FeignClient` set the `fallback` attribute to the class name that implements the fallback. You also need to declare your implementation as a Spring bean.
|
||||
Spring Cloud CircuitBreaker supports the notion of a fallback: a default code path that is executed when the circuit is open or there is an error. To enable fallbacks for a given `@FeignClient` set the `fallback` attribute to the class name that implements the fallback. You also need to declare your implementation as a Spring bean.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@@ -517,10 +523,7 @@ public interface UserClient extends UserService {
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: It is generally not advisable to share an interface between a
|
||||
server and a client. It introduces tight coupling, and also actually
|
||||
doesn't work with Spring MVC in its current form (method parameter
|
||||
mapping is not inherited).
|
||||
WARNING: `@FeignClient` interfaces should not be shared between server and client and annotating `@FeignClient` interfaces with `@RequestMapping` on class level is no longer supported.
|
||||
|
||||
=== Feign request/response compression
|
||||
|
||||
@@ -714,4 +717,4 @@ feign.autoconfiguration.jackson.enabled=true
|
||||
|
||||
== Configuration properties
|
||||
|
||||
To see the list of all Sleuth related configuration properties please check link:appendix.html[the Appendix page].
|
||||
To see the list of all Spring Cloud OpenFeign related configuration properties please check link:appendix.html[the Appendix page].
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>2.2.7.RELEASE</version>
|
||||
<version>2.2.11.BUILD-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Spring Cloud OpenFeign</name>
|
||||
<description>Spring Cloud OpenFeign</description>
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-build</artifactId>
|
||||
<version>2.3.2.RELEASE</version>
|
||||
<version>2.3.6.BUILD-SNAPSHOT</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<scm>
|
||||
@@ -26,8 +26,8 @@
|
||||
<properties>
|
||||
<main.basedir>${basedir}</main.basedir>
|
||||
<jackson.version>2.11.3</jackson.version>
|
||||
<spring-cloud-commons.version>2.2.7.RELEASE</spring-cloud-commons.version>
|
||||
<spring-cloud-netflix.version>2.2.7.RELEASE</spring-cloud-netflix.version>
|
||||
<spring-cloud-commons.version>2.2.9.RELEASE</spring-cloud-commons.version>
|
||||
<spring-cloud-netflix.version>2.2.9.RELEASE</spring-cloud-netflix.version>
|
||||
|
||||
<!-- Plugin versions -->
|
||||
<maven-eclipse-plugin.version>2.10</maven-eclipse-plugin.version>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>2.2.7.RELEASE</version>
|
||||
<version>2.2.11.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-openfeign-core</artifactId>
|
||||
@@ -108,6 +108,11 @@
|
||||
<artifactId>feign-slf4j</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-hc5</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-httpclient</artifactId>
|
||||
|
||||
+50
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -28,6 +28,7 @@ import javax.annotation.PreDestroy;
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
import feign.Client;
|
||||
import feign.Feign;
|
||||
import feign.hc5.ApacheHttp5Client;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
import feign.okhttp.OkHttpClient;
|
||||
import okhttp3.ConnectionPool;
|
||||
@@ -40,6 +41,7 @@ import org.apache.http.conn.HttpClientConnectionManager;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
@@ -54,10 +56,12 @@ import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
|
||||
import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
|
||||
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
|
||||
import org.springframework.cloud.openfeign.support.DefaultGzipDecoderConfiguration;
|
||||
import org.springframework.cloud.openfeign.support.FeignEncoderProperties;
|
||||
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
|
||||
import org.springframework.cloud.openfeign.support.PageJacksonModule;
|
||||
import org.springframework.cloud.openfeign.support.SortJacksonModule;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -68,11 +72,14 @@ import org.springframework.data.domain.Sort;
|
||||
* @author Julien Roy
|
||||
* @author Grzegorz Poznachowski
|
||||
* @author Nikita Konev
|
||||
* @author Tim Peeters
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Nguyen Ky Thanh
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(Feign.class)
|
||||
@EnableConfigurationProperties({ FeignClientProperties.class,
|
||||
FeignHttpClientProperties.class })
|
||||
FeignHttpClientProperties.class, FeignEncoderProperties.class })
|
||||
@Import(DefaultGzipDecoderConfiguration.class)
|
||||
public class FeignAutoConfiguration {
|
||||
|
||||
@@ -114,8 +121,7 @@ public class FeignAutoConfiguration {
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnMissingClass({ "feign.hystrix.HystrixFeign",
|
||||
"org.springframework.cloud.client.circuitbreaker.CircuitBreaker" })
|
||||
@Conditional(DefaultFeignTargeterConditions.class)
|
||||
protected static class DefaultFeignTargeterConfiguration {
|
||||
|
||||
@Bean
|
||||
@@ -127,7 +133,10 @@ public class FeignAutoConfiguration {
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Conditional(FeignCircuitBreakerDisabledConditions.class)
|
||||
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
|
||||
@ConditionalOnProperty(value = "feign.hystrix.enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
protected static class HystrixFeignTargeterConfiguration {
|
||||
|
||||
@Bean
|
||||
@@ -140,15 +149,9 @@ public class FeignAutoConfiguration {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(CircuitBreaker.class)
|
||||
@ConditionalOnProperty("feign.circuitbreaker.enabled")
|
||||
@ConditionalOnProperty(value = "feign.circuitbreaker.enabled", havingValue = "true")
|
||||
protected static class CircuitBreakerPresentFeignTargeterConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(CircuitBreakerFactory.class)
|
||||
public Targeter defaultFeignTargeter() {
|
||||
return new DefaultTargeter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnBean(CircuitBreakerFactory.class)
|
||||
@@ -168,6 +171,7 @@ public class FeignAutoConfiguration {
|
||||
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
|
||||
@ConditionalOnMissingBean(CloseableHttpClient.class)
|
||||
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
|
||||
@Conditional(HttpClient5DisabledConditions.class)
|
||||
protected static class HttpClientFeignConfiguration {
|
||||
|
||||
private final Timer connectionManagerTimer = new Timer(
|
||||
@@ -286,4 +290,39 @@ public class FeignAutoConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(ApacheHttp5Client.class)
|
||||
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
|
||||
@ConditionalOnMissingBean(org.apache.hc.client5.http.impl.classic.CloseableHttpClient.class)
|
||||
@ConditionalOnProperty(value = "feign.httpclient.hc5.enabled", havingValue = "true")
|
||||
@Import(org.springframework.cloud.openfeign.clientconfig.HttpClient5FeignConfiguration.class)
|
||||
protected static class HttpClient5FeignConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(Client.class)
|
||||
public Client feignClient(
|
||||
org.apache.hc.client5.http.impl.classic.CloseableHttpClient httpClient5) {
|
||||
return new ApacheHttp5Client(httpClient5);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class DefaultFeignTargeterConditions extends AllNestedConditions {
|
||||
|
||||
DefaultFeignTargeterConditions() {
|
||||
super(ConfigurationPhase.PARSE_CONFIGURATION);
|
||||
}
|
||||
|
||||
@Conditional(FeignCircuitBreakerDisabledConditions.class)
|
||||
static class FeignCircuitBreakerDisabled {
|
||||
|
||||
}
|
||||
|
||||
@Conditional(HystrixDisabledConditions.class)
|
||||
static class HystrixDisabled {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@ public final class FeignCircuitBreaker {
|
||||
public Feign build(final FallbackFactory<?> nullableFallbackFactory) {
|
||||
super.invocationHandlerFactory(
|
||||
(target, dispatch) -> new FeignCircuitBreakerInvocationHandler(
|
||||
circuitBreakerFactory, feignClientName, target, dispatch,
|
||||
circuitBreakerFactory, target, dispatch,
|
||||
nullableFallbackFactory));
|
||||
return super.build();
|
||||
}
|
||||
|
||||
+3
-6
@@ -24,6 +24,7 @@ import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import feign.Feign;
|
||||
import feign.InvocationHandlerFactory;
|
||||
import feign.Target;
|
||||
|
||||
@@ -36,8 +37,6 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
|
||||
|
||||
private final CircuitBreakerFactory factory;
|
||||
|
||||
private final String feignClientName;
|
||||
|
||||
private final Target<?> target;
|
||||
|
||||
private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
|
||||
@@ -46,12 +45,10 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
|
||||
|
||||
private final Map<Method, Method> fallbackMethodMap;
|
||||
|
||||
FeignCircuitBreakerInvocationHandler(CircuitBreakerFactory factory,
|
||||
String feignClientName, Target<?> target,
|
||||
FeignCircuitBreakerInvocationHandler(CircuitBreakerFactory factory, Target<?> target,
|
||||
Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
|
||||
FallbackFactory<?> nullableFallbackFactory) {
|
||||
this.factory = factory;
|
||||
this.feignClientName = feignClientName;
|
||||
this.target = checkNotNull(target, "target");
|
||||
this.dispatch = checkNotNull(dispatch, "dispatch");
|
||||
this.fallbackMethodMap = toFallbackMethod(dispatch);
|
||||
@@ -79,7 +76,7 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
|
||||
else if ("toString".equals(method.getName())) {
|
||||
return toString();
|
||||
}
|
||||
String circuitName = this.feignClientName + "_" + method.getName();
|
||||
String circuitName = Feign.configKey(target.type(), method);
|
||||
CircuitBreaker circuitBreaker = this.factory.create(circuitName);
|
||||
Supplier<Object> supplier = asSupplier(method, args);
|
||||
if (this.nullableFallbackFactory != null) {
|
||||
|
||||
+21
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -33,6 +33,7 @@ import org.springframework.core.annotation.AliasFor;
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @author Venil Noronha
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -73,9 +74,28 @@ public @interface FeignClient {
|
||||
|
||||
/**
|
||||
* @return the <code>@Qualifier</code> value for the feign client.
|
||||
* @deprecated in favour of {@link #qualifiers()}.
|
||||
*
|
||||
* If both {@link #qualifier()} and {@link #qualifiers()} are present, we will use the
|
||||
* latter, unless the array returned by {@link #qualifiers()} is empty or only
|
||||
* contains <code>null</code> or whitespace values, in which case we'll fall back
|
||||
* first to {@link #qualifier()} and, if that's also not present, to the default =
|
||||
* <code>contextId + "FeignClient"</code>.
|
||||
*/
|
||||
@Deprecated
|
||||
String qualifier() default "";
|
||||
|
||||
/**
|
||||
* @return the <code>@Qualifiers</code> value for the feign client.
|
||||
*
|
||||
* If both {@link #qualifier()} and {@link #qualifiers()} are present, we will use the
|
||||
* latter, unless the array returned by {@link #qualifiers()} is empty or only
|
||||
* contains <code>null</code> or whitespace values, in which case we'll fall back
|
||||
* first to {@link #qualifier()} and, if that's also not present, to the default =
|
||||
* <code>contextId + "FeignClient"</code>.
|
||||
*/
|
||||
String[] qualifiers() default {};
|
||||
|
||||
/**
|
||||
* @return an absolute URL or resolvable hostname (the protocol is optional).
|
||||
*/
|
||||
|
||||
+14
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import feign.Feign;
|
||||
import feign.hystrix.FallbackFactory;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -28,6 +29,7 @@ import org.springframework.context.ApplicationContext;
|
||||
*
|
||||
* @author Sven Döring
|
||||
* @author Matt King
|
||||
* @author Sam Kruglov
|
||||
*/
|
||||
public class FeignClientBuilder {
|
||||
|
||||
@@ -69,6 +71,18 @@ public class FeignClientBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a {@link FeignBuilderCustomizer} to the underlying
|
||||
* {@link Feign.Builder}. May be called multiple times.
|
||||
* @param customizer applied in the same order as supplied here after applying
|
||||
* customizers found in the context.
|
||||
* @return the {@link Builder} with the customizer added
|
||||
*/
|
||||
public Builder<T> customize(final FeignBuilderCustomizer customizer) {
|
||||
this.feignClientFactoryBean.addCustomizer(customizer);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> contextId(final String contextId) {
|
||||
this.feignClientFactoryBean.setContextId(contextId);
|
||||
return this;
|
||||
|
||||
+41
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -35,6 +35,8 @@ import feign.Target.HardCodedTarget;
|
||||
import feign.codec.Decoder;
|
||||
import feign.codec.Encoder;
|
||||
import feign.codec.ErrorDecoder;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
@@ -45,6 +47,7 @@ import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.cloud.openfeign.clientconfig.FeignClientConfigurer;
|
||||
import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient;
|
||||
import org.springframework.cloud.openfeign.loadbalancer.RetryableFeignBlockingLoadBalancerClient;
|
||||
import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
@@ -61,6 +64,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Ilia Ilinykh
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Sam Kruglov
|
||||
*/
|
||||
public class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
|
||||
ApplicationContextAware, BeanFactoryAware {
|
||||
@@ -70,6 +74,8 @@ public class FeignClientFactoryBean implements FactoryBean<Object>, Initializing
|
||||
* lifecycle race condition.
|
||||
***********************************/
|
||||
|
||||
private static Log LOG = LogFactory.getLog(FeignClientFactoryBean.class);
|
||||
|
||||
private Class<?> type;
|
||||
|
||||
private String name;
|
||||
@@ -96,6 +102,10 @@ public class FeignClientFactoryBean implements FactoryBean<Object>, Initializing
|
||||
|
||||
private int connectTimeoutMillis = new Request.Options().connectTimeoutMillis();
|
||||
|
||||
private boolean followRedirects = new Request.Options().isFollowRedirects();
|
||||
|
||||
private List<FeignBuilderCustomizer> additionalCustomizers = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
Assert.hasText(contextId, "Context id must be set");
|
||||
@@ -131,6 +141,7 @@ public class FeignClientFactoryBean implements FactoryBean<Object>, Initializing
|
||||
.forEach(feignBuilderCustomizer -> feignBuilderCustomizer
|
||||
.customize(builder));
|
||||
}
|
||||
additionalCustomizers.forEach(customizer -> customizer.customize(builder));
|
||||
}
|
||||
|
||||
protected void configureFeign(FeignContext context, Feign.Builder builder) {
|
||||
@@ -192,6 +203,7 @@ public class FeignClientFactoryBean implements FactoryBean<Object>, Initializing
|
||||
builder.options(options);
|
||||
readTimeoutMillis = options.readTimeoutMillis();
|
||||
connectTimeoutMillis = options.connectTimeoutMillis();
|
||||
followRedirects = options.isFollowRedirects();
|
||||
}
|
||||
Map<String, RequestInterceptor> requestInterceptors = getInheritedAwareInstances(
|
||||
context, RequestInterceptor.class);
|
||||
@@ -231,9 +243,11 @@ public class FeignClientFactoryBean implements FactoryBean<Object>, Initializing
|
||||
? config.getConnectTimeout() : connectTimeoutMillis;
|
||||
readTimeoutMillis = config.getReadTimeout() != null ? config.getReadTimeout()
|
||||
: readTimeoutMillis;
|
||||
followRedirects = config.isFollowRedirects() != null ? config.isFollowRedirects()
|
||||
: followRedirects;
|
||||
|
||||
builder.options(new Request.Options(connectTimeoutMillis, TimeUnit.MILLISECONDS,
|
||||
readTimeoutMillis, TimeUnit.MILLISECONDS, true));
|
||||
readTimeoutMillis, TimeUnit.MILLISECONDS, followRedirects));
|
||||
|
||||
if (config.getRetryer() != null) {
|
||||
Retryer retryer = getOrInstantiate(config.getRetryer());
|
||||
@@ -339,7 +353,7 @@ public class FeignClientFactoryBean implements FactoryBean<Object>, Initializing
|
||||
}
|
||||
|
||||
throw new IllegalStateException(
|
||||
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
|
||||
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon or spring-cloud-starter-loadbalancer?");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -359,6 +373,11 @@ public class FeignClientFactoryBean implements FactoryBean<Object>, Initializing
|
||||
Feign.Builder builder = feign(context);
|
||||
|
||||
if (!StringUtils.hasText(url)) {
|
||||
|
||||
if (LOG.isInfoEnabled()) {
|
||||
LOG.info("For '" + name
|
||||
+ "' URL not provided. Will try picking an instance via load-balancing.");
|
||||
}
|
||||
if (!name.startsWith("http")) {
|
||||
url = "http://" + name;
|
||||
}
|
||||
@@ -385,6 +404,12 @@ public class FeignClientFactoryBean implements FactoryBean<Object>, Initializing
|
||||
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
|
||||
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
|
||||
}
|
||||
if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
|
||||
// not load balancing because we have a url,
|
||||
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
|
||||
client = ((RetryableFeignBlockingLoadBalancerClient) client)
|
||||
.getDelegate();
|
||||
}
|
||||
builder.client(client);
|
||||
}
|
||||
Targeter targeter = get(context, Targeter.class);
|
||||
@@ -471,6 +496,10 @@ public class FeignClientFactoryBean implements FactoryBean<Object>, Initializing
|
||||
this.inheritParentContext = inheritParentContext;
|
||||
}
|
||||
|
||||
public void addCustomizer(FeignBuilderCustomizer customizer) {
|
||||
additionalCustomizers.add(customizer);
|
||||
}
|
||||
|
||||
public ApplicationContext getApplicationContext() {
|
||||
return applicationContext;
|
||||
}
|
||||
@@ -513,13 +542,17 @@ public class FeignClientFactoryBean implements FactoryBean<Object>, Initializing
|
||||
&& Objects.equals(fallback, that.fallback)
|
||||
&& Objects.equals(fallbackFactory, that.fallbackFactory)
|
||||
&& Objects.equals(name, that.name) && Objects.equals(path, that.path)
|
||||
&& Objects.equals(type, that.type) && Objects.equals(url, that.url);
|
||||
&& Objects.equals(type, that.type) && Objects.equals(url, that.url)
|
||||
&& Objects.equals(connectTimeoutMillis, that.connectTimeoutMillis)
|
||||
&& Objects.equals(readTimeoutMillis, that.readTimeoutMillis)
|
||||
&& Objects.equals(followRedirects, that.followRedirects);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(applicationContext, beanFactory, decode404,
|
||||
inheritParentContext, fallback, fallbackFactory, name, path, type, url);
|
||||
inheritParentContext, fallback, fallbackFactory, name, path, type, url,
|
||||
readTimeoutMillis, connectTimeoutMillis, followRedirects);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -533,6 +566,9 @@ public class FeignClientFactoryBean implements FactoryBean<Object>, Initializing
|
||||
.append("beanFactory=").append(beanFactory).append(", ")
|
||||
.append("fallback=").append(fallback).append(", ")
|
||||
.append("fallbackFactory=").append(fallbackFactory).append("}")
|
||||
.append("connectTimeoutMillis=").append(connectTimeoutMillis).append("}")
|
||||
.append("readTimeoutMillis=").append(readTimeoutMillis).append("}")
|
||||
.append("followRedirects=").append(followRedirects).append("}")
|
||||
.toString();
|
||||
}
|
||||
|
||||
|
||||
+15
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -37,6 +37,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
* @author Eko Kurniawan Khannedy
|
||||
* @author Ilia Ilinykh
|
||||
* @author Ram Anaswara
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@ConfigurationProperties("feign.client")
|
||||
public class FeignClientProperties {
|
||||
@@ -136,6 +137,8 @@ public class FeignClientProperties {
|
||||
|
||||
private ExceptionPropagationPolicy exceptionPropagationPolicy;
|
||||
|
||||
private Boolean followRedirects;
|
||||
|
||||
public Logger.Level getLoggerLevel() {
|
||||
return loggerLevel;
|
||||
}
|
||||
@@ -244,6 +247,14 @@ public class FeignClientProperties {
|
||||
this.exceptionPropagationPolicy = exceptionPropagationPolicy;
|
||||
}
|
||||
|
||||
public Boolean isFollowRedirects() {
|
||||
return followRedirects;
|
||||
}
|
||||
|
||||
public void setFollowRedirects(Boolean followRedirects) {
|
||||
this.followRedirects = followRedirects;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@@ -266,8 +277,8 @@ public class FeignClientProperties {
|
||||
&& Objects.equals(exceptionPropagationPolicy,
|
||||
that.exceptionPropagationPolicy)
|
||||
&& Objects.equals(defaultRequestHeaders, that.defaultRequestHeaders)
|
||||
&& Objects.equals(defaultQueryParameters,
|
||||
that.defaultQueryParameters);
|
||||
&& Objects.equals(defaultQueryParameters, that.defaultQueryParameters)
|
||||
&& Objects.equals(followRedirects, that.followRedirects);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -275,7 +286,7 @@ public class FeignClientProperties {
|
||||
return Objects.hash(loggerLevel, connectTimeout, readTimeout, retryer,
|
||||
errorDecoder, requestInterceptors, decode404, encoder, decoder,
|
||||
contract, exceptionPropagationPolicy, defaultQueryParameters,
|
||||
defaultRequestHeaders);
|
||||
defaultRequestHeaders, followRedirects);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+23
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -23,6 +23,7 @@ import com.netflix.hystrix.HystrixCommand;
|
||||
import feign.Contract;
|
||||
import feign.Feign;
|
||||
import feign.Logger;
|
||||
import feign.QueryMapEncoder;
|
||||
import feign.Retryer;
|
||||
import feign.codec.Decoder;
|
||||
import feign.codec.Encoder;
|
||||
@@ -45,7 +46,9 @@ import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
|
||||
import org.springframework.cloud.openfeign.clientconfig.FeignClientConfigurer;
|
||||
import org.springframework.cloud.openfeign.support.AbstractFormWriter;
|
||||
import org.springframework.cloud.openfeign.support.FeignEncoderProperties;
|
||||
import org.springframework.cloud.openfeign.support.PageableSpringEncoder;
|
||||
import org.springframework.cloud.openfeign.support.PageableSpringQueryMapEncoder;
|
||||
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
|
||||
import org.springframework.cloud.openfeign.support.SpringDecoder;
|
||||
import org.springframework.cloud.openfeign.support.SpringEncoder;
|
||||
@@ -63,6 +66,8 @@ import static feign.form.ContentType.MULTIPART;
|
||||
* @author Dave Syer
|
||||
* @author Venil Noronha
|
||||
* @author Darren Foong
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Hyeonmin Park
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class FeignClientsConfiguration {
|
||||
@@ -85,6 +90,9 @@ public class FeignClientsConfiguration {
|
||||
@Autowired(required = false)
|
||||
private FeignClientProperties feignClientProperties;
|
||||
|
||||
@Autowired(required = false)
|
||||
private FeignEncoderProperties encoderProperties;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Decoder feignDecoder() {
|
||||
@@ -96,7 +104,7 @@ public class FeignClientsConfiguration {
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnMissingClass("org.springframework.data.domain.Pageable")
|
||||
public Encoder feignEncoder(ObjectProvider<AbstractFormWriter> formWriterProvider) {
|
||||
return springEncoder(formWriterProvider);
|
||||
return springEncoder(formWriterProvider, encoderProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -105,7 +113,7 @@ public class FeignClientsConfiguration {
|
||||
public Encoder feignEncoderPageable(
|
||||
ObjectProvider<AbstractFormWriter> formWriterProvider) {
|
||||
PageableSpringEncoder encoder = new PageableSpringEncoder(
|
||||
springEncoder(formWriterProvider));
|
||||
springEncoder(formWriterProvider, encoderProperties));
|
||||
|
||||
if (springDataWebProperties != null) {
|
||||
encoder.setPageParameter(
|
||||
@@ -118,6 +126,13 @@ public class FeignClientsConfiguration {
|
||||
return encoder;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = "org.springframework.data.domain.Pageable")
|
||||
@ConditionalOnMissingBean
|
||||
public QueryMapEncoder feignQueryMapEncoderPageable() {
|
||||
return new PageableSpringQueryMapEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Contract feignContract(ConversionService feignConversionService) {
|
||||
@@ -162,15 +177,17 @@ public class FeignClientsConfiguration {
|
||||
};
|
||||
}
|
||||
|
||||
private Encoder springEncoder(ObjectProvider<AbstractFormWriter> formWriterProvider) {
|
||||
private Encoder springEncoder(ObjectProvider<AbstractFormWriter> formWriterProvider,
|
||||
FeignEncoderProperties encoderProperties) {
|
||||
AbstractFormWriter formWriter = formWriterProvider.getIfAvailable();
|
||||
|
||||
if (formWriter != null) {
|
||||
return new SpringEncoder(new SpringPojoFormEncoder(formWriter),
|
||||
this.messageConverters);
|
||||
this.messageConverters, encoderProperties);
|
||||
}
|
||||
else {
|
||||
return new SpringEncoder(new SpringFormEncoder(), this.messageConverters);
|
||||
return new SpringEncoder(new SpringFormEncoder(), this.messageConverters,
|
||||
encoderProperties);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+24
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -20,8 +20,12 @@ import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -48,6 +52,7 @@ import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.core.type.filter.AnnotationTypeFilter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -57,6 +62,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Gang Li
|
||||
* @author Michal Domagala
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
class FeignClientsRegistrar
|
||||
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
|
||||
@@ -243,7 +249,6 @@ class FeignClientsRegistrar
|
||||
definition.setLazyInit(true);
|
||||
validate(attributes);
|
||||
|
||||
String alias = contextId + "FeignClient";
|
||||
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
|
||||
beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
|
||||
beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);
|
||||
@@ -253,13 +258,13 @@ class FeignClientsRegistrar
|
||||
|
||||
beanDefinition.setPrimary(primary);
|
||||
|
||||
String qualifier = getQualifier(attributes);
|
||||
if (StringUtils.hasText(qualifier)) {
|
||||
alias = qualifier;
|
||||
String[] qualifiers = getQualifiers(attributes);
|
||||
if (ObjectUtils.isEmpty(qualifiers)) {
|
||||
qualifiers = new String[] { contextId + "FeignClient" };
|
||||
}
|
||||
|
||||
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
|
||||
new String[] { alias });
|
||||
qualifiers);
|
||||
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
|
||||
}
|
||||
|
||||
@@ -379,6 +384,19 @@ class FeignClientsRegistrar
|
||||
return null;
|
||||
}
|
||||
|
||||
private String[] getQualifiers(Map<String, Object> client) {
|
||||
if (client == null) {
|
||||
return null;
|
||||
}
|
||||
List<String> qualifierList = new ArrayList<>(
|
||||
Arrays.asList((String[]) client.get("qualifiers")));
|
||||
qualifierList.removeIf(qualifier -> !StringUtils.hasText(qualifier));
|
||||
if (qualifierList.isEmpty() && getQualifier(client) != null) {
|
||||
qualifierList = Collections.singletonList(getQualifier(client));
|
||||
}
|
||||
return !qualifierList.isEmpty() ? qualifierList.toArray(new String[0]) : null;
|
||||
}
|
||||
|
||||
private String getClientName(Map<String, Object> client) {
|
||||
if (client == null) {
|
||||
return null;
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2013-2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
|
||||
/**
|
||||
* @author Nguyen Ky Thanh
|
||||
*/
|
||||
public class HttpClient5DisabledConditions extends AnyNestedCondition {
|
||||
|
||||
public HttpClient5DisabledConditions() {
|
||||
super(ConfigurationPhase.PARSE_CONFIGURATION);
|
||||
}
|
||||
|
||||
@ConditionalOnMissingClass("feign.hc5.ApacheHttp5Client")
|
||||
static class ApacheHttp5ClientClassMissing {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(value = "feign.httpclient.hc5.enabled", havingValue = "false",
|
||||
matchIfMissing = true)
|
||||
static class HttpClient5Disabled {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
|
||||
/**
|
||||
* @author Tim Peeters
|
||||
*/
|
||||
class HystrixDisabledConditions extends AnyNestedCondition {
|
||||
|
||||
HystrixDisabledConditions() {
|
||||
super(ConfigurationPhase.PARSE_CONFIGURATION);
|
||||
}
|
||||
|
||||
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
|
||||
static class HystrixFeignClassMissing {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(value = "feign.hystrix.enabled", havingValue = "false")
|
||||
static class HystrixFeignDisabled {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -75,7 +75,7 @@ public class MatrixVariableParameterProcessor implements AnnotatedParameterProce
|
||||
private String expandMap(Object object) {
|
||||
Map<String, Object> paramMap = (Map) object;
|
||||
|
||||
return paramMap.keySet().stream()
|
||||
return paramMap.keySet().stream().filter(key -> paramMap.get(key) != null)
|
||||
.map(key -> ";" + key + "=" + paramMap.get(key).toString())
|
||||
.collect(Collectors.joining());
|
||||
}
|
||||
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright 2013-2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign.clientconfig;
|
||||
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClients;
|
||||
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
|
||||
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
|
||||
import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory;
|
||||
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
|
||||
import org.apache.hc.core5.http.io.SocketConfig;
|
||||
import org.apache.hc.core5.http.ssl.TLS;
|
||||
import org.apache.hc.core5.io.CloseMode;
|
||||
import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
|
||||
import org.apache.hc.core5.pool.PoolReusePolicy;
|
||||
import org.apache.hc.core5.ssl.SSLContexts;
|
||||
import org.apache.hc.core5.util.TimeValue;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Default configuration for {@link CloseableHttpClient}.
|
||||
*
|
||||
* @author Nguyen Ky Thanh
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnMissingBean(CloseableHttpClient.class)
|
||||
public class HttpClient5FeignConfiguration {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(HttpClient5FeignConfiguration.class);
|
||||
|
||||
private CloseableHttpClient httpClient5;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(HttpClientConnectionManager.class)
|
||||
public HttpClientConnectionManager hc5ConnectionManager(
|
||||
FeignHttpClientProperties httpClientProperties) {
|
||||
return PoolingHttpClientConnectionManagerBuilder.create()
|
||||
.setSSLSocketFactory(httpsSSLConnectionSocketFactory(
|
||||
httpClientProperties.isDisableSslValidation()))
|
||||
.setMaxConnTotal(httpClientProperties.getMaxConnections())
|
||||
.setMaxConnPerRoute(httpClientProperties.getMaxConnectionsPerRoute())
|
||||
.setConnPoolPolicy(PoolReusePolicy.valueOf(
|
||||
httpClientProperties.getHc5().getPoolReusePolicy().name()))
|
||||
.setPoolConcurrencyPolicy(PoolConcurrencyPolicy.valueOf(
|
||||
httpClientProperties.getHc5().getPoolConcurrencyPolicy().name()))
|
||||
.setConnectionTimeToLive(
|
||||
TimeValue.of(httpClientProperties.getTimeToLive(),
|
||||
httpClientProperties.getTimeToLiveUnit()))
|
||||
.setDefaultSocketConfig(SocketConfig.custom()
|
||||
.setSoTimeout(Timeout.of(
|
||||
httpClientProperties.getHc5().getSocketTimeout(),
|
||||
httpClientProperties.getHc5().getSocketTimeoutUnit()))
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CloseableHttpClient httpClient5(HttpClientConnectionManager connectionManager,
|
||||
FeignHttpClientProperties httpClientProperties) {
|
||||
httpClient5 = HttpClients.custom().disableCookieManagement().useSystemProperties()
|
||||
.setConnectionManager(connectionManager).evictExpiredConnections()
|
||||
.setDefaultRequestConfig(
|
||||
RequestConfig.custom()
|
||||
.setConnectTimeout(Timeout.of(
|
||||
httpClientProperties.getConnectionTimeout(),
|
||||
TimeUnit.MILLISECONDS))
|
||||
.setRedirectsEnabled(
|
||||
httpClientProperties.isFollowRedirects())
|
||||
.build())
|
||||
.build();
|
||||
return httpClient5;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
if (httpClient5 != null) {
|
||||
httpClient5.close(CloseMode.GRACEFUL);
|
||||
}
|
||||
}
|
||||
|
||||
private LayeredConnectionSocketFactory httpsSSLConnectionSocketFactory(
|
||||
boolean isDisableSslValidation) {
|
||||
final SSLConnectionSocketFactoryBuilder sslConnectionSocketFactoryBuilder = SSLConnectionSocketFactoryBuilder
|
||||
.create().setTlsVersions(TLS.V_1_3, TLS.V_1_2);
|
||||
|
||||
if (isDisableSslValidation) {
|
||||
try {
|
||||
final SSLContext sslContext = SSLContext.getInstance("SSL");
|
||||
sslContext.init(null,
|
||||
new TrustManager[] { new DisabledValidationTrustManager() },
|
||||
new SecureRandom());
|
||||
sslConnectionSocketFactoryBuilder.setSslContext(sslContext);
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
LOG.warn("Error creating SSLContext", e);
|
||||
}
|
||||
catch (KeyManagementException e) {
|
||||
LOG.warn("Error creating SSLContext", e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
sslConnectionSocketFactoryBuilder
|
||||
.setSslContext(SSLContexts.createSystemDefault());
|
||||
}
|
||||
|
||||
return sslConnectionSocketFactoryBuilder.build();
|
||||
}
|
||||
|
||||
static class DisabledValidationTrustManager implements X509TrustManager {
|
||||
|
||||
DisabledValidationTrustManager() {
|
||||
}
|
||||
|
||||
public void checkClientTrusted(X509Certificate[] x509Certificates, String s)
|
||||
throws CertificateException {
|
||||
}
|
||||
|
||||
public void checkServerTrusted(X509Certificate[] x509Certificates, String s)
|
||||
throws CertificateException {
|
||||
}
|
||||
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+8
-37
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016-2020 the original author or authors.
|
||||
* Copyright 2016-2021 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.
|
||||
@@ -16,37 +16,30 @@
|
||||
|
||||
package org.springframework.cloud.openfeign.hateoas;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.hateoas.RepresentationModel;
|
||||
import org.springframework.hateoas.mediatype.MessageResolver;
|
||||
import org.springframework.hateoas.mediatype.hal.CurieProvider;
|
||||
import org.springframework.hateoas.mediatype.hal.DefaultCurieProvider;
|
||||
import org.springframework.hateoas.mediatype.hal.HalConfiguration;
|
||||
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule;
|
||||
import org.springframework.hateoas.server.LinkRelationProvider;
|
||||
import org.springframework.hateoas.mediatype.hal.HalMediaTypeConfiguration;
|
||||
import org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter;
|
||||
|
||||
import static org.springframework.hateoas.MediaTypes.HAL_JSON;
|
||||
|
||||
/**
|
||||
* @author Hector Espert
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnWebApplication
|
||||
@@ -54,41 +47,19 @@ import static org.springframework.hateoas.MediaTypes.HAL_JSON;
|
||||
@AutoConfigureAfter({ JacksonAutoConfiguration.class,
|
||||
HttpMessageConvertersAutoConfiguration.class,
|
||||
RepositoryRestMvcAutoConfiguration.class })
|
||||
@AutoConfigureBefore(HypermediaAutoConfiguration.class)
|
||||
public class FeignHalAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(HalMediaTypeConfiguration.class)
|
||||
@ConditionalOnMissingBean
|
||||
public TypeConstrainedMappingJackson2HttpMessageConverter halJacksonHttpMessageConverter(
|
||||
ObjectProvider<ObjectMapper> objectMapper,
|
||||
ObjectProvider<HalConfiguration> halConfiguration,
|
||||
ObjectProvider<MessageResolver> messageResolver,
|
||||
ObjectProvider<CurieProvider> curieProvider,
|
||||
ObjectProvider<LinkRelationProvider> linkRelationProvider) {
|
||||
|
||||
HalMediaTypeConfiguration halConfiguration) {
|
||||
ObjectMapper mapper = objectMapper.getIfAvailable(ObjectMapper::new).copy();
|
||||
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
|
||||
HalConfiguration configuration = halConfiguration
|
||||
.getIfAvailable(HalConfiguration::new);
|
||||
|
||||
CurieProvider curieProviderInstance = curieProvider
|
||||
.getIfAvailable(() -> new DefaultCurieProvider(Collections.emptyMap()));
|
||||
|
||||
Jackson2HalModule.HalHandlerInstantiator halHandlerInstantiator = new Jackson2HalModule.HalHandlerInstantiator(
|
||||
linkRelationProvider.getIfAvailable(), curieProviderInstance,
|
||||
messageResolver.getIfAvailable(), configuration);
|
||||
|
||||
mapper.setHandlerInstantiator(halHandlerInstantiator);
|
||||
|
||||
if (!Jackson2HalModule.isAlreadyRegisteredIn(mapper)) {
|
||||
Jackson2HalModule halModule = new Jackson2HalModule();
|
||||
mapper.registerModule(halModule);
|
||||
}
|
||||
|
||||
halConfiguration.configureObjectMapper(mapper);
|
||||
TypeConstrainedMappingJackson2HttpMessageConverter converter = new TypeConstrainedMappingJackson2HttpMessageConverter(
|
||||
RepresentationModel.class);
|
||||
converter.setSupportedMediaTypes(Arrays.asList(HAL_JSON));
|
||||
converter.setSupportedMediaTypes(Collections.singletonList(HAL_JSON));
|
||||
converter.setObjectMapper(mapper);
|
||||
return converter;
|
||||
}
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -38,6 +38,7 @@ import org.springframework.context.annotation.Import;
|
||||
* <code>spring.cloud.loadbalancer.ribbon.enabled</code> to <code>true</code>.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Nguyen Ky Thanh
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@ConditionalOnClass(Feign.class)
|
||||
@@ -51,6 +52,7 @@ import org.springframework.context.annotation.Import;
|
||||
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
|
||||
@Import({ HttpClientFeignLoadBalancerConfiguration.class,
|
||||
OkHttpFeignLoadBalancerConfiguration.class,
|
||||
HttpClient5FeignLoadBalancerConfiguration.class,
|
||||
DefaultFeignLoadBalancerConfiguration.class })
|
||||
public class FeignLoadBalancerAutoConfiguration {
|
||||
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2013-2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign.loadbalancer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import feign.Client;
|
||||
import feign.hc5.ApacheHttp5Client;
|
||||
import org.apache.hc.client5.http.classic.HttpClient;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
|
||||
import org.springframework.cloud.openfeign.clientconfig.HttpClient5FeignConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
|
||||
/**
|
||||
* Configuration instantiating a {@link BlockingLoadBalancerClient}-based {@link Client}
|
||||
* object that uses {@link ApacheHttp5Client} under the hood.
|
||||
*
|
||||
* @author Nguyen Ky Thanh
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(ApacheHttp5Client.class)
|
||||
@ConditionalOnBean(BlockingLoadBalancerClient.class)
|
||||
@ConditionalOnProperty(value = "feign.httpclient.hc5.enabled", havingValue = "true")
|
||||
@Import(HttpClient5FeignConfiguration.class)
|
||||
class HttpClient5FeignLoadBalancerConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@Conditional(OnRetryNotEnabledCondition.class)
|
||||
public Client feignClient(BlockingLoadBalancerClient loadBalancerClient,
|
||||
HttpClient httpClient5) {
|
||||
Client delegate = new ApacheHttp5Client(httpClient5);
|
||||
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
|
||||
@ConditionalOnBean(LoadBalancedRetryFactory.class)
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled",
|
||||
havingValue = "true", matchIfMissing = true)
|
||||
public Client feignRetryClient(BlockingLoadBalancerClient loadBalancerClient,
|
||||
HttpClient httpClient5,
|
||||
List<LoadBalancedRetryFactory> loadBalancedRetryFactories) {
|
||||
AnnotationAwareOrderComparator.sort(loadBalancedRetryFactories);
|
||||
Client delegate = new ApacheHttp5Client(httpClient5);
|
||||
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient,
|
||||
loadBalancedRetryFactories.get(0));
|
||||
}
|
||||
|
||||
}
|
||||
+4
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
|
||||
import org.springframework.cloud.openfeign.HttpClient5DisabledConditions;
|
||||
import org.springframework.cloud.openfeign.clientconfig.HttpClientFeignConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
@@ -40,12 +41,14 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
* object that uses {@link ApacheHttpClient} under the hood.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Nguyen Ky Thanh
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(ApacheHttpClient.class)
|
||||
@ConditionalOnBean(BlockingLoadBalancerClient.class)
|
||||
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
|
||||
@Conditional(HttpClient5DisabledConditions.class)
|
||||
@Import(HttpClientFeignConfiguration.class)
|
||||
class HttpClientFeignLoadBalancerConfiguration {
|
||||
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign.loadbalancer;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.URI;
|
||||
|
||||
import feign.Response;
|
||||
|
||||
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
|
||||
|
||||
/**
|
||||
* A {@link RetryableStatusCodeException} for {@link Response}s.
|
||||
*
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class LoadBalancerResponseStatusCodeException
|
||||
extends RetryableStatusCodeException {
|
||||
|
||||
private final Response response;
|
||||
|
||||
public LoadBalancerResponseStatusCodeException(String serviceId, Response response,
|
||||
byte[] body, URI uri) {
|
||||
super(serviceId, response.status(), response, uri);
|
||||
this.response = Response.builder()
|
||||
.body(new ByteArrayInputStream(body), body.length)
|
||||
.headers(response.headers()).reason(response.reason())
|
||||
.status(response.status()).request(response.request()).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response getResponse() {
|
||||
return this.response;
|
||||
}
|
||||
|
||||
}
|
||||
+5
-3
@@ -36,7 +36,6 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancedRecoveryCallbac
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
|
||||
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
|
||||
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@@ -46,6 +45,7 @@ import org.springframework.retry.backoff.BackOffPolicy;
|
||||
import org.springframework.retry.backoff.NoBackOffPolicy;
|
||||
import org.springframework.retry.policy.NeverRetryPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
/**
|
||||
* A {@link Client} implementation that provides Spring Retry support for requests
|
||||
@@ -112,9 +112,11 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
|
||||
LOG.debug(
|
||||
String.format("Retrying on status code: %d", responseStatus));
|
||||
}
|
||||
byte[] byteArray = response.body() == null ? new byte[] {}
|
||||
: StreamUtils.copyToByteArray(response.body().asInputStream());
|
||||
response.close();
|
||||
throw new RetryableStatusCodeException(serviceId, responseStatus,
|
||||
response, URI.create(request.url()));
|
||||
throw new LoadBalancerResponseStatusCodeException(serviceId, response,
|
||||
byteArray, URI.create(request.url()));
|
||||
}
|
||||
return response;
|
||||
}, new LoadBalancedRecoveryCallback<Response, Response>() {
|
||||
|
||||
+32
-26
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -24,6 +24,7 @@ import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.netflix.client.AbstractLoadBalancerAwareClient;
|
||||
import com.netflix.client.ClientRequest;
|
||||
@@ -65,16 +66,19 @@ public class FeignLoadBalancer extends
|
||||
|
||||
protected ServerIntrospector serverIntrospector;
|
||||
|
||||
protected boolean followRedirects;
|
||||
|
||||
public FeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig,
|
||||
ServerIntrospector serverIntrospector) {
|
||||
super(lb, clientConfig);
|
||||
this.setRetryHandler(RetryHandler.DEFAULT);
|
||||
setRetryHandler(RetryHandler.DEFAULT);
|
||||
this.clientConfig = clientConfig;
|
||||
this.ribbon = RibbonProperties.from(clientConfig);
|
||||
RibbonProperties ribbon = this.ribbon;
|
||||
this.connectTimeout = ribbon.getConnectTimeout();
|
||||
this.readTimeout = ribbon.getReadTimeout();
|
||||
connectTimeout = ribbon.getConnectTimeout();
|
||||
readTimeout = ribbon.getReadTimeout();
|
||||
this.serverIntrospector = serverIntrospector;
|
||||
followRedirects = ribbon.isFollowRedirects();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -83,11 +87,13 @@ public class FeignLoadBalancer extends
|
||||
Request.Options options;
|
||||
if (configOverride != null) {
|
||||
RibbonProperties override = RibbonProperties.from(configOverride);
|
||||
options = new Request.Options(override.connectTimeout(this.connectTimeout),
|
||||
override.readTimeout(this.readTimeout));
|
||||
options = new Request.Options(override.connectTimeout(connectTimeout),
|
||||
TimeUnit.MILLISECONDS, override.readTimeout(readTimeout),
|
||||
TimeUnit.MILLISECONDS, override.isFollowRedirects(followRedirects));
|
||||
}
|
||||
else {
|
||||
options = new Request.Options(this.connectTimeout, this.readTimeout);
|
||||
options = new Request.Options(connectTimeout, TimeUnit.MILLISECONDS,
|
||||
readTimeout, TimeUnit.MILLISECONDS, followRedirects);
|
||||
}
|
||||
Response response = request.client().execute(request.toRequest(), options);
|
||||
return new RibbonResponse(request.getUri(), response);
|
||||
@@ -96,24 +102,24 @@ public class FeignLoadBalancer extends
|
||||
@Override
|
||||
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
|
||||
RibbonRequest request, IClientConfig requestConfig) {
|
||||
if (this.ribbon.isOkToRetryOnAllOperations()) {
|
||||
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
|
||||
if (ribbon.isOkToRetryOnAllOperations()) {
|
||||
return new RequestSpecificRetryHandler(true, true, getRetryHandler(),
|
||||
requestConfig);
|
||||
}
|
||||
if (!request.toRequest().httpMethod().name().equals("GET")) {
|
||||
return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(),
|
||||
return new RequestSpecificRetryHandler(true, false, getRetryHandler(),
|
||||
requestConfig);
|
||||
}
|
||||
else {
|
||||
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
|
||||
return new RequestSpecificRetryHandler(true, true, getRetryHandler(),
|
||||
requestConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI reconstructURIWithServer(Server server, URI original) {
|
||||
URI uri = updateToSecureConnectionIfNeeded(original, this.clientConfig,
|
||||
this.serverIntrospector, server);
|
||||
URI uri = updateToSecureConnectionIfNeeded(original, clientConfig,
|
||||
serverIntrospector, server);
|
||||
return super.reconstructURIWithServer(server, uri);
|
||||
}
|
||||
|
||||
@@ -137,11 +143,11 @@ public class FeignLoadBalancer extends
|
||||
}
|
||||
|
||||
Request toRequest() {
|
||||
return toRequest(this.request);
|
||||
return toRequest(request);
|
||||
}
|
||||
|
||||
Client client() {
|
||||
return this.client;
|
||||
return client;
|
||||
}
|
||||
|
||||
HttpRequest toHttpRequest() {
|
||||
@@ -179,16 +185,16 @@ public class FeignLoadBalancer extends
|
||||
}
|
||||
|
||||
public Request getRequest() {
|
||||
return this.request;
|
||||
return request;
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return this.client;
|
||||
return client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
return new RibbonRequest(this.client, this.request, getUri());
|
||||
return new RibbonRequest(client, request, getUri());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -206,37 +212,37 @@ public class FeignLoadBalancer extends
|
||||
|
||||
@Override
|
||||
public Object getPayload() {
|
||||
return this.response.body();
|
||||
return response.body();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPayload() {
|
||||
return this.response.body() != null;
|
||||
return response.body() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuccess() {
|
||||
return this.response.status() == 200;
|
||||
return response.status() == 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getRequestedURI() {
|
||||
return this.uri;
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Collection<String>> getHeaders() {
|
||||
return this.response.headers();
|
||||
return response.headers();
|
||||
}
|
||||
|
||||
Response toResponse() {
|
||||
return this.response;
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (this.response != null && this.response.body() != null) {
|
||||
this.response.body().close();
|
||||
if (response != null && response.body() != null) {
|
||||
response.body().close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -41,6 +41,7 @@ import org.springframework.context.annotation.Primary;
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Nguyen Ky Thanh
|
||||
*/
|
||||
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
|
||||
@@ -53,6 +54,7 @@ import org.springframework.context.annotation.Primary;
|
||||
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
|
||||
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
|
||||
OkHttpFeignLoadBalancedConfiguration.class,
|
||||
HttpClient5FeignLoadBalancedConfiguration.class,
|
||||
DefaultFeignLoadBalancedConfiguration.class })
|
||||
public class FeignRibbonClientAutoConfiguration {
|
||||
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2013-2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign.ribbon;
|
||||
|
||||
import feign.Client;
|
||||
import feign.hc5.ApacheHttp5Client;
|
||||
import org.apache.hc.client5.http.classic.HttpClient;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.cloud.openfeign.clientconfig.HttpClient5FeignConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* Configuration instantiating a {@link LoadBalancerFeignClient}-based {@link Client}
|
||||
* object that uses {@link ApacheHttp5Client} under the hood.
|
||||
*
|
||||
* @author Nguyen Ky Thanh
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(ApacheHttp5Client.class)
|
||||
@ConditionalOnProperty(value = "feign.httpclient.hc5.enabled", havingValue = "true")
|
||||
@Import(HttpClient5FeignConfiguration.class)
|
||||
class HttpClient5FeignLoadBalancedConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(Client.class)
|
||||
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
|
||||
SpringClientFactory clientFactory, HttpClient httpClient5) {
|
||||
Client delegate = new ApacheHttp5Client(httpClient5);
|
||||
return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
|
||||
}
|
||||
|
||||
}
|
||||
+5
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -24,18 +24,22 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.cloud.openfeign.HttpClient5DisabledConditions;
|
||||
import org.springframework.cloud.openfeign.clientconfig.HttpClientFeignConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Nguyen Ky Thanh
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(ApacheHttpClient.class)
|
||||
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
|
||||
@Conditional(HttpClient5DisabledConditions.class)
|
||||
@Import(HttpClientFeignConfiguration.class)
|
||||
class HttpClientFeignLoadBalancedConfiguration {
|
||||
|
||||
|
||||
+4
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -31,6 +31,7 @@ import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*
|
||||
*/
|
||||
public class LoadBalancerFeignClient implements Client {
|
||||
@@ -126,6 +127,8 @@ public class LoadBalancerFeignClient implements Client {
|
||||
setProperty(CommonClientConfigKey.ConnectTimeout,
|
||||
options.connectTimeoutMillis());
|
||||
setProperty(CommonClientConfigKey.ReadTimeout, options.readTimeoutMillis());
|
||||
setProperty(CommonClientConfigKey.FollowRedirects,
|
||||
options.isFollowRedirects());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2013-2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign.support;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Properties for {@link SpringEncoder}.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 2.2.8
|
||||
*/
|
||||
@ConfigurationProperties("feign.encoder")
|
||||
public class FeignEncoderProperties {
|
||||
|
||||
/**
|
||||
* Indicates whether the charset should be derived from the {@code Content-Type}
|
||||
* header.
|
||||
*/
|
||||
private boolean charsetFromContentType = false;
|
||||
|
||||
public boolean isCharsetFromContentType() {
|
||||
return charsetFromContentType;
|
||||
}
|
||||
|
||||
public void setCharsetFromContentType(boolean charsetFromContentType) {
|
||||
this.charsetFromContentType = charsetFromContentType;
|
||||
}
|
||||
|
||||
}
|
||||
+128
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -22,6 +22,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
* @author Nguyen Ky Thanh
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "feign.httpclient")
|
||||
public class FeignHttpClientProperties {
|
||||
@@ -82,6 +83,11 @@ public class FeignHttpClientProperties {
|
||||
|
||||
private int connectionTimerRepeat = DEFAULT_CONNECTION_TIMER_REPEAT;
|
||||
|
||||
/**
|
||||
* Apache HttpClient5 additional properties.
|
||||
*/
|
||||
private Hc5Properties hc5 = new Hc5Properties();
|
||||
|
||||
public int getConnectionTimerRepeat() {
|
||||
return this.connectionTimerRepeat;
|
||||
}
|
||||
@@ -146,4 +152,125 @@ public class FeignHttpClientProperties {
|
||||
this.connectionTimeout = connectionTimeout;
|
||||
}
|
||||
|
||||
public Hc5Properties getHc5() {
|
||||
return hc5;
|
||||
}
|
||||
|
||||
public void setHc5(Hc5Properties hc5) {
|
||||
this.hc5 = hc5;
|
||||
}
|
||||
|
||||
public static class Hc5Properties {
|
||||
|
||||
/**
|
||||
* Default value for pool concurrency policy.
|
||||
*/
|
||||
public static final PoolConcurrencyPolicy DEFAULT_POOL_CONCURRENCY_POLICY = PoolConcurrencyPolicy.STRICT;
|
||||
|
||||
/**
|
||||
* Default value for pool reuse policy.
|
||||
*/
|
||||
public static final PoolReusePolicy DEFAULT_POOL_REUSE_POLICY = PoolReusePolicy.FIFO;
|
||||
|
||||
/**
|
||||
* Default value for socket timeout.
|
||||
*/
|
||||
public static final int DEFAULT_SOCKET_TIMEOUT = 5;
|
||||
|
||||
/**
|
||||
* Default value for socket timeout unit.
|
||||
*/
|
||||
public static final TimeUnit DEFAULT_SOCKET_TIMEOUT_UNIT = TimeUnit.SECONDS;
|
||||
|
||||
/**
|
||||
* Pool concurrency policies.
|
||||
*/
|
||||
private PoolConcurrencyPolicy poolConcurrencyPolicy = DEFAULT_POOL_CONCURRENCY_POLICY;
|
||||
|
||||
/**
|
||||
* Pool connection re-use policies.
|
||||
*/
|
||||
private PoolReusePolicy poolReusePolicy = DEFAULT_POOL_REUSE_POLICY;
|
||||
|
||||
/**
|
||||
* Default value for socket timeout.
|
||||
*/
|
||||
private int socketTimeout = DEFAULT_SOCKET_TIMEOUT;
|
||||
|
||||
/**
|
||||
* Default value for socket timeout unit.
|
||||
*/
|
||||
private TimeUnit socketTimeoutUnit = DEFAULT_SOCKET_TIMEOUT_UNIT;
|
||||
|
||||
public PoolConcurrencyPolicy getPoolConcurrencyPolicy() {
|
||||
return this.poolConcurrencyPolicy;
|
||||
}
|
||||
|
||||
public void setPoolConcurrencyPolicy(
|
||||
PoolConcurrencyPolicy poolConcurrencyPolicy) {
|
||||
this.poolConcurrencyPolicy = poolConcurrencyPolicy;
|
||||
}
|
||||
|
||||
public PoolReusePolicy getPoolReusePolicy() {
|
||||
return poolReusePolicy;
|
||||
}
|
||||
|
||||
public void setPoolReusePolicy(PoolReusePolicy poolReusePolicy) {
|
||||
this.poolReusePolicy = poolReusePolicy;
|
||||
}
|
||||
|
||||
public TimeUnit getSocketTimeoutUnit() {
|
||||
return socketTimeoutUnit;
|
||||
}
|
||||
|
||||
public void setSocketTimeoutUnit(TimeUnit socketTimeoutUnit) {
|
||||
this.socketTimeoutUnit = socketTimeoutUnit;
|
||||
}
|
||||
|
||||
public int getSocketTimeout() {
|
||||
return socketTimeout;
|
||||
}
|
||||
|
||||
public void setSocketTimeout(int socketTimeout) {
|
||||
this.socketTimeout = socketTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumeration of pool concurrency policies.
|
||||
*/
|
||||
public enum PoolConcurrencyPolicy {
|
||||
|
||||
/**
|
||||
* Higher concurrency but with lax connection max limit guarantees.
|
||||
*/
|
||||
LAX,
|
||||
|
||||
/**
|
||||
* Strict connection max limit guarantees.
|
||||
*/
|
||||
STRICT
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumeration of pooled connection re-use policies.
|
||||
*/
|
||||
public enum PoolReusePolicy {
|
||||
|
||||
/**
|
||||
* Re-use as few connections as possible making it possible for connections to
|
||||
* become idle and expire.
|
||||
*/
|
||||
LIFO,
|
||||
|
||||
/**
|
||||
* Re-use all connections equally preventing them from becoming idle and
|
||||
* expiring.
|
||||
*/
|
||||
FIFO
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2013-2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import feign.querymap.BeanQueryMapEncoder;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
/**
|
||||
* Provides support for encoding Pageable annotated as
|
||||
* {@link org.springframework.cloud.openfeign.SpringQueryMap}.
|
||||
*
|
||||
* @author Hyeonmin Park
|
||||
* @since 2.2.8
|
||||
*/
|
||||
public class PageableSpringQueryMapEncoder extends BeanQueryMapEncoder {
|
||||
|
||||
@Override
|
||||
public Map<String, Object> encode(Object object) {
|
||||
if (supports(object)) {
|
||||
Map<String, Object> queryMap = new HashMap<>();
|
||||
|
||||
if (object instanceof Pageable) {
|
||||
Pageable pageable = (Pageable) object;
|
||||
|
||||
if (pageable.isPaged()) {
|
||||
queryMap.put("page", pageable.getPageNumber());
|
||||
queryMap.put("size", pageable.getPageSize());
|
||||
}
|
||||
|
||||
if (pageable.getSort() != null) {
|
||||
applySort(queryMap, pageable.getSort());
|
||||
}
|
||||
}
|
||||
else if (object instanceof Sort) {
|
||||
Sort sort = (Sort) object;
|
||||
applySort(queryMap, sort);
|
||||
}
|
||||
return queryMap;
|
||||
}
|
||||
else {
|
||||
return super.encode(object);
|
||||
}
|
||||
}
|
||||
|
||||
private void applySort(Map<String, Object> queryMap, Sort sort) {
|
||||
List<String> sortQueries = new ArrayList<>();
|
||||
for (Sort.Order order : sort) {
|
||||
sortQueries.add(order.getProperty() + "%2C" + order.getDirection());
|
||||
}
|
||||
if (!sortQueries.isEmpty()) {
|
||||
queryMap.put("sort", sortQueries);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean supports(Object object) {
|
||||
return object instanceof Pageable || object instanceof Sort;
|
||||
}
|
||||
|
||||
}
|
||||
+116
-59
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -22,10 +22,11 @@ import java.io.OutputStream;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import feign.Request;
|
||||
import feign.RequestTemplate;
|
||||
import feign.codec.EncodeException;
|
||||
import feign.codec.Encoder;
|
||||
@@ -48,6 +49,10 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import static org.springframework.cloud.openfeign.support.FeignUtils.getHeaders;
|
||||
import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHeaders;
|
||||
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;
|
||||
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA;
|
||||
import static org.springframework.http.MediaType.MULTIPART_MIXED;
|
||||
import static org.springframework.http.MediaType.MULTIPART_RELATED;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
@@ -55,7 +60,10 @@ import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHead
|
||||
* @author Ahmad Mozafarnia
|
||||
* @author Aaron Whiteside
|
||||
* @author Darren Foong
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Can Bezmen
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class SpringEncoder implements Encoder {
|
||||
|
||||
private static final Log log = LogFactory.getLog(SpringEncoder.class);
|
||||
@@ -64,15 +72,23 @@ public class SpringEncoder implements Encoder {
|
||||
|
||||
private final ObjectFactory<HttpMessageConverters> messageConverters;
|
||||
|
||||
private final FeignEncoderProperties encoderProperties;
|
||||
|
||||
public SpringEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
|
||||
this.springFormEncoder = new SpringFormEncoder();
|
||||
this.messageConverters = messageConverters;
|
||||
this(new SpringFormEncoder(), messageConverters);
|
||||
}
|
||||
|
||||
public SpringEncoder(SpringFormEncoder springFormEncoder,
|
||||
ObjectFactory<HttpMessageConverters> messageConverters) {
|
||||
this(springFormEncoder, messageConverters, new FeignEncoderProperties());
|
||||
}
|
||||
|
||||
public SpringEncoder(SpringFormEncoder springFormEncoder,
|
||||
ObjectFactory<HttpMessageConverters> messageConverters,
|
||||
FeignEncoderProperties encoderProperties) {
|
||||
this.springFormEncoder = springFormEncoder;
|
||||
this.messageConverters = messageConverters;
|
||||
this.encoderProperties = encoderProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,8 +105,8 @@ public class SpringEncoder implements Encoder {
|
||||
requestContentType = MediaType.valueOf(type);
|
||||
}
|
||||
|
||||
if (Objects.equals(requestContentType, MediaType.MULTIPART_FORM_DATA)) {
|
||||
this.springFormEncoder.encode(requestBody, bodyType, request);
|
||||
if (isFormRelatedContentType(requestContentType)) {
|
||||
springFormEncoder.encode(requestBody, bodyType, request);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
@@ -100,58 +116,76 @@ public class SpringEncoder implements Encoder {
|
||||
+ "should be specified as MediaType.MULTIPART_FORM_DATA_VALUE");
|
||||
}
|
||||
}
|
||||
|
||||
for (HttpMessageConverter messageConverter : this.messageConverters
|
||||
.getObject().getConverters()) {
|
||||
FeignOutputMessage outputMessage;
|
||||
try {
|
||||
if (messageConverter instanceof GenericHttpMessageConverter) {
|
||||
outputMessage = checkAndWrite(requestBody, bodyType,
|
||||
requestContentType,
|
||||
(GenericHttpMessageConverter) messageConverter, request);
|
||||
}
|
||||
else {
|
||||
outputMessage = checkAndWrite(requestBody, requestContentType,
|
||||
messageConverter, request);
|
||||
}
|
||||
}
|
||||
catch (IOException | HttpMessageConversionException ex) {
|
||||
throw new EncodeException("Error converting request body", ex);
|
||||
}
|
||||
if (outputMessage != null) {
|
||||
// clear headers
|
||||
request.headers(null);
|
||||
// converters can modify headers, so update the request
|
||||
// with the modified headers
|
||||
request.headers(getHeaders(outputMessage.getHeaders()));
|
||||
|
||||
// do not use charset for binary data and protobuf
|
||||
Charset charset;
|
||||
if (messageConverter instanceof ByteArrayHttpMessageConverter) {
|
||||
charset = null;
|
||||
}
|
||||
else if (messageConverter instanceof ProtobufHttpMessageConverter
|
||||
&& ProtobufHttpMessageConverter.PROTOBUF.isCompatibleWith(
|
||||
outputMessage.getHeaders().getContentType())) {
|
||||
charset = null;
|
||||
}
|
||||
else {
|
||||
charset = StandardCharsets.UTF_8;
|
||||
}
|
||||
request.body(Request.Body.encoded(
|
||||
outputMessage.getOutputStream().toByteArray(), charset));
|
||||
return;
|
||||
}
|
||||
}
|
||||
String message = "Could not write request: no suitable HttpMessageConverter "
|
||||
+ "found for request type [" + requestBody.getClass().getName() + "]";
|
||||
if (requestContentType != null) {
|
||||
message += " and content type [" + requestContentType + "]";
|
||||
}
|
||||
throw new EncodeException(message);
|
||||
encodeWithMessageConverter(requestBody, bodyType, request,
|
||||
requestContentType);
|
||||
}
|
||||
}
|
||||
|
||||
private void encodeWithMessageConverter(Object requestBody, Type bodyType,
|
||||
RequestTemplate request, MediaType requestContentType) {
|
||||
for (HttpMessageConverter messageConverter : messageConverters.getObject()
|
||||
.getConverters()) {
|
||||
FeignOutputMessage outputMessage;
|
||||
try {
|
||||
if (messageConverter instanceof GenericHttpMessageConverter) {
|
||||
outputMessage = checkAndWrite(requestBody, bodyType,
|
||||
requestContentType,
|
||||
(GenericHttpMessageConverter) messageConverter, request);
|
||||
}
|
||||
else {
|
||||
outputMessage = checkAndWrite(requestBody, requestContentType,
|
||||
messageConverter, request);
|
||||
}
|
||||
}
|
||||
catch (IOException | HttpMessageConversionException ex) {
|
||||
throw new EncodeException("Error converting request body", ex);
|
||||
}
|
||||
if (outputMessage != null) {
|
||||
// clear headers
|
||||
request.headers(null);
|
||||
// converters can modify headers, so update the request
|
||||
// with the modified headers
|
||||
request.headers(getHeaders(outputMessage.getHeaders()));
|
||||
|
||||
// do not use charset for binary data and protobuf
|
||||
Charset charset;
|
||||
|
||||
MediaType contentType = outputMessage.getHeaders().getContentType();
|
||||
Charset charsetFromContentType = contentType != null
|
||||
? contentType.getCharset() : null;
|
||||
|
||||
if (encoderProperties != null
|
||||
&& encoderProperties.isCharsetFromContentType()
|
||||
&& charsetFromContentType != null) {
|
||||
charset = charsetFromContentType;
|
||||
}
|
||||
else if (shouldHaveNullCharset(messageConverter, outputMessage)) {
|
||||
charset = null;
|
||||
}
|
||||
else {
|
||||
charset = StandardCharsets.UTF_8;
|
||||
}
|
||||
request.body(outputMessage.getOutputStream().toByteArray(), charset);
|
||||
return;
|
||||
}
|
||||
}
|
||||
String message = "Could not write request: no suitable HttpMessageConverter "
|
||||
+ "found for request type [" + requestBody.getClass().getName() + "]";
|
||||
if (requestContentType != null) {
|
||||
message += " and content type [" + requestContentType + "]";
|
||||
}
|
||||
throw new EncodeException(message);
|
||||
}
|
||||
|
||||
private boolean shouldHaveNullCharset(HttpMessageConverter messageConverter,
|
||||
FeignOutputMessage outputMessage) {
|
||||
return binaryContentType(outputMessage)
|
||||
|| messageConverter instanceof ByteArrayHttpMessageConverter
|
||||
|| messageConverter instanceof ProtobufHttpMessageConverter
|
||||
&& ProtobufHttpMessageConverter.PROTOBUF.isCompatibleWith(
|
||||
outputMessage.getHeaders().getContentType());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private FeignOutputMessage checkAndWrite(Object body, MediaType contentType,
|
||||
HttpMessageConverter converter, RequestTemplate request) throws IOException {
|
||||
@@ -195,6 +229,29 @@ public class SpringEncoder implements Encoder {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFormRelatedContentType(MediaType requestContentType) {
|
||||
return isMultipartType(requestContentType)
|
||||
|| isFormUrlEncoded(requestContentType);
|
||||
}
|
||||
|
||||
private boolean isMultipartType(MediaType requestContentType) {
|
||||
return Arrays.asList(MULTIPART_FORM_DATA, MULTIPART_MIXED, MULTIPART_RELATED)
|
||||
.contains(requestContentType);
|
||||
}
|
||||
|
||||
private boolean isFormUrlEncoded(MediaType requestContentType) {
|
||||
return Objects.equals(APPLICATION_FORM_URLENCODED, requestContentType);
|
||||
}
|
||||
|
||||
private boolean binaryContentType(FeignOutputMessage outputMessage) {
|
||||
MediaType contentType = outputMessage.getHeaders().getContentType();
|
||||
return contentType == null || Stream
|
||||
.of(MediaType.APPLICATION_CBOR, MediaType.APPLICATION_OCTET_STREAM,
|
||||
MediaType.APPLICATION_PDF, MediaType.IMAGE_GIF,
|
||||
MediaType.IMAGE_JPEG, MediaType.IMAGE_PNG)
|
||||
.anyMatch(mediaType -> mediaType.includes(contentType));
|
||||
}
|
||||
|
||||
private final class FeignOutputMessage implements HttpOutputMessage {
|
||||
|
||||
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
@@ -202,21 +259,21 @@ public class SpringEncoder implements Encoder {
|
||||
private final HttpHeaders httpHeaders;
|
||||
|
||||
private FeignOutputMessage(RequestTemplate request) {
|
||||
this.httpHeaders = getHttpHeaders(request.headers());
|
||||
httpHeaders = getHttpHeaders(request.headers());
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getBody() throws IOException {
|
||||
return this.outputStream;
|
||||
return outputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
return this.httpHeaders;
|
||||
return httpHeaders;
|
||||
}
|
||||
|
||||
public ByteArrayOutputStream getOutputStream() {
|
||||
return this.outputStream;
|
||||
return outputStream;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+11
-37
@@ -35,6 +35,8 @@ import feign.Feign;
|
||||
import feign.MethodMetadata;
|
||||
import feign.Param;
|
||||
import feign.Request;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
|
||||
import org.springframework.cloud.openfeign.CollectionFormat;
|
||||
@@ -83,6 +85,8 @@ import static org.springframework.core.annotation.AnnotatedElementUtils.findMerg
|
||||
public class SpringMvcContract extends Contract.BaseContract
|
||||
implements ResourceLoaderAware {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(SpringMvcContract.class);
|
||||
|
||||
private static final String ACCEPT = "Accept";
|
||||
|
||||
private static final String CONTENT_TYPE = "Content-Type";
|
||||
@@ -181,49 +185,19 @@ public class SpringMvcContract extends Contract.BaseContract
|
||||
|
||||
@Override
|
||||
protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
|
||||
if (clz.getInterfaces().length == 0) {
|
||||
RequestMapping classAnnotation = findMergedAnnotation(clz,
|
||||
RequestMapping.class);
|
||||
if (classAnnotation != null) {
|
||||
// Prepend path from class annotation if specified
|
||||
if (classAnnotation.value().length > 0) {
|
||||
String pathValue = emptyToNull(classAnnotation.value()[0]);
|
||||
pathValue = resolve(pathValue);
|
||||
if (!pathValue.startsWith("/")) {
|
||||
pathValue = "/" + pathValue;
|
||||
}
|
||||
data.template().uri(pathValue);
|
||||
if (data.template().decodeSlash() != decodeSlash) {
|
||||
data.template().decodeSlash(decodeSlash);
|
||||
}
|
||||
}
|
||||
}
|
||||
RequestMapping classAnnotation = findMergedAnnotation(clz, RequestMapping.class);
|
||||
if (classAnnotation != null) {
|
||||
LOG.error("Cannot process class: " + clz.getName()
|
||||
+ ". @RequestMapping annotation is not allowed on @FeignClient interfaces.");
|
||||
throw new IllegalArgumentException(
|
||||
"@RequestMapping annotation not allowed on @FeignClient interfaces");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
|
||||
processedMethods.put(Feign.configKey(targetType, method), method);
|
||||
MethodMetadata md = super.parseAndValidateMetadata(targetType, method);
|
||||
|
||||
RequestMapping classAnnotation = findMergedAnnotation(targetType,
|
||||
RequestMapping.class);
|
||||
if (classAnnotation != null) {
|
||||
// produces - use from class annotation only if method has not specified this
|
||||
if (!md.template().headers().containsKey(ACCEPT)) {
|
||||
parseProduces(md, method, classAnnotation);
|
||||
}
|
||||
|
||||
// consumes -- use from class annotation only if method has not specified this
|
||||
if (!md.template().headers().containsKey(CONTENT_TYPE)) {
|
||||
parseConsumes(md, method, classAnnotation);
|
||||
}
|
||||
|
||||
// headers -- class annotation is inherited to methods, always write these if
|
||||
// present
|
||||
parseHeaders(md, method, classAnnotation);
|
||||
}
|
||||
return md;
|
||||
return super.parseAndValidateMetadata(targetType, method);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+6
@@ -26,6 +26,12 @@
|
||||
"description": "Enables the use of the Apache HTTP Client by Feign.",
|
||||
"defaultValue": "true"
|
||||
},
|
||||
{
|
||||
"name": "feign.httpclient.hc5.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the use of the Apache HTTP Client 5 by Feign.",
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"name": "feign.okhttp.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import feign.hystrix.HystrixFeign;
|
||||
import org.assertj.core.api.Condition;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* @author Tim Peeters
|
||||
*/
|
||||
class FeignAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner runner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(FeignAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void shouldInstantiateHystrixTargeterToMaintainBackwardsCompatibility() {
|
||||
runner.run(ctx -> assertOnlyOneTargeterPresent(ctx, HystrixTargeter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateHystrixTargeterWhenExplicitlyEnabled() {
|
||||
runner.withPropertyValues("feign.hystrix.enabled=true")
|
||||
.run(ctx -> assertOnlyOneTargeterPresent(ctx, HystrixTargeter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateDefaultTargeterWhenHystrixIsDisabled() {
|
||||
runner.withPropertyValues("feign.hystrix.enabled=false")
|
||||
.run(ctx -> assertOnlyOneTargeterPresent(ctx, DefaultTargeter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateDefaultTargeterWhenHystrixFeignClassIsMissing() {
|
||||
runner.withPropertyValues("feign.hystrix.enabled=true")
|
||||
.withClassLoader(new FilteredClassLoader(HystrixFeign.class))
|
||||
.run(ctx -> assertOnlyOneTargeterPresent(ctx, DefaultTargeter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateDefaultTargeterWhenHystrixFeignAndCircuitBreakerClassesAreMissing() {
|
||||
runner.withPropertyValues("feign.hystrix.enabled=true",
|
||||
"feign.circuitbreaker.enabled=true")
|
||||
.withClassLoader(
|
||||
new FilteredClassLoader(HystrixFeign.class, CircuitBreaker.class))
|
||||
.run(ctx -> assertOnlyOneTargeterPresent(ctx, DefaultTargeter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateDefaultTargeterWhenHystrixFeignClassIsMissingAndFeignCircuitBreakerIsDisabled() {
|
||||
runner.withClassLoader(new FilteredClassLoader(HystrixFeign.class))
|
||||
.withPropertyValues("feign.circuitbreaker.enabled=false")
|
||||
.run(ctx -> assertOnlyOneTargeterPresent(ctx, DefaultTargeter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateFeignCircuitBreakerTargeterWhenEnabled() {
|
||||
runner.withBean(CircuitBreakerFactory.class,
|
||||
() -> mock(CircuitBreakerFactory.class))
|
||||
.withPropertyValues("feign.circuitbreaker.enabled=true")
|
||||
.run(ctx -> assertOnlyOneTargeterPresent(ctx,
|
||||
FeignCircuitBreakerTargeter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateFeignCircuitBreakerTargeterWhenBothHystrixAndCircuitBreakerAreEnabled() {
|
||||
runner.withBean(CircuitBreakerFactory.class,
|
||||
() -> mock(CircuitBreakerFactory.class))
|
||||
.withPropertyValues("feign.hystrix.enabled=true",
|
||||
"feign.circuitbreaker.enabled=true")
|
||||
.run(ctx -> assertOnlyOneTargeterPresent(ctx,
|
||||
FeignCircuitBreakerTargeter.class));
|
||||
}
|
||||
|
||||
private void assertOnlyOneTargeterPresent(ConfigurableApplicationContext ctx,
|
||||
Class<?> beanClass) {
|
||||
assertThat(ctx.getBeansOfType(Targeter.class)).hasSize(1)
|
||||
.hasValueSatisfying(new Condition<>(beanClass::isInstance, String
|
||||
.format("Targeter should be an instance of %s", beanClass)));
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -33,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Matt King
|
||||
* @author Sam Kruglov
|
||||
*/
|
||||
public class FeignBuilderCustomizerTests {
|
||||
|
||||
@@ -78,6 +79,25 @@ public class FeignBuilderCustomizerTests {
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildCustomizerOrderedWithAdditional() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
FeignBuilderCustomizerTests.SampleConfiguration3.class);
|
||||
|
||||
FeignClientFactoryBean clientFactoryBean = context
|
||||
.getBean(FeignClientFactoryBean.class);
|
||||
clientFactoryBean.addCustomizer(builder -> builder.logLevel(Logger.Level.BASIC));
|
||||
clientFactoryBean.addCustomizer(Feign.Builder::doNotCloseAfterDecode);
|
||||
FeignContext feignContext = context.getBean(FeignContext.class);
|
||||
|
||||
Feign.Builder builder = clientFactoryBean.feign(feignContext);
|
||||
assertFeignBuilderField(builder, "logLevel", Logger.Level.BASIC);
|
||||
assertFeignBuilderField(builder, "decode404", true);
|
||||
assertFeignBuilderField(builder, "closeAfterDecode", false);
|
||||
|
||||
context.close();
|
||||
}
|
||||
|
||||
private static FeignClientFactoryBean defaultFeignClientFactoryBean() {
|
||||
FeignClientFactoryBean feignClientFactoryBean = new FeignClientFactoryBean();
|
||||
feignClientFactoryBean.setContextId("test");
|
||||
|
||||
+26
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -24,6 +24,7 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import feign.Feign;
|
||||
import feign.hystrix.FallbackFactory;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Before;
|
||||
@@ -40,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Sven Döring
|
||||
* @author Sam Kruglov
|
||||
*/
|
||||
public class FeignClientBuilderTests {
|
||||
|
||||
@@ -58,6 +60,14 @@ public class FeignClientBuilderTests {
|
||||
|
||||
private static void assertFactoryBeanField(final FeignClientBuilder.Builder builder,
|
||||
final String fieldName, final Object expectedValue) {
|
||||
final Object value = getFactoryBeanField(builder, fieldName);
|
||||
assertThat(value).as("Expected value for the field '" + fieldName + "':")
|
||||
.isEqualTo(expectedValue);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T getFactoryBeanField(final FeignClientBuilder.Builder builder,
|
||||
final String fieldName) {
|
||||
final Field factoryBeanField = ReflectionUtils
|
||||
.findField(FeignClientBuilder.Builder.class, "feignClientFactoryBean");
|
||||
ReflectionUtils.makeAccessible(factoryBeanField);
|
||||
@@ -67,9 +77,7 @@ public class FeignClientBuilderTests {
|
||||
final Field field = ReflectionUtils.findField(FeignClientFactoryBean.class,
|
||||
fieldName);
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
final Object value = ReflectionUtils.getField(field, factoryBean);
|
||||
assertThat(value).as("Expected value for the field '" + fieldName + "':")
|
||||
.isEqualTo(expectedValue);
|
||||
return (T) ReflectionUtils.getField(field, factoryBean);
|
||||
}
|
||||
|
||||
@Before
|
||||
@@ -84,9 +92,9 @@ public class FeignClientBuilderTests {
|
||||
for (final Method method : FeignClient.class.getMethods()) {
|
||||
methodNames.add(method.getName());
|
||||
}
|
||||
methodNames.removeAll(
|
||||
Arrays.asList("annotationType", "value", "serviceId", "qualifier",
|
||||
"configuration", "primary", "equals", "hashCode", "toString"));
|
||||
methodNames.removeAll(Arrays.asList("annotationType", "value", "serviceId",
|
||||
"qualifier", "qualifiers", "configuration", "primary", "equals",
|
||||
"hashCode", "toString"));
|
||||
Collections.sort(methodNames);
|
||||
// If this safety check fails the Builder has to be updated.
|
||||
// (1) Either a field was removed from the FeignClient annotation and so it has to
|
||||
@@ -131,7 +139,8 @@ public class FeignClientBuilderTests {
|
||||
.forType(TestFeignClient.class, "TestClient").inheritParentContext(false)
|
||||
.fallback(TestFeignClientFallback.class)
|
||||
.fallbackFactory(TestFeignClientFallbackFactory.class).decode404(true)
|
||||
.url("Url/").path("/Path").contextId("TestContext");
|
||||
.url("Url/").path("/Path").contextId("TestContext")
|
||||
.customize(Feign.Builder::doNotCloseAfterDecode);
|
||||
|
||||
// then:
|
||||
assertFactoryBeanField(builder, "applicationContext", this.applicationContext);
|
||||
@@ -147,14 +156,21 @@ public class FeignClientBuilderTests {
|
||||
assertFactoryBeanField(builder, "fallback", TestFeignClientFallback.class);
|
||||
assertFactoryBeanField(builder, "fallbackFactory",
|
||||
TestFeignClientFallbackFactory.class);
|
||||
List<FeignBuilderCustomizer> additionalCustomizers = getFactoryBeanField(builder,
|
||||
"additionalCustomizers");
|
||||
assertThat(additionalCustomizers).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forType_build() {
|
||||
// given:
|
||||
Mockito.when(this.applicationContext.getBean(FeignContext.class))
|
||||
.thenThrow(new ClosedFileSystemException()); // throw an unusual exception
|
||||
// in the
|
||||
.thenThrow(new ClosedFileSystemException()); // throw
|
||||
// an
|
||||
// unusual
|
||||
// exception
|
||||
// in
|
||||
// the
|
||||
// FeignClientFactoryBean
|
||||
final FeignClientBuilder.Builder builder = this.feignClientBuilder
|
||||
.forType(TestClient.class, "TestClient");
|
||||
|
||||
+9
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import feign.Contract;
|
||||
import feign.ExceptionPropagationPolicy;
|
||||
import feign.Feign;
|
||||
@@ -45,13 +47,13 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@SpringBootTest(classes = FeignClientOverrideDefaultsTests.TestConfiguration.class)
|
||||
@DirtiesContext
|
||||
@@ -128,6 +130,7 @@ class FeignClientOverrideDefaultsTests {
|
||||
Request.Options options = context.getInstance("bar", Request.Options.class);
|
||||
assertThat(options.connectTimeoutMillis()).isEqualTo(1);
|
||||
assertThat(options.readTimeoutMillis()).isEqualTo(1);
|
||||
assertThat(options.isFollowRedirects()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -166,7 +169,7 @@ class FeignClientOverrideDefaultsTests {
|
||||
configuration = BarConfiguration.class)
|
||||
interface BarClient {
|
||||
|
||||
@RequestMapping(value = "/", method = RequestMethod.GET)
|
||||
@GetMapping("/")
|
||||
String get();
|
||||
|
||||
}
|
||||
@@ -238,7 +241,8 @@ class FeignClientOverrideDefaultsTests {
|
||||
|
||||
@Bean
|
||||
Request.Options feignRequestOptions() {
|
||||
return new Request.Options(1, 1);
|
||||
return new Request.Options(1, TimeUnit.MILLISECONDS, 1, TimeUnit.MILLISECONDS,
|
||||
false);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
+16
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -246,6 +246,21 @@ public class FeignClientUsingPropertiesTests {
|
||||
assertThat(options.readTimeoutMillis()).isEqualTo(5000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSetFollowRedirects() {
|
||||
FeignClientFactoryBean testFactoryBean = new FeignClientFactoryBean();
|
||||
testFactoryBean.setContextId("test");
|
||||
testFactoryBean.setType(FeignClientFactoryBean.class);
|
||||
testFactoryBean.setApplicationContext(applicationContext);
|
||||
|
||||
TimeoutClient client = testFactoryBean.feign(context).target(TimeoutClient.class,
|
||||
"http://localhost:" + port);
|
||||
|
||||
Request.Options options = getRequestOptions((Proxy) client);
|
||||
|
||||
assertThat(options.isFollowRedirects()).isFalse();
|
||||
}
|
||||
|
||||
private Request.Options getRequestOptions(Proxy client) {
|
||||
Object invocationHandler = ReflectionTestUtils.getField(client, "h");
|
||||
Map<Method, InvocationHandlerFactory.MethodHandler> dispatch = (Map<Method, InvocationHandlerFactory.MethodHandler>) ReflectionTestUtils
|
||||
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2013-2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.openfeign.test.NoSecurityConfiguration;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link FeignClientsRegistrar}.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@SpringBootTest(
|
||||
classes = FeignClientsRegistrarIntegrationTests.QualifiersTestConfig.class)
|
||||
class FeignClientsRegistrarIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
ConfigurableApplicationContext context;
|
||||
|
||||
@Test
|
||||
void shouldUseQualifiersIfPresent() {
|
||||
assertThat(context.getBean("qualifier1")).isNotNull();
|
||||
assertThat(context.getBean("qualifier2")).isNotNull();
|
||||
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
|
||||
.isThrownBy(() -> context.getBean("qualifier3"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseQualifierIfQualifiersArrayNotPresent() {
|
||||
assertThat(context.getBean("qualifier4")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseDefaultQualifierWhenNonePresent() {
|
||||
assertThat(context.getBean("noQualifiersFeignClient")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseQualifierWhenEmptyQualifiers() {
|
||||
assertThat(context.getBean("test1")).isNotNull();
|
||||
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
|
||||
.isThrownBy(() -> context.getBean("emptyQualifiersFeignClient"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseQualifierWhenWhitespaceQualifiers() {
|
||||
assertThat(context.getBean("test2")).isNotNull();
|
||||
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
|
||||
.isThrownBy(() -> context.getBean("whitespaceQualifiersFeignClient"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseDefaultQualifierWhenEmptyQualifiers() {
|
||||
assertThat(context.getBean("emptyQualifiersNoQualifierFeignClient")).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseDefaultQualifierWhenWhitespaceQualifiers() {
|
||||
assertThat(context.getBean("whitespaceQualifiersNoQualifierFeignClient"))
|
||||
.isNotNull();
|
||||
}
|
||||
|
||||
@FeignClient(name = "qualifiersClient", qualifiers = { "qualifier1", "qualifier2" },
|
||||
qualifier = "qualifier3")
|
||||
protected interface QualifiersClient {
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "qualifierClient", qualifier = "qualifier4")
|
||||
protected interface QualifierClient {
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "noQualifiers")
|
||||
protected interface NoQualifiersClient {
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "emptyQualifiers", qualifier = "test1", qualifiers = {})
|
||||
protected interface EmptyQualifiersClient {
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "whitespaceQualifiers", qualifier = "test2", qualifiers = { " " })
|
||||
protected interface WhitespaceQualifiersClient {
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "emptyQualifiersNoQualifier", qualifiers = {})
|
||||
protected interface EmptyQualifiersNoQualifierClient {
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "whitespaceQualifiersNoQualifier", qualifiers = { " " })
|
||||
protected interface WhitespaceQualifiersNoQualifierClient {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableAutoConfiguration
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
@EnableFeignClients(clients = { QualifiersClient.class, QualifierClient.class,
|
||||
NoQualifiersClient.class, EmptyQualifiersClient.class,
|
||||
WhitespaceQualifiersClient.class, EmptyQualifiersNoQualifierClient.class,
|
||||
WhitespaceQualifiersNoQualifierClient.class })
|
||||
protected static class QualifiersTestConfig {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -80,7 +80,7 @@ public class FeignClientsRegistrarTests {
|
||||
private String testGetName(String name) {
|
||||
FeignClientsRegistrar registrar = new FeignClientsRegistrar();
|
||||
registrar.setEnvironment(new MockEnvironment());
|
||||
return registrar.getName(Collections.<String, Object>singletonMap("name", name));
|
||||
return registrar.getName(Collections.singletonMap("name", name));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@@ -102,7 +102,6 @@ public class FeignClientsRegistrarTests {
|
||||
assertThatCode(() -> config.refresh()).as(
|
||||
"Case https://github.com/spring-cloud/spring-cloud-openfeign/issues/331 should be solved")
|
||||
.doesNotThrowAnyException();
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "fallbackTestClient", url = "http://localhost:8080/",
|
||||
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright 2013-2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import feign.Client;
|
||||
import feign.hc5.ApacheHttp5Client;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
||||
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
|
||||
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.commons.httpclient.HttpClientConfiguration;
|
||||
import org.springframework.cloud.test.ClassPathExclusions;
|
||||
import org.springframework.cloud.test.ModifiedClassPathRunner;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Nguyen Ky Thanh
|
||||
*/
|
||||
public class FeignHttpClient5ConfigurationTests {
|
||||
|
||||
private static void verifyHc4BeansAvailable(ConfigurableApplicationContext context) {
|
||||
org.apache.http.impl.client.CloseableHttpClient httpClient4 = context
|
||||
.getBean(org.apache.http.impl.client.CloseableHttpClient.class);
|
||||
assertThat(httpClient4).isNotNull();
|
||||
org.apache.http.conn.HttpClientConnectionManager connectionManager4 = context
|
||||
.getBean(org.apache.http.conn.HttpClientConnectionManager.class);
|
||||
assertThat(connectionManager4).isInstanceOf(
|
||||
org.apache.http.impl.conn.PoolingHttpClientConnectionManager.class);
|
||||
Client client = context.getBean(Client.class);
|
||||
assertThat(client).isInstanceOf(ApacheHttpClient.class);
|
||||
}
|
||||
|
||||
private static void verifyHc5BeansAvailable(ConfigurableApplicationContext context) {
|
||||
CloseableHttpClient httpClient = context.getBean(CloseableHttpClient.class);
|
||||
assertThat(httpClient).isNotNull();
|
||||
HttpClientConnectionManager connectionManager = context
|
||||
.getBean(HttpClientConnectionManager.class);
|
||||
assertThat(connectionManager)
|
||||
.isInstanceOf(PoolingHttpClientConnectionManager.class);
|
||||
Client client = context.getBean(Client.class);
|
||||
assertThat(client).isInstanceOf(ApacheHttp5Client.class);
|
||||
}
|
||||
|
||||
@RunWith(ModifiedClassPathRunner.class)
|
||||
@ClassPathExclusions("ribbon-loadbalancer-{version:\\d.*}.jar")
|
||||
public static class WithoutLoadBalancerInClasspath {
|
||||
|
||||
@Test
|
||||
public void verifyHttpClient5AutoConfig() {
|
||||
ConfigurableApplicationContext context = new SpringApplicationBuilder()
|
||||
.properties("feign.httpclient.hc5.enabled=true",
|
||||
"feign.httpclient.enabled=false")
|
||||
.web(WebApplicationType.NONE)
|
||||
.sources(HttpClientConfiguration.class, FeignAutoConfiguration.class)
|
||||
.run();
|
||||
|
||||
verifyHc5BeansAvailable(context);
|
||||
|
||||
if (context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hc5ShouldWinIfTheBothVersionsAvailable() {
|
||||
ConfigurableApplicationContext context = new SpringApplicationBuilder()
|
||||
.properties("feign.httpclient.hc5.enabled=true",
|
||||
"feign.httpclient.enabled=true")
|
||||
.web(WebApplicationType.NONE)
|
||||
.sources(HttpClientConfiguration.class, FeignAutoConfiguration.class)
|
||||
.run();
|
||||
|
||||
Client client = context.getBean(Client.class);
|
||||
assertThat(client).isInstanceOf(ApacheHttp5Client.class);
|
||||
|
||||
if (context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hc4ShouldBeTheDefaultIfHc5NotEnabled() {
|
||||
ConfigurableApplicationContext context = new SpringApplicationBuilder()
|
||||
.properties("feign.httpclient.hc5.enabled=false",
|
||||
"feign.httpclient.enabled=true")
|
||||
.web(WebApplicationType.NONE)
|
||||
.sources(HttpClientConfiguration.class, FeignAutoConfiguration.class)
|
||||
.run();
|
||||
|
||||
verifyHc4BeansAvailable(context);
|
||||
|
||||
if (context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RunWith(ModifiedClassPathRunner.class)
|
||||
@ClassPathExclusions({ "ribbon-loadbalancer-{version:\\d.*}.jar",
|
||||
"feign-hc5-{version:\\d.*}.jar", "httpclient5-{version:\\d.*}.jar",
|
||||
"httpcore5-{version:\\d.*}.jar", "httpcore5-h2-{version:\\d.*}.jar" })
|
||||
public static class WithoutLoadBalancerAndHc5InClasspath {
|
||||
|
||||
@Test
|
||||
public void hc4ShouldWinEvenHc5ConfigEnabled() {
|
||||
ConfigurableApplicationContext context = new SpringApplicationBuilder()
|
||||
.properties("feign.httpclient.hc5.enabled=true")
|
||||
.web(WebApplicationType.NONE)
|
||||
.sources(HttpClientConfiguration.class, FeignAutoConfiguration.class)
|
||||
.run();
|
||||
|
||||
verifyHc4BeansAvailable(context);
|
||||
|
||||
if (context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hc4ShouldBeTheDefault() {
|
||||
ConfigurableApplicationContext context = new SpringApplicationBuilder()
|
||||
.web(WebApplicationType.NONE)
|
||||
.sources(HttpClientConfiguration.class, FeignAutoConfiguration.class)
|
||||
.run();
|
||||
|
||||
verifyHc4BeansAvailable(context);
|
||||
|
||||
if (context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+23
-24
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -23,10 +23,9 @@ import feign.Client;
|
||||
import feign.Feign;
|
||||
import feign.Target;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
@@ -36,7 +35,6 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.SocketUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -48,14 +46,14 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = FeignHttpClientUrlTests.TestConfig.class,
|
||||
webEnvironment = DEFINED_PORT,
|
||||
value = { "spring.application.name=feignclienturltest",
|
||||
"feign.hystrix.enabled=false", "feign.okhttp.enabled=false" })
|
||||
@DirtiesContext
|
||||
public class FeignHttpClientUrlTests {
|
||||
class FeignHttpClientUrlTests {
|
||||
|
||||
static int port;
|
||||
|
||||
@@ -68,37 +66,37 @@ public class FeignHttpClientUrlTests {
|
||||
@Autowired
|
||||
private BeanUrlClient beanClient;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
@BeforeAll
|
||||
static void beforeClass() {
|
||||
port = SocketUtils.findAvailableTcpPort();
|
||||
System.setProperty("server.port", String.valueOf(port));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() {
|
||||
@AfterAll
|
||||
static void afterClass() {
|
||||
System.clearProperty("server.port");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUrlHttpClient() {
|
||||
assertThat(this.urlClient).as("UrlClient was null").isNotNull();
|
||||
Hello hello = this.urlClient.getHello();
|
||||
void testUrlHttpClient() {
|
||||
assertThat(urlClient).as("UrlClient was null").isNotNull();
|
||||
Hello hello = urlClient.getHello();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world 1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanUrl() {
|
||||
Hello hello = this.beanClient.getHello();
|
||||
void testBeanUrl() {
|
||||
Hello hello = beanClient.getHello();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world 1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanUrlNoProtocol() {
|
||||
Hello hello = this.beanClientNoProtocol.getHello();
|
||||
void testBeanUrlNoProtocol() {
|
||||
Hello hello = beanClientNoProtocol.getHello();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world 1"));
|
||||
@@ -182,15 +180,16 @@ public class FeignHttpClientUrlTests {
|
||||
|
||||
private String message;
|
||||
|
||||
public Hello() {
|
||||
Hello() {
|
||||
|
||||
}
|
||||
|
||||
public Hello(String message) {
|
||||
Hello(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return this.message;
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
@@ -206,12 +205,12 @@ public class FeignHttpClientUrlTests {
|
||||
return false;
|
||||
}
|
||||
Hello that = (Hello) o;
|
||||
return Objects.equals(this.message, that.message);
|
||||
return Objects.equals(message, that.message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.message);
|
||||
return Objects.hash(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright 2013-2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Objects;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Feign;
|
||||
import feign.Target;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.openfeign.test.NoSecurityConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.SocketUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@SpringBootTest(classes = FeignHttpClientUrlTestsWithLoadBalancer.TestConfig.class,
|
||||
webEnvironment = DEFINED_PORT,
|
||||
value = { "spring.application.name=feignclienturlwithloadbalancertest",
|
||||
"feign.hystrix.enabled=false", "feign.okhttp.enabled=false",
|
||||
"spring.cloud.loadbalancer.ribbon.enabled=false",
|
||||
"spring.cloud.loadbalancer.retry.enabled=false" })
|
||||
@DirtiesContext
|
||||
class FeignHttpClientUrlTestsWithLoadBalancer {
|
||||
|
||||
static int port;
|
||||
|
||||
@Autowired
|
||||
BeanUrlClientNoProtocol beanClientNoProtocol;
|
||||
|
||||
@Autowired
|
||||
private UrlClient urlClient;
|
||||
|
||||
@Autowired
|
||||
private BeanUrlClient beanClient;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeClass() {
|
||||
port = SocketUtils.findAvailableTcpPort();
|
||||
System.setProperty("server.port", String.valueOf(port));
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void afterClass() {
|
||||
System.clearProperty("server.port");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUrlHttpClient() {
|
||||
assertThat(urlClient).as("UrlClient was null").isNotNull();
|
||||
Hello hello = urlClient.getHello();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world 1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBeanUrl() {
|
||||
Hello hello = beanClient.getHello();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world 1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBeanUrlNoProtocol() {
|
||||
Hello hello = beanClientNoProtocol.getHello();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world 1"));
|
||||
}
|
||||
|
||||
// this tests that
|
||||
@FeignClient(name = "localappurl", url = "http://localhost:${server.port}/")
|
||||
protected interface UrlClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "beanappurl", url = "#{SERVER_URL}path")
|
||||
protected interface BeanUrlClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "beanappurlnoprotocol", url = "#{SERVER_URL_NO_PROTOCOL}path")
|
||||
protected interface BeanUrlClientNoProtocol {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@EnableFeignClients(clients = { UrlClient.class, BeanUrlClient.class,
|
||||
BeanUrlClientNoProtocol.class })
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
protected static class TestConfig {
|
||||
|
||||
@GetMapping("/hello")
|
||||
public Hello getHello() {
|
||||
return new Hello("hello world 1");
|
||||
}
|
||||
|
||||
@GetMapping("/path/hello")
|
||||
public Hello getHelloWithPath() {
|
||||
return getHello();
|
||||
}
|
||||
|
||||
@Bean(name = "SERVER_URL")
|
||||
public String serverUrl() {
|
||||
return "http://localhost:" + port + "/";
|
||||
}
|
||||
|
||||
@Bean(name = "SERVER_URL_NO_PROTOCOL")
|
||||
public String serverUrlNoProtocol() {
|
||||
return "localhost:" + port + "/";
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Targeter feignTargeter() {
|
||||
return new Targeter() {
|
||||
@Override
|
||||
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
|
||||
FeignContext context, Target.HardCodedTarget<T> target) {
|
||||
Field field = ReflectionUtils.findField(Feign.Builder.class,
|
||||
"client");
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
Client client = (Client) ReflectionUtils.getField(field, feign);
|
||||
if (target.name().equals("localappurl")) {
|
||||
assertThat(client).isInstanceOf(ApacheHttpClient.class)
|
||||
.as("client was wrong type");
|
||||
}
|
||||
return feign.target(target);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Hello {
|
||||
|
||||
private String message;
|
||||
|
||||
Hello() {
|
||||
|
||||
}
|
||||
|
||||
Hello(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Hello that = (Hello) o;
|
||||
return Objects.equals(message, that.message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright 2013-2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Objects;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Feign;
|
||||
import feign.Target;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.openfeign.test.NoSecurityConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.SocketUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@SpringBootTest(
|
||||
classes = FeignHttpClientUrlTestsWithRetryableLoadBalancer.TestConfig.class,
|
||||
webEnvironment = DEFINED_PORT,
|
||||
value = { "spring.application.name=feignclienturlwithretryableloadbalancertest",
|
||||
"feign.hystrix.enabled=false", "feign.okhttp.enabled=false",
|
||||
"spring.cloud.loadbalancer.ribbon.enabled=false" })
|
||||
@DirtiesContext
|
||||
class FeignHttpClientUrlTestsWithRetryableLoadBalancer {
|
||||
|
||||
static int port;
|
||||
|
||||
@Autowired
|
||||
BeanUrlClientNoProtocol beanClientNoProtocol;
|
||||
|
||||
@Autowired
|
||||
private UrlClient urlClient;
|
||||
|
||||
@Autowired
|
||||
private BeanUrlClient beanClient;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeClass() {
|
||||
port = SocketUtils.findAvailableTcpPort();
|
||||
System.setProperty("server.port", String.valueOf(port));
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void afterClass() {
|
||||
System.clearProperty("server.port");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUrlHttpClient() {
|
||||
assertThat(urlClient).as("UrlClient was null").isNotNull();
|
||||
Hello hello = urlClient.getHello();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world 1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBeanUrl() {
|
||||
Hello hello = beanClient.getHello();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world 1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBeanUrlNoProtocol() {
|
||||
Hello hello = beanClientNoProtocol.getHello();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world 1"));
|
||||
}
|
||||
|
||||
// this tests that
|
||||
@FeignClient(name = "localappurl", url = "http://localhost:${server.port}/")
|
||||
protected interface UrlClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "beanappurl", url = "#{SERVER_URL}path")
|
||||
protected interface BeanUrlClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "beanappurlnoprotocol", url = "#{SERVER_URL_NO_PROTOCOL}path")
|
||||
protected interface BeanUrlClientNoProtocol {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@EnableFeignClients(clients = { UrlClient.class, BeanUrlClient.class,
|
||||
BeanUrlClientNoProtocol.class })
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
protected static class TestConfig {
|
||||
|
||||
@GetMapping("/hello")
|
||||
public Hello getHello() {
|
||||
return new Hello("hello world 1");
|
||||
}
|
||||
|
||||
@GetMapping("/path/hello")
|
||||
public Hello getHelloWithPath() {
|
||||
return getHello();
|
||||
}
|
||||
|
||||
@Bean(name = "SERVER_URL")
|
||||
public String serverUrl() {
|
||||
return "http://localhost:" + port + "/";
|
||||
}
|
||||
|
||||
@Bean(name = "SERVER_URL_NO_PROTOCOL")
|
||||
public String serverUrlNoProtocol() {
|
||||
return "localhost:" + port + "/";
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Targeter feignTargeter() {
|
||||
return new Targeter() {
|
||||
@Override
|
||||
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
|
||||
FeignContext context, Target.HardCodedTarget<T> target) {
|
||||
Field field = ReflectionUtils.findField(Feign.Builder.class,
|
||||
"client");
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
Client client = (Client) ReflectionUtils.getField(field, feign);
|
||||
if (target.name().equals("localappurl")) {
|
||||
assertThat(client).isInstanceOf(ApacheHttpClient.class)
|
||||
.as("client was wrong type");
|
||||
}
|
||||
return feign.target(target);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Hello {
|
||||
|
||||
private String message;
|
||||
|
||||
Hello() {
|
||||
|
||||
}
|
||||
|
||||
Hello(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Hello that = (Hello) o;
|
||||
return Objects.equals(message, that.message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-4
@@ -30,7 +30,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
|
||||
import org.springframework.cloud.client.circuitbreaker.ConfigBuilder;
|
||||
@@ -72,9 +71,6 @@ public class CircuitBreakerTests {
|
||||
@Autowired
|
||||
TestClientWithFactory testClientWithFactory;
|
||||
|
||||
@LocalServerPort
|
||||
private int port = 0;
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeClass() {
|
||||
System.setProperty("server.port",
|
||||
|
||||
+127
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -71,7 +71,6 @@ public class FeignPageableEncodingTests {
|
||||
|
||||
@Test
|
||||
public void testPageable() {
|
||||
|
||||
// given
|
||||
Pageable pageable = PageRequest.of(0, 10, Sort.Direction.ASC, "sortProperty");
|
||||
|
||||
@@ -92,7 +91,6 @@ public class FeignPageableEncodingTests {
|
||||
assertThat(order.getDirection()).isEqualTo(Sort.Direction.ASC);
|
||||
assertThat(order.getProperty()).isEqualTo("sortProperty");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -120,7 +118,6 @@ public class FeignPageableEncodingTests {
|
||||
Sort.Order order = optionalOrder.get();
|
||||
assertThat(order.getDirection()).isEqualTo(Sort.Direction.DESC);
|
||||
assertThat(order.getProperty()).isEqualTo("sortProperty");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -152,7 +149,133 @@ public class FeignPageableEncodingTests {
|
||||
Sort.Order secondOrder = orderList.get(1);
|
||||
assertThat(secondOrder.getDirection()).isEqualTo(Sort.Direction.ASC);
|
||||
assertThat(secondOrder.getProperty()).isEqualTo("sortProperty2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPageableWithoutSort() {
|
||||
// given
|
||||
Pageable pageable = PageRequest.of(0, 10);
|
||||
|
||||
// when
|
||||
final ResponseEntity<Page<Invoice>> response = this.invoiceClient
|
||||
.getInvoicesPaged(pageable);
|
||||
|
||||
// then
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isNotNull();
|
||||
assertThat(pageable.getPageSize()).isEqualTo(response.getBody().getSize());
|
||||
assertThat(response.getBody().getPageable().getSort().isSorted()).isFalse();
|
||||
|
||||
List<Invoice> invoiceList = response.getBody().getContent();
|
||||
assertThat(invoiceList).hasSizeGreaterThanOrEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPageableWithoutSortWithBody() {
|
||||
// given
|
||||
Pageable pageable = PageRequest.of(0, 10);
|
||||
|
||||
// when
|
||||
final ResponseEntity<Page<Invoice>> response = this.invoiceClient
|
||||
.getInvoicesPagedWithBody(pageable, "InvoiceTitleFromBody");
|
||||
|
||||
// then
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isNotNull();
|
||||
assertThat(pageable.getPageSize()).isEqualTo(response.getBody().getSize());
|
||||
|
||||
List<Invoice> invoiceList = response.getBody().getContent();
|
||||
assertThat(invoiceList).hasSizeGreaterThanOrEqualTo(1);
|
||||
|
||||
Invoice firstInvoice = invoiceList.get(0);
|
||||
assertThat(firstInvoice.getTitle()).startsWith("InvoiceTitleFromBody");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPageableWithBody() {
|
||||
// given
|
||||
Pageable pageable = PageRequest.of(0, 10, Sort
|
||||
.by(Sort.Order.desc("sortProperty1"), Sort.Order.asc("sortProperty2")));
|
||||
|
||||
// when
|
||||
final ResponseEntity<Page<Invoice>> response = this.invoiceClient
|
||||
.getInvoicesPagedWithBody(pageable, "InvoiceTitleFromBody");
|
||||
|
||||
// then
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isNotNull();
|
||||
assertThat(pageable.getPageSize()).isEqualTo(response.getBody().getSize());
|
||||
|
||||
List<Invoice> invoiceList = response.getBody().getContent();
|
||||
assertThat(invoiceList).hasSizeGreaterThanOrEqualTo(1);
|
||||
|
||||
Invoice firstInvoice = invoiceList.get(0);
|
||||
assertThat(firstInvoice.getTitle()).startsWith("InvoiceTitleFromBody");
|
||||
|
||||
Sort sort = response.getBody().getPageable().getSort();
|
||||
assertThat(sort).hasSize(2);
|
||||
|
||||
List<Sort.Order> orderList = sort.toList();
|
||||
assertThat(orderList).hasSize(2);
|
||||
|
||||
Sort.Order firstOrder = orderList.get(0);
|
||||
assertThat(firstOrder.getDirection()).isEqualTo(Sort.Direction.DESC);
|
||||
assertThat(firstOrder.getProperty()).isEqualTo("sortProperty1");
|
||||
|
||||
Sort.Order secondOrder = orderList.get(1);
|
||||
assertThat(secondOrder.getDirection()).isEqualTo(Sort.Direction.ASC);
|
||||
assertThat(secondOrder.getProperty()).isEqualTo("sortProperty2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnpagedWithBody() {
|
||||
// given
|
||||
Pageable unpaged = Pageable.unpaged();
|
||||
|
||||
// when
|
||||
final ResponseEntity<Page<Invoice>> response = this.invoiceClient
|
||||
.getInvoicesPagedWithBody(unpaged, "InvoiceTitleFromBody");
|
||||
|
||||
// then
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isNotNull();
|
||||
|
||||
List<Invoice> invoiceList = response.getBody().getContent();
|
||||
assertThat(invoiceList).hasSizeGreaterThanOrEqualTo(1);
|
||||
|
||||
Invoice firstInvoice = invoiceList.get(0);
|
||||
assertThat(firstInvoice.getTitle()).startsWith("InvoiceTitleFromBody");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSortWithBody() {
|
||||
// given
|
||||
Sort sort = Sort.by(Sort.Order.desc("amount"));
|
||||
|
||||
// when
|
||||
final ResponseEntity<Page<Invoice>> response = this.invoiceClient
|
||||
.getInvoicesSortedWithBody(sort, "InvoiceTitleFromBody");
|
||||
|
||||
// then
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isNotNull();
|
||||
assertThat(sort).isEqualTo(response.getBody().getSort());
|
||||
|
||||
List<Invoice> invoiceList = response.getBody().getContent();
|
||||
assertThat(invoiceList).hasSizeGreaterThanOrEqualTo(1);
|
||||
|
||||
Invoice firstInvoice = invoiceList.get(0);
|
||||
assertThat(firstInvoice.getTitle()).startsWith("InvoiceTitleFromBody");
|
||||
|
||||
for (int ind = 0; ind < invoiceList.size() - 1; ind++) {
|
||||
assertThat(invoiceList.get(ind).getAmount())
|
||||
.isGreaterThanOrEqualTo(invoiceList.get(ind + 1).getAmount());
|
||||
}
|
||||
}
|
||||
|
||||
@EnableFeignClients(clients = InvoiceClient.class)
|
||||
|
||||
+17
@@ -19,10 +19,12 @@ package org.springframework.cloud.openfeign.encoding.app.client;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.cloud.openfeign.SpringQueryMap;
|
||||
import org.springframework.cloud.openfeign.encoding.app.domain.Invoice;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
@@ -30,6 +32,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||
* Simple Feign client for retrieving the invoice list.
|
||||
*
|
||||
* @author Jakub Narloch
|
||||
* @author Hyeonmin Park
|
||||
*/
|
||||
@FeignClient("local")
|
||||
public interface InvoiceClient {
|
||||
@@ -39,6 +42,20 @@ public interface InvoiceClient {
|
||||
ResponseEntity<Page<Invoice>> getInvoicesPaged(
|
||||
org.springframework.data.domain.Pageable pageable);
|
||||
|
||||
@RequestMapping(value = "invoicesPagedWithBody", method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
ResponseEntity<Page<Invoice>> getInvoicesPagedWithBody(
|
||||
@SpringQueryMap org.springframework.data.domain.Pageable pageable,
|
||||
@RequestBody String titlePrefix);
|
||||
|
||||
@RequestMapping(value = "invoicesSortedWithBody", method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
ResponseEntity<Page<Invoice>> getInvoicesSortedWithBody(
|
||||
@SpringQueryMap org.springframework.data.domain.Sort sort,
|
||||
@RequestBody String titlePrefix);
|
||||
|
||||
@RequestMapping(value = "invoices", method = RequestMethod.GET,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
ResponseEntity<List<Invoice>> getInvoices();
|
||||
|
||||
+63
-4
@@ -18,12 +18,14 @@ package org.springframework.cloud.openfeign.encoding.app.resource;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.cloud.openfeign.encoding.app.domain.Invoice;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@@ -35,6 +37,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
* An sample REST controller, that potentially returns large response - used for testing.
|
||||
*
|
||||
* @author Jakub Narloch
|
||||
* @author Hyeonmin Park
|
||||
*/
|
||||
@RestController
|
||||
public class InvoiceResource {
|
||||
@@ -43,7 +46,7 @@ public class InvoiceResource {
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<List<Invoice>> getInvoices() {
|
||||
|
||||
return ResponseEntity.ok(createInvoiceList(100));
|
||||
return ResponseEntity.ok(createInvoiceList(null, 100, null));
|
||||
}
|
||||
|
||||
@RequestMapping(value = "invoices", method = RequestMethod.POST,
|
||||
@@ -58,20 +61,76 @@ public class InvoiceResource {
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<Page<Invoice>> getInvoicesPaged(
|
||||
org.springframework.data.domain.Pageable pageable) {
|
||||
Page<Invoice> page = new PageImpl<>(createInvoiceList(pageable.getPageSize()),
|
||||
Page<Invoice> page = new PageImpl<>(
|
||||
createInvoiceList(null, pageable.getPageSize(), pageable.getSort()),
|
||||
pageable, 100);
|
||||
return ResponseEntity.ok(page);
|
||||
}
|
||||
|
||||
private List<Invoice> createInvoiceList(int count) {
|
||||
@RequestMapping(value = "invoicesPagedWithBody", method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<Page<Invoice>> getInvoicesPagedWithBody(
|
||||
org.springframework.data.domain.Pageable pageable,
|
||||
@RequestBody String titlePrefix) {
|
||||
Page<Invoice> page = new PageImpl<>(createInvoiceList(titlePrefix,
|
||||
pageable.getPageSize(), pageable.getSort()), pageable, 100);
|
||||
return ResponseEntity.ok(page);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "invoicesSortedWithBody", method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<Page<Invoice>> getInvoicesSortedWithBody(
|
||||
org.springframework.data.domain.Sort sort, @RequestBody String titlePrefix) {
|
||||
Page<Invoice> page = new PageImpl<>(createInvoiceList(titlePrefix, 100, sort),
|
||||
PageRequest.of(0, 100, sort), 100);
|
||||
return ResponseEntity.ok(page);
|
||||
}
|
||||
|
||||
private List<Invoice> createInvoiceList(String titlePrefix, int count,
|
||||
org.springframework.data.domain.Sort sort) {
|
||||
if (titlePrefix == null) {
|
||||
titlePrefix = "Invoice";
|
||||
}
|
||||
final List<Invoice> invoices = new ArrayList<>();
|
||||
for (int ind = 0; ind < count; ind++) {
|
||||
final Invoice invoice = new Invoice();
|
||||
invoice.setTitle("Invoice " + (ind + 1));
|
||||
invoice.setTitle(titlePrefix + " " + (ind + 1));
|
||||
invoice.setAmount(new BigDecimal(
|
||||
String.format(Locale.US, "%.2f", Math.random() * 1000)));
|
||||
invoices.add(invoice);
|
||||
}
|
||||
if (sort != null) {
|
||||
Comparator<Invoice> comparatorForSort = null;
|
||||
for (org.springframework.data.domain.Sort.Order order : sort) {
|
||||
Comparator<Invoice> comparatorForOrder;
|
||||
if (order.getProperty().equals("title")) {
|
||||
comparatorForOrder = Comparator.comparing(Invoice::getTitle);
|
||||
}
|
||||
else if (order.getProperty().equals("amount")) {
|
||||
comparatorForOrder = Comparator.comparing(Invoice::getAmount);
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (order.isDescending()) {
|
||||
comparatorForOrder = comparatorForOrder.reversed();
|
||||
}
|
||||
|
||||
if (comparatorForSort == null) {
|
||||
comparatorForSort = comparatorForOrder;
|
||||
}
|
||||
else {
|
||||
comparatorForSort = comparatorForSort
|
||||
.thenComparing(comparatorForOrder);
|
||||
}
|
||||
}
|
||||
if (comparatorForSort != null) {
|
||||
invoices.sort(comparatorForSort);
|
||||
}
|
||||
}
|
||||
return invoices;
|
||||
}
|
||||
|
||||
|
||||
+2
-7
@@ -20,7 +20,6 @@ import feign.RequestTemplate;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
|
||||
import org.springframework.cloud.openfeign.support.SpringEncoder;
|
||||
@@ -41,12 +40,8 @@ public class ProtobufNotInClasspathTest {
|
||||
|
||||
@Test
|
||||
public void testEncodeWhenProtobufNotInClasspath() {
|
||||
ObjectFactory<HttpMessageConverters> converters = new ObjectFactory<HttpMessageConverters>() {
|
||||
@Override
|
||||
public HttpMessageConverters getObject() throws BeansException {
|
||||
return new HttpMessageConverters(new StringHttpMessageConverter());
|
||||
}
|
||||
};
|
||||
ObjectFactory<HttpMessageConverters> converters = () -> new HttpMessageConverters(
|
||||
new StringHttpMessageConverter());
|
||||
RequestTemplate requestTemplate = new RequestTemplate();
|
||||
requestTemplate.method(POST);
|
||||
new SpringEncoder(converters).encode("a=b", String.class, requestTemplate);
|
||||
|
||||
+9
-19
@@ -40,11 +40,9 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.BDDMockito;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
|
||||
import org.springframework.cloud.openfeign.support.SpringEncoder;
|
||||
@@ -73,7 +71,7 @@ public class ProtobufSpringEncoderTest {
|
||||
.build();
|
||||
|
||||
@Test
|
||||
public void testProtobuf() throws IOException, URISyntaxException {
|
||||
public void testProtobuf() throws IOException {
|
||||
// protobuf convert to request by feign and ProtobufHttpMessageConverter
|
||||
RequestTemplate requestTemplate = newRequestTemplate();
|
||||
newEncoder().encode(this.request, Request.class, requestTemplate);
|
||||
@@ -110,12 +108,8 @@ public class ProtobufSpringEncoderTest {
|
||||
}
|
||||
|
||||
private SpringEncoder newEncoder() {
|
||||
ObjectFactory<HttpMessageConverters> converters = new ObjectFactory<HttpMessageConverters>() {
|
||||
@Override
|
||||
public HttpMessageConverters getObject() throws BeansException {
|
||||
return new HttpMessageConverters(new ProtobufHttpMessageConverter());
|
||||
}
|
||||
};
|
||||
ObjectFactory<HttpMessageConverters> converters = () -> new HttpMessageConverters(
|
||||
new ProtobufHttpMessageConverter());
|
||||
return new SpringEncoder(converters);
|
||||
}
|
||||
|
||||
@@ -126,17 +120,13 @@ public class ProtobufSpringEncoderTest {
|
||||
}
|
||||
|
||||
private HttpEntity toApacheHttpEntity(RequestTemplate requestTemplate)
|
||||
throws IOException, URISyntaxException {
|
||||
throws IOException {
|
||||
final List<HttpUriRequest> request = new ArrayList<>(1);
|
||||
BDDMockito.given(this.httpClient.execute(ArgumentMatchers.<HttpUriRequest>any()))
|
||||
.will(new Answer<HttpResponse>() {
|
||||
@Override
|
||||
public HttpResponse answer(InvocationOnMock invocationOnMock)
|
||||
throws Throwable {
|
||||
request.add((HttpUriRequest) invocationOnMock.getArguments()[0]);
|
||||
return new BasicHttpResponse(new BasicStatusLine(
|
||||
new ProtocolVersion("http", 1, 1), 200, null));
|
||||
}
|
||||
BDDMockito.given(this.httpClient.execute(ArgumentMatchers.any()))
|
||||
.will((Answer<HttpResponse>) invocationOnMock -> {
|
||||
request.add((HttpUriRequest) invocationOnMock.getArguments()[0]);
|
||||
return new BasicHttpResponse(new BasicStatusLine(
|
||||
new ProtocolVersion("http", 1, 1), 200, null));
|
||||
});
|
||||
new ApacheHttpClient(this.httpClient).execute(
|
||||
requestTemplate.resolve(new HashMap<>()).request(),
|
||||
|
||||
+15
-11
@@ -16,7 +16,7 @@
|
||||
|
||||
package org.springframework.cloud.openfeign.hateoas;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.Test;
|
||||
@@ -26,9 +26,11 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.hateoas.mediatype.MessageResolver;
|
||||
import org.springframework.hateoas.mediatype.hal.CurieProvider;
|
||||
import org.springframework.hateoas.mediatype.hal.HalConfiguration;
|
||||
import org.springframework.hateoas.mediatype.hal.HalMediaTypeConfiguration;
|
||||
import org.springframework.hateoas.mediatype.hal.Jackson2HalModule;
|
||||
import org.springframework.hateoas.server.LinkRelationProvider;
|
||||
import org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter;
|
||||
@@ -45,20 +47,20 @@ import static org.springframework.hateoas.MediaTypes.HAL_JSON;
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class FeignHalAutoConfigurationTests {
|
||||
|
||||
@Mock
|
||||
private ObjectProvider<ObjectMapper> objectMapper;
|
||||
|
||||
@Mock
|
||||
private ObjectProvider<HalConfiguration> halConfiguration;
|
||||
|
||||
@Mock
|
||||
private ObjectProvider<LinkRelationProvider> relProvider;
|
||||
private ObjectProvider<ObjectMapper> objectMapper;
|
||||
|
||||
@Mock
|
||||
private LinkRelationProvider relProvider;
|
||||
|
||||
@Mock
|
||||
private ObjectProvider<CurieProvider> curieProvider;
|
||||
|
||||
@Mock
|
||||
private ObjectProvider<MessageResolver> messageResolver;
|
||||
private MessageResolver messageResolver;
|
||||
|
||||
@InjectMocks
|
||||
private FeignHalAutoConfiguration feignHalAutoConfiguration;
|
||||
@@ -70,17 +72,19 @@ public class FeignHalAutoConfigurationTests {
|
||||
|
||||
when(halConfiguration.getIfAvailable(any()))
|
||||
.thenReturn(mock(HalConfiguration.class));
|
||||
when(relProvider.getIfAvailable()).thenReturn(mock(LinkRelationProvider.class));
|
||||
when(curieProvider.getIfAvailable(any())).thenReturn(mock(CurieProvider.class));
|
||||
when(messageResolver.getIfAvailable()).thenReturn(mock(MessageResolver.class));
|
||||
|
||||
HalMediaTypeConfiguration halMediaTypeConfiguration = new HalMediaTypeConfiguration(
|
||||
relProvider, curieProvider, halConfiguration, messageResolver,
|
||||
new DefaultListableBeanFactory());
|
||||
|
||||
TypeConstrainedMappingJackson2HttpMessageConverter converter = feignHalAutoConfiguration
|
||||
.halJacksonHttpMessageConverter(objectMapper, halConfiguration,
|
||||
messageResolver, curieProvider, relProvider);
|
||||
.halJacksonHttpMessageConverter(objectMapper, halMediaTypeConfiguration);
|
||||
|
||||
assertThat(converter).isNotNull();
|
||||
assertThat(converter.getObjectMapper()).isNotNull();
|
||||
assertThat(converter.getSupportedMediaTypes()).isEqualTo(Arrays.asList(HAL_JSON));
|
||||
assertThat(converter.getSupportedMediaTypes())
|
||||
.isEqualTo(Collections.singletonList(HAL_JSON));
|
||||
|
||||
assertThat(Jackson2HalModule.isAlreadyRegisteredIn(converter.getObjectMapper()))
|
||||
.isTrue();
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ import static org.mockito.Mockito.when;
|
||||
* {@link FeignBlockingLoadBalancerClient} and its delegates.
|
||||
*
|
||||
* @see <a href=
|
||||
* "https://github.com/spring-cloud/spring-cloud-commons/blob/master/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/blocking/client/BlockingLoadBalancerClientTests.java">BlockingLoadBalancerClientTests</a>
|
||||
* "https://github.com/spring-cloud/spring-cloud-commons/blob/main/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/blocking/client/BlockingLoadBalancerClientTests.java">BlockingLoadBalancerClientTests</a>
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
|
||||
+49
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -19,6 +19,7 @@ package org.springframework.cloud.openfeign.loadbalancer;
|
||||
import java.util.Map;
|
||||
|
||||
import feign.Client;
|
||||
import feign.hc5.ApacheHttp5Client;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
import feign.okhttp.OkHttpClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -38,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Nguyen Ky Thanh
|
||||
*/
|
||||
class FeignLoadBalancerAutoConfigurationTests {
|
||||
|
||||
@@ -73,6 +75,30 @@ class FeignLoadBalancerAutoConfigurationTests {
|
||||
assertThatBeanNotPresent(context, LoadBalancerFeignClient.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateHttpFeignClient5WhenEnabled() {
|
||||
ConfigurableApplicationContext context = initContext(
|
||||
"spring.cloud.loadbalancer.ribbon.enabled=false",
|
||||
"feign.httpclient.enabled=false", "feign.okhttp.enabled=false",
|
||||
"feign.httpclient.hc5.enabled=true",
|
||||
"spring.cloud.loadbalancer.retry.enabled=false");
|
||||
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
|
||||
assertLoadBalanced(context, ApacheHttp5Client.class);
|
||||
assertThatBeanNotPresent(context, LoadBalancerFeignClient.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateHttpFeignClient5WhenBothHttpClientAndHttpClient5Enabled() {
|
||||
ConfigurableApplicationContext context = initContext(
|
||||
"spring.cloud.loadbalancer.ribbon.enabled=false",
|
||||
"feign.httpclient.enabled=true", "feign.okhttp.enabled=false",
|
||||
"feign.httpclient.hc5.enabled=true",
|
||||
"spring.cloud.loadbalancer.retry.enabled=false");
|
||||
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
|
||||
assertLoadBalanced(context, ApacheHttp5Client.class);
|
||||
assertThatBeanNotPresent(context, LoadBalancerFeignClient.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateRetryableDefaultFeignBlockingLoadBalancerClientWhenHttpClientDisabled() {
|
||||
ConfigurableApplicationContext context = initContext(
|
||||
@@ -102,6 +128,28 @@ class FeignLoadBalancerAutoConfigurationTests {
|
||||
assertThatBeanNotPresent(context, LoadBalancerFeignClient.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateRetryableHttpFeignClient5WhenEnabled() {
|
||||
ConfigurableApplicationContext context = initContext(
|
||||
"spring.cloud.loadbalancer.ribbon.enabled=false",
|
||||
"feign.httpclient.enabled=false", "feign.okhttp.enabled=false",
|
||||
"feign.httpclient.hc5.enabled=true");
|
||||
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
|
||||
assertLoadBalancedWithRetries(context, ApacheHttp5Client.class);
|
||||
assertThatBeanNotPresent(context, LoadBalancerFeignClient.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateRetryableHttpFeignClient5WhenBothHttpClientAndHttpClient5Enabled() {
|
||||
ConfigurableApplicationContext context = initContext(
|
||||
"spring.cloud.loadbalancer.ribbon.enabled=false",
|
||||
"feign.httpclient.enabled=true", "feign.okhttp.enabled=false",
|
||||
"feign.httpclient.hc5.enabled=true");
|
||||
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
|
||||
assertLoadBalancedWithRetries(context, ApacheHttp5Client.class);
|
||||
assertThatBeanNotPresent(context, LoadBalancerFeignClient.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotProcessLoadBalancerConfigurationWhenRibbonEnabled() {
|
||||
ConfigurableApplicationContext context = initContext(
|
||||
|
||||
+35
-1
@@ -16,7 +16,10 @@
|
||||
|
||||
package org.springframework.cloud.openfeign.loadbalancer;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
@@ -27,6 +30,7 @@ import java.util.Map;
|
||||
import feign.Client;
|
||||
import feign.Request;
|
||||
import feign.Response;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -58,7 +62,7 @@ import static org.mockito.Mockito.when;
|
||||
* {@link RetryableFeignBlockingLoadBalancerClient} and its delegates.
|
||||
*
|
||||
* @see <a href=
|
||||
* "https://github.com/spring-cloud/spring-cloud-commons/blob/master/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/blocking/client/BlockingLoadBalancerClientTests.java">BlockingLoadBalancerClientTests</a>
|
||||
* "https://github.com/spring-cloud/spring-cloud-commons/blob/main/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/blocking/client/BlockingLoadBalancerClientTests.java">BlockingLoadBalancerClientTests</a>
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@@ -113,6 +117,15 @@ class RetryableFeignBlockingLoadBalancerClientTests {
|
||||
return Response.builder().request(testRequest()).status(status).build();
|
||||
}
|
||||
|
||||
private Response testResponse(int status, String body) {
|
||||
// ByteArrayInputStream ignores close() and must be wrapped
|
||||
InputStream reallyCloseable = new BufferedInputStream(
|
||||
new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)));
|
||||
return Response.builder().request(testRequest()).status(status)
|
||||
.body(reallyCloseable, null).build();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExecuteOriginalRequestIfInstanceNotFound() throws IOException {
|
||||
Request request = testRequest();
|
||||
@@ -148,6 +161,27 @@ class RetryableFeignBlockingLoadBalancerClientTests {
|
||||
verify(delegate, times(2)).execute(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExposeResponseBodyOnRetry() throws IOException {
|
||||
properties.getRetryableStatusCodes().add(503);
|
||||
Request request = testRequest();
|
||||
when(delegate.execute(any(), any())).thenReturn(testResponse(503, "foo"),
|
||||
testResponse(503, "foo"));
|
||||
when(retryFactory.createRetryPolicy(any(), eq(loadBalancerClient)))
|
||||
.thenReturn(new BlockingLoadBalancedRetryPolicy("test",
|
||||
loadBalancerClient, properties));
|
||||
when(loadBalancerClient.reconstructURI(serviceInstance,
|
||||
URI.create("http://test/path")))
|
||||
.thenReturn(URI.create("http://testhost:80/path"));
|
||||
|
||||
Response response = feignBlockingLoadBalancerClient.execute(request,
|
||||
new Request.Options());
|
||||
|
||||
String bodyContent = IOUtils
|
||||
.toString(response.body().asReader(StandardCharsets.UTF_8));
|
||||
assertThat(bodyContent).isEqualTo("foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPassCorrectRequestToDelegate() throws IOException {
|
||||
Request request = testRequest();
|
||||
|
||||
+40
-48
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -44,6 +44,7 @@ import org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer.RibbonReques
|
||||
import org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer.RibbonResponse;
|
||||
|
||||
import static com.netflix.client.config.CommonClientConfigKey.ConnectTimeout;
|
||||
import static com.netflix.client.config.CommonClientConfigKey.FollowRedirects;
|
||||
import static com.netflix.client.config.CommonClientConfigKey.IsSecure;
|
||||
import static com.netflix.client.config.CommonClientConfigKey.MaxAutoRetries;
|
||||
import static com.netflix.client.config.CommonClientConfigKey.MaxAutoRetriesNextServer;
|
||||
@@ -71,53 +72,47 @@ public class FeignLoadBalancerTests {
|
||||
|
||||
private FeignLoadBalancer feignLoadBalancer;
|
||||
|
||||
private ServerIntrospector inspector = new DefaultServerIntrospector();
|
||||
|
||||
private Integer defaultConnectTimeout = 10000;
|
||||
|
||||
private Integer defaultReadTimeout = 10000;
|
||||
private final ServerIntrospector inspector = new DefaultServerIntrospector();
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(this.config.get(MaxAutoRetries, DEFAULT_MAX_AUTO_RETRIES)).thenReturn(1);
|
||||
when(this.config.get(MaxAutoRetriesNextServer,
|
||||
DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER)).thenReturn(1);
|
||||
when(this.config.get(OkToRetryOnAllOperations, eq(anyBoolean())))
|
||||
.thenReturn(true);
|
||||
when(this.config.get(ConnectTimeout)).thenReturn(this.defaultConnectTimeout);
|
||||
when(this.config.get(ReadTimeout)).thenReturn(this.defaultReadTimeout);
|
||||
when(this.config.get(OkToRetryOnAllOperations, false)).thenReturn(true);
|
||||
when(config.get(MaxAutoRetries, DEFAULT_MAX_AUTO_RETRIES)).thenReturn(1);
|
||||
when(config.get(MaxAutoRetriesNextServer, DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER))
|
||||
.thenReturn(1);
|
||||
when(config.get(OkToRetryOnAllOperations, eq(anyBoolean()))).thenReturn(true);
|
||||
when(config.get(ConnectTimeout)).thenReturn(10000);
|
||||
when(config.get(ReadTimeout)).thenReturn(10000);
|
||||
when(config.get(eq(FollowRedirects), any())).thenReturn(true);
|
||||
when(config.get(OkToRetryOnAllOperations, false)).thenReturn(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUriInsecure() throws Exception {
|
||||
when(this.config.get(IsSecure)).thenReturn(false);
|
||||
when(config.get(IsSecure)).thenReturn(false);
|
||||
|
||||
this.feignLoadBalancer = new FeignLoadBalancer(this.lb, this.config,
|
||||
this.inspector);
|
||||
feignLoadBalancer = new FeignLoadBalancer(lb, config, inspector);
|
||||
Request request = new RequestTemplate().method(GET).target("https://foo/")
|
||||
.resolve(new HashMap<>()).request();
|
||||
RibbonRequest ribbonRequest = new RibbonRequest(this.delegate, request,
|
||||
RibbonRequest ribbonRequest = new RibbonRequest(delegate, request,
|
||||
new URI(request.url()));
|
||||
|
||||
Response response = Response.builder().request(request).status(200).reason("Test")
|
||||
.headers(Collections.emptyMap()).body(new byte[0]).build();
|
||||
when(this.delegate.execute(any(Request.class), any(Options.class)))
|
||||
when(delegate.execute(any(Request.class), any(Options.class)))
|
||||
.thenReturn(response);
|
||||
|
||||
RibbonResponse resp = this.feignLoadBalancer.execute(ribbonRequest, null);
|
||||
RibbonResponse resp = feignLoadBalancer.execute(ribbonRequest, null);
|
||||
|
||||
assertThat(resp.getRequestedURI()).isEqualTo(new URI("https://foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecureUriFromClientConfig() throws Exception {
|
||||
when(this.config.get(IsSecure)).thenReturn(true);
|
||||
this.feignLoadBalancer = new FeignLoadBalancer(this.lb, this.config,
|
||||
this.inspector);
|
||||
when(config.get(IsSecure)).thenReturn(true);
|
||||
feignLoadBalancer = new FeignLoadBalancer(lb, config, inspector);
|
||||
Server server = new Server("foo", 7777);
|
||||
URI uri = this.feignLoadBalancer.reconstructURIWithServer(server,
|
||||
URI uri = feignLoadBalancer.reconstructURIWithServer(server,
|
||||
new URI("https://foo/"));
|
||||
assertThat(uri).isEqualTo(new URI("https://foo:7777/"));
|
||||
}
|
||||
@@ -125,33 +120,31 @@ public class FeignLoadBalancerTests {
|
||||
@Test
|
||||
public void testInsecureUriFromInsecureClientConfigToSecureServerIntrospector()
|
||||
throws Exception {
|
||||
when(this.config.get(IsSecure)).thenReturn(false);
|
||||
this.feignLoadBalancer = new FeignLoadBalancer(this.lb, this.config,
|
||||
new ServerIntrospector() {
|
||||
@Override
|
||||
public boolean isSecure(Server server) {
|
||||
return true;
|
||||
}
|
||||
when(config.get(IsSecure)).thenReturn(false);
|
||||
feignLoadBalancer = new FeignLoadBalancer(lb, config, new ServerIntrospector() {
|
||||
@Override
|
||||
public boolean isSecure(Server server) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getMetadata(Server server) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public Map<String, String> getMetadata(Server server) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
Server server = new Server("foo", 7777);
|
||||
URI uri = this.feignLoadBalancer.reconstructURIWithServer(server,
|
||||
URI uri = feignLoadBalancer.reconstructURIWithServer(server,
|
||||
new URI("https://foo/"));
|
||||
assertThat(uri).isEqualTo(new URI("https://foo:7777/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSecureUriFromClientConfigOverride() throws Exception {
|
||||
this.feignLoadBalancer = new FeignLoadBalancer(this.lb, this.config,
|
||||
this.inspector);
|
||||
feignLoadBalancer = new FeignLoadBalancer(lb, config, inspector);
|
||||
Server server = Mockito.mock(Server.class);
|
||||
when(server.getPort()).thenReturn(443);
|
||||
when(server.getHost()).thenReturn("foo");
|
||||
URI uri = this.feignLoadBalancer.reconstructURIWithServer(server,
|
||||
URI uri = feignLoadBalancer.reconstructURIWithServer(server,
|
||||
new URI("https://bar/"));
|
||||
assertThat(uri).isEqualTo(new URI("https://foo:443/"));
|
||||
}
|
||||
@@ -163,7 +156,7 @@ public class FeignLoadBalancerTests {
|
||||
|
||||
assertThat(request.url()).isEqualTo(url);
|
||||
|
||||
RibbonRequest ribbonRequest = new RibbonRequest(this.delegate, request,
|
||||
RibbonRequest ribbonRequest = new RibbonRequest(delegate, request,
|
||||
new URI(request.url()));
|
||||
|
||||
Request cloneRequest = ribbonRequest.toRequest();
|
||||
@@ -174,7 +167,7 @@ public class FeignLoadBalancerTests {
|
||||
|
||||
@Test
|
||||
public void testOverrideFeignLoadBalancer() throws Exception {
|
||||
when(this.config.get(IsSecure)).thenReturn(false);
|
||||
when(config.get(IsSecure)).thenReturn(false);
|
||||
Server server1 = new Server("foo", 6666);
|
||||
Server server2 = new Server("foo", 7777);
|
||||
BaseLoadBalancer baseLoadBalancer = new BaseLoadBalancer();
|
||||
@@ -185,8 +178,7 @@ public class FeignLoadBalancerTests {
|
||||
}
|
||||
});
|
||||
|
||||
this.feignLoadBalancer = new FeignLoadBalancer(baseLoadBalancer, this.config,
|
||||
this.inspector) {
|
||||
feignLoadBalancer = new FeignLoadBalancer(baseLoadBalancer, config, inspector) {
|
||||
protected void customizeLoadBalancerCommandBuilder(
|
||||
final FeignLoadBalancer.RibbonRequest request,
|
||||
final IClientConfig config,
|
||||
@@ -196,13 +188,13 @@ public class FeignLoadBalancerTests {
|
||||
};
|
||||
Request request = new RequestTemplate().method(GET).resolve(new HashMap<>())
|
||||
.request();
|
||||
RibbonResponse resp = this.feignLoadBalancer.executeWithLoadBalancer(
|
||||
new RibbonRequest(this.delegate, request, new URI(request.url())), null);
|
||||
RibbonResponse resp = feignLoadBalancer.executeWithLoadBalancer(
|
||||
new RibbonRequest(delegate, request, new URI(request.url())), null);
|
||||
assertThat(resp.getRequestedURI().getPort()).isEqualTo(7777);
|
||||
request = new RequestTemplate().method(GET).header("c_ip", "666")
|
||||
.resolve(new HashMap<>()).request();
|
||||
resp = this.feignLoadBalancer.executeWithLoadBalancer(
|
||||
new RibbonRequest(this.delegate, request, new URI(request.url())), null);
|
||||
resp = feignLoadBalancer.executeWithLoadBalancer(
|
||||
new RibbonRequest(delegate, request, new URI(request.url())), null);
|
||||
assertThat(resp.getRequestedURI().getPort()).isEqualTo(6666);
|
||||
}
|
||||
|
||||
|
||||
+3
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -50,9 +50,8 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
|
||||
webEnvironment = RANDOM_PORT,
|
||||
value = { "spring.application.name=feignribbonclientpathtest",
|
||||
"feign.okhttp.enabled=false", "feign.httpclient.enabled=false",
|
||||
"feign.hystrix.enabled=false", "test.path.prefix=/base/path" // For
|
||||
// pathWithPlaceholder
|
||||
// test
|
||||
"feign.hystrix.enabled=false", "feign.httpclient.hc5.enabled=false",
|
||||
"test.path.prefix=/base/path" // For pathWithPlaceholder test
|
||||
})
|
||||
@DirtiesContext
|
||||
public class FeignRibbonClientPathTests {
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -56,7 +56,8 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
|
||||
webEnvironment = RANDOM_PORT,
|
||||
value = { "spring.application.name=feignclientretrytest",
|
||||
"feign.okhttp.enabled=false", "feign.httpclient.enabled=false",
|
||||
"feign.hystrix.enabled=false", "localapp.ribbon.MaxAutoRetries=2",
|
||||
"feign.hystrix.enabled=false", "feign.httpclient.hc5.enabled=false",
|
||||
"localapp.ribbon.MaxAutoRetries=2",
|
||||
"localapp.ribbon.MaxAutoRetriesNextServer=3" })
|
||||
@DirtiesContext
|
||||
public class FeignRibbonClientRetryTests {
|
||||
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2013-2021 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
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign.ribbon;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import javax.net.ssl.SSLContextSpi;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import feign.Client;
|
||||
import feign.hc5.ApacheHttp5Client;
|
||||
import org.apache.hc.client5.http.impl.io.DefaultHttpClientConnectionOperator;
|
||||
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
|
||||
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
|
||||
import org.apache.hc.core5.http.URIScheme;
|
||||
import org.apache.hc.core5.http.config.Lookup;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Nguyen Ky Thanh
|
||||
*/
|
||||
@SpringBootTest(
|
||||
classes = FeignRibbonHttpClient5ConfigurationTests.FeignRibbonHttpClientConfigurationTestsApplication.class,
|
||||
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
properties = { "feign.httpclient.disableSslValidation=true",
|
||||
"feign.httpclient.hc5.enabled=true", "feign.httpclient.enabled=false" })
|
||||
@DirtiesContext
|
||||
class FeignRibbonHttpClient5ConfigurationTests {
|
||||
|
||||
@Autowired
|
||||
private HttpClientConnectionManager connectionManager;
|
||||
|
||||
@Autowired
|
||||
private Client client;
|
||||
|
||||
@Test
|
||||
void disableSslTest() throws Exception {
|
||||
Lookup<ConnectionSocketFactory> socketFactoryRegistry = getConnectionSocketFactoryLookup(
|
||||
connectionManager);
|
||||
assertThat(socketFactoryRegistry.lookup(URIScheme.HTTPS.id)).isNotNull();
|
||||
assertThat(getX509TrustManager(socketFactoryRegistry).getAcceptedIssuers())
|
||||
.isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void verifyHttpClient5IsPickedUp() {
|
||||
assertThat(client).isInstanceOf(LoadBalancerFeignClient.class);
|
||||
Client delegate = (Client) getField(client, "delegate");
|
||||
assertThat(delegate).isInstanceOf(ApacheHttp5Client.class);
|
||||
}
|
||||
|
||||
private Lookup<ConnectionSocketFactory> getConnectionSocketFactoryLookup(
|
||||
HttpClientConnectionManager connectionManager) {
|
||||
DefaultHttpClientConnectionOperator connectionOperator = (DefaultHttpClientConnectionOperator) this
|
||||
.getField(connectionManager, "connectionOperator");
|
||||
return (Lookup) getField(connectionOperator, "socketFactoryRegistry");
|
||||
}
|
||||
|
||||
private X509TrustManager getX509TrustManager(
|
||||
Lookup<ConnectionSocketFactory> socketFactoryRegistry) {
|
||||
ConnectionSocketFactory connectionSocketFactory = (ConnectionSocketFactory) socketFactoryRegistry
|
||||
.lookup(URIScheme.HTTPS.id);
|
||||
SSLSocketFactory sslSocketFactory = (SSLSocketFactory) this
|
||||
.getField(connectionSocketFactory, "socketFactory");
|
||||
SSLContextSpi sslContext = (SSLContextSpi) getField(sslSocketFactory, "context");
|
||||
return (X509TrustManager) getField(sslContext, "trustManager");
|
||||
}
|
||||
|
||||
protected <T> Object getField(Object target, String name) {
|
||||
Field field = ReflectionUtils.findField(target.getClass(), name);
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
Object value = ReflectionUtils.getField(field, target);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableAutoConfiguration
|
||||
static class FeignRibbonHttpClientConfigurationTestsApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
new SpringApplicationBuilder(FeignRibbonClientRetryTests.Application.class)
|
||||
.run(args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+84
-87
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -59,6 +59,7 @@ import org.springframework.retry.backoff.BackOffInterruptedException;
|
||||
import org.springframework.retry.backoff.BackOffPolicy;
|
||||
|
||||
import static com.netflix.client.config.CommonClientConfigKey.ConnectTimeout;
|
||||
import static com.netflix.client.config.CommonClientConfigKey.FollowRedirects;
|
||||
import static com.netflix.client.config.CommonClientConfigKey.MaxAutoRetries;
|
||||
import static com.netflix.client.config.CommonClientConfigKey.MaxAutoRetriesNextServer;
|
||||
import static com.netflix.client.config.CommonClientConfigKey.OkToRetryOnAllOperations;
|
||||
@@ -92,29 +93,28 @@ public class RetryableFeignLoadBalancerTests {
|
||||
@Mock
|
||||
private IClientConfig config;
|
||||
|
||||
private ServerIntrospector inspector = new DefaultServerIntrospector();
|
||||
private final ServerIntrospector inspector = new DefaultServerIntrospector();
|
||||
|
||||
private Integer defaultConnectTimeout = 10000;
|
||||
private final Integer defaultConnectTimeout = 10000;
|
||||
|
||||
private Integer defaultReadTimeout = 10000;
|
||||
private final Integer defaultReadTimeout = 10000;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
when(this.config.get(MaxAutoRetries, DEFAULT_MAX_AUTO_RETRIES)).thenReturn(1);
|
||||
when(this.config.get(MaxAutoRetriesNextServer,
|
||||
DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER)).thenReturn(1);
|
||||
when(this.config.get(OkToRetryOnAllOperations, eq(anyBoolean())))
|
||||
.thenReturn(true);
|
||||
when(this.config.get(ConnectTimeout)).thenReturn(this.defaultConnectTimeout);
|
||||
when(this.config.get(ReadTimeout)).thenReturn(this.defaultReadTimeout);
|
||||
when(this.config.get(OkToRetryOnAllOperations, false)).thenReturn(true);
|
||||
when(config.get(MaxAutoRetries, DEFAULT_MAX_AUTO_RETRIES)).thenReturn(1);
|
||||
when(config.get(MaxAutoRetriesNextServer, DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER))
|
||||
.thenReturn(1);
|
||||
when(config.get(OkToRetryOnAllOperations, eq(anyBoolean()))).thenReturn(true);
|
||||
when(config.get(ConnectTimeout)).thenReturn(defaultConnectTimeout);
|
||||
when(config.get(ReadTimeout)).thenReturn(defaultReadTimeout);
|
||||
when(config.get(OkToRetryOnAllOperations, false)).thenReturn(true);
|
||||
when(config.get(eq(FollowRedirects), any())).thenReturn(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void executeNoFailure() throws Exception {
|
||||
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(this.lb,
|
||||
this.config);
|
||||
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
|
||||
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
|
||||
doReturn(lbContext).when(clientFactory).getLoadBalancerContext(any(String.class));
|
||||
IClientConfig config = mock(IClientConfig.class);
|
||||
@@ -123,9 +123,9 @@ public class RetryableFeignLoadBalancerTests {
|
||||
anyInt());
|
||||
doReturn(true).when(config)
|
||||
.get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), eq(false));
|
||||
doReturn(this.defaultConnectTimeout).when(config)
|
||||
doReturn(defaultConnectTimeout).when(config)
|
||||
.get(eq(CommonClientConfigKey.ConnectTimeout));
|
||||
doReturn(this.defaultReadTimeout).when(config)
|
||||
doReturn(defaultReadTimeout).when(config)
|
||||
.get(eq(CommonClientConfigKey.ReadTimeout));
|
||||
doReturn("404,502,foo, ,").when(config).getPropertyAsString(
|
||||
eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES), eq(""));
|
||||
@@ -141,8 +141,9 @@ public class RetryableFeignLoadBalancerTests {
|
||||
.headers(new HashMap<>()).build();
|
||||
doReturn(response).when(client).execute(any(Request.class),
|
||||
any(Request.Options.class));
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(this.lb,
|
||||
config, this.inspector, loadBalancedRetryFactory);
|
||||
when(config.get(eq(FollowRedirects), any())).thenReturn(true);
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config,
|
||||
inspector, loadBalancedRetryFactory);
|
||||
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
|
||||
assertThat(ribbonResponse.toResponse().status()).isEqualTo(200);
|
||||
verify(client, times(1)).execute(any(Request.class), any(Request.Options.class));
|
||||
@@ -157,8 +158,8 @@ public class RetryableFeignLoadBalancerTests {
|
||||
client, feignRequest, new URI("https://foo"));
|
||||
doThrow(new IOException("boom")).when(client).execute(any(Request.class),
|
||||
any(Request.Options.class));
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(this.lb,
|
||||
this.config, this.inspector, new LoadBalancedRetryFactory() {
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config,
|
||||
inspector, new LoadBalancedRetryFactory() {
|
||||
@Override
|
||||
public LoadBalancedRetryPolicy createRetryPolicy(String s,
|
||||
ServiceInstanceChooser serviceInstanceChooser) {
|
||||
@@ -189,8 +190,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
|
||||
@Test
|
||||
public void executeRetry() throws Exception {
|
||||
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(this.lb,
|
||||
this.config);
|
||||
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
|
||||
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
|
||||
IClientConfig config = mock(IClientConfig.class);
|
||||
doReturn(1).when(config).get(eq(CommonClientConfigKey.MaxAutoRetries), anyInt());
|
||||
@@ -198,14 +198,15 @@ public class RetryableFeignLoadBalancerTests {
|
||||
anyInt());
|
||||
doReturn(true).when(config)
|
||||
.get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), eq(false));
|
||||
doReturn(this.defaultConnectTimeout).when(config)
|
||||
doReturn(defaultConnectTimeout).when(config)
|
||||
.get(eq(CommonClientConfigKey.ConnectTimeout));
|
||||
doReturn(this.defaultReadTimeout).when(config)
|
||||
doReturn(defaultReadTimeout).when(config)
|
||||
.get(eq(CommonClientConfigKey.ReadTimeout));
|
||||
doReturn("").when(config).getPropertyAsString(
|
||||
eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES), eq(""));
|
||||
doReturn(config).when(clientFactory).getClientConfig(eq("default"));
|
||||
doReturn(lbContext).when(clientFactory).getLoadBalancerContext(any(String.class));
|
||||
when(config.get(eq(FollowRedirects), any())).thenReturn(true);
|
||||
MyBackOffPolicy backOffPolicy = new MyBackOffPolicy();
|
||||
RibbonLoadBalancedRetryFactory loadBalancedRetryFactory = new RibbonLoadBalancedRetryFactory(
|
||||
clientFactory) {
|
||||
@@ -224,8 +225,8 @@ public class RetryableFeignLoadBalancerTests {
|
||||
doThrow(new IOException("boom")).doReturn(response).when(client)
|
||||
.execute(any(Request.class), any(Request.Options.class));
|
||||
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(this.lb,
|
||||
config, this.inspector, loadBalancedRetryFactory);
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config,
|
||||
inspector, loadBalancedRetryFactory);
|
||||
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
|
||||
assertThat(ribbonResponse.toResponse().status()).isEqualTo(200);
|
||||
verify(client, times(2)).execute(any(Request.class), any(Request.Options.class));
|
||||
@@ -234,8 +235,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
|
||||
@Test
|
||||
public void executeRetryOnStatusCode() throws Exception {
|
||||
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(this.lb,
|
||||
this.config);
|
||||
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
|
||||
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
|
||||
IClientConfig config = mock(IClientConfig.class);
|
||||
doReturn(1).when(config).get(eq(CommonClientConfigKey.MaxAutoRetries), anyInt());
|
||||
@@ -243,9 +243,9 @@ public class RetryableFeignLoadBalancerTests {
|
||||
anyInt());
|
||||
doReturn(true).when(config)
|
||||
.get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), eq(false));
|
||||
doReturn(this.defaultConnectTimeout).when(config)
|
||||
doReturn(defaultConnectTimeout).when(config)
|
||||
.get(eq(CommonClientConfigKey.ConnectTimeout));
|
||||
doReturn(this.defaultReadTimeout).when(config)
|
||||
doReturn(defaultReadTimeout).when(config)
|
||||
.get(eq(CommonClientConfigKey.ReadTimeout));
|
||||
doReturn("404").when(config).getPropertyAsString(
|
||||
eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES), eq(""));
|
||||
@@ -270,8 +270,9 @@ public class RetryableFeignLoadBalancerTests {
|
||||
.headers(new HashMap<>()).build();
|
||||
doReturn(fourOFourResponse).doReturn(response).when(client)
|
||||
.execute(any(Request.class), any(Request.Options.class));
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(this.lb,
|
||||
config, this.inspector, loadBalancedRetryFactory);
|
||||
when(config.get(eq(FollowRedirects), any())).thenReturn(true);
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config,
|
||||
inspector, loadBalancedRetryFactory);
|
||||
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
|
||||
assertThat(ribbonResponse.toResponse().status()).isEqualTo(200);
|
||||
verify(client, times(2)).execute(any(Request.class), any(Request.Options.class));
|
||||
@@ -281,11 +282,10 @@ public class RetryableFeignLoadBalancerTests {
|
||||
@Test
|
||||
public void executeRetryOnStatusCodeWithEmptyBody() throws Exception {
|
||||
int retriesNextServer = 0;
|
||||
when(this.config.get(MaxAutoRetriesNextServer,
|
||||
DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER)).thenReturn(retriesNextServer);
|
||||
doReturn(new Server("foo", 80)).when(this.lb).chooseServer(any());
|
||||
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(this.lb,
|
||||
this.config);
|
||||
when(config.get(MaxAutoRetriesNextServer, DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER))
|
||||
.thenReturn(retriesNextServer);
|
||||
doReturn(new Server("foo", 80)).when(lb).chooseServer(any());
|
||||
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
|
||||
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
|
||||
IClientConfig config = mock(IClientConfig.class);
|
||||
doReturn(1).when(config).get(eq(CommonClientConfigKey.MaxAutoRetries), anyInt());
|
||||
@@ -293,9 +293,9 @@ public class RetryableFeignLoadBalancerTests {
|
||||
.get(eq(CommonClientConfigKey.MaxAutoRetriesNextServer), anyInt());
|
||||
doReturn(true).when(config)
|
||||
.get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), eq(false));
|
||||
doReturn(this.defaultConnectTimeout).when(config)
|
||||
doReturn(defaultConnectTimeout).when(config)
|
||||
.get(eq(CommonClientConfigKey.ConnectTimeout));
|
||||
doReturn(this.defaultReadTimeout).when(config)
|
||||
doReturn(defaultReadTimeout).when(config)
|
||||
.get(eq(CommonClientConfigKey.ReadTimeout));
|
||||
doReturn("404").when(config).getPropertyAsString(
|
||||
eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES), eq(""));
|
||||
@@ -320,8 +320,9 @@ public class RetryableFeignLoadBalancerTests {
|
||||
.headers(new HashMap<>()).build();
|
||||
doReturn(fourOFourResponse).doReturn(response).when(client)
|
||||
.execute(any(Request.class), any(Request.Options.class));
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(this.lb,
|
||||
config, this.inspector, loadBalancedRetryFactory);
|
||||
when(config.get(eq(FollowRedirects), any())).thenReturn(true);
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config,
|
||||
inspector, loadBalancedRetryFactory);
|
||||
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
|
||||
assertThat(ribbonResponse.toResponse().status()).isEqualTo(404);
|
||||
assertThat(ribbonResponse.toResponse().body().length())
|
||||
@@ -332,8 +333,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
|
||||
@Test
|
||||
public void getRequestSpecificRetryHandler() throws Exception {
|
||||
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(this.lb,
|
||||
this.config);
|
||||
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
|
||||
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
|
||||
doReturn(lbContext).when(clientFactory).getLoadBalancerContext(any(String.class));
|
||||
RibbonLoadBalancedRetryFactory loadBalancedRetryFactory = new RibbonLoadBalancedRetryFactory(
|
||||
@@ -347,10 +347,10 @@ public class RetryableFeignLoadBalancerTests {
|
||||
.headers(new HashMap<>()).build();
|
||||
doReturn(response).when(client).execute(any(Request.class),
|
||||
any(Request.Options.class));
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(this.lb,
|
||||
this.config, this.inspector, loadBalancedRetryFactory);
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config,
|
||||
inspector, loadBalancedRetryFactory);
|
||||
RequestSpecificRetryHandler retryHandler = feignLb
|
||||
.getRequestSpecificRetryHandler(request, this.config);
|
||||
.getRequestSpecificRetryHandler(request, config);
|
||||
assertThat(retryHandler.getMaxRetriesOnNextServer()).isEqualTo(1);
|
||||
assertThat(retryHandler.getMaxRetriesOnSameServer()).isEqualTo(1);
|
||||
|
||||
@@ -358,8 +358,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
|
||||
@Test
|
||||
public void choose() throws Exception {
|
||||
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(this.lb,
|
||||
this.config);
|
||||
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
|
||||
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
|
||||
doReturn(lbContext).when(clientFactory).getLoadBalancerContext(any(String.class));
|
||||
RibbonLoadBalancedRetryFactory loadBalancedRetryFactory = new RibbonLoadBalancedRetryFactory(
|
||||
@@ -367,8 +366,6 @@ public class RetryableFeignLoadBalancerTests {
|
||||
Request feignRequest = Request.create(GET, "https://foo", new HashMap<>(),
|
||||
new byte[] {}, UTF_8, null);
|
||||
Client client = mock(Client.class);
|
||||
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(
|
||||
client, feignRequest, new URI("https://foo"));
|
||||
Response response = Response.builder().request(feignRequest).status(200)
|
||||
.headers(new HashMap<>()).build();
|
||||
doReturn(response).when(client).execute(any(Request.class),
|
||||
@@ -405,7 +402,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
public List<Server> getAllServers() {
|
||||
return null;
|
||||
}
|
||||
}, this.config, this.inspector, loadBalancedRetryFactory);
|
||||
}, config, inspector, loadBalancedRetryFactory);
|
||||
ServiceInstance serviceInstance = feignLb.choose("foo");
|
||||
assertThat(serviceInstance.getHost()).isEqualTo("foo");
|
||||
assertThat(serviceInstance.getPort()).isEqualTo(80);
|
||||
@@ -414,8 +411,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
|
||||
@Test
|
||||
public void retryListenerTest() throws Exception {
|
||||
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(this.lb,
|
||||
this.config);
|
||||
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
|
||||
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
|
||||
IClientConfig config = mock(IClientConfig.class);
|
||||
doReturn(1).when(config).get(eq(CommonClientConfigKey.MaxAutoRetries), anyInt());
|
||||
@@ -423,14 +419,15 @@ public class RetryableFeignLoadBalancerTests {
|
||||
anyInt());
|
||||
doReturn(true).when(config)
|
||||
.get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), eq(false));
|
||||
doReturn(this.defaultConnectTimeout).when(config)
|
||||
doReturn(defaultConnectTimeout).when(config)
|
||||
.get(eq(CommonClientConfigKey.ConnectTimeout));
|
||||
doReturn(this.defaultReadTimeout).when(config)
|
||||
doReturn(defaultReadTimeout).when(config)
|
||||
.get(eq(CommonClientConfigKey.ReadTimeout));
|
||||
doReturn("").when(config).getPropertyAsString(
|
||||
eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES), eq(""));
|
||||
doReturn(config).when(clientFactory).getClientConfig(eq("default"));
|
||||
doReturn(lbContext).when(clientFactory).getLoadBalancerContext(any(String.class));
|
||||
when(config.get(eq(FollowRedirects), any())).thenReturn(true);
|
||||
MyBackOffPolicy backOffPolicy = new MyBackOffPolicy();
|
||||
MyRetryListener myRetryListener = new MyRetryListener();
|
||||
RibbonLoadBalancedRetryFactory loadBalancedRetryFactory = new RibbonLoadBalancedRetryFactory(
|
||||
@@ -455,8 +452,8 @@ public class RetryableFeignLoadBalancerTests {
|
||||
doThrow(new IOException("boom")).doReturn(response).when(client)
|
||||
.execute(any(Request.class), any(Request.Options.class));
|
||||
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(this.lb,
|
||||
config, this.inspector, loadBalancedRetryFactory);
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config,
|
||||
inspector, loadBalancedRetryFactory);
|
||||
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
|
||||
assertThat(ribbonResponse.toResponse().status()).isEqualTo(200);
|
||||
verify(client, times(2)).execute(any(Request.class), any(Request.Options.class));
|
||||
@@ -466,8 +463,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
|
||||
@Test(expected = TerminatedRetryException.class)
|
||||
public void retryListenerTestNoRetry() throws Exception {
|
||||
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(this.lb,
|
||||
this.config);
|
||||
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
|
||||
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
|
||||
IClientConfig config = mock(IClientConfig.class);
|
||||
doReturn(1).when(config).get(eq(CommonClientConfigKey.MaxAutoRetries), anyInt());
|
||||
@@ -475,14 +471,15 @@ public class RetryableFeignLoadBalancerTests {
|
||||
anyInt());
|
||||
doReturn(true).when(config)
|
||||
.get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), eq(false));
|
||||
doReturn(this.defaultConnectTimeout).when(config)
|
||||
doReturn(defaultConnectTimeout).when(config)
|
||||
.get(eq(CommonClientConfigKey.ConnectTimeout));
|
||||
doReturn(this.defaultReadTimeout).when(config)
|
||||
doReturn(defaultReadTimeout).when(config)
|
||||
.get(eq(CommonClientConfigKey.ReadTimeout));
|
||||
doReturn("").when(config).getPropertyAsString(
|
||||
eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES), eq(""));
|
||||
doReturn(config).when(clientFactory).getClientConfig(eq("default"));
|
||||
doReturn(lbContext).when(clientFactory).getLoadBalancerContext(any(String.class));
|
||||
when(config.get(eq(FollowRedirects), any())).thenReturn(true);
|
||||
MyBackOffPolicy backOffPolicy = new MyBackOffPolicy();
|
||||
MyRetryListenerNotRetry myRetryListenerNotRetry = new MyRetryListenerNotRetry();
|
||||
RibbonLoadBalancedRetryFactory loadBalancedRetryFactory = new RibbonLoadBalancedRetryFactory(
|
||||
@@ -502,15 +499,14 @@ public class RetryableFeignLoadBalancerTests {
|
||||
Client client = mock(Client.class);
|
||||
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(
|
||||
client, feignRequest, new URI("https://listener"));
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(this.lb,
|
||||
config, this.inspector, loadBalancedRetryFactory);
|
||||
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config,
|
||||
inspector, loadBalancedRetryFactory);
|
||||
feignLb.execute(request, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void retryWithDefaultConstructorTest() throws Exception {
|
||||
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(this.lb,
|
||||
this.config);
|
||||
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
|
||||
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
|
||||
IClientConfig config = mock(IClientConfig.class);
|
||||
doReturn(1).when(config).get(eq(CommonClientConfigKey.MaxAutoRetries), anyInt());
|
||||
@@ -518,14 +514,15 @@ public class RetryableFeignLoadBalancerTests {
|
||||
anyInt());
|
||||
doReturn(true).when(config)
|
||||
.get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), eq(false));
|
||||
doReturn(this.defaultConnectTimeout).when(config)
|
||||
doReturn(defaultConnectTimeout).when(config)
|
||||
.get(eq(CommonClientConfigKey.ConnectTimeout));
|
||||
doReturn(this.defaultReadTimeout).when(config)
|
||||
doReturn(defaultReadTimeout).when(config)
|
||||
.get(eq(CommonClientConfigKey.ReadTimeout));
|
||||
doReturn("").when(config).getPropertyAsString(
|
||||
eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES), eq(""));
|
||||
doReturn(config).when(clientFactory).getClientConfig(eq("default"));
|
||||
doReturn(lbContext).when(clientFactory).getLoadBalancerContext(any(String.class));
|
||||
when(config.get(eq(FollowRedirects), any())).thenReturn(true);
|
||||
MyBackOffPolicy backOffPolicy = new MyBackOffPolicy();
|
||||
RibbonLoadBalancedRetryFactory loadBalancedRetryPolicyFactory = new RibbonLoadBalancedRetryFactory(
|
||||
clientFactory) {
|
||||
@@ -543,8 +540,8 @@ public class RetryableFeignLoadBalancerTests {
|
||||
.headers(new HashMap<>()).build();
|
||||
doThrow(new IOException("boom")).doReturn(response).when(client)
|
||||
.execute(any(Request.class), any(Request.Options.class));
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(this.lb,
|
||||
config, this.inspector, loadBalancedRetryPolicyFactory);
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config,
|
||||
inspector, loadBalancedRetryPolicyFactory);
|
||||
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
|
||||
assertThat(ribbonResponse.toResponse().status()).isEqualTo(200);
|
||||
verify(client, times(2)).execute(any(Request.class), any(Request.Options.class));
|
||||
@@ -553,8 +550,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
|
||||
@Test
|
||||
public void executeRetryFail() throws Exception {
|
||||
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(this.lb,
|
||||
this.config);
|
||||
RibbonLoadBalancerContext lbContext = new RibbonLoadBalancerContext(lb, config);
|
||||
lbContext.setRetryHandler(new DefaultLoadBalancerRetryHandler(1, 0, true));
|
||||
SpringClientFactory clientFactory = mock(SpringClientFactory.class);
|
||||
IClientConfig config = mock(IClientConfig.class);
|
||||
@@ -563,14 +559,15 @@ public class RetryableFeignLoadBalancerTests {
|
||||
anyInt());
|
||||
doReturn(true).when(config)
|
||||
.get(eq(CommonClientConfigKey.OkToRetryOnAllOperations), eq(false));
|
||||
doReturn(this.defaultConnectTimeout).when(config)
|
||||
doReturn(defaultConnectTimeout).when(config)
|
||||
.get(eq(CommonClientConfigKey.ConnectTimeout));
|
||||
doReturn(this.defaultReadTimeout).when(config)
|
||||
doReturn(defaultReadTimeout).when(config)
|
||||
.get(eq(CommonClientConfigKey.ReadTimeout));
|
||||
doReturn("404").when(config).getPropertyAsString(
|
||||
eq(RibbonLoadBalancedRetryPolicy.RETRYABLE_STATUS_CODES), eq(""));
|
||||
doReturn(config).when(clientFactory).getClientConfig(eq("default"));
|
||||
doReturn(lbContext).when(clientFactory).getLoadBalancerContext(any(String.class));
|
||||
when(config.get(eq(FollowRedirects), any())).thenReturn(true);
|
||||
MyBackOffPolicy backOffPolicy = new MyBackOffPolicy();
|
||||
RibbonLoadBalancedRetryFactory loadBalancedRetryFactory = new RibbonLoadBalancedRetryFactory(
|
||||
clientFactory) {
|
||||
@@ -598,28 +595,28 @@ public class RetryableFeignLoadBalancerTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream asInputStream() throws IOException {
|
||||
public InputStream asInputStream() {
|
||||
return new ByteArrayInputStream("test".getBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader asReader() throws IOException {
|
||||
public Reader asReader() {
|
||||
return new InputStreamReader(asInputStream(), UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader asReader(Charset charset) throws IOException {
|
||||
public Reader asReader(Charset charset) {
|
||||
return new InputStreamReader(asInputStream(), charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
public void close() {
|
||||
}
|
||||
}).build();
|
||||
doReturn(fourOFourResponse).when(client).execute(any(Request.class),
|
||||
any(Request.Options.class));
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(this.lb,
|
||||
config, this.inspector, loadBalancedRetryFactory);
|
||||
RetryableFeignLoadBalancer feignLb = new RetryableFeignLoadBalancer(lb, config,
|
||||
inspector, loadBalancedRetryFactory);
|
||||
FeignLoadBalancer.RibbonResponse ribbonResponse = feignLb.execute(request, null);
|
||||
verify(client, times(2)).execute(any(Request.class), any(Request.Options.class));
|
||||
assertThat(backOffPolicy.getCount()).isEqualTo(1);
|
||||
@@ -629,7 +626,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
assertThat(new String(buf, 0, read)).isEqualTo("test");
|
||||
}
|
||||
|
||||
class MyBackOffPolicy implements BackOffPolicy {
|
||||
static class MyBackOffPolicy implements BackOffPolicy {
|
||||
|
||||
private int count = 0;
|
||||
|
||||
@@ -641,16 +638,16 @@ public class RetryableFeignLoadBalancerTests {
|
||||
@Override
|
||||
public void backOff(BackOffContext backOffContext)
|
||||
throws BackOffInterruptedException {
|
||||
this.count++;
|
||||
count++;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return this.count;
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MyRetryListener implements RetryListener {
|
||||
static class MyRetryListener implements RetryListener {
|
||||
|
||||
private int onError = 0;
|
||||
|
||||
@@ -669,16 +666,16 @@ public class RetryableFeignLoadBalancerTests {
|
||||
@Override
|
||||
public <T, E extends Throwable> void onError(RetryContext context,
|
||||
RetryCallback<T, E> callback, Throwable throwable) {
|
||||
this.onError++;
|
||||
onError++;
|
||||
}
|
||||
|
||||
public int getOnError() {
|
||||
return this.onError;
|
||||
return onError;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MyRetryListenerNotRetry implements RetryListener {
|
||||
static class MyRetryListenerNotRetry implements RetryListener {
|
||||
|
||||
@Override
|
||||
public <T, E extends Throwable> boolean open(RetryContext context,
|
||||
|
||||
+37
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.openfeign.support;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -23,6 +25,8 @@ import org.junit.runner.RunWith;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties.Hc5Properties.PoolConcurrencyPolicy;
|
||||
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties.Hc5Properties.PoolReusePolicy;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -30,9 +34,12 @@ import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.cloud.openfeign.support.FeignHttpClientProperties.Hc5Properties.DEFAULT_SOCKET_TIMEOUT;
|
||||
import static org.springframework.cloud.openfeign.support.FeignHttpClientProperties.Hc5Properties.DEFAULT_SOCKET_TIMEOUT_UNIT;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
* @author Nguyen Ky Thanh
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@DirtiesContext
|
||||
@@ -62,16 +69,32 @@ public class FeignHttpClientPropertiesTests {
|
||||
.isEqualTo(FeignHttpClientProperties.DEFAULT_DISABLE_SSL_VALIDATION);
|
||||
assertThat(getProperties().isFollowRedirects())
|
||||
.isEqualTo(FeignHttpClientProperties.DEFAULT_FOLLOW_REDIRECTS);
|
||||
assertThat(getProperties().getHc5().getPoolConcurrencyPolicy())
|
||||
.isEqualTo(PoolConcurrencyPolicy.STRICT);
|
||||
assertThat(getProperties().getHc5().getPoolReusePolicy())
|
||||
.isEqualTo(PoolReusePolicy.FIFO);
|
||||
assertThat(getProperties().getHc5().getSocketTimeout())
|
||||
.isEqualTo(DEFAULT_SOCKET_TIMEOUT);
|
||||
assertThat(getProperties().getHc5().getSocketTimeoutUnit())
|
||||
.isEqualTo(DEFAULT_SOCKET_TIMEOUT_UNIT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomization() {
|
||||
TestPropertyValues.of("feign.httpclient.maxConnections=2",
|
||||
"feign.httpclient.connectionTimeout=2",
|
||||
"feign.httpclient.maxConnectionsPerRoute=2",
|
||||
"feign.httpclient.timeToLive=2",
|
||||
"feign.httpclient.disableSslValidation=true",
|
||||
"feign.httpclient.followRedirects=false").applyTo(this.context);
|
||||
TestPropertyValues
|
||||
.of("feign.httpclient.maxConnections=2",
|
||||
"feign.httpclient.connectionTimeout=2",
|
||||
"feign.httpclient.maxConnectionsPerRoute=2",
|
||||
"feign.httpclient.timeToLive=2",
|
||||
"feign.httpclient.disableSslValidation=true",
|
||||
"feign.httpclient.followRedirects=false",
|
||||
"feign.httpclient.disableSslValidation=true",
|
||||
"feign.httpclient.followRedirects=false",
|
||||
"feign.httpclient.hc5.poolConcurrencyPolicy=lax",
|
||||
"feign.httpclient.hc5.poolReusePolicy=lifo",
|
||||
"feign.httpclient.hc5.socketTimeout=200",
|
||||
"feign.httpclient.hc5.socketTimeoutUnit=milliseconds")
|
||||
.applyTo(this.context);
|
||||
setupContext();
|
||||
assertThat(getProperties().getMaxConnections()).isEqualTo(2);
|
||||
assertThat(getProperties().getConnectionTimeout()).isEqualTo(2);
|
||||
@@ -79,6 +102,13 @@ public class FeignHttpClientPropertiesTests {
|
||||
assertThat(getProperties().getTimeToLive()).isEqualTo(2L);
|
||||
assertThat(getProperties().isDisableSslValidation()).isTrue();
|
||||
assertThat(getProperties().isFollowRedirects()).isFalse();
|
||||
assertThat(getProperties().getHc5().getPoolConcurrencyPolicy())
|
||||
.isEqualTo(PoolConcurrencyPolicy.LAX);
|
||||
assertThat(getProperties().getHc5().getPoolReusePolicy())
|
||||
.isEqualTo(PoolReusePolicy.LIFO);
|
||||
assertThat(getProperties().getHc5().getSocketTimeout()).isEqualTo(200);
|
||||
assertThat(getProperties().getHc5().getSocketTimeoutUnit())
|
||||
.isEqualTo(TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void setupContext() {
|
||||
|
||||
+52
@@ -36,9 +36,11 @@ import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.cloud.openfeign.FeignContext;
|
||||
import org.springframework.cloud.openfeign.encoding.HttpEncoding;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -47,6 +49,7 @@ import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.http.converter.ResourceHttpMessageConverter;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@@ -57,13 +60,16 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.http.HttpHeaders.ACCEPT;
|
||||
import static org.springframework.http.HttpHeaders.CONTENT_LENGTH;
|
||||
import static org.springframework.http.HttpHeaders.CONTENT_TYPE;
|
||||
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
|
||||
import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE;
|
||||
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
|
||||
import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Ahmad Mozafarnia
|
||||
* @author Can Bezmen
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = SpringEncoderTests.Application.class,
|
||||
@@ -79,6 +85,9 @@ public class SpringEncoderTests {
|
||||
@Qualifier("myHttpMessageConverter")
|
||||
private HttpMessageConverter<?> myConverter;
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("myGenericHttpMessageConverter")
|
||||
private GenericHttpMessageConverter<?> myGenericConverter;
|
||||
@@ -185,6 +194,44 @@ public class SpringEncoderTests {
|
||||
.as("Body content cannot be decoded").contains("hi");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromURLEncodedValue() {
|
||||
Encoder encoder = context.getInstance("formUrlEncoded", Encoder.class);
|
||||
assertThat(encoder).isNotNull();
|
||||
RequestTemplate request = new RequestTemplate();
|
||||
request.header(CONTENT_TYPE, APPLICATION_FORM_URLENCODED_VALUE);
|
||||
String body = "test";
|
||||
encoder.encode(body, String.class, request);
|
||||
assertThat(new String(request.requestBody().asBytes()))
|
||||
.as("Body content cannot be decoded").contains(body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoCharsetForBinaryFiles() {
|
||||
Encoder encoder = context.getInstance("test", Encoder.class);
|
||||
assertThat(encoder).isNotNull();
|
||||
RequestTemplate request = new RequestTemplate();
|
||||
request.header(CONTENT_TYPE, APPLICATION_OCTET_STREAM_VALUE);
|
||||
Resource resource = applicationContext.getResource("classpath:dummy.pdf");
|
||||
|
||||
encoder.encode(resource, Resource.class, request);
|
||||
|
||||
assertThat(request.requestBody().getEncoding()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUTF8CharsetForTextFiles() {
|
||||
Encoder encoder = context.getInstance("test", Encoder.class);
|
||||
assertThat(encoder).isNotNull();
|
||||
RequestTemplate request = new RequestTemplate();
|
||||
request.header(CONTENT_TYPE, TEXT_PLAIN_VALUE);
|
||||
String test = "test";
|
||||
|
||||
encoder.encode(test, String.class, request);
|
||||
|
||||
assertThat(request.requestBody().getEncoding().get().name()).isEqualTo("UTF-8");
|
||||
}
|
||||
|
||||
protected interface TestClient {
|
||||
|
||||
}
|
||||
@@ -213,6 +260,11 @@ public class SpringEncoderTests {
|
||||
return new MyHttpMessageConverter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ResourceHttpMessageConverter resourceHttpMessageConverter() {
|
||||
return new ResourceHttpMessageConverter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
GenericHttpMessageConverter<?> myGenericHttpMessageConverter() {
|
||||
return new MyGenericHttpMessageConverter();
|
||||
|
||||
+21
-59
@@ -62,6 +62,7 @@ import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY;
|
||||
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE;
|
||||
import static feign.CollectionFormat.SSV;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
/**
|
||||
@@ -182,18 +183,12 @@ public class SpringMvcContractTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessAnnotations_Class_AnnotationsGetSpecificTest()
|
||||
throws Exception {
|
||||
Method method = TestTemplate_Class_Annotations.class
|
||||
.getDeclaredMethod("getSpecificTest", String.class, String.class);
|
||||
MethodMetadata data = contract
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url()).isEqualTo("/prepend/{classId}/test/{testId}");
|
||||
assertThat(data.template().method()).isEqualTo("GET");
|
||||
|
||||
assertThat(data.indexToName().get(0).iterator().next()).isEqualTo("classId");
|
||||
assertThat(data.indexToName().get(1).iterator().next()).isEqualTo("testId");
|
||||
public void testProcessAnnotations_Class_Annotations_RequestMapping() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> {
|
||||
Method method = TestTemplate_Class_RequestMapping.class
|
||||
.getDeclaredMethod("getSpecificTest", String.class, String.class);
|
||||
contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -203,7 +198,7 @@ public class SpringMvcContractTests {
|
||||
MethodMetadata data = contract
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url()).isEqualTo("/prepend/{classId}");
|
||||
assertThat(data.template().url()).isEqualTo("/");
|
||||
assertThat(data.template().method()).isEqualTo("GET");
|
||||
|
||||
assertThat(data.indexToName().get(0).iterator().next()).isEqualTo("classId");
|
||||
@@ -211,22 +206,6 @@ public class SpringMvcContractTests {
|
||||
assertThat(data.template().decodeSlash()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessAnnotations_Class_AnnotationsGetAllTests_EncodeSlash()
|
||||
throws Exception {
|
||||
contract = new SpringMvcContract(Collections.emptyList(), getConversionService(),
|
||||
false);
|
||||
|
||||
Method method = TestTemplate_Class_Annotations.class
|
||||
.getDeclaredMethod("getAllTests", String.class);
|
||||
MethodMetadata data = contract
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url()).isEqualTo("/prepend/{classId}");
|
||||
|
||||
assertThat(data.template().decodeSlash()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessAnnotations_ExtendedInterface() throws Exception {
|
||||
Method extendedMethod = TestTemplate_Extended.class.getMethod("getAllTests",
|
||||
@@ -249,27 +228,6 @@ public class SpringMvcContractTests {
|
||||
assertThat(data.template().decodeSlash()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessAnnotations_ExtendedInterface_EncodeSlash() throws Exception {
|
||||
contract = new SpringMvcContract(Collections.emptyList(), getConversionService(),
|
||||
false);
|
||||
|
||||
Method extendedMethod = TestTemplate_Extended.class.getMethod("getAllTests",
|
||||
String.class);
|
||||
MethodMetadata extendedData = contract.parseAndValidateMetadata(
|
||||
extendedMethod.getDeclaringClass(), extendedMethod);
|
||||
|
||||
Method method = TestTemplate_Class_Annotations.class
|
||||
.getDeclaredMethod("getAllTests", String.class);
|
||||
MethodMetadata data = contract
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url()).isEqualTo(extendedData.template().url());
|
||||
assertThat(data.template().method()).isEqualTo(extendedData.template().method());
|
||||
|
||||
assertThat(data.template().decodeSlash()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessAnnotations_SimplePost() throws Exception {
|
||||
Method method = TestTemplate_Simple.class.getDeclaredMethod("postTest",
|
||||
@@ -305,8 +263,7 @@ public class SpringMvcContractTests {
|
||||
MethodMetadata data = contract
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url())
|
||||
.isEqualTo("/advanced/test/{id}?amount=" + "{amount}");
|
||||
assertThat(data.template().url()).isEqualTo("/test/{id}?amount=" + "{amount}");
|
||||
assertThat(data.template().method()).isEqualTo("PUT");
|
||||
assertThat(data.template().headers().get("Accept").iterator().next())
|
||||
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
@@ -341,8 +298,7 @@ public class SpringMvcContractTests {
|
||||
MethodMetadata data = contract
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url())
|
||||
.isEqualTo("/advanced/test/{id}?amount=" + "{amount}");
|
||||
assertThat(data.template().url()).isEqualTo("/test/{id}?amount=" + "{amount}");
|
||||
assertThat(data.template().method()).isEqualTo("PUT");
|
||||
assertThat(data.template().headers().get("Accept").iterator().next())
|
||||
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
@@ -366,8 +322,7 @@ public class SpringMvcContractTests {
|
||||
MethodMetadata data = contract
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url())
|
||||
.isEqualTo("/advanced/test2?amount=" + "{amount}");
|
||||
assertThat(data.template().url()).isEqualTo("/test2?amount=" + "{amount}");
|
||||
assertThat(data.template().method()).isEqualTo("PUT");
|
||||
assertThat(data.template().headers().get("Accept").iterator().next())
|
||||
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
@@ -429,7 +384,7 @@ public class SpringMvcContractTests {
|
||||
MethodMetadata data = contract
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url()).isEqualTo("/advanced");
|
||||
assertThat(data.template().url()).isEqualTo("/");
|
||||
assertThat(data.template().method()).isEqualTo("GET");
|
||||
assertThat(data.template().headers().get("Accept").iterator().next())
|
||||
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
@@ -539,7 +494,7 @@ public class SpringMvcContractTests {
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url())
|
||||
.isEqualTo("/advanced/testfallback/{id}?amount=" + "{amount}");
|
||||
.isEqualTo("/testfallback/{id}?amount=" + "{amount}");
|
||||
assertThat(data.template().method()).isEqualTo("PUT");
|
||||
assertThat(data.template().headers().get("Accept").iterator().next())
|
||||
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
@@ -708,6 +663,14 @@ public class SpringMvcContractTests {
|
||||
}
|
||||
|
||||
@RequestMapping("/prepend/{classId}")
|
||||
public interface TestTemplate_Class_RequestMapping {
|
||||
|
||||
@RequestMapping(value = "/test/{testId}", method = RequestMethod.GET)
|
||||
TestObject getSpecificTest(@PathVariable("classId") String classId,
|
||||
@PathVariable("testId") String testId);
|
||||
|
||||
}
|
||||
|
||||
public interface TestTemplate_Class_Annotations {
|
||||
|
||||
@RequestMapping(value = "/test/{testId}", method = RequestMethod.GET)
|
||||
@@ -812,7 +775,6 @@ public class SpringMvcContractTests {
|
||||
}
|
||||
|
||||
@JsonAutoDetect
|
||||
@RequestMapping("/advanced")
|
||||
public interface TestTemplate_Advanced {
|
||||
|
||||
@CollectionFormat(SSV)
|
||||
|
||||
+87
-79
@@ -17,7 +17,6 @@
|
||||
package org.springframework.cloud.openfeign.valid;
|
||||
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.text.ParseException;
|
||||
import java.time.LocalDate;
|
||||
@@ -49,7 +48,6 @@ import feign.Feign;
|
||||
import feign.Logger;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import feign.Target;
|
||||
import feign.codec.EncodeException;
|
||||
import feign.hystrix.FallbackFactory;
|
||||
import feign.hystrix.SetterFactory;
|
||||
@@ -91,6 +89,7 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
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.RequestMapping;
|
||||
@@ -102,6 +101,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.core.IsInstanceOf.instanceOf;
|
||||
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
@@ -110,6 +110,7 @@ import static org.hamcrest.core.IsInstanceOf.instanceOf;
|
||||
* @author Halvdan Hoem Grelland
|
||||
* @author Aaron Whiteside
|
||||
* @author Darren Foong
|
||||
* @author Can Bezmen
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = FeignClientTests.Application.class,
|
||||
@@ -178,23 +179,23 @@ public class FeignClientTests {
|
||||
|
||||
@Test
|
||||
public void testClient() {
|
||||
assertThat(this.testClient).as("testClient was null").isNotNull();
|
||||
assertThat(Proxy.isProxyClass(this.testClient.getClass()))
|
||||
assertThat(testClient).as("testClient was null").isNotNull();
|
||||
assertThat(Proxy.isProxyClass(testClient.getClass()))
|
||||
.as("testClient is not a java Proxy").isTrue();
|
||||
InvocationHandler invocationHandler = Proxy.getInvocationHandler(this.testClient);
|
||||
InvocationHandler invocationHandler = Proxy.getInvocationHandler(testClient);
|
||||
assertThat(invocationHandler).as("invocationHandler was null").isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestMappingClassLevelPropertyReplacement() {
|
||||
Hello hello = this.testClient.getHelloUsingPropertyPlaceHolder();
|
||||
Hello hello = testClient.getHelloUsingPropertyPlaceHolder();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello(OI_TERRA_2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleType() {
|
||||
Hello hello = this.testClient.getHello();
|
||||
Hello hello = testClient.getHello();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello(HELLO_WORLD_1));
|
||||
@@ -202,20 +203,20 @@ public class FeignClientTests {
|
||||
|
||||
@Test
|
||||
public void testOptional() {
|
||||
Optional<Hello> hello = this.testClient.getOptionalHello();
|
||||
Optional<Hello> hello = testClient.getOptionalHello();
|
||||
assertThat(hello).isNotNull().isPresent().contains(new Hello(HELLO_WORLD_1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenericType() {
|
||||
List<Hello> hellos = this.testClient.getHellos();
|
||||
List<Hello> hellos = testClient.getHellos();
|
||||
assertThat(hellos).as("hellos was null").isNotNull();
|
||||
assertThat(getHelloList()).as("hellos didn't match").isEqualTo(hellos);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestInterceptors() {
|
||||
List<String> headers = this.testClient.getHelloHeaders();
|
||||
List<String> headers = testClient.getHelloHeaders();
|
||||
assertThat(headers).as("headers was null").isNotNull();
|
||||
assertThat(headers.contains("myheader1value"))
|
||||
.as("headers didn't contain myheader1value").isTrue();
|
||||
@@ -225,24 +226,23 @@ public class FeignClientTests {
|
||||
|
||||
@Test
|
||||
public void testHeaderPlaceholders() {
|
||||
String header = this.testClient.getHelloHeadersPlaceholders();
|
||||
String header = testClient.getHelloHeadersPlaceholders();
|
||||
assertThat(header).as("header was null").isNotNull();
|
||||
assertThat(header).as("header was wrong").isEqualTo("myPlaceholderHeaderValue");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFeignClientType() throws IllegalAccessException {
|
||||
assertThat(this.feignClient).isInstanceOf(LoadBalancerFeignClient.class);
|
||||
LoadBalancerFeignClient client = (LoadBalancerFeignClient) this.feignClient;
|
||||
public void testFeignClientType() {
|
||||
assertThat(feignClient).isInstanceOf(LoadBalancerFeignClient.class);
|
||||
LoadBalancerFeignClient client = (LoadBalancerFeignClient) feignClient;
|
||||
Client delegate = client.getDelegate();
|
||||
assertThat(delegate).isInstanceOf(Client.Default.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServiceId() {
|
||||
assertThat(this.testClientServiceId).as("testClientServiceId was null")
|
||||
.isNotNull();
|
||||
final Hello hello = this.testClientServiceId.getHello();
|
||||
assertThat(testClientServiceId).as("testClientServiceId was null").isNotNull();
|
||||
final Hello hello = testClientServiceId.getHello();
|
||||
assertThat(hello).as("The hello response was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello(HELLO_WORLD_1));
|
||||
@@ -251,7 +251,7 @@ public class FeignClientTests {
|
||||
@Test
|
||||
public void testParams() {
|
||||
List<String> list = Arrays.asList("a", "1", "test");
|
||||
List<String> params = this.testClient.getParams(list);
|
||||
List<String> params = testClient.getParams(list);
|
||||
assertThat(params).as("params was null").isNotNull();
|
||||
assertThat(params.size()).as("params size was wrong").isEqualTo(list.size());
|
||||
}
|
||||
@@ -260,14 +260,14 @@ public class FeignClientTests {
|
||||
public void testFormattedParams() {
|
||||
List<LocalDate> list = Arrays.asList(LocalDate.of(2001, 1, 1),
|
||||
LocalDate.of(2018, 6, 10));
|
||||
List<LocalDate> params = this.testClient.getFormattedParams(list);
|
||||
List<LocalDate> params = testClient.getFormattedParams(list);
|
||||
assertThat(params).as("params was null").isNotNull();
|
||||
assertThat(params).as("params not converted correctly").isEqualTo(list);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHystrixCommand() throws NoSuchMethodException {
|
||||
HystrixCommand<List<Hello>> command = this.testClient.getHellosHystrix();
|
||||
HystrixCommand<List<Hello>> command = testClient.getHellosHystrix();
|
||||
assertThat(command).as("command was null").isNotNull();
|
||||
assertThat(command.getCommandGroup().name()).as(
|
||||
"Hystrix command group name should match the name of the feign client")
|
||||
@@ -284,7 +284,7 @@ public class FeignClientTests {
|
||||
|
||||
@Test
|
||||
public void testSingle() {
|
||||
Single<Hello> single = this.testClient.getHelloSingle();
|
||||
Single<Hello> single = testClient.getHelloSingle();
|
||||
assertThat(single).as("single was null").isNotNull();
|
||||
Hello hello = single.toBlocking().value();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
@@ -294,7 +294,7 @@ public class FeignClientTests {
|
||||
|
||||
@Test
|
||||
public void testNoContentResponse() {
|
||||
ResponseEntity<Void> response = this.testClient.noContent();
|
||||
ResponseEntity<Void> response = testClient.noContent();
|
||||
assertThat(response).as("response was null").isNotNull();
|
||||
assertThat(response.getStatusCode()).as("status code was wrong")
|
||||
.isEqualTo(HttpStatus.NO_CONTENT);
|
||||
@@ -302,7 +302,7 @@ public class FeignClientTests {
|
||||
|
||||
@Test
|
||||
public void testHeadResponse() {
|
||||
ResponseEntity<Void> response = this.testClient.head();
|
||||
ResponseEntity<Void> response = testClient.head();
|
||||
assertThat(response).as("response was null").isNotNull();
|
||||
assertThat(response.getStatusCode()).as("status code was wrong")
|
||||
.isEqualTo(HttpStatus.OK);
|
||||
@@ -310,7 +310,7 @@ public class FeignClientTests {
|
||||
|
||||
@Test
|
||||
public void testHttpEntity() {
|
||||
HttpEntity<Hello> entity = this.testClient.getHelloEntity();
|
||||
HttpEntity<Hello> entity = testClient.getHelloEntity();
|
||||
assertThat(entity).as("entity was null").isNotNull();
|
||||
Hello hello = entity.getBody();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
@@ -320,7 +320,7 @@ public class FeignClientTests {
|
||||
|
||||
@Test
|
||||
public void testMoreComplexHeader() {
|
||||
String response = this.testClient.moreComplexContentType("{\"value\":\"OK\"}");
|
||||
String response = testClient.moreComplexContentType("{\"value\":\"OK\"}");
|
||||
assertThat(response).as("response was null").isNotNull();
|
||||
assertThat(response).as("didn't respond with {\"value\":\"OK\"}")
|
||||
.isEqualTo("{\"value\":\"OK\"}");
|
||||
@@ -328,7 +328,7 @@ public class FeignClientTests {
|
||||
|
||||
@Test
|
||||
public void testDecodeNotFound() {
|
||||
ResponseEntity<String> response = this.decodingTestClient.notFound();
|
||||
ResponseEntity<String> response = decodingTestClient.notFound();
|
||||
assertThat(response).as("response was null").isNotNull();
|
||||
assertThat(response.getStatusCode()).as("status code was wrong")
|
||||
.isEqualTo(HttpStatus.NOT_FOUND);
|
||||
@@ -337,35 +337,35 @@ public class FeignClientTests {
|
||||
|
||||
@Test
|
||||
public void testOptionalNotFound() {
|
||||
Optional<String> s = this.decodingTestClient.optional();
|
||||
Optional<String> s = decodingTestClient.optional();
|
||||
assertThat(s).isNotPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertingExpander() {
|
||||
assertThat(this.testClient.getToString(Arg.A)).isEqualTo(Arg.A.toString());
|
||||
assertThat(this.testClient.getToString(Arg.B)).isEqualTo(Arg.B.toString());
|
||||
assertThat(testClient.getToString(Arg.A)).isEqualTo(Arg.A.toString());
|
||||
assertThat(testClient.getToString(Arg.B)).isEqualTo(Arg.B.toString());
|
||||
|
||||
assertThat(this.testClient.getToString(new OtherArg("foo"))).isEqualTo("bar");
|
||||
assertThat(testClient.getToString(new OtherArg("foo"))).isEqualTo("bar");
|
||||
List<OtherArg> args = new ArrayList<>();
|
||||
args.add(new OtherArg("foo"));
|
||||
args.add(new OtherArg("goo"));
|
||||
List<String> expectedResult = new ArrayList<>();
|
||||
expectedResult.add("bar");
|
||||
expectedResult.add("goo");
|
||||
assertThat(this.testClient.getToString(args)).isEqualTo(expectedResult);
|
||||
assertThat(testClient.getToString(args)).isEqualTo(expectedResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHystrixFallbackWorks() {
|
||||
Hello hello = this.hystrixClient.fail();
|
||||
Hello hello = hystrixClient.fail();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello.getMessage()).as("message was wrong").isEqualTo("fallback");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHystrixFallbackSingle() {
|
||||
Single<Hello> single = this.hystrixClient.failSingle();
|
||||
Single<Hello> single = hystrixClient.failSingle();
|
||||
assertThat(single).as("single was null").isNotNull();
|
||||
Hello hello = single.toBlocking().value();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
@@ -375,7 +375,7 @@ public class FeignClientTests {
|
||||
|
||||
@Test
|
||||
public void testHystrixFallbackCommand() {
|
||||
HystrixCommand<Hello> command = this.hystrixClient.failCommand();
|
||||
HystrixCommand<Hello> command = hystrixClient.failCommand();
|
||||
assertThat(command).as("command was null").isNotNull();
|
||||
Hello hello = command.execute();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
@@ -385,7 +385,7 @@ public class FeignClientTests {
|
||||
|
||||
@Test
|
||||
public void testHystrixFallbackObservable() {
|
||||
Observable<Hello> observable = this.hystrixClient.failObservable();
|
||||
Observable<Hello> observable = hystrixClient.failObservable();
|
||||
assertThat(observable).as("observable was null").isNotNull();
|
||||
Hello hello = observable.toBlocking().first();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
@@ -395,7 +395,7 @@ public class FeignClientTests {
|
||||
|
||||
@Test
|
||||
public void testHystrixFallbackFuture() throws Exception {
|
||||
Future<Hello> future = this.hystrixClient.failFuture();
|
||||
Future<Hello> future = hystrixClient.failFuture();
|
||||
assertThat(future).as("future was null").isNotNull();
|
||||
Hello hello = future.get(1, TimeUnit.SECONDS);
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
@@ -405,7 +405,7 @@ public class FeignClientTests {
|
||||
|
||||
@Test
|
||||
public void testHystrixClientWithFallBackFactory() throws Exception {
|
||||
Hello hello = this.hystrixClientWithFallBackFactory.fail();
|
||||
Hello hello = hystrixClientWithFallBackFactory.fail();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello.getMessage()).as("hello#message was null").isNotNull();
|
||||
assertThat(hello.getMessage().contains("500")).as(
|
||||
@@ -415,22 +415,29 @@ public class FeignClientTests {
|
||||
|
||||
@Test(expected = HystrixRuntimeException.class)
|
||||
public void testInvalidTypeHystrixFallbackFactory() throws Exception {
|
||||
this.invalidTypeHystrixClientWithFallBackFactory.fail();
|
||||
invalidTypeHystrixClientWithFallBackFactory.fail();
|
||||
}
|
||||
|
||||
@Test(expected = HystrixRuntimeException.class)
|
||||
public void testNullHystrixFallbackFactory() throws Exception {
|
||||
this.nullHystrixClientWithFallBackFactory.fail();
|
||||
nullHystrixClientWithFallBackFactory.fail();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormURLEncoded() {
|
||||
Hello hello = new Hello(HELLO_WORLD_1);
|
||||
Hello response = testClient.postFormUrlEncoded(hello);
|
||||
assertThat(response).isEqualTo(hello);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void namedFeignClientWorks() {
|
||||
assertThat(this.namedHystrixClient).as("namedHystrixClient was null").isNotNull();
|
||||
assertThat(namedHystrixClient).as("namedHystrixClient was null").isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHystrixSetterFactory() {
|
||||
HystrixCommand<List<Hello>> command = this.hystrixSetterFactoryClient
|
||||
HystrixCommand<List<Hello>> command = hystrixSetterFactoryClient
|
||||
.getHellosHystrix();
|
||||
assertThat(command).as("command was null").isNotNull();
|
||||
String setterPrefix = TestHystrixSetterFactoryClientConfig.SETTER_PREFIX;
|
||||
@@ -449,13 +456,13 @@ public class FeignClientTests {
|
||||
|
||||
@Test
|
||||
public void testSingleRequestPart() {
|
||||
String response = this.multipartClient.singlePart("abc");
|
||||
String response = multipartClient.singlePart("abc");
|
||||
assertThat(response).isEqualTo("abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSinglePojoRequestPart() {
|
||||
String response = this.multipartClient.singlePojoPart(new Hello(HELLO_WORLD_1));
|
||||
String response = multipartClient.singlePojoPart(new Hello(HELLO_WORLD_1));
|
||||
assertThat(response).isEqualTo(HELLO_WORLD_1);
|
||||
}
|
||||
|
||||
@@ -463,7 +470,7 @@ public class FeignClientTests {
|
||||
public void testMultipleRequestParts() {
|
||||
MockMultipartFile file = new MockMultipartFile("file", "hello.bin", null,
|
||||
"hello".getBytes());
|
||||
String response = this.multipartClient.multipart("abc", "123", file);
|
||||
String response = multipartClient.multipart("abc", "123", file);
|
||||
assertThat(response).isEqualTo("abc123hello.bin");
|
||||
}
|
||||
|
||||
@@ -473,8 +480,7 @@ public class FeignClientTests {
|
||||
Hello pojo2 = new Hello(OI_TERRA_2);
|
||||
MockMultipartFile file = new MockMultipartFile("file", "hello.bin", null,
|
||||
"hello".getBytes());
|
||||
String response = this.multipartClient.multipartPojo("abc", "123", pojo1, pojo2,
|
||||
file);
|
||||
String response = multipartClient.multipartPojo("abc", "123", pojo1, pojo2, file);
|
||||
assertThat(response).isEqualTo("abc123hello world 1oi terra 2hello.bin");
|
||||
}
|
||||
|
||||
@@ -483,10 +489,10 @@ public class FeignClientTests {
|
||||
List<MultipartFile> multipartFiles = Arrays.asList(
|
||||
new MockMultipartFile("file1", "hello1.bin", null, "hello".getBytes()),
|
||||
new MockMultipartFile("file2", "hello2.bin", null, "hello".getBytes()));
|
||||
String partNames = this.multipartClient
|
||||
String partNames = multipartClient
|
||||
.requestPartListOfMultipartFilesReturnsPartNames(multipartFiles);
|
||||
assertThat(partNames).isEqualTo("files,files");
|
||||
String fileNames = this.multipartClient
|
||||
String fileNames = multipartClient
|
||||
.requestPartListOfMultipartFilesReturnsFileNames(multipartFiles);
|
||||
assertThat(fileNames).contains("hello1.bin", "hello2.bin");
|
||||
}
|
||||
@@ -499,9 +505,8 @@ public class FeignClientTests {
|
||||
"hello".getBytes());
|
||||
MockMultipartFile file2 = new MockMultipartFile("file2", "hello2.bin", null,
|
||||
"hello".getBytes());
|
||||
String response = this.multipartClient
|
||||
.requestPartListOfPojosAndListOfMultipartFiles(
|
||||
Arrays.asList(pojo1, pojo2), Arrays.asList(file1, file2));
|
||||
String response = multipartClient.requestPartListOfPojosAndListOfMultipartFiles(
|
||||
Arrays.asList(pojo1, pojo2), Arrays.asList(file1, file2));
|
||||
assertThat(response).isEqualTo("hello world 1oi terra 2hello1.binhello2.bin");
|
||||
}
|
||||
|
||||
@@ -510,7 +515,7 @@ public class FeignClientTests {
|
||||
String partName = UUID.randomUUID().toString();
|
||||
MockMultipartFile file1 = new MockMultipartFile(partName, "hello1.bin", null,
|
||||
"hello".getBytes());
|
||||
String response = this.multipartClient.requestBodySingleMultipartFile(file1);
|
||||
String response = multipartClient.requestBodySingleMultipartFile(file1);
|
||||
assertThat(response).isEqualTo(partName);
|
||||
}
|
||||
|
||||
@@ -520,7 +525,7 @@ public class FeignClientTests {
|
||||
"hello".getBytes());
|
||||
MockMultipartFile file2 = new MockMultipartFile("file2", "hello2.bin", null,
|
||||
"hello".getBytes());
|
||||
String response = this.multipartClient
|
||||
String response = multipartClient
|
||||
.requestBodyListOfMultipartFiles(Arrays.asList(file1, file2));
|
||||
assertThat(response).contains("file1", "file2");
|
||||
}
|
||||
@@ -535,7 +540,7 @@ public class FeignClientTests {
|
||||
form.put("file1", file1);
|
||||
form.put("file2", file2);
|
||||
form.put("hello", "world");
|
||||
String response = this.multipartClient.requestBodyMap(form);
|
||||
String response = multipartClient.requestBodyMap(form);
|
||||
assertThat(response).contains("file1", "file2", "hello");
|
||||
}
|
||||
|
||||
@@ -544,7 +549,7 @@ public class FeignClientTests {
|
||||
MockMultipartFile file = new MockMultipartFile("file1", "hello1.bin", null,
|
||||
"hello".getBytes());
|
||||
expected.expectCause(instanceOf(EncodeException.class));
|
||||
this.multipartClient.invalid(file);
|
||||
multipartClient.invalid(file);
|
||||
}
|
||||
|
||||
protected enum Arg {
|
||||
@@ -615,6 +620,10 @@ public class FeignClientTests {
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/tostring")
|
||||
String getToString(@RequestParam("arg") Arg arg);
|
||||
|
||||
@PostMapping(path = "/form-urlencoded",
|
||||
consumes = APPLICATION_FORM_URLENCODED_VALUE)
|
||||
Hello postFormUrlEncoded(Hello hello);
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/tostring2")
|
||||
String getToString(@RequestParam("arg") OtherArg arg);
|
||||
|
||||
@@ -776,7 +785,7 @@ public class FeignClientTests {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.value;
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -810,13 +819,9 @@ public class FeignClientTests {
|
||||
|
||||
@Override
|
||||
public HystrixClientWithFallBackFactory create(final Throwable cause) {
|
||||
return new HystrixClientWithFallBackFactory() {
|
||||
@Override
|
||||
public Hello fail() {
|
||||
assertThat(cause).isNotNull().as("Cause was null");
|
||||
return new Hello(
|
||||
"Hello from the fallback side: " + cause.getMessage());
|
||||
}
|
||||
return () -> {
|
||||
assertThat(cause).isNotNull().as("Cause was null");
|
||||
return new Hello("Hello from the fallback side: " + cause.getMessage());
|
||||
};
|
||||
}
|
||||
|
||||
@@ -876,18 +881,15 @@ public class FeignClientTests {
|
||||
|
||||
@Bean
|
||||
public SetterFactory commandKeyIsRequestLineSetterFactory() {
|
||||
return new SetterFactory() {
|
||||
@Override
|
||||
public HystrixCommand.Setter create(Target<?> target, Method method) {
|
||||
String groupKey = SETTER_PREFIX + target.name();
|
||||
RequestMapping requestMapping = method
|
||||
.getAnnotation(RequestMapping.class);
|
||||
String commandKey = SETTER_PREFIX + requestMapping.method()[0] + " "
|
||||
+ requestMapping.path()[0];
|
||||
return HystrixCommand.Setter
|
||||
.withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
|
||||
.andCommandKey(HystrixCommandKey.Factory.asKey(commandKey));
|
||||
}
|
||||
return (target, method) -> {
|
||||
String groupKey = SETTER_PREFIX + target.name();
|
||||
RequestMapping requestMapping = method
|
||||
.getAnnotation(RequestMapping.class);
|
||||
String commandKey = SETTER_PREFIX + requestMapping.method()[0] + " "
|
||||
+ requestMapping.path()[0];
|
||||
return HystrixCommand.Setter
|
||||
.withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
|
||||
.andCommandKey(HystrixCommandKey.Factory.asKey(commandKey));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1154,6 +1156,12 @@ public class FeignClientTests {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@PostMapping(path = "/form-urlencoded",
|
||||
consumes = APPLICATION_FORM_URLENCODED_VALUE)
|
||||
Hello postFormUrlEncoded(Hello hello) {
|
||||
return hello;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Hello {
|
||||
@@ -1168,7 +1176,7 @@ public class FeignClientTests {
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return this.message;
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
@@ -1184,12 +1192,12 @@ public class FeignClientTests {
|
||||
return false;
|
||||
}
|
||||
Hello that = (Hello) o;
|
||||
return Objects.equals(this.message, that.message);
|
||||
return Objects.equals(message, that.message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.message);
|
||||
return Objects.hash(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1213,7 +1221,7 @@ public class FeignClientTests {
|
||||
|
||||
@Bean
|
||||
public ServerList<Server> ribbonServerList() {
|
||||
return new StaticServerList<>(new Server("localhost", this.port));
|
||||
return new StaticServerList<>(new Server("localhost", port));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -22,3 +22,4 @@ feign.client.config.unwrap.readTimeout=1000
|
||||
feign.client.config.unwrap.exceptionPropagationPolicy=unwrap
|
||||
feign.client.config.readTimeout.readTimeout=1000
|
||||
feign.client.config.connectTimeout.connectTimeout=1000
|
||||
feign.client.config.default.followRedirects=false
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
<parent>
|
||||
<artifactId>spring-cloud-dependencies-parent</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>2.3.2.RELEASE</version>
|
||||
<version>2.3.6.BUILD-SNAPSHOT</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-openfeign-dependencies</artifactId>
|
||||
<version>2.2.7.RELEASE</version>
|
||||
<version>2.2.11.BUILD-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>spring-cloud-openfeign-dependencies</name>
|
||||
<description>Spring Cloud OpenFeign Dependencies</description>
|
||||
<properties>
|
||||
<feign.version>10.10.1</feign.version>
|
||||
<feign.version>10.12</feign.version>
|
||||
<feign-form.version>3.8.0</feign-form.version>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>2.2.7.RELEASE</version>
|
||||
<version>2.2.11.BUILD-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user