Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2c473db2a | ||
|
|
ae33802a4b | ||
|
|
c761919bc9 | ||
|
|
998baed0d3 | ||
|
|
4fc80e6b01 | ||
|
|
c101fd2c6e | ||
|
|
71930fd9f0 | ||
|
|
b5283e2f88 | ||
|
|
6aeb1ff676 | ||
|
|
44c63798b0 | ||
|
|
769b38c5c1 | ||
|
|
3c402201ec | ||
|
|
deb150ed88 | ||
|
|
6de9fdd3e1 | ||
|
|
df1f982046 | ||
|
|
baf7cf95f2 | ||
|
|
45861dd657 | ||
|
|
00ea88c67f | ||
|
|
d897b18e16 | ||
|
|
8c08204c78 | ||
|
|
1f5bdd4224 | ||
|
|
2f43715f6f | ||
|
|
280291ec93 | ||
|
|
0de9dd614d | ||
|
|
93fcf38c24 | ||
|
|
be929043c3 | ||
|
|
c7316b008a | ||
|
|
989616ebe6 | ||
|
|
58b0c7866c | ||
|
|
421df91a80 | ||
|
|
5aa1b7a529 | ||
|
|
ec1f2ad5bb | ||
|
|
fbcebb372c | ||
|
|
1a00bbf705 | ||
|
|
5baa8c8ed5 | ||
|
|
d0e035b8ff | ||
|
|
1615a5a68c | ||
|
|
0c1ab36fe1 | ||
|
|
0684ee4bc2 | ||
|
|
0b34d89adc | ||
|
|
c33bc490cd | ||
|
|
d738b3b224 | ||
|
|
0c1d281970 | ||
|
|
26fb4deb18 | ||
|
|
0bf0426f60 | ||
|
|
9f9ce39789 | ||
|
|
4454c820cf | ||
|
|
b8e966fe14 | ||
|
|
e47255e79d | ||
|
|
96268d16da | ||
|
|
ac019b4af1 | ||
|
|
4d0caec7ee | ||
|
|
b8aea256da | ||
|
|
f653a6730d | ||
|
|
92f7ba9901 | ||
|
|
b2c208ba5f | ||
|
|
e2a54c1359 | ||
|
|
cdfd3695c2 | ||
|
|
a5412cc872 | ||
|
|
59216f2f82 | ||
|
|
aa01ccb5b2 | ||
|
|
4d827623f0 | ||
|
|
86713156f6 | ||
|
|
5e494aa196 | ||
|
|
9db146761a | ||
|
|
af418a04ac | ||
|
|
bcea218513 | ||
|
|
1c49f88e2b | ||
|
|
3dbaf056ec | ||
|
|
5637465ec8 |
@@ -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,38 @@
|
||||
# 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: [ 3.0.x ]
|
||||
pull_request:
|
||||
branches: [ 3.0.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 -P sonar
|
||||
- uses: codecov/codecov-action@v1
|
||||
with:
|
||||
fail_ci_if_error: true
|
||||
+3
-3
@@ -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=main&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"]
|
||||
|
||||
@@ -319,4 +319,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>3.0.2</version>
|
||||
<version>3.0.4</version>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-openfeign-docs</artifactId>
|
||||
<packaging>jar</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=main&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].
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
|
||||
|feign.autoconfiguration.jackson.enabled | `false` | If true, PageJacksonModule and SortJacksonModule bean will be provided for Jackson page decoding.
|
||||
|feign.circuitbreaker.enabled | `false` | If true, an OpenFeign client will be wrapped with a Spring Cloud CircuitBreaker circuit breaker.
|
||||
|feign.circuitbreaker.group.enabled | `false` | If true, an OpenFeign client will be wrapped with a Spring Cloud CircuitBreaker circuit breaker with with group.
|
||||
|feign.client.config | |
|
||||
|feign.client.decode-slash | `true` | Feign clients do not encode slash `/` characters by default. To change this behavior, set the `decodeSlash` to `false`.
|
||||
|feign.client.default-config | `default` |
|
||||
|feign.client.default-to-properties | `true` |
|
||||
|feign.client.refresh-enabled | `false` | Enables options value refresh capability for Feign.
|
||||
|feign.compression.request.enabled | `false` | Enables the request sent by Feign to be compressed.
|
||||
|feign.compression.request.mime-types | `[text/xml, application/xml, application/json]` | The list of supported mime types.
|
||||
|feign.compression.request.min-request-size | `2048` | The minimum threshold content size.
|
||||
|
||||
@@ -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,7 +55,7 @@ public interface StoreClient {
|
||||
}
|
||||
----
|
||||
|
||||
In the `@FeignClient` annotation the String value ("stores" above) is an arbitrary client name, which is used to create a https://github.com/spring-cloud/spring-cloud-commons/blob/master/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/client/BlockingLoadBalancerClient.java[Spring Cloud LoadBalancer client].
|
||||
In the `@FeignClient` annotation the String value ("stores" above) is an arbitrary client name, which is used to create a https://github.com/spring-cloud/spring-cloud-commons/blob/main/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/client/BlockingLoadBalancerClient.java[Spring Cloud LoadBalancer client].
|
||||
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.
|
||||
@@ -66,10 +66,15 @@ The load-balancer client above will want to discover the physical addresses
|
||||
for the "stores" service. If your application is a Eureka client then
|
||||
it will resolve the service in the Eureka service registry. If you
|
||||
don't want to use Eureka, you can configure a list of servers
|
||||
in your external configuration using https://cloud.spring.io/spring-cloud-static/spring-cloud-commons/current/reference/html/#simplediscoveryclient[`SimpleDiscoveryClient`].
|
||||
in your external configuration using https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#simplediscoveryclient[`SimpleDiscoveryClient`].
|
||||
|
||||
Spring Cloud OpenFeign supports all the features available for the blocking mode of Spring Cloud LoadBalancer. You can read more about them in the https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#spring-cloud-loadbalancer[project documentation].
|
||||
|
||||
TIP: To use `@EnableFeignClients` annotation on `@Configuration`-annotated-classes, make sure to specify where the clients are located, for example:
|
||||
`@EnableFeignClients(basePackages = "com.example.clients")`
|
||||
or list them explicitly:
|
||||
`@EnableFeignClients(clients = InventoryServiceFeignClient.class)`
|
||||
|
||||
[[spring-cloud-feign-overriding-defaults]]
|
||||
=== Overriding Feign Defaults
|
||||
|
||||
@@ -343,12 +348,30 @@ 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 `<feignClientClassName>#<calledMethod>(<parameterTypes>)`. When calling a `@FeignClient` with `FooClient` interface and the called interface method that has no parameters is `bar` then the circuit breaker name will be `FooClient#bar()`.
|
||||
|
||||
NOTE: As of 2020.0.2, the circuit breaker name pattern has changed from `<feignClientName>_<calledMethod>`.
|
||||
Using `CircuitBreakerNameResolver` introduced in 2020.0.4, circuit breaker names can retain the old pattern.
|
||||
|
||||
Providing a bean of `CircuitBreakerNameResolver`, you can change the circuit breaker name pattern.
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@Configuration
|
||||
public class FooConfiguration {
|
||||
@Bean
|
||||
public CircuitBreakerNameResolver circuitBreakerNameResolver() {
|
||||
return (String feignClientName, Target<?> target, Method method) -> feignClientName + "_" + method.getName();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
To enable Spring Cloud CircuitBreaker group set the `feign.circuitbreaker.group.enabled` property to `true` (by default `false`).
|
||||
|
||||
|
||||
[[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]
|
||||
----
|
||||
@@ -469,9 +492,9 @@ 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).
|
||||
server and a client. It introduces tight coupling, and is also not supported by
|
||||
all the maintained Spring MVC versions (method parameter
|
||||
mapping is not inherited in some versions).
|
||||
|
||||
=== Feign request/response compression
|
||||
|
||||
@@ -717,6 +740,17 @@ You may consider enabling Jackson Modules for the support `org.springframework.d
|
||||
feign.autoconfiguration.jackson.enabled=true
|
||||
----
|
||||
|
||||
=== Spring `@RefreshScope` Support
|
||||
If Feign client refresh is enabled, each feign client is created with `feign.Request.Options` as a refresh-scoped bean. This means properties such as `connectTimeout` and `readTimeout` can be refreshed against any Feign client instance through `POST /actuator/refresh`.
|
||||
|
||||
By default, refresh behavior in Feign clients is disabled. Use the following property to enable refresh behavior:
|
||||
[source,java]
|
||||
----
|
||||
feign.client.refresh-enabled=true
|
||||
----
|
||||
TIP: DO NOT annotate the `@FeignClient` interface with the `@RefreshScope` annotation.
|
||||
|
||||
|
||||
== Configuration properties
|
||||
|
||||
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>3.0.2</version>
|
||||
<version>3.0.4</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>3.0.2</version>
|
||||
<version>3.0.4</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<scm>
|
||||
@@ -26,7 +26,7 @@
|
||||
<properties>
|
||||
<main.basedir>${basedir}</main.basedir>
|
||||
<jackson.version>2.11.3</jackson.version>
|
||||
<spring-cloud-commons.version>3.0.2</spring-cloud-commons.version>
|
||||
<spring-cloud-commons.version>3.0.4</spring-cloud-commons.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>3.0.2</version>
|
||||
<version>3.0.4</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-openfeign-core</artifactId>
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.Method;
|
||||
|
||||
import feign.Target;
|
||||
|
||||
/**
|
||||
* Used to resolve a circuitbreaker name which will be used in
|
||||
* {@link org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory}.
|
||||
*
|
||||
* @author Kwangyong Kim
|
||||
* @since 2020.0.4
|
||||
*/
|
||||
public interface CircuitBreakerNameResolver {
|
||||
|
||||
String resolveCircuitBreakerName(String feignClientName, Target<?> target, Method method);
|
||||
|
||||
}
|
||||
+25
-2
@@ -17,6 +17,7 @@
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
@@ -29,6 +30,7 @@ import com.fasterxml.jackson.databind.Module;
|
||||
import feign.Client;
|
||||
import feign.Feign;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.Target;
|
||||
import feign.hc5.ApacheHttp5Client;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
import feign.okhttp.OkHttpClient;
|
||||
@@ -42,6 +44,7 @@ import org.apache.http.conn.HttpClientConnectionManager;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
@@ -77,6 +80,8 @@ import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResour
|
||||
* @author Tim Peeters
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Nguyen Ky Thanh
|
||||
* @author Andrii Bohutskyi
|
||||
* @author Kwangyong Kim
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(Feign.class)
|
||||
@@ -144,11 +149,29 @@ public class FeignAutoConfiguration {
|
||||
return new DefaultTargeter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(CircuitBreakerNameResolver.class)
|
||||
public CircuitBreakerNameResolver circuitBreakerNameResolver() {
|
||||
return new DefaultCircuitBreakerNameResolver();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnBean(CircuitBreakerFactory.class)
|
||||
public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory) {
|
||||
return new FeignCircuitBreakerTargeter(circuitBreakerFactory);
|
||||
public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory,
|
||||
@Value("${feign.circuitbreaker.group.enabled:false}") boolean circuitBreakerGroupEnabled,
|
||||
CircuitBreakerNameResolver circuitBreakerNameResolver) {
|
||||
return new FeignCircuitBreakerTargeter(circuitBreakerFactory, circuitBreakerGroupEnabled,
|
||||
circuitBreakerNameResolver);
|
||||
}
|
||||
|
||||
static class DefaultCircuitBreakerNameResolver implements CircuitBreakerNameResolver {
|
||||
|
||||
@Override
|
||||
public String resolveCircuitBreakerName(String feignClientName, Target<?> target, Method method) {
|
||||
return Feign.configKey(target.getClass(), method);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+18
-1
@@ -26,6 +26,8 @@ import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
|
||||
* Allows Feign interfaces to work with {@link CircuitBreaker}.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Andrii Bohutskyi
|
||||
* @author Kwangyong Kim
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public final class FeignCircuitBreaker {
|
||||
@@ -50,6 +52,10 @@ public final class FeignCircuitBreaker {
|
||||
|
||||
private String feignClientName;
|
||||
|
||||
private boolean circuitBreakerGroupEnabled;
|
||||
|
||||
private CircuitBreakerNameResolver circuitBreakerNameResolver;
|
||||
|
||||
Builder circuitBreakerFactory(CircuitBreakerFactory circuitBreakerFactory) {
|
||||
this.circuitBreakerFactory = circuitBreakerFactory;
|
||||
return this;
|
||||
@@ -60,6 +66,16 @@ public final class FeignCircuitBreaker {
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder circuitBreakerGroupEnabled(boolean circuitBreakerGroupEnabled) {
|
||||
this.circuitBreakerGroupEnabled = circuitBreakerGroupEnabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder circuitBreakerNameResolver(CircuitBreakerNameResolver circuitBreakerNameResolver) {
|
||||
this.circuitBreakerNameResolver = circuitBreakerNameResolver;
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T> T target(Target<T> target, T fallback) {
|
||||
return build(fallback != null ? new FallbackFactory.Default<T>(fallback) : null).newInstance(target);
|
||||
}
|
||||
@@ -75,7 +91,8 @@ public final class FeignCircuitBreaker {
|
||||
|
||||
public Feign build(final FallbackFactory<?> nullableFallbackFactory) {
|
||||
super.invocationHandlerFactory((target, dispatch) -> new FeignCircuitBreakerInvocationHandler(
|
||||
circuitBreakerFactory, target, dispatch, nullableFallbackFactory));
|
||||
circuitBreakerFactory, feignClientName, target, dispatch, nullableFallbackFactory,
|
||||
circuitBreakerGroupEnabled, circuitBreakerNameResolver));
|
||||
return super.build();
|
||||
}
|
||||
|
||||
|
||||
+21
-6
@@ -24,12 +24,13 @@ import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import feign.Feign;
|
||||
import feign.InvocationHandlerFactory;
|
||||
import feign.Target;
|
||||
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
|
||||
import static feign.Util.checkNotNull;
|
||||
|
||||
@@ -37,6 +38,8 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
|
||||
|
||||
private final CircuitBreakerFactory factory;
|
||||
|
||||
private final String feignClientName;
|
||||
|
||||
private final Target<?> target;
|
||||
|
||||
private final Map<Method, InvocationHandlerFactory.MethodHandler> dispatch;
|
||||
@@ -45,13 +48,21 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
|
||||
|
||||
private final Map<Method, Method> fallbackMethodMap;
|
||||
|
||||
FeignCircuitBreakerInvocationHandler(CircuitBreakerFactory factory, Target<?> target,
|
||||
Map<Method, InvocationHandlerFactory.MethodHandler> dispatch, FallbackFactory<?> nullableFallbackFactory) {
|
||||
private final boolean circuitBreakerGroupEnabled;
|
||||
|
||||
private final CircuitBreakerNameResolver circuitBreakerNameResolver;
|
||||
|
||||
FeignCircuitBreakerInvocationHandler(CircuitBreakerFactory factory, String feignClientName, Target<?> target,
|
||||
Map<Method, InvocationHandlerFactory.MethodHandler> dispatch, FallbackFactory<?> nullableFallbackFactory,
|
||||
boolean circuitBreakerGroupEnabled, CircuitBreakerNameResolver circuitBreakerNameResolver) {
|
||||
this.factory = factory;
|
||||
this.feignClientName = feignClientName;
|
||||
this.target = checkNotNull(target, "target");
|
||||
this.dispatch = checkNotNull(dispatch, "dispatch");
|
||||
this.fallbackMethodMap = toFallbackMethod(dispatch);
|
||||
this.nullableFallbackFactory = nullableFallbackFactory;
|
||||
this.circuitBreakerGroupEnabled = circuitBreakerGroupEnabled;
|
||||
this.circuitBreakerNameResolver = circuitBreakerNameResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,8 +84,10 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
|
||||
else if ("toString".equals(method.getName())) {
|
||||
return toString();
|
||||
}
|
||||
String circuitName = Feign.configKey(target.type(), method);
|
||||
CircuitBreaker circuitBreaker = this.factory.create(circuitName);
|
||||
|
||||
String circuitName = circuitBreakerNameResolver.resolveCircuitBreakerName(feignClientName, target, method);
|
||||
CircuitBreaker circuitBreaker = circuitBreakerGroupEnabled ? factory.create(circuitName, feignClientName)
|
||||
: factory.create(circuitName);
|
||||
Supplier<Object> supplier = asSupplier(method, args);
|
||||
if (this.nullableFallbackFactory != null) {
|
||||
Function<Throwable, Object> fallbackFunction = throwable -> {
|
||||
@@ -92,9 +105,11 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
|
||||
}
|
||||
|
||||
private Supplier<Object> asSupplier(final Method method, final Object[] args) {
|
||||
final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
|
||||
return () -> {
|
||||
try {
|
||||
return this.dispatch.get(method).invoke(args);
|
||||
RequestContextHolder.setRequestAttributes(requestAttributes);
|
||||
return dispatch.get(method).invoke(args);
|
||||
}
|
||||
catch (RuntimeException throwable) {
|
||||
throw throwable;
|
||||
|
||||
+11
-2
@@ -27,8 +27,15 @@ class FeignCircuitBreakerTargeter implements Targeter {
|
||||
|
||||
private final CircuitBreakerFactory circuitBreakerFactory;
|
||||
|
||||
FeignCircuitBreakerTargeter(CircuitBreakerFactory circuitBreakerFactory) {
|
||||
private final boolean circuitBreakerGroupEnabled;
|
||||
|
||||
private final CircuitBreakerNameResolver circuitBreakerNameResolver;
|
||||
|
||||
FeignCircuitBreakerTargeter(CircuitBreakerFactory circuitBreakerFactory, boolean circuitBreakerGroupEnabled,
|
||||
CircuitBreakerNameResolver circuitBreakerNameResolver) {
|
||||
this.circuitBreakerFactory = circuitBreakerFactory;
|
||||
this.circuitBreakerGroupEnabled = circuitBreakerGroupEnabled;
|
||||
this.circuitBreakerNameResolver = circuitBreakerNameResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -81,7 +88,9 @@ class FeignCircuitBreakerTargeter implements Targeter {
|
||||
}
|
||||
|
||||
private FeignCircuitBreaker.Builder builder(String feignClientName, FeignCircuitBreaker.Builder builder) {
|
||||
return builder.circuitBreakerFactory(this.circuitBreakerFactory).feignClientName(feignClientName);
|
||||
return builder.circuitBreakerFactory(circuitBreakerFactory).feignClientName(feignClientName)
|
||||
.circuitBreakerGroupEnabled(circuitBreakerGroupEnabled)
|
||||
.circuitBreakerNameResolver(circuitBreakerNameResolver);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+15
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import feign.Feign;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
@@ -26,6 +28,7 @@ import org.springframework.context.ApplicationContext;
|
||||
*
|
||||
* @author Sven Döring
|
||||
* @author Matt King
|
||||
* @author Sam Kruglov
|
||||
*/
|
||||
public class FeignClientBuilder {
|
||||
|
||||
@@ -76,6 +79,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;
|
||||
|
||||
+86
-16
@@ -17,6 +17,7 @@
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -36,6 +37,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;
|
||||
@@ -63,6 +66,8 @@ import org.springframework.util.StringUtils;
|
||||
* @author Ilia Ilinykh
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Jonatan Ivanov
|
||||
* @author Sam Kruglov
|
||||
* @author Jasbir Singh
|
||||
*/
|
||||
public class FeignClientFactoryBean
|
||||
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware, BeanFactoryAware {
|
||||
@@ -72,6 +77,8 @@ public class FeignClientFactoryBean
|
||||
* lifecycle race condition.
|
||||
***********************************/
|
||||
|
||||
private static Log LOG = LogFactory.getLog(FeignClientFactoryBean.class);
|
||||
|
||||
private Class<?> type;
|
||||
|
||||
private String name;
|
||||
@@ -100,6 +107,10 @@ public class FeignClientFactoryBean
|
||||
|
||||
private boolean followRedirects = new Request.Options().isFollowRedirects();
|
||||
|
||||
private boolean refreshableClient = false;
|
||||
|
||||
private final List<FeignBuilderCustomizer> additionalCustomizers = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
Assert.hasText(contextId, "Context id must be set");
|
||||
@@ -133,6 +144,7 @@ public class FeignClientFactoryBean
|
||||
customizerMap.values().stream().sorted(AnnotationAwareOrderComparator.INSTANCE)
|
||||
.forEach(feignBuilderCustomizer -> feignBuilderCustomizer.customize(builder));
|
||||
}
|
||||
additionalCustomizers.forEach(customizer -> customizer.customize(builder));
|
||||
}
|
||||
|
||||
protected void configureFeign(FeignContext context, Feign.Builder builder) {
|
||||
@@ -180,6 +192,10 @@ public class FeignClientFactoryBean
|
||||
}
|
||||
}
|
||||
Request.Options options = getInheritedAwareOptional(context, Request.Options.class);
|
||||
if (options == null) {
|
||||
options = getOptionsByName(context, contextId);
|
||||
}
|
||||
|
||||
if (options != null) {
|
||||
builder.options(options);
|
||||
readTimeoutMillis = options.readTimeoutMillis();
|
||||
@@ -223,12 +239,15 @@ public class FeignClientFactoryBean
|
||||
builder.logLevel(config.getLoggerLevel());
|
||||
}
|
||||
|
||||
connectTimeoutMillis = config.getConnectTimeout() != null ? config.getConnectTimeout() : connectTimeoutMillis;
|
||||
readTimeoutMillis = config.getReadTimeout() != null ? config.getReadTimeout() : readTimeoutMillis;
|
||||
followRedirects = config.isFollowRedirects() != null ? config.isFollowRedirects() : followRedirects;
|
||||
if (!refreshableClient) {
|
||||
connectTimeoutMillis = config.getConnectTimeout() != null ? 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, followRedirects));
|
||||
builder.options(new Request.Options(connectTimeoutMillis, TimeUnit.MILLISECONDS, readTimeoutMillis,
|
||||
TimeUnit.MILLISECONDS, followRedirects));
|
||||
}
|
||||
|
||||
if (config.getRetryer() != null) {
|
||||
Retryer retryer = getOrInstantiate(config.getRetryer());
|
||||
@@ -258,13 +277,8 @@ public class FeignClientFactoryBean
|
||||
builder.encoder(getOrInstantiate(config.getEncoder()));
|
||||
}
|
||||
|
||||
if (Objects.nonNull(config.getDefaultRequestHeaders())) {
|
||||
builder.requestInterceptor(requestTemplate -> requestTemplate.headers(config.getDefaultRequestHeaders()));
|
||||
}
|
||||
|
||||
if (Objects.nonNull(config.getDefaultQueryParameters())) {
|
||||
builder.requestInterceptor(requestTemplate -> requestTemplate.queries(config.getDefaultQueryParameters()));
|
||||
}
|
||||
addDefaultRequestHeaders(config, builder);
|
||||
addDefaultQueryParams(config, builder);
|
||||
|
||||
if (Objects.nonNull(config.getDecoder())) {
|
||||
builder.decoder(getOrInstantiate(config.getDecoder()));
|
||||
@@ -283,6 +297,35 @@ public class FeignClientFactoryBean
|
||||
}
|
||||
}
|
||||
|
||||
private void addDefaultQueryParams(FeignClientProperties.FeignClientConfiguration config, Feign.Builder builder) {
|
||||
Map<String, Collection<String>> defaultQueryParameters = config.getDefaultQueryParameters();
|
||||
if (Objects.nonNull(defaultQueryParameters)) {
|
||||
builder.requestInterceptor(requestTemplate -> {
|
||||
Map<String, Collection<String>> queries = requestTemplate.queries();
|
||||
defaultQueryParameters.keySet().forEach(key -> {
|
||||
if (!queries.containsKey(key)) {
|
||||
requestTemplate.query(key, defaultQueryParameters.get(key));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void addDefaultRequestHeaders(FeignClientProperties.FeignClientConfiguration config,
|
||||
Feign.Builder builder) {
|
||||
Map<String, Collection<String>> defaultRequestHeaders = config.getDefaultRequestHeaders();
|
||||
if (Objects.nonNull(defaultRequestHeaders)) {
|
||||
builder.requestInterceptor(requestTemplate -> {
|
||||
Map<String, Collection<String>> headers = requestTemplate.headers();
|
||||
defaultRequestHeaders.keySet().forEach(key -> {
|
||||
if (!headers.containsKey(key)) {
|
||||
requestTemplate.header(key, defaultRequestHeaders.get(key));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T getOrInstantiate(Class<T> tClass) {
|
||||
try {
|
||||
return beanFactory != null ? beanFactory.getBean(tClass) : applicationContext.getBean(tClass);
|
||||
@@ -334,6 +377,20 @@ public class FeignClientFactoryBean
|
||||
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?");
|
||||
}
|
||||
|
||||
/**
|
||||
* Meant to get Options bean from context with bean name.
|
||||
* @param context context of Feign client
|
||||
* @param contextId name of feign client
|
||||
* @return returns Options found in context
|
||||
*/
|
||||
protected Request.Options getOptionsByName(FeignContext context, String contextId) {
|
||||
if (refreshableClient) {
|
||||
return context.getInstance(contextId, Request.Options.class.getCanonicalName() + "-" + contextId,
|
||||
Request.Options.class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getObject() {
|
||||
return getTarget();
|
||||
@@ -350,6 +407,10 @@ public class FeignClientFactoryBean
|
||||
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;
|
||||
}
|
||||
@@ -460,6 +521,10 @@ public class FeignClientFactoryBean
|
||||
this.inheritParentContext = inheritParentContext;
|
||||
}
|
||||
|
||||
public void addCustomizer(FeignBuilderCustomizer customizer) {
|
||||
additionalCustomizers.add(customizer);
|
||||
}
|
||||
|
||||
public ApplicationContext getApplicationContext() {
|
||||
return applicationContext;
|
||||
}
|
||||
@@ -486,6 +551,10 @@ public class FeignClientFactoryBean
|
||||
this.fallbackFactory = fallbackFactory;
|
||||
}
|
||||
|
||||
public void setRefreshableClient(boolean refreshableClient) {
|
||||
this.refreshableClient = refreshableClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@@ -502,13 +571,14 @@ public class FeignClientFactoryBean
|
||||
&& Objects.equals(path, that.path) && 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);
|
||||
&& Objects.equals(followRedirects, that.followRedirects)
|
||||
&& Objects.equals(refreshableClient, that.refreshableClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(applicationContext, beanFactory, decode404, inheritParentContext, fallback, fallbackFactory,
|
||||
name, path, type, url, readTimeoutMillis, connectTimeoutMillis, followRedirects);
|
||||
name, path, type, url, readTimeoutMillis, connectTimeoutMillis, followRedirects, refreshableClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -520,8 +590,8 @@ public class FeignClientFactoryBean
|
||||
.append(", ").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();
|
||||
.append(readTimeoutMillis).append("}").append("followRedirects=").append(followRedirects)
|
||||
.append("refreshableClient=").append(refreshableClient).append("}").toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+31
@@ -29,6 +29,9 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import feign.Request;
|
||||
|
||||
import org.springframework.aop.scope.ScopedProxyUtils;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
|
||||
@@ -63,6 +66,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Michal Domagala
|
||||
* @author Marcin Grzejszczak
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Jasbir Singh
|
||||
*/
|
||||
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
|
||||
|
||||
@@ -213,6 +217,7 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
|
||||
factoryBean.setName(name);
|
||||
factoryBean.setContextId(contextId);
|
||||
factoryBean.setType(clazz);
|
||||
factoryBean.setRefreshableClient(isClientRefreshEnabled());
|
||||
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
|
||||
factoryBean.setUrl(getUrl(beanFactory, attributes));
|
||||
factoryBean.setPath(getPath(beanFactory, attributes));
|
||||
@@ -249,6 +254,8 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
|
||||
|
||||
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
|
||||
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
|
||||
|
||||
registerOptionsBeanDefinition(registry, contextId);
|
||||
}
|
||||
|
||||
private void validate(Map<String, Object> attributes) {
|
||||
@@ -408,4 +415,28 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is meant to create {@link Request.Options} beans definition with
|
||||
* refreshScope.
|
||||
* @param registry spring bean definition registry
|
||||
* @param contextId name of feign client
|
||||
*/
|
||||
private void registerOptionsBeanDefinition(BeanDefinitionRegistry registry, String contextId) {
|
||||
if (isClientRefreshEnabled()) {
|
||||
String beanName = Request.Options.class.getCanonicalName() + "-" + contextId;
|
||||
BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(OptionsFactoryBean.class);
|
||||
definitionBuilder.setScope("refresh");
|
||||
definitionBuilder.addPropertyValue("contextId", contextId);
|
||||
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(definitionBuilder.getBeanDefinition(),
|
||||
beanName);
|
||||
definitionHolder = ScopedProxyUtils.createScopedProxy(definitionHolder, registry, true);
|
||||
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isClientRefreshEnabled() {
|
||||
return environment.getProperty("feign.client.refresh-enabled", Boolean.class, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
@@ -30,6 +30,7 @@ import org.springframework.lang.Nullable;
|
||||
* @author Spencer Gibb
|
||||
* @author Dave Syer
|
||||
* @author Matt King
|
||||
* @author Jasbir Singh
|
||||
*/
|
||||
public class FeignContext extends NamedContextFactory<FeignClientSpecification> {
|
||||
|
||||
@@ -52,4 +53,8 @@ public class FeignContext extends NamedContextFactory<FeignClientSpecification>
|
||||
return getContext(name).getBeansOfType(type);
|
||||
}
|
||||
|
||||
public <T> T getInstance(String contextName, String beanName, Class<T> type) {
|
||||
return getContext(contextName).getBean(beanName, type);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import feign.Request;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
|
||||
/**
|
||||
* This factory bean is meant to create {@link Request.Options} instance as per the
|
||||
* applicable configurations.
|
||||
*
|
||||
* @author Jasbir Singh
|
||||
*/
|
||||
public class OptionsFactoryBean implements FactoryBean<Request.Options>, ApplicationContextAware {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private String contextId;
|
||||
|
||||
private Request.Options options;
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return Request.Options.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request.Options getObject() throws Exception {
|
||||
if (options != null) {
|
||||
return options;
|
||||
}
|
||||
|
||||
options = new Request.Options();
|
||||
FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
|
||||
options = createOptionsWithApplicableValues(properties.getConfig().get(properties.getDefaultConfig()), options);
|
||||
options = createOptionsWithApplicableValues(properties.getConfig().get(contextId), options);
|
||||
return options;
|
||||
}
|
||||
|
||||
public void setContextId(String contextId) {
|
||||
this.contextId = contextId;
|
||||
}
|
||||
|
||||
private Request.Options createOptionsWithApplicableValues(
|
||||
FeignClientProperties.FeignClientConfiguration clientConfiguration, Request.Options options) {
|
||||
if (Objects.isNull(clientConfiguration)) {
|
||||
return options;
|
||||
}
|
||||
|
||||
int connectTimeoutMillis = Objects.nonNull(clientConfiguration.getConnectTimeout())
|
||||
? clientConfiguration.getConnectTimeout() : options.connectTimeoutMillis();
|
||||
int readTimeoutMillis = Objects.nonNull(clientConfiguration.getReadTimeout())
|
||||
? clientConfiguration.getReadTimeout() : options.readTimeoutMillis();
|
||||
boolean followRedirects = Objects.nonNull(clientConfiguration.isFollowRedirects())
|
||||
? clientConfiguration.isFollowRedirects() : options.isFollowRedirects();
|
||||
return new Request.Options(connectTimeoutMillis, TimeUnit.MILLISECONDS, readTimeoutMillis,
|
||||
TimeUnit.MILLISECONDS, followRedirects);
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -72,8 +72,8 @@ public class MatrixVariableParameterProcessor implements AnnotatedParameterProce
|
||||
private String expandMap(Object object) {
|
||||
Map<String, Object> paramMap = (Map) object;
|
||||
|
||||
return paramMap.keySet().stream().map(key -> ";" + key + "=" + paramMap.get(key).toString())
|
||||
.collect(Collectors.joining());
|
||||
return paramMap.keySet().stream().filter(key -> paramMap.get(key) != null)
|
||||
.map(key -> ";" + key + "=" + paramMap.get(key).toString()).collect(Collectors.joining());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+8
-35
@@ -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,75 +16,48 @@
|
||||
|
||||
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
|
||||
@ConditionalOnClass(RepresentationModel.class)
|
||||
@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) {
|
||||
|
||||
ObjectProvider<ObjectMapper> objectMapper, 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;
|
||||
}
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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
-2
@@ -46,7 +46,6 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValida
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.ResponseData;
|
||||
import org.springframework.cloud.client.loadbalancer.RetryableRequestContext;
|
||||
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@@ -57,6 +56,7 @@ import org.springframework.retry.backoff.NoBackOffPolicy;
|
||||
import org.springframework.retry.policy.NeverRetryPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils.buildRequestData;
|
||||
|
||||
@@ -161,8 +161,11 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
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>() {
|
||||
|
||||
+21
-8
@@ -24,6 +24,7 @@ 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.RequestTemplate;
|
||||
@@ -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
|
||||
@@ -56,6 +61,7 @@ import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHead
|
||||
* @author Aaron Whiteside
|
||||
* @author Darren Foong
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Can Bezmen
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class SpringEncoder implements Encoder {
|
||||
@@ -95,8 +101,8 @@ public class SpringEncoder implements Encoder {
|
||||
requestContentType = MediaType.valueOf(type);
|
||||
}
|
||||
|
||||
if (isMultipartType(requestContentType)) {
|
||||
this.springFormEncoder.encode(requestBody, bodyType, request);
|
||||
if (isFormRelatedContentType(requestContentType)) {
|
||||
springFormEncoder.encode(requestBody, bodyType, request);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
@@ -207,9 +213,16 @@ public class SpringEncoder implements Encoder {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFormRelatedContentType(MediaType requestContentType) {
|
||||
return isMultipartType(requestContentType) || isFormUrlEncoded(requestContentType);
|
||||
}
|
||||
|
||||
private boolean isMultipartType(MediaType requestContentType) {
|
||||
return Arrays.asList(MediaType.MULTIPART_FORM_DATA, MediaType.MULTIPART_MIXED, MediaType.MULTIPART_RELATED)
|
||||
.contains(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) {
|
||||
@@ -227,21 +240,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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+12
@@ -14,6 +14,12 @@
|
||||
"description": "If true, an OpenFeign client will be wrapped with a Spring Cloud CircuitBreaker circuit breaker.",
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"name": "feign.circuitbreaker.group.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "If true, an OpenFeign client will be wrapped with a Spring Cloud CircuitBreaker circuit breaker with with group.",
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"name": "feign.httpclient.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
@@ -55,6 +61,12 @@
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables metrics capability for Feign.",
|
||||
"defaultValue": "true"
|
||||
},
|
||||
{
|
||||
"name": "feign.client.refresh-enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables options value refresh capability for Feign.",
|
||||
"defaultValue": "false"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+54
-1
@@ -16,12 +16,16 @@
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import feign.Target;
|
||||
import org.assertj.core.api.Condition;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
|
||||
import org.springframework.cloud.openfeign.FeignAutoConfiguration.CircuitBreakerPresentFeignTargeterConfiguration.DefaultCircuitBreakerNameResolver;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -30,6 +34,8 @@ import static org.mockito.Mockito.mock;
|
||||
/**
|
||||
* @author Tim Peeters
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Andrii Bohutskyi
|
||||
* @author Kwangyong Kim
|
||||
*/
|
||||
class FeignAutoConfigurationTests {
|
||||
|
||||
@@ -45,14 +51,61 @@ class FeignAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
void shouldInstantiateFeignCircuitBreakerTargeterWhenEnabled() {
|
||||
runner.withBean(CircuitBreakerFactory.class, () -> mock(CircuitBreakerFactory.class))
|
||||
.withPropertyValues("feign.circuitbreaker.enabled=true").run(ctx -> {
|
||||
assertOnlyOneTargeterPresent(ctx, FeignCircuitBreakerTargeter.class);
|
||||
assertThatFeignCircuitBreakerTargeterHasGroupEnabledPropertyWithValue(ctx, false);
|
||||
assertThatFeignCircuitBreakerTargeterHasSameCircuitBreakerNameResolver(ctx,
|
||||
DefaultCircuitBreakerNameResolver.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateFeignCircuitBreakerTargeterWithEnabledGroup() {
|
||||
runner.withBean(CircuitBreakerFactory.class, () -> mock(CircuitBreakerFactory.class))
|
||||
.withPropertyValues("feign.circuitbreaker.enabled=true")
|
||||
.run(ctx -> assertOnlyOneTargeterPresent(ctx, FeignCircuitBreakerTargeter.class));
|
||||
.withPropertyValues("feign.circuitbreaker.group.enabled=true").run(ctx -> {
|
||||
assertOnlyOneTargeterPresent(ctx, FeignCircuitBreakerTargeter.class);
|
||||
assertThatFeignCircuitBreakerTargeterHasGroupEnabledPropertyWithValue(ctx, true);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateFeignCircuitBreakerTargeterWhenEnabledWithCustomCircuitBreakerNameResolver() {
|
||||
runner.withBean(CircuitBreakerFactory.class, () -> mock(CircuitBreakerFactory.class))
|
||||
.withBean(CircuitBreakerNameResolver.class, CustomCircuitBreakerNameResolver::new)
|
||||
.withPropertyValues("feign.circuitbreaker.enabled=true").run(ctx -> {
|
||||
assertOnlyOneTargeterPresent(ctx, FeignCircuitBreakerTargeter.class);
|
||||
assertThatFeignCircuitBreakerTargeterHasSameCircuitBreakerNameResolver(ctx,
|
||||
CustomCircuitBreakerNameResolver.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)));
|
||||
|
||||
}
|
||||
|
||||
private void assertThatFeignCircuitBreakerTargeterHasGroupEnabledPropertyWithValue(
|
||||
ConfigurableApplicationContext ctx, boolean expectedValue) {
|
||||
final FeignCircuitBreakerTargeter bean = ctx.getBean(FeignCircuitBreakerTargeter.class);
|
||||
assertThat(bean).hasFieldOrPropertyWithValue("circuitBreakerGroupEnabled", expectedValue);
|
||||
}
|
||||
|
||||
private void assertThatFeignCircuitBreakerTargeterHasSameCircuitBreakerNameResolver(
|
||||
ConfigurableApplicationContext ctx, Class<?> beanClass) {
|
||||
final CircuitBreakerNameResolver bean = ctx.getBean(CircuitBreakerNameResolver.class);
|
||||
assertThat(bean).isExactlyInstanceOf(beanClass);
|
||||
}
|
||||
|
||||
static class CustomCircuitBreakerNameResolver implements CircuitBreakerNameResolver {
|
||||
|
||||
@Override
|
||||
public String resolveCircuitBreakerName(String feignClientName, Target<?> target, Method method) {
|
||||
return feignClientName + "_" + method.getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+19
@@ -33,6 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Matt King
|
||||
* @author Sam Kruglov
|
||||
*/
|
||||
public class FeignBuilderCustomizerTests {
|
||||
|
||||
@@ -74,6 +75,24 @@ 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");
|
||||
|
||||
+12
-3
@@ -24,6 +24,7 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import feign.Feign;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@@ -39,6 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Sven Döring
|
||||
* @author Sam Kruglov
|
||||
*/
|
||||
public class FeignClientBuilderTests {
|
||||
|
||||
@@ -56,6 +58,12 @@ 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);
|
||||
@@ -64,8 +72,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
|
||||
@@ -136,7 +143,7 @@ public class FeignClientBuilderTests {
|
||||
// when:
|
||||
final FeignClientBuilder.Builder builder = this.feignClientBuilder
|
||||
.forType(TestFeignClient.class, new FeignClientFactoryBean(), "TestClient").decode404(true)
|
||||
.path("Path/").url("Url/").contextId("TestContext");
|
||||
.path("Path/").url("Url/").contextId("TestContext").customize(Feign.Builder::doNotCloseAfterDecode);
|
||||
|
||||
// then:
|
||||
assertFactoryBeanField(builder, "applicationContext", this.applicationContext);
|
||||
@@ -148,6 +155,8 @@ public class FeignClientBuilderTests {
|
||||
assertFactoryBeanField(builder, "url", "http://Url/");
|
||||
assertFactoryBeanField(builder, "path", "/Path");
|
||||
assertFactoryBeanField(builder, "decode404", true);
|
||||
List<FeignBuilderCustomizer> additionalCustomizers = getFactoryBeanField(builder, "additionalCustomizers");
|
||||
assertThat(additionalCustomizers).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+2
-3
@@ -32,8 +32,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
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;
|
||||
|
||||
@@ -83,7 +82,7 @@ class FeignClientDisabledClientLevelFeaturesTests {
|
||||
@FeignClient(name = "bar", url = "https://bar", configuration = BarConfiguration.class)
|
||||
interface BarClient {
|
||||
|
||||
@RequestMapping(value = "/", method = RequestMethod.GET)
|
||||
@GetMapping("/")
|
||||
String get();
|
||||
|
||||
}
|
||||
|
||||
+2
-3
@@ -32,8 +32,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
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;
|
||||
|
||||
@@ -82,7 +81,7 @@ class FeignClientDisabledFeaturesTests {
|
||||
@FeignClient(name = "bar", url = "https://bar", configuration = BarConfiguration.class)
|
||||
interface BarClient {
|
||||
|
||||
@RequestMapping(value = "/", method = RequestMethod.GET)
|
||||
@GetMapping("/")
|
||||
String get();
|
||||
|
||||
}
|
||||
|
||||
+7
-7
@@ -25,8 +25,9 @@ import feign.InvocationHandlerFactory;
|
||||
import feign.RequestLine;
|
||||
import feign.Response;
|
||||
import feign.codec.ErrorDecoder;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledForJreRange;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
@@ -35,10 +36,8 @@ import org.springframework.cloud.openfeign.support.SpringMvcContract;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
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;
|
||||
|
||||
@@ -46,7 +45,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Michael Cramer
|
||||
* @author Jonatan Ivanov
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = FeignClientErrorDecoderTests.TestConfiguration.class)
|
||||
@DirtiesContext
|
||||
public class FeignClientErrorDecoderTests {
|
||||
@@ -73,12 +71,14 @@ public class FeignClientErrorDecoderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledForJreRange(min = JRE.JAVA_16)
|
||||
public void useConfiguredErrorDecoderWhenAlsoErrorDecoderFactoryIsAvailable() {
|
||||
Object errorDecoder = getErrorDecoderFromClient(this.foo);
|
||||
assertThat(errorDecoder).isInstanceOf(ErrorDecoder.Default.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledForJreRange(min = JRE.JAVA_16)
|
||||
public void useErrorDecoderFromErrorDecoderFactory() {
|
||||
Object errorDecoder = getErrorDecoderFromClient(this.bar);
|
||||
assertThat(errorDecoder).isInstanceOf(ErrorDecoderImpl.class);
|
||||
@@ -132,7 +132,7 @@ public class FeignClientErrorDecoderTests {
|
||||
@FeignClient(name = "bar", url = "http://bar", configuration = BarConfiguration.class)
|
||||
interface BarClient {
|
||||
|
||||
@RequestMapping(value = "/", method = RequestMethod.GET)
|
||||
@GetMapping("/")
|
||||
String get();
|
||||
|
||||
}
|
||||
|
||||
+6
-4
@@ -25,7 +25,9 @@ import java.util.Map;
|
||||
|
||||
import feign.Client;
|
||||
import feign.InvocationHandlerFactory;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledForJreRange;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
|
||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
@@ -37,10 +39,9 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.web.bind.annotation.RequestMethod.GET;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
@@ -66,6 +67,7 @@ public class FeignClientFactoryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledForJreRange(min = JRE.JAVA_16)
|
||||
public void shouldRedirectToDelegateWhenUrlSet() {
|
||||
new ApplicationContextRunner().withUserConfiguration(TestConfig.class).run(this::defaultClientUsed);
|
||||
}
|
||||
@@ -87,7 +89,7 @@ public class FeignClientFactoryTests {
|
||||
|
||||
interface TestType {
|
||||
|
||||
@RequestMapping(value = "/", method = GET)
|
||||
@GetMapping("/")
|
||||
String hello();
|
||||
|
||||
}
|
||||
|
||||
+13
-16
@@ -46,8 +46,9 @@ import feign.codec.EncodeException;
|
||||
import feign.codec.Encoder;
|
||||
import feign.codec.ErrorDecoder;
|
||||
import feign.micrometer.MicrometerCapability;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledForJreRange;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -61,18 +62,15 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||
|
||||
/**
|
||||
@@ -82,7 +80,6 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
|
||||
* @author Jonatan Ivanov
|
||||
*/
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = FeignClientUsingPropertiesTests.Application.class, webEnvironment = RANDOM_PORT)
|
||||
@TestPropertySource("classpath:feign-properties.properties")
|
||||
@DirtiesContext
|
||||
@@ -161,16 +158,14 @@ public class FeignClientUsingPropertiesTests {
|
||||
assertThat(response).isEqualTo("OK");
|
||||
}
|
||||
|
||||
@Test(expected = RetryableException.class)
|
||||
@Test
|
||||
public void testBar() {
|
||||
barClient().bar();
|
||||
fail("it should timeout");
|
||||
assertThatThrownBy(() -> barClient().bar()).isInstanceOf(RetryableException.class);
|
||||
}
|
||||
|
||||
@Test(expected = SocketTimeoutException.class)
|
||||
@Test
|
||||
public void testUnwrap() throws Exception {
|
||||
unwrapClient().unwrap();
|
||||
fail("it should timeout");
|
||||
assertThatThrownBy(() -> unwrapClient().unwrap()).isInstanceOf(SocketTimeoutException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -205,6 +200,7 @@ public class FeignClientUsingPropertiesTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledForJreRange(min = JRE.JAVA_16)
|
||||
public void readTimeoutShouldWorkWhenConnectTimeoutNotSet() {
|
||||
FeignClientFactoryBean readTimeoutFactoryBean = new FeignClientFactoryBean();
|
||||
readTimeoutFactoryBean.setContextId("readTimeout");
|
||||
@@ -221,6 +217,7 @@ public class FeignClientUsingPropertiesTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledForJreRange(min = JRE.JAVA_16)
|
||||
public void connectTimeoutShouldWorkWhenReadTimeoutNotSet() {
|
||||
FeignClientFactoryBean readTimeoutFactoryBean = new FeignClientFactoryBean();
|
||||
readTimeoutFactoryBean.setContextId("connectTimeout");
|
||||
@@ -250,6 +247,7 @@ public class FeignClientUsingPropertiesTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisabledForJreRange(min = JRE.JAVA_16)
|
||||
public void shouldSetFollowRedirects() {
|
||||
FeignClientFactoryBean testFactoryBean = new FeignClientFactoryBean();
|
||||
testFactoryBean.setContextId("test");
|
||||
@@ -295,8 +293,7 @@ public class FeignClientUsingPropertiesTests {
|
||||
|
||||
protected interface FormClient {
|
||||
|
||||
@RequestMapping(value = "/form", method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
@PostMapping(value = "/form", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
String form(Map<String, String> form);
|
||||
|
||||
}
|
||||
@@ -328,7 +325,7 @@ public class FeignClientUsingPropertiesTests {
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
protected static class Application {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/foo")
|
||||
@GetMapping("/foo")
|
||||
public String foo(HttpServletRequest request) throws IllegalAccessException {
|
||||
if ("Foo".equals(request.getHeader("Foo")) && "Bar".equals(request.getHeader("Bar"))) {
|
||||
return "OK";
|
||||
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* 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.util.concurrent.TimeUnit;
|
||||
|
||||
import feign.Request;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.context.scope.refresh.RefreshScope;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.context.support.GenericWebApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Jasbir Singh
|
||||
*/
|
||||
@SpringBootTest
|
||||
@TestPropertySource("classpath:feign-refreshable-properties.properties")
|
||||
@DirtiesContext
|
||||
public class FeignClientWithRefreshableOptionsTest {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Autowired
|
||||
private RefreshScope refreshScope;
|
||||
|
||||
@Autowired
|
||||
private Application.RefreshableClient refreshableClient;
|
||||
|
||||
@Autowired
|
||||
private Application.ReadTimeoutClient readTimeoutClient;
|
||||
|
||||
@Autowired
|
||||
private Application.ConnectTimeoutClient connectTimeoutClient;
|
||||
|
||||
@Autowired
|
||||
private Application.OverrideOptionsClient overrideOptionsClient;
|
||||
|
||||
@Autowired
|
||||
private FeignClientProperties clientProperties;
|
||||
|
||||
@Test
|
||||
public void overridedOptionsBeanShouldBePresentInsteadOfRefreshable() {
|
||||
OptionsTestClient.OptionsResponseForTests options = overrideOptionsClient.override();
|
||||
assertConnectionAndReadTimeout(options, 1, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refreshScopeBeanDefinitionShouldBePresent() {
|
||||
BeanDefinition beanDefinition = ((GenericWebApplicationContext) applicationContext)
|
||||
.getBeanDefinition(Request.Options.class.getCanonicalName() + "-" + "refreshableClient");
|
||||
BeanDefinition originBeanDefinition = beanDefinition.getOriginatingBeanDefinition();
|
||||
assertThat(originBeanDefinition.getBeanClassName()).isEqualTo(OptionsFactoryBean.class.getCanonicalName());
|
||||
assertThat(originBeanDefinition.getScope()).isEqualTo("refresh");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withConfigDefaultConnectTimeoutAndReadTimeout() {
|
||||
OptionsTestClient.OptionsResponseForTests options = refreshableClient.refreshable();
|
||||
assertConnectionAndReadTimeout(options, 5000, 5000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readTimeoutShouldWorkWhenConnectTimeoutNotSet() {
|
||||
OptionsTestClient.OptionsResponseForTests options = readTimeoutClient.readTimeout();
|
||||
assertConnectionAndReadTimeout(options, 5000, 2000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void connectTimeoutShouldWorkWhenReadTimeoutNotSet() {
|
||||
OptionsTestClient.OptionsResponseForTests options = connectTimeoutClient.connectTimeout();
|
||||
assertConnectionAndReadTimeout(options, 2000, 5000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void connectTimeoutShouldNotChangeWithoutContextRefresh() {
|
||||
OptionsTestClient.OptionsResponseForTests options = connectTimeoutClient.connectTimeout();
|
||||
assertConnectionAndReadTimeout(options, 2000, 5000);
|
||||
|
||||
clientProperties.getConfig().get("connectTimeout").setConnectTimeout(5000);
|
||||
options = connectTimeoutClient.connectTimeout();
|
||||
assertConnectionAndReadTimeout(options, 2000, 5000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void connectTimeoutShouldChangeAfterContextRefresh() {
|
||||
OptionsTestClient.OptionsResponseForTests options = connectTimeoutClient.connectTimeout();
|
||||
assertConnectionAndReadTimeout(options, 2000, 5000);
|
||||
|
||||
clientProperties.getConfig().get("connectTimeout").setConnectTimeout(5000);
|
||||
refreshScope.refreshAll();
|
||||
options = connectTimeoutClient.connectTimeout();
|
||||
assertConnectionAndReadTimeout(options, 5000, 5000);
|
||||
}
|
||||
|
||||
private void assertConnectionAndReadTimeout(OptionsTestClient.OptionsResponseForTests options,
|
||||
int expectedConnectTimeoutInMillis, int expectedReadTimeoutInMillis) {
|
||||
assertThat(options.getConnectTimeout()).isEqualTo(expectedConnectTimeoutInMillis);
|
||||
assertThat(options.getReadTimeout()).isEqualTo(expectedReadTimeoutInMillis);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
@EnableConfigurationProperties(FeignClientProperties.class)
|
||||
@EnableFeignClients(clients = { Application.OverrideOptionsClient.class, Application.RefreshableClient.class,
|
||||
Application.ReadTimeoutClient.class, Application.ConnectTimeoutClient.class })
|
||||
protected static class Application {
|
||||
|
||||
@Bean
|
||||
OptionsTestClient client() {
|
||||
return new OptionsTestClient();
|
||||
}
|
||||
|
||||
@FeignClient(name = "overrideOptionsClient", configuration = OverrideConfig.class)
|
||||
protected interface OverrideOptionsClient {
|
||||
|
||||
@GetMapping("/override")
|
||||
OptionsTestClient.OptionsResponseForTests override();
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "refreshableClient")
|
||||
protected interface RefreshableClient {
|
||||
|
||||
@GetMapping("/refreshable")
|
||||
OptionsTestClient.OptionsResponseForTests refreshable();
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "readTimeout")
|
||||
protected interface ReadTimeoutClient {
|
||||
|
||||
@GetMapping("/readTimeout")
|
||||
OptionsTestClient.OptionsResponseForTests readTimeout();
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "connectTimeout")
|
||||
protected interface ConnectTimeoutClient {
|
||||
|
||||
@GetMapping("/connectTimeout")
|
||||
OptionsTestClient.OptionsResponseForTests connectTimeout();
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
protected class OverrideConfig {
|
||||
|
||||
@Bean
|
||||
public Request.Options options() {
|
||||
return new Request.Options(1, TimeUnit.MILLISECONDS, 1, TimeUnit.MILLISECONDS, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+3
-4
@@ -25,8 +25,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
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;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
@@ -105,7 +104,7 @@ public class FeignClientsRegistrarTests {
|
||||
@FeignClient(name = "fallbackTestClient", url = "http://localhost:8080/", fallback = FallbackClient.class)
|
||||
protected interface FallbackClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
String fallbackTest();
|
||||
|
||||
}
|
||||
@@ -114,7 +113,7 @@ public class FeignClientsRegistrarTests {
|
||||
fallbackFactory = FallbackFactoryClient.class)
|
||||
protected interface FallbackFactoryClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
String fallbackFactoryTest();
|
||||
|
||||
}
|
||||
|
||||
+12
-4
@@ -66,10 +66,18 @@ public class FeignHttpClientConfigurationTests {
|
||||
|
||||
@Test
|
||||
public void disableSslTest() throws Exception {
|
||||
HttpClientConnectionManager connectionManager = this.context.getBean(HttpClientConnectionManager.class);
|
||||
Lookup<ConnectionSocketFactory> socketFactoryRegistry = getConnectionSocketFactoryLookup(connectionManager);
|
||||
assertThat(socketFactoryRegistry.lookup("https")).isNotNull();
|
||||
assertThat(this.getX509TrustManager(socketFactoryRegistry).getAcceptedIssuers()).isNull();
|
||||
try {
|
||||
HttpClientConnectionManager connectionManager = this.context.getBean(HttpClientConnectionManager.class);
|
||||
Lookup<ConnectionSocketFactory> socketFactoryRegistry = getConnectionSocketFactoryLookup(connectionManager);
|
||||
assertThat(socketFactoryRegistry.lookup("https")).isNotNull();
|
||||
assertThat(this.getX509TrustManager(socketFactoryRegistry).getAcceptedIssuers()).isNull();
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
// FIXME: java 16 need junit 5 compatible modified classpath extension
|
||||
if (e.getMessage() == null || !e.getMessage().startsWith("Unable to make field private final")) {
|
||||
ReflectionUtils.rethrowRuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Lookup<ConnectionSocketFactory> getConnectionSocketFactoryLookup(
|
||||
|
||||
+6
-7
@@ -37,8 +37,7 @@ 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.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -102,7 +101,7 @@ class FeignHttpClientUrlTests {
|
||||
@FeignClient(name = "localappurl", url = "http://localhost:${server.port}/")
|
||||
protected interface UrlClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
@@ -110,7 +109,7 @@ class FeignHttpClientUrlTests {
|
||||
@FeignClient(name = "beanappurl", url = "#{SERVER_URL}path")
|
||||
protected interface BeanUrlClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
@@ -118,7 +117,7 @@ class FeignHttpClientUrlTests {
|
||||
@FeignClient(name = "beanappurlnoprotocol", url = "#{SERVER_URL_NO_PROTOCOL}path")
|
||||
protected interface BeanUrlClientNoProtocol {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
@@ -130,12 +129,12 @@ class FeignHttpClientUrlTests {
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
protected static class TestConfig {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
public Hello getHello() {
|
||||
return new Hello("hello world 1");
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/path/hello")
|
||||
@GetMapping("/path/hello")
|
||||
public Hello getHelloWithPath() {
|
||||
return getHello();
|
||||
}
|
||||
|
||||
+3
-5
@@ -38,8 +38,6 @@ 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;
|
||||
@@ -104,7 +102,7 @@ class FeignHttpClientUrlTestsWithRetryableLoadBalancer {
|
||||
@FeignClient(name = "localappurl", url = "http://localhost:${server.port}/")
|
||||
protected interface UrlClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
@@ -112,7 +110,7 @@ class FeignHttpClientUrlTestsWithRetryableLoadBalancer {
|
||||
@FeignClient(name = "beanappurl", url = "#{SERVER_URL}path")
|
||||
protected interface BeanUrlClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
@@ -120,7 +118,7 @@ class FeignHttpClientUrlTestsWithRetryableLoadBalancer {
|
||||
@FeignClient(name = "beanappurlnoprotocol", url = "#{SERVER_URL_NO_PROTOCOL}path")
|
||||
protected interface BeanUrlClientNoProtocol {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import feign.Client;
|
||||
import feign.Request;
|
||||
import feign.Response;
|
||||
|
||||
/**
|
||||
* @author Jasbir Singh
|
||||
*/
|
||||
public class OptionsTestClient implements Client {
|
||||
|
||||
private static ObjectMapper mapper;
|
||||
|
||||
static {
|
||||
mapper = new ObjectMapper();
|
||||
mapper.registerModule(new JavaTimeModule()).configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response execute(Request request, Request.Options options) throws IOException {
|
||||
return Response.builder().status(200).request(request).headers(headers()).body(prepareResponse(options))
|
||||
.build();
|
||||
}
|
||||
|
||||
private Map<String, Collection<String>> headers() {
|
||||
Map<String, Collection<String>> headers = new LinkedHashMap<>();
|
||||
headers.put("Content-Type", Collections.singletonList("application/json"));
|
||||
return headers;
|
||||
}
|
||||
|
||||
private byte[] prepareResponse(Request.Options options) {
|
||||
try {
|
||||
|
||||
OptionsResponseForTests response = new OptionsResponseForTests(options.connectTimeoutMillis(),
|
||||
TimeUnit.MILLISECONDS, options.readTimeoutMillis(), TimeUnit.MILLISECONDS);
|
||||
return mapper.writeValueAsString(response).getBytes();
|
||||
}
|
||||
catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
static class OptionsResponseForTests {
|
||||
|
||||
private long connectTimeout;
|
||||
|
||||
private TimeUnit connectTimeoutUnit;
|
||||
|
||||
private long readTimeout;
|
||||
|
||||
private TimeUnit readTimeoutUnit;
|
||||
|
||||
OptionsResponseForTests(long connectTimeout, TimeUnit connectTimeoutUnit, long readTimeout,
|
||||
TimeUnit readTimeoutUnit) {
|
||||
this.connectTimeout = connectTimeout;
|
||||
this.connectTimeoutUnit = connectTimeoutUnit;
|
||||
this.readTimeout = readTimeout;
|
||||
this.readTimeoutUnit = readTimeoutUnit;
|
||||
}
|
||||
|
||||
public long getConnectTimeout() {
|
||||
return connectTimeout;
|
||||
}
|
||||
|
||||
public TimeUnit getConnectTimeoutUnit() {
|
||||
return connectTimeoutUnit;
|
||||
}
|
||||
|
||||
public long getReadTimeout() {
|
||||
return readTimeout;
|
||||
}
|
||||
|
||||
public TimeUnit getReadTimeoutUnit() {
|
||||
return readTimeoutUnit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OptionsResponseForTests{" + "connectTimeout=" + connectTimeout + ", connectTimeoutUnit="
|
||||
+ connectTimeoutUnit + ", readTimeout=" + readTimeout + ", readTimeoutUnit=" + readTimeoutUnit
|
||||
+ '}';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+6
-8
@@ -39,8 +39,6 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
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;
|
||||
@@ -154,22 +152,22 @@ public class SpringDecoderTests extends FeignClientFactoryBean {
|
||||
|
||||
protected interface TestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/helloresponse")
|
||||
@GetMapping("/helloresponse")
|
||||
ResponseEntity<Hello> getHelloResponse();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hellovoid")
|
||||
@GetMapping("/hellovoid")
|
||||
ResponseEntity<Void> getHelloVoid();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hellos")
|
||||
@GetMapping("/hellos")
|
||||
List<Hello> getHellos();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hellostrings")
|
||||
@GetMapping("/hellostrings")
|
||||
List<String> getHelloStrings();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
|
||||
@GetMapping("/hellonotfound")
|
||||
ResponseEntity<String> getNotFound();
|
||||
|
||||
@GetMapping("/helloWildcard")
|
||||
|
||||
+2
-3
@@ -39,8 +39,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -122,7 +121,7 @@ public class BeansFeignClientTests {
|
||||
return feignClientBuilder.forType(TestClient.class, "builderapp").build();
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
public Hello getHello() {
|
||||
return new Hello("hello world 1");
|
||||
}
|
||||
|
||||
+2
-3
@@ -19,14 +19,13 @@ package org.springframework.cloud.openfeign.beans;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.cloud.openfeign.beans.BeansFeignClientTests.Hello;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Primary
|
||||
@FeignClient("localapp")
|
||||
public interface TestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
|
||||
+2
-3
@@ -18,13 +18,12 @@ package org.springframework.cloud.openfeign.beans.extra;
|
||||
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.cloud.openfeign.beans.BeansFeignClientTests.Hello;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@FeignClient(value = "otherapp", qualifier = "uniquequalifier")
|
||||
public interface TestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.circuitbreaker;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
|
||||
|
||||
/**
|
||||
* Asynchronous circuit breaker.
|
||||
*
|
||||
* @author John Niang
|
||||
*/
|
||||
class AsyncCircuitBreaker implements CircuitBreaker {
|
||||
|
||||
final Duration timeout;
|
||||
|
||||
final ExecutorService executorService;
|
||||
|
||||
AsyncCircuitBreaker(Duration timeout) {
|
||||
this(timeout, Executors.newCachedThreadPool());
|
||||
}
|
||||
|
||||
AsyncCircuitBreaker(Duration timeout, ExecutorService executorService) {
|
||||
this.timeout = timeout;
|
||||
this.executorService = executorService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T run(Supplier<T> toRun, Function<Throwable, T> fallback) {
|
||||
Future<T> future = executorService.submit(toRun::get);
|
||||
try {
|
||||
return future.get(timeout.toMillis(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
return fallback.apply(t);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* 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.circuitbreaker;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import feign.RequestInterceptor;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
|
||||
import org.springframework.cloud.client.circuitbreaker.ConfigBuilder;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
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.http.HttpHeaders;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Tests for asynchronous circuit breaker.
|
||||
*
|
||||
* @author John Niang
|
||||
*/
|
||||
@SpringBootTest(classes = AsyncCircuitBreakerTest.Application.class, webEnvironment = RANDOM_PORT,
|
||||
properties = "feign.circuitbreaker.enabled=true")
|
||||
@AutoConfigureMockMvc
|
||||
class AsyncCircuitBreakerTest {
|
||||
|
||||
@Autowired
|
||||
MockMvc mvc;
|
||||
|
||||
@Test
|
||||
void shouldWorkNormally() throws Exception {
|
||||
mvc.perform(get("/hello/proxy")).andDo(print()).andExpect(status().isOk())
|
||||
.andExpect(content().string("openfeign"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotProxyAnyHeadersWithoutHeaderSet() throws Exception {
|
||||
mvc.perform(get("/headers/" + HttpHeaders.AUTHORIZATION + "/proxy")).andDo(print()).andExpect(status().isOk())
|
||||
.andExpect(content().string(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProxyHeaderWhenHeaderSet() throws Exception {
|
||||
String authorization = UUID.randomUUID().toString();
|
||||
mvc.perform(get("/headers/" + HttpHeaders.AUTHORIZATION + "/proxy").header(HttpHeaders.AUTHORIZATION,
|
||||
authorization)).andDo(print()).andExpect(status().isOk()).andExpect(content().string(authorization));
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableFeignClients(clients = { TestClient.class })
|
||||
@Import({ NoSecurityConfiguration.class, TestController.class })
|
||||
static class Application {
|
||||
|
||||
@Bean
|
||||
CircuitBreakerFactory<Duration, ConfigBuilder<Duration>> circuitBreakerFactory() {
|
||||
return new CircuitBreakerFactory<Duration, ConfigBuilder<Duration>>() {
|
||||
|
||||
Function<String, Duration> defaultConfiguration = id -> Duration.ofMillis(1000);
|
||||
|
||||
@Override
|
||||
public CircuitBreaker create(String id) {
|
||||
Duration timeout = super.getConfigurations().computeIfAbsent(id, defaultConfiguration);
|
||||
return new AsyncCircuitBreaker(timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConfigBuilder<Duration> configBuilder(String id) {
|
||||
return () -> Duration.ofMillis(100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureDefault(Function<String, Duration> defaultConfiguration) {
|
||||
this.defaultConfiguration = defaultConfiguration;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
RequestInterceptor proxyHeaderRequestInterceptor() {
|
||||
return template -> {
|
||||
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
|
||||
.getRequestAttributes();
|
||||
String authorization = Objects.requireNonNull(requestAttributes).getRequest()
|
||||
.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
if (authorization != null) {
|
||||
// proxy authorization header
|
||||
template.header(HttpHeaders.AUTHORIZATION, authorization);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class TestController {
|
||||
|
||||
final ObjectProvider<TestClient> testClient;
|
||||
|
||||
TestController(ObjectProvider<TestClient> testClient) {
|
||||
this.testClient = testClient;
|
||||
}
|
||||
|
||||
@GetMapping("/hello")
|
||||
String hello() {
|
||||
return "openfeign";
|
||||
}
|
||||
|
||||
@GetMapping("/hello/proxy")
|
||||
String helloProxy() {
|
||||
return testClient.getObject().hello();
|
||||
}
|
||||
|
||||
@GetMapping("/headers/{headerName}")
|
||||
String header(HttpServletRequest request, @PathVariable String headerName) {
|
||||
return request.getHeader(headerName);
|
||||
}
|
||||
|
||||
@GetMapping("/headers/{headerName}/proxy")
|
||||
String headerProxy(@PathVariable String headerName) {
|
||||
return testClient.getObject().header(headerName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "async-circuit-breaker-test", url = "http://localhost:${local.server.port}")
|
||||
interface TestClient {
|
||||
|
||||
@GetMapping("/hello")
|
||||
String hello();
|
||||
|
||||
@GetMapping("/headers/{headerName}")
|
||||
String header(@PathVariable String headerName);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+5
-6
@@ -45,8 +45,7 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.SocketUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -115,10 +114,10 @@ public class CircuitBreakerTests {
|
||||
@FeignClient(name = "test", url = "http://localhost:${server.port}/", fallback = Fallback.class)
|
||||
protected interface TestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
|
||||
@GetMapping("/hellonotfound")
|
||||
String getException();
|
||||
|
||||
}
|
||||
@@ -142,10 +141,10 @@ public class CircuitBreakerTests {
|
||||
fallbackFactory = TestFallbackFactory.class)
|
||||
protected interface TestClientWithFactory {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
|
||||
@GetMapping("/hellonotfound")
|
||||
String getException();
|
||||
|
||||
}
|
||||
|
||||
+3
-4
@@ -40,8 +40,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.util.SocketUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -97,10 +96,10 @@ public class CircuitBreakerWithNoFallbackTests {
|
||||
@FeignClient(name = "test", url = "http://localhost:${server.port}/")
|
||||
protected interface CircuitBreakerTestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
|
||||
@GetMapping("/hellonotfound")
|
||||
String getException();
|
||||
|
||||
}
|
||||
|
||||
+9
-9
@@ -24,9 +24,9 @@ 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.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
/**
|
||||
* Simple Feign client for retrieving the invoice list.
|
||||
@@ -37,23 +37,23 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||
@FeignClient("local")
|
||||
public interface InvoiceClient {
|
||||
|
||||
@RequestMapping(value = "invoicesPaged", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@GetMapping(value = "invoicesPaged", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
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)
|
||||
@PostMapping(value = "invoicesPagedWithBody", 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)
|
||||
@PostMapping(value = "invoicesSortedWithBody", 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)
|
||||
@GetMapping(value = "invoices", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
ResponseEntity<List<Invoice>> getInvoices();
|
||||
|
||||
@RequestMapping(value = "invoices", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
@PostMapping(value = "invoices", consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
ResponseEntity<List<Invoice>> saveInvoices(List<Invoice> invoices);
|
||||
|
||||
|
||||
+9
-9
@@ -28,9 +28,9 @@ 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.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
@@ -42,28 +42,28 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RestController
|
||||
public class InvoiceResource {
|
||||
|
||||
@RequestMapping(value = "invoices", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@GetMapping(value = "invoices", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<List<Invoice>> getInvoices() {
|
||||
|
||||
return ResponseEntity.ok(createInvoiceList(null, 100, null));
|
||||
}
|
||||
|
||||
@RequestMapping(value = "invoices", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
@PostMapping(value = "invoices", consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
ResponseEntity<List<Invoice>> saveInvoices(@RequestBody List<Invoice> invoices) {
|
||||
|
||||
return ResponseEntity.ok(invoices);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "invoicesPaged", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@GetMapping(value = "invoicesPaged", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<Page<Invoice>> getInvoicesPaged(org.springframework.data.domain.Pageable pageable) {
|
||||
Page<Invoice> page = new PageImpl<>(createInvoiceList(null, pageable.getPageSize(), pageable.getSort()),
|
||||
pageable, 100);
|
||||
return ResponseEntity.ok(page);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "invoicesPagedWithBody", method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@PostMapping(value = "invoicesPagedWithBody", 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()),
|
||||
@@ -71,8 +71,8 @@ public class InvoiceResource {
|
||||
return ResponseEntity.ok(page);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "invoicesSortedWithBody", method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@PostMapping(value = "invoicesSortedWithBody", 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),
|
||||
|
||||
+14
-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;
|
||||
@@ -41,24 +43,25 @@ import static org.springframework.hateoas.MediaTypes.HAL_JSON;
|
||||
|
||||
/**
|
||||
* @author Hector Espert
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@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;
|
||||
@@ -69,17 +72,17 @@ public class FeignHalAutoConfigurationTests {
|
||||
when(objectMapper.getIfAvailable(any())).thenReturn(mapper);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
+5
-6
@@ -28,8 +28,7 @@ import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
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;
|
||||
|
||||
@@ -74,7 +73,7 @@ public class FeignClientValidationTests {
|
||||
@FeignClient(name = "bar")
|
||||
interface Client {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/")
|
||||
@GetMapping("/")
|
||||
String get();
|
||||
|
||||
}
|
||||
@@ -90,7 +89,7 @@ public class FeignClientValidationTests {
|
||||
@FeignClient(contextId = "foo", name = "bar")
|
||||
interface FooClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/")
|
||||
@GetMapping("/")
|
||||
String get();
|
||||
|
||||
}
|
||||
@@ -98,7 +97,7 @@ public class FeignClientValidationTests {
|
||||
@FeignClient(name = "bar")
|
||||
interface BarClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/")
|
||||
@GetMapping("/")
|
||||
String get();
|
||||
|
||||
}
|
||||
@@ -113,7 +112,7 @@ public class FeignClientValidationTests {
|
||||
@FeignClient("foo_bar")
|
||||
interface Client {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/")
|
||||
@GetMapping("/")
|
||||
String get();
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -63,7 +63,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)
|
||||
|
||||
+28
-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;
|
||||
@@ -29,6 +32,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
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;
|
||||
@@ -66,7 +70,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)
|
||||
@@ -117,6 +121,13 @@ 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 +159,22 @@ class RetryableFeignBlockingLoadBalancerClientTests {
|
||||
verify(delegate, times(2)).execute(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExposeResponseBodyOnRetry() throws IOException {
|
||||
properties.getRetry().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(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();
|
||||
|
||||
+13
@@ -60,6 +60,7 @@ 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;
|
||||
@@ -68,6 +69,7 @@ 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, webEnvironment = WebEnvironment.RANDOM_PORT,
|
||||
@@ -179,6 +181,17 @@ public class SpringEncoderTests {
|
||||
assertThat(new String(request.requestBody().asBytes())).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);
|
||||
|
||||
+24
-26
@@ -50,6 +50,7 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.MatrixVariable;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -587,13 +588,13 @@ public class SpringMvcContractTests {
|
||||
@RequestMapping(value = "/test/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
ResponseEntity<TestObject> getTest(@PathVariable("id") String id);
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
TestObject getTest();
|
||||
|
||||
@GetMapping(value = "/test/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
ResponseEntity<TestObject> getMappingTest(@PathVariable("id") String id);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
TestObject postTest(@RequestBody TestObject object);
|
||||
|
||||
@PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@@ -604,7 +605,7 @@ public class SpringMvcContractTests {
|
||||
@RequestMapping("/prepend/{classId}")
|
||||
public interface TestTemplate_Class_Annotations {
|
||||
|
||||
@RequestMapping(value = "/test/{testId}", method = RequestMethod.GET)
|
||||
@GetMapping("/test/{testId}")
|
||||
TestObject getSpecificTest(@PathVariable("classId") String classId, @PathVariable("testId") String testId);
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET)
|
||||
@@ -618,47 +619,46 @@ public class SpringMvcContractTests {
|
||||
|
||||
public interface TestTemplate_Headers {
|
||||
|
||||
@RequestMapping(value = "/test/{id}", method = RequestMethod.GET, headers = "X-Foo=bar")
|
||||
@GetMapping(value = "/test/{id}", headers = "X-Foo=bar")
|
||||
ResponseEntity<TestObject> getTest(@PathVariable("id") String id);
|
||||
|
||||
}
|
||||
|
||||
public interface TestTemplate_HeadersWithoutValues {
|
||||
|
||||
@RequestMapping(value = "/test/{id}", method = RequestMethod.GET,
|
||||
headers = { "X-Foo", "!X-Bar", "X-Baz!=fooBar" })
|
||||
@GetMapping(value = "/test/{id}", headers = { "X-Foo", "!X-Bar", "X-Baz!=fooBar" })
|
||||
ResponseEntity<TestObject> getTest(@PathVariable("id") String id);
|
||||
|
||||
}
|
||||
|
||||
public interface TestTemplate_ListParams {
|
||||
|
||||
@RequestMapping(value = "/test", method = RequestMethod.GET)
|
||||
@GetMapping("/test")
|
||||
ResponseEntity<TestObject> getTest(@RequestParam("id") List<String> id);
|
||||
|
||||
}
|
||||
|
||||
public interface TestTemplate_ListParamsWithoutName {
|
||||
|
||||
@RequestMapping(value = "/test", method = RequestMethod.GET)
|
||||
@GetMapping("/test")
|
||||
ResponseEntity<TestObject> getTest(@RequestParam List<String> id);
|
||||
|
||||
}
|
||||
|
||||
public interface TestTemplate_MapParams {
|
||||
|
||||
@RequestMapping(value = "/test", method = RequestMethod.GET)
|
||||
@GetMapping("/test")
|
||||
ResponseEntity<TestObject> getTest(@RequestParam Map<String, String> params);
|
||||
|
||||
}
|
||||
|
||||
public interface TestTemplate_HeaderMap {
|
||||
|
||||
@RequestMapping(path = "/headerMap")
|
||||
@GetMapping("/headerMap")
|
||||
String headerMap(@RequestHeader MultiValueMap<String, String> headerMap,
|
||||
@RequestHeader(name = "aHeader") String aHeader);
|
||||
|
||||
@RequestMapping(path = "/headerMapMoreThanOnce")
|
||||
@GetMapping("/headerMapMoreThanOnce")
|
||||
String headerMapMoreThanOnce(@RequestHeader MultiValueMap<String, String> headerMap1,
|
||||
@RequestHeader MultiValueMap<String, String> headerMap2);
|
||||
|
||||
@@ -666,36 +666,35 @@ public class SpringMvcContractTests {
|
||||
|
||||
public interface TestTemplate_QueryMap {
|
||||
|
||||
@RequestMapping(path = "/queryMap")
|
||||
@GetMapping("/queryMap")
|
||||
String queryMap(@RequestParam MultiValueMap<String, String> queryMap,
|
||||
@RequestParam(name = "aParam") String aParam);
|
||||
|
||||
@RequestMapping(path = "/queryMapMoreThanOnce")
|
||||
@GetMapping("/queryMapMoreThanOnce")
|
||||
String queryMapMoreThanOnce(@RequestParam MultiValueMap<String, String> queryMap1,
|
||||
@RequestParam MultiValueMap<String, String> queryMap2);
|
||||
|
||||
@RequestMapping(path = "/queryMapObject")
|
||||
@GetMapping("/queryMapObject")
|
||||
String queryMapObject(@SpringQueryMap TestObject queryMap, @RequestParam(name = "aParam") String aParam);
|
||||
|
||||
}
|
||||
|
||||
public interface TestTemplate_RequestPart {
|
||||
|
||||
@RequestMapping(path = "/requestPart", method = RequestMethod.POST,
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@PostMapping(path = "/requestPart", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
void requestWithMultipleParts(@RequestPart("file") MultipartFile file, @RequestPart("id") String identifier);
|
||||
|
||||
}
|
||||
|
||||
public interface TestTemplate_MatrixVariable {
|
||||
|
||||
@RequestMapping(path = "/matrixVariable/{params}")
|
||||
@GetMapping("/matrixVariable/{params}")
|
||||
String matrixVariable(@MatrixVariable("params") Map<String, Object> params);
|
||||
|
||||
@RequestMapping(path = "/matrixVariableObject/{param}")
|
||||
@GetMapping("/matrixVariableObject/{param}")
|
||||
String matrixVariableObject(@MatrixVariable("param") Object object);
|
||||
|
||||
@RequestMapping(path = "/matrixVariable/{params}")
|
||||
@GetMapping("/matrixVariable/{params}")
|
||||
String matrixVariableNotNamed(@MatrixVariable Map<String, Object> params);
|
||||
|
||||
}
|
||||
@@ -709,21 +708,20 @@ public class SpringMvcContractTests {
|
||||
ResponseEntity<TestObject> getWithCollectionFormat();
|
||||
|
||||
@ExceptionHandler
|
||||
@RequestMapping(path = "/test/{id}", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@PutMapping(path = "/test/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
ResponseEntity<TestObject> getTest(@RequestHeader("Authorization") String auth, @PathVariable("id") String id,
|
||||
@RequestParam("amount") Integer amount);
|
||||
|
||||
@RequestMapping(path = "/test2", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@PutMapping(path = "/test2", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
ResponseEntity<TestObject> getTest2(@RequestHeader(name = "Authorization") String auth,
|
||||
@RequestParam(name = "amount") Integer amount);
|
||||
|
||||
@ExceptionHandler
|
||||
@RequestMapping(path = "/testfallback/{id}", method = RequestMethod.PUT,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@PutMapping(path = "/testfallback/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
ResponseEntity<TestObject> getTestFallback(@RequestHeader String Authorization, @PathVariable String id,
|
||||
@RequestParam Integer amount);
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
TestObject getTest();
|
||||
|
||||
@GetMapping(produces = "application/json")
|
||||
@@ -735,7 +733,7 @@ public class SpringMvcContractTests {
|
||||
|
||||
String CUSTOM_PATTERN = "dd-MM-yyyy HH:mm";
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET)
|
||||
@GetMapping
|
||||
String getTest(@RequestParam(name = "localDateTime") @DateTimeFormat(
|
||||
pattern = CUSTOM_PATTERN) LocalDateTime localDateTime);
|
||||
|
||||
@@ -745,7 +743,7 @@ public class SpringMvcContractTests {
|
||||
|
||||
String CUSTOM_PATTERN = "$###,###.###";
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET)
|
||||
@GetMapping
|
||||
String getTest(@RequestParam("amount") @NumberFormat(pattern = CUSTOM_PATTERN) BigDecimal amount);
|
||||
|
||||
}
|
||||
|
||||
+2
-3
@@ -17,8 +17,7 @@
|
||||
package org.springframework.cloud.openfeign.testclients;
|
||||
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
@@ -26,7 +25,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||
@FeignClient(name = "localapp")
|
||||
public interface TestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
String getHello();
|
||||
|
||||
}
|
||||
|
||||
+3
-4
@@ -37,8 +37,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -83,7 +82,7 @@ public class FeignClientNotPrimaryTests {
|
||||
@FeignClient(name = "localapp", primary = false)
|
||||
protected interface TestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
@@ -101,7 +100,7 @@ public class FeignClientNotPrimaryTests {
|
||||
return new PrimaryTestClient();
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hello")
|
||||
@GetMapping("/hello")
|
||||
public Hello getHello() {
|
||||
return new Hello(HELLO_WORLD_1);
|
||||
}
|
||||
|
||||
+4
-5
@@ -27,8 +27,7 @@ import org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoCon
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
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;
|
||||
|
||||
@@ -70,7 +69,7 @@ public class FeignClientValidationTests {
|
||||
@FeignClient(name = "example", url = "https://example.com")
|
||||
interface Client {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/")
|
||||
@GetMapping("/")
|
||||
@Deprecated
|
||||
String get();
|
||||
|
||||
@@ -86,7 +85,7 @@ public class FeignClientValidationTests {
|
||||
@FeignClient(name = "example", url = "${feignClient.url:https://example.com}")
|
||||
interface Client {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/")
|
||||
@GetMapping("/")
|
||||
@Deprecated
|
||||
String get();
|
||||
|
||||
@@ -102,7 +101,7 @@ public class FeignClientValidationTests {
|
||||
@FeignClient("foo")
|
||||
interface Client {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/")
|
||||
@GetMapping("/")
|
||||
@Deprecated
|
||||
String get();
|
||||
|
||||
|
||||
+7
-7
@@ -41,11 +41,11 @@ import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -107,17 +107,17 @@ class FeignHttpClientTests {
|
||||
|
||||
protected interface BaseTestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@GetMapping(value = "/hello", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
Hello getHello();
|
||||
|
||||
@RequestMapping(method = RequestMethod.PATCH, value = "/hellop", consumes = "application/json")
|
||||
@PatchMapping(value = "/hellop", consumes = "application/json")
|
||||
ResponseEntity<Void> patchHello(Hello hello);
|
||||
|
||||
}
|
||||
|
||||
protected interface UserService {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/users/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@GetMapping(value = "/users/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
User getUser(@PathVariable("id") long id);
|
||||
|
||||
}
|
||||
@@ -136,12 +136,12 @@ class FeignHttpClientTests {
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
protected static class Application implements UserService {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
public Hello getHello() {
|
||||
return new Hello("hello world 1");
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.PATCH, value = "/hellop")
|
||||
@PatchMapping("/hellop")
|
||||
public ResponseEntity<Void> patchHello(@RequestBody Hello hello,
|
||||
@RequestHeader("Content-Length") int contentLength) {
|
||||
if (contentLength <= 0) {
|
||||
|
||||
+7
-7
@@ -40,11 +40,11 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -107,17 +107,17 @@ class FeignOkHttpTests {
|
||||
|
||||
protected interface BaseTestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
@RequestMapping(method = RequestMethod.PATCH, value = "/hellop", consumes = "application/json")
|
||||
@PatchMapping(value = "/hellop", consumes = "application/json")
|
||||
ResponseEntity<Void> patchHello(Hello hello);
|
||||
|
||||
}
|
||||
|
||||
protected interface UserService {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/users/{id}")
|
||||
@GetMapping("/users/{id}")
|
||||
User getUser(@PathVariable("id") long id);
|
||||
|
||||
}
|
||||
@@ -138,12 +138,12 @@ class FeignOkHttpTests {
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
protected static class Application implements UserService {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
public Hello getHello() {
|
||||
return new Hello("hello world 1");
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.PATCH, value = "/hellop")
|
||||
@PatchMapping("/hellop")
|
||||
public ResponseEntity<Void> patchHello(@RequestBody Hello hello,
|
||||
@RequestHeader("Content-Length") int contentLength) {
|
||||
if (contentLength <= 0) {
|
||||
|
||||
+82
-97
@@ -38,7 +38,6 @@ import javax.servlet.http.Part;
|
||||
import feign.Client;
|
||||
import feign.Logger;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import feign.codec.EncodeException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -65,7 +64,6 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.format.Formatter;
|
||||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -73,6 +71,8 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
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;
|
||||
@@ -84,6 +84,9 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
|
||||
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
|
||||
import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
@@ -277,6 +280,13 @@ class ValidFeignClientTests {
|
||||
assertThat(testClient.getToString(args)).isEqualTo(expectedResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFormURLEncoded() {
|
||||
Hello hello = new Hello(HELLO_WORLD_1);
|
||||
Hello response = testClient.postFormUrlEncoded(hello);
|
||||
assertThat(response).isEqualTo(hello);
|
||||
}
|
||||
|
||||
@Test
|
||||
void namedFeignClientWorks() {
|
||||
assertThat(namedFeignClient).as("namedFeignClient was null").isNotNull();
|
||||
@@ -382,52 +392,42 @@ class ValidFeignClientTests {
|
||||
@FeignClient(name = "localapp8")
|
||||
protected interface MultipartClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/singlePart",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/singlePart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String singlePart(@RequestPart("hello") String hello);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/singlePojoPart",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/singlePojoPart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String singlePojoPart(@RequestPart("hello") Hello hello);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipart",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String multipart(@RequestPart("hello") String hello, @RequestPart("world") String world,
|
||||
@RequestPart("file") MultipartFile file);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartPojo",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartPojo", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String multipartPojo(@RequestPart("hello") String hello, @RequestPart("world") String world,
|
||||
@RequestPart("pojo1") Hello pojo1, @RequestPart("pojo2") Hello pojo2,
|
||||
@RequestPart("file") MultipartFile file);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String requestPartListOfMultipartFilesReturnsPartNames(@RequestPart("files") List<MultipartFile> files);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartFilenames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartFilenames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String requestPartListOfMultipartFilesReturnsFileNames(@RequestPart("files") List<MultipartFile> files);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartPojosFiles",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartPojosFiles", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String requestPartListOfPojosAndListOfMultipartFiles(@RequestPart("pojos") List<Hello> pojos,
|
||||
@RequestPart("files") List<MultipartFile> files);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String requestBodyListOfMultipartFiles(@RequestBody List<MultipartFile> files);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String requestBodySingleMultipartFile(@RequestBody MultipartFile file);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String requestBodyMap(@RequestBody Map<String, ?> form);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/invalid",
|
||||
consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/invalid", consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE,
|
||||
produces = TEXT_PLAIN_VALUE)
|
||||
String invalid(@RequestBody MultipartFile file);
|
||||
|
||||
}
|
||||
@@ -435,36 +435,36 @@ class ValidFeignClientTests {
|
||||
@FeignClient(name = "localapp", configuration = TestClientConfig.class)
|
||||
protected interface TestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Optional<Hello> getOptionalHello();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "${feignClient.methodLevelRequestMappingPath}")
|
||||
@GetMapping("${feignClient.methodLevelRequestMappingPath}")
|
||||
Hello getHelloUsingPropertyPlaceHolder();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hellos")
|
||||
@GetMapping("/hellos")
|
||||
List<Hello> getHellos();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hellostrings")
|
||||
@GetMapping("/hellostrings")
|
||||
List<String> getHelloStrings();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/helloheaders")
|
||||
@GetMapping("/helloheaders")
|
||||
List<String> getHelloHeaders();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/helloheadersplaceholders",
|
||||
@GetMapping(path = "/helloheadersplaceholders",
|
||||
headers = "myPlaceholderHeader=${feignClient.myPlaceholderHeader}")
|
||||
String getHelloHeadersPlaceholders();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/helloparams")
|
||||
@GetMapping("/helloparams")
|
||||
List<String> getParams(@RequestParam("params") List<String> params);
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/formattedparams")
|
||||
@GetMapping("/formattedparams")
|
||||
List<LocalDate> getFormattedParams(
|
||||
@RequestParam("params") @DateTimeFormat(pattern = "dd-MM-yyyy") List<LocalDate> params);
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/noContent")
|
||||
@GetMapping("/noContent")
|
||||
ResponseEntity<Void> noContent();
|
||||
|
||||
@RequestMapping(method = RequestMethod.HEAD, path = "/head")
|
||||
@@ -477,21 +477,24 @@ class ValidFeignClientTests {
|
||||
produces = "application/vnd.io.spring.cloud.test.v1+json", path = "/complex")
|
||||
String moreComplexContentType(String body);
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/tostring")
|
||||
@GetMapping("/tostring")
|
||||
String getToString(@RequestParam("arg") Arg arg);
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/tostring2")
|
||||
@GetMapping("/tostring2")
|
||||
String getToString(@RequestParam("arg") OtherArg arg);
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/tostringcollection")
|
||||
@GetMapping("/tostringcollection")
|
||||
Collection<String> getToString(@RequestParam("arg") Collection<OtherArg> args);
|
||||
|
||||
@PostMapping(path = "/form-urlencoded", consumes = APPLICATION_FORM_URLENCODED_VALUE)
|
||||
Hello postFormUrlEncoded(Hello hello);
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "localapp1")
|
||||
protected interface TestClientServiceId {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
@@ -499,10 +502,10 @@ class ValidFeignClientTests {
|
||||
@FeignClient(name = "localapp2", decode404 = true)
|
||||
protected interface DecodingTestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/notFound")
|
||||
@GetMapping("/notFound")
|
||||
ResponseEntity<String> notFound();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/notFound")
|
||||
@GetMapping("/notFound")
|
||||
Optional<String> optional();
|
||||
|
||||
}
|
||||
@@ -526,22 +529,12 @@ class ValidFeignClientTests {
|
||||
|
||||
@Bean
|
||||
public RequestInterceptor interceptor1() {
|
||||
return new RequestInterceptor() {
|
||||
@Override
|
||||
public void apply(RequestTemplate template) {
|
||||
template.header(MYHEADER1, "myheader1value");
|
||||
}
|
||||
};
|
||||
return template -> template.header(MYHEADER1, "myheader1value");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RequestInterceptor interceptor2() {
|
||||
return new RequestInterceptor() {
|
||||
@Override
|
||||
public void apply(RequestTemplate template) {
|
||||
template.header(MYHEADER2, "myheader2value");
|
||||
}
|
||||
};
|
||||
return template -> template.header(MYHEADER2, "myheader2value");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -570,27 +563,21 @@ class ValidFeignClientTests {
|
||||
|
||||
@Bean
|
||||
FeignFormatterRegistrar feignFormatterRegistrar() {
|
||||
return new FeignFormatterRegistrar() {
|
||||
return registry -> registry.addFormatter(new Formatter<OtherArg>() {
|
||||
|
||||
@Override
|
||||
public void registerFormatters(FormatterRegistry registry) {
|
||||
registry.addFormatter(new Formatter<OtherArg>() {
|
||||
|
||||
@Override
|
||||
public String print(OtherArg object, Locale locale) {
|
||||
if ("foo".equals(object.value)) {
|
||||
return "bar";
|
||||
}
|
||||
return object.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OtherArg parse(String text, Locale locale) throws ParseException {
|
||||
return new OtherArg(text);
|
||||
}
|
||||
});
|
||||
public String print(OtherArg object, Locale locale) {
|
||||
if ("foo".equals(object.value)) {
|
||||
return "bar";
|
||||
}
|
||||
return object.value;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public OtherArg parse(String text, Locale locale) throws ParseException {
|
||||
return new OtherArg(text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -598,23 +585,23 @@ class ValidFeignClientTests {
|
||||
return new JsonFormWriter();
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hello")
|
||||
@GetMapping("/hello")
|
||||
public Hello getHello() {
|
||||
return new Hello(HELLO_WORLD_1);
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hello2")
|
||||
@GetMapping("/hello2")
|
||||
public Hello getHello2() {
|
||||
return new Hello(OI_TERRA_2);
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hellos")
|
||||
@GetMapping("/hellos")
|
||||
public List<Hello> getHellos() {
|
||||
ArrayList<Hello> hellos = getHelloList();
|
||||
return hellos;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hellostrings")
|
||||
@GetMapping("/hellostrings")
|
||||
public List<String> getHelloStrings() {
|
||||
ArrayList<String> hellos = new ArrayList<>();
|
||||
hellos.add(HELLO_WORLD_1);
|
||||
@@ -622,7 +609,7 @@ class ValidFeignClientTests {
|
||||
return hellos;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/helloheaders")
|
||||
@GetMapping("/helloheaders")
|
||||
public List<String> getHelloHeaders(@RequestHeader(MYHEADER1) String myheader1,
|
||||
@RequestHeader(MYHEADER2) String myheader2) {
|
||||
ArrayList<String> headers = new ArrayList<>();
|
||||
@@ -631,23 +618,23 @@ class ValidFeignClientTests {
|
||||
return headers;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/helloheadersplaceholders")
|
||||
@GetMapping("/helloheadersplaceholders")
|
||||
public String getHelloHeadersPlaceholders(@RequestHeader("myPlaceholderHeader") String myPlaceholderHeader) {
|
||||
return myPlaceholderHeader;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/helloparams")
|
||||
@GetMapping("/helloparams")
|
||||
public List<String> getParams(@RequestParam("params") List<String> params) {
|
||||
return params;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/formattedparams")
|
||||
@GetMapping("/formattedparams")
|
||||
public List<LocalDate> getFormattedParams(
|
||||
@RequestParam("params") @DateTimeFormat(pattern = "dd-MM-yyyy") List<LocalDate> params) {
|
||||
return params;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/noContent")
|
||||
@GetMapping("/noContent")
|
||||
ResponseEntity<Void> noContent() {
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
@@ -662,7 +649,7 @@ class ValidFeignClientTests {
|
||||
throw new RuntimeException("always fails");
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/notFound")
|
||||
@GetMapping("/notFound")
|
||||
ResponseEntity<String> notFound() {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body((String) null);
|
||||
}
|
||||
@@ -676,17 +663,17 @@ class ValidFeignClientTests {
|
||||
return body;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/tostring")
|
||||
@GetMapping("/tostring")
|
||||
String getToString(@RequestParam("arg") Arg arg) {
|
||||
return arg.toString();
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/tostring2")
|
||||
@GetMapping("/tostring2")
|
||||
String getToString(@RequestParam("arg") OtherArg arg) {
|
||||
return arg.value;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/tostringcollection")
|
||||
@GetMapping("/tostringcollection")
|
||||
Collection<String> getToString(@RequestParam("arg") Collection<OtherArg> args) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (OtherArg arg : args) {
|
||||
@@ -695,47 +682,40 @@ class ValidFeignClientTests {
|
||||
return result;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/singlePart",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/singlePart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String singlePart(@RequestPart("hello") String hello) {
|
||||
return hello;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/singlePojoPart",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/singlePojoPart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String singlePojoPart(@RequestPart("hello") Hello hello) {
|
||||
return hello.getMessage();
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipart",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String multipart(@RequestPart("hello") String hello, @RequestPart("world") String world,
|
||||
@RequestPart("file") MultipartFile file) {
|
||||
return hello + world + file.getOriginalFilename();
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartPojo",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartPojo", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String multipartPojo(@RequestPart("hello") String hello, @RequestPart("world") String world,
|
||||
@RequestPart("pojo1") Hello pojo1, @RequestPart("pojo2") Hello pojo2,
|
||||
@RequestPart("file") MultipartFile file) {
|
||||
return hello + world + pojo1.getMessage() + pojo2.getMessage() + file.getOriginalFilename();
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String multipartNames(HttpServletRequest request) throws Exception {
|
||||
return request.getParts().stream().map(Part::getName).collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartFilenames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartFilenames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String multipartFilenames(HttpServletRequest request) throws Exception {
|
||||
return request.getParts().stream().map(Part::getSubmittedFileName).collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartPojosFiles",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartPojosFiles", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String requestPartListOfPojosAndListOfMultipartFiles(@RequestPart("pojos") List<Hello> pojos,
|
||||
@RequestPart("files") List<MultipartFile> files) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
@@ -751,6 +731,11 @@ class ValidFeignClientTests {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@PostMapping(path = "/form-urlencoded", consumes = APPLICATION_FORM_URLENCODED_VALUE)
|
||||
Hello postFormUrlEncoded(Hello hello) {
|
||||
return hello;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Hello {
|
||||
|
||||
+2
-3
@@ -35,8 +35,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -70,7 +69,7 @@ public class FeignClientEnvVarTests {
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
protected static class Application {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
public String getHello() {
|
||||
return "hello world 1";
|
||||
}
|
||||
|
||||
+4
-5
@@ -37,8 +37,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -83,7 +82,7 @@ public class FeignClientScanningTests {
|
||||
@FeignClient("localapp123")
|
||||
protected interface TestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
String getHello();
|
||||
|
||||
}
|
||||
@@ -91,7 +90,7 @@ public class FeignClientScanningTests {
|
||||
@FeignClient("${feignClient.localappName}")
|
||||
protected interface TestClientByKey {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
String getHello();
|
||||
|
||||
}
|
||||
@@ -104,7 +103,7 @@ public class FeignClientScanningTests {
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
protected static class Application {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
public String getHello() {
|
||||
return "hello world 1";
|
||||
}
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
# This configuration used by test class FeignClientWithRefreshableOptionsTest
|
||||
logging.level.org.springframework.cloud.openfeign=debug
|
||||
feign.client.default-to-properties=true
|
||||
feign.client.default-config=default
|
||||
feign.client.refresh-enabled=true
|
||||
feign.client.config.default.connectTimeout=5000
|
||||
feign.client.config.default.readTimeout=5000
|
||||
feign.client.config.default.loggerLevel=full
|
||||
feign.client.config.connectTimeout.connectTimeout=2000
|
||||
feign.client.config.readTimeout.readTimeout=2000
|
||||
@@ -6,16 +6,16 @@
|
||||
<parent>
|
||||
<artifactId>spring-cloud-dependencies-parent</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>3.0.2</version>
|
||||
<version>3.0.4</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-openfeign-dependencies</artifactId>
|
||||
<version>3.0.2</version>
|
||||
<version>3.0.4</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>
|
||||
<spring-security-oauth2-autoconfigure.version>2.1.2.RELEASE</spring-security-oauth2-autoconfigure.version>
|
||||
</properties>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>3.0.2</version>
|
||||
<version>3.0.4</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user