Compare commits

...
25 Commits
Author SHA1 Message Date
Olga MaciaszekSharma 161789e33a Going back to snapshots. 2021-11-17 23:12:16 +01:00
buildmaster 650b14ac22 Bumping versions 2021-10-21 11:07:10 +00:00
Olga MaciaszekSharma 17d0161faf Reformat. 2021-10-21 12:40:20 +02:00
Olga MaciaszekSharma d6783a6f1e Block clas-level request mapping on Feign clients. 2021-10-21 12:14:19 +02:00
Olga MaciaszekSharma 1275875ddb Reformat. 2021-07-07 18:58:48 +02:00
Olga MaciaszekSharma c4b9492a9b Bumping versions to 2.2.10.BUILD-SNAPSHOT after release
This reverts commit f7e490c466.
2021-07-07 18:46:53 +02:00
Olga MaciaszekSharma 3acf5f9e92 Going back to snapshots
This reverts commit 6479db0a54.
2021-07-07 18:46:20 +02:00
buildmaster e9e38d0274 Bumping versions 2021-07-07 14:07:20 +00:00
Olga MaciaszekSharma 28b50401c5 Revert "Update SNAPSHOT to 2.2.9.RELEASE"
This reverts commit 1005749eb5.
2021-07-07 14:38:12 +02:00
Olga MaciaszekSharma 6479db0a54 Revert "Going back to snapshots"
This reverts commit 190f5a9083.
2021-07-07 14:37:39 +02:00
Olga MaciaszekSharma f7e490c466 Revert "Bumping versions to 2.2.10.BUILD-SNAPSHOT after release"
This reverts commit d60cda824d.
2021-07-07 14:37:03 +02:00
buildmaster d60cda824d Bumping versions to 2.2.10.BUILD-SNAPSHOT after release 2021-07-06 14:53:53 +00:00
buildmaster 190f5a9083 Going back to snapshots 2021-07-06 14:53:53 +00:00
buildmaster 1005749eb5 Update SNAPSHOT to 2.2.9.RELEASE 2021-07-06 14:52:01 +00:00
Michal Domagala df1f982046 Fix for RetryableFeignBlockingLoadBalancerClient closes stream (#569) 2021-07-05 13:03:06 +02:00
Michal Domagala baf7cf95f2 Simplify Log.info logic (#556) 2021-07-05 12:43:59 +02:00
Olga MaciaszekSharma 00ea88c67f Fix typo. Refactor. 2021-07-05 11:55:41 +02:00
Olga MaciaszekSharma d897b18e16 Add missing author tags. Refactor. 2021-07-05 11:26:06 +02:00
Can Bezmen 8c08204c78 Issue #549
* Fixed: Form URL Encoded bug fixed.

Fixes gh-549.
2021-07-05 11:19:29 +02:00
Olga MaciaszekSharma 2f43715f6f Change log level. Fixes gh-552. 2021-06-10 16:04:17 +02:00
Nick Smith 421df91a80 Filters out null matrix parameters 2021-05-24 15:58:27 +02:00
Olga MaciaszekSharma ec1f2ad5bb Refactor FeignHalConfiguration to use HalMediaTypeConfiguration under the hood. Fixes gh-541. 2021-05-12 13:51:43 +02:00
Olga MaciaszekSharma 5baa8c8ed5 Remove master references. 2021-04-26 19:24:36 +02:00
buildmaster 0684ee4bc2 Bumping versions to 2.2.9.BUILD-SNAPSHOT after release 2021-04-21 17:11:52 +00:00
buildmaster 0b34d89adc Going back to snapshots 2021-04-21 17:11:52 +00:00
24 changed files with 306 additions and 291 deletions
+4 -4
View File
@@ -3,7 +3,7 @@
Spring Cloud is released under the non-restrictive Apache 2.0 license, Spring Cloud is released under the non-restrictive Apache 2.0 license,
and follows a very standard Github development process, using Github 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 to contribute even something trivial please do not hesitate, but
follow the guidelines below. follow the guidelines below.
@@ -17,7 +17,7 @@ given the ability to merge pull requests.
## Code of Conduct ## Code of Conduct
This project adheres to the Contributor Covenant [code of 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. unacceptable behavior to spring-code-of-conduct@pivotal.io.
## Code Conventions and Housekeeping ## 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 * Use the Spring Framework code format conventions. If you use Eclipse
you can import formatter settings using the you can import formatter settings using the
`eclipse-code-formatter.xml` file from 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. [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 * 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 `@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). than cosmetic changes).
* Add some Javadocs and, if you change the namespace, some XSD doc elements. * 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. * 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). 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), * 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 if you are fixing an existing issue please add `Fixes gh-XXXX` at the end of the commit
+4 -18
View File
@@ -7,7 +7,7 @@ Edit the files in the src/main/asciidoc/ directory instead.
image::https://github.com/spring-cloud/spring-cloud-openfeign/workflows/Build/badge.svg?branch=2.2.x&style=svg["Build",link="https://github.com/spring-cloud/spring-cloud-openfeign/actions"] image::https://github.com/spring-cloud/spring-cloud-openfeign/workflows/Build/badge.svg?branch=2.2.x&style=svg["Build",link="https://github.com/spring-cloud/spring-cloud-openfeign/actions"]
image:https://codecov.io/gh/spring-cloud/spring-cloud-openfeign/branch/master/graph/badge.svg["Codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-openfeign"] image:https://codecov.io/gh/spring-cloud/spring-cloud-openfeign/branch/main/graph/badge.svg["Codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-openfeign"]
image:https://api.codacy.com/project/badge/Grade/97b04c4e609c4b4f86b415e4437a6484["Codacy code quality", link="https://www.codacy.com/app/Spring-Cloud/spring-cloud-openfeign?utm_source=github.com&utm_medium=referral&utm_content=spring-cloud/spring-cloud-openfeign&utm_campaign=Badge_Grade"] image:https://api.codacy.com/project/badge/Grade/97b04c4e609c4b4f86b415e4437a6484["Codacy code quality", link="https://www.codacy.com/app/Spring-Cloud/spring-cloud-openfeign?utm_source=github.com&utm_medium=referral&utm_content=spring-cloud/spring-cloud-openfeign&utm_campaign=Badge_Grade"]
@@ -66,23 +66,9 @@ the `.mvn` configuration, so if you find you have to do it to make a
build succeed, please raise a ticket to get the settings added to build succeed, please raise a ticket to get the settings added to
source control. source control.
For hints on how to build the project look in `.travis.yml` if there The projects that require middleware (i.e. Redis) for testing generally
is one. There should be a "script" and maybe "install" command. Also require that a local instance of [Docker](https://www.docker.com/get-started) is installed and running.
look at the "services" section to see if any services need to be
running locally (e.g. mongo or rabbit). Ignore the git-related bits
that you might find in "before_install" since they're related to setting git
credentials and you already have those.
The projects that require middleware generally include a
`docker-compose.yml`, so consider using
https://docs.docker.com/compose/[Docker Compose] to run the middeware servers
in Docker containers. See the README in the
https://github.com/spring-cloud-samples/scripts[scripts demo
repository] for specific instructions about the common cases of mongo,
rabbit and redis.
NOTE: If all else fails, build with the command from `.travis.yml` (usually
`./mvnw install`).
=== Documentation === Documentation
@@ -319,4 +305,4 @@ IMPORTANT: Remember to set the `Scan Scope` to `All sources` since we apply chec
== License == 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
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId> <artifactId>spring-cloud-openfeign</artifactId>
<version>2.2.8.RELEASE</version> <version>2.2.11.BUILD-SNAPSHOT</version>
</parent> </parent>
<artifactId>spring-cloud-openfeign-docs</artifactId> <artifactId>spring-cloud-openfeign-docs</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
+4 -4
View File
@@ -1,6 +1,6 @@
image::https://github.com/spring-cloud/spring-cloud-openfeign/workflows/Build/badge.svg?branch=2.2.x&style=svg["Build",link="https://github.com/spring-cloud/spring-cloud-openfeign/actions"] image::https://github.com/spring-cloud/spring-cloud-openfeign/workflows/Build/badge.svg?branch=2.2.x&style=svg["Build",link="https://github.com/spring-cloud/spring-cloud-openfeign/actions"]
image:https://codecov.io/gh/spring-cloud/spring-cloud-openfeign/branch/master/graph/badge.svg["Codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-openfeign"] image:https://codecov.io/gh/spring-cloud/spring-cloud-openfeign/branch/main/graph/badge.svg["Codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-openfeign"]
image:https://api.codacy.com/project/badge/Grade/97b04c4e609c4b4f86b415e4437a6484["Codacy code quality", link="https://www.codacy.com/app/Spring-Cloud/spring-cloud-openfeign?utm_source=github.com&utm_medium=referral&utm_content=spring-cloud/spring-cloud-openfeign&utm_campaign=Badge_Grade"] 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 == 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 == 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 == 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].
+6 -6
View File
@@ -39,7 +39,7 @@ function check_if_anything_to_sync() {
} }
function retrieve_current_branch() { 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 # 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 # If there is a branch already passed will reuse it - otherwise will try to find it
CURRENT_BRANCH=${BRANCH} CURRENT_BRANCH=${BRANCH}
@@ -134,8 +134,8 @@ function add_docs_from_target() {
# Copies the docs by using the retrieved properties from Maven build # Copies the docs by using the retrieved properties from Maven build
function copy_docs_for_current_version() { function copy_docs_for_current_version() {
if [[ "${CURRENT_BRANCH}" == "master" ]] ; then if [[ "${CURRENT_BRANCH}" == "main" ]] ; then
echo -e "Current branch is master - will copy the current docs only to the root folder" echo -e "Current branch is main - will copy the current docs only to the root folder"
for f in docs/target/generated-docs/*; do for f in docs/target/generated-docs/*; do
file=${f#docs/target/generated-docs/*} file=${f#docs/target/generated-docs/*}
if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then 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" 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 # 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! # This is a little extreme. Use with care!
################################################################### ###################################################################
if [[ "${COMMIT_SUCCESSFUL}" == "yes" ]] ; then 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: the script will work in the following manner:
- if there's no gh-pages / target for docs module then the script ends - 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 - for any other branch (if that branch is whitelisted) a subfolder with branch name is created
and docs are copied there and docs are copied there
- if the version switch is passed (-v) then a tag with (v) prefix will be retrieved and a folder - 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 retrieve_doc_properties
stash_changes stash_changes
add_docs_from_target add_docs_from_target
checkout_previous_branch checkout_previous_branch
+1 -1
View File
@@ -27,7 +27,7 @@ public class WebApplication {
## Contributing ## 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 ## 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 either a https://github.com/Netflix/ribbon[Ribbon] load-balancer (see <<spring-cloud-ribbon,below for details of Ribbon support>> and <<spring-cloud-circuitbreaker,below for details of Spring Cloud CircuitBreaker support>>) or https://github.com/spring-cloud/spring-cloud-commons/blob/master/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancer.java[Spring Cloud LoadBalancer]. In the `@FeignClient` annotation the String value ("stores" above) is an arbitrary client name, which is used to create either a https://github.com/Netflix/ribbon[Ribbon] load-balancer (see <<spring-cloud-ribbon,below for details of Ribbon support>> and <<spring-cloud-circuitbreaker,below for details of Spring Cloud CircuitBreaker support>>) or https://github.com/spring-cloud/spring-cloud-commons/blob/main/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancer.java[Spring Cloud LoadBalancer].
You can also specify a URL using the `url` attribute You can also specify a URL using the `url` attribute
(absolute value or just a hostname). The name of the bean in the (absolute value or just a hostname). The name of the bean in the
application context is the fully qualified name of the interface. application context is the fully qualified name of the interface.
@@ -523,10 +523,7 @@ public interface UserClient extends UserService {
} }
---- ----
NOTE: It is generally not advisable to share an interface between a WARNING: `@FeignClient` interfaces should not be shared between server and client and annotating `@FeignClient` interfaces with `@RequestMapping` on class level is no longer supported.
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).
=== Feign request/response compression === Feign request/response compression
+4 -4
View File
@@ -4,14 +4,14 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 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> <modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-openfeign</artifactId> <artifactId>spring-cloud-openfeign</artifactId>
<version>2.2.8.RELEASE</version> <version>2.2.11.BUILD-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>Spring Cloud OpenFeign</name> <name>Spring Cloud OpenFeign</name>
<description>Spring Cloud OpenFeign</description> <description>Spring Cloud OpenFeign</description>
<parent> <parent>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId> <artifactId>spring-cloud-build</artifactId>
<version>2.3.4.RELEASE</version> <version>2.3.6.BUILD-SNAPSHOT</version>
<relativePath/> <relativePath/>
</parent> </parent>
<scm> <scm>
@@ -26,8 +26,8 @@
<properties> <properties>
<main.basedir>${basedir}</main.basedir> <main.basedir>${basedir}</main.basedir>
<jackson.version>2.11.3</jackson.version> <jackson.version>2.11.3</jackson.version>
<spring-cloud-commons.version>2.2.8.RELEASE</spring-cloud-commons.version> <spring-cloud-commons.version>2.2.9.RELEASE</spring-cloud-commons.version>
<spring-cloud-netflix.version>2.2.8.RELEASE</spring-cloud-netflix.version> <spring-cloud-netflix.version>2.2.9.RELEASE</spring-cloud-netflix.version>
<!-- Plugin versions --> <!-- Plugin versions -->
<maven-eclipse-plugin.version>2.10</maven-eclipse-plugin.version> <maven-eclipse-plugin.version>2.10</maven-eclipse-plugin.version>
+1 -1
View File
@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId> <artifactId>spring-cloud-openfeign</artifactId>
<version>2.2.8.RELEASE</version> <version>2.2.11.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository --> <relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent> </parent>
<artifactId>spring-cloud-openfeign-core</artifactId> <artifactId>spring-cloud-openfeign-core</artifactId>
@@ -373,12 +373,10 @@ public class FeignClientFactoryBean implements FactoryBean<Object>, Initializing
Feign.Builder builder = feign(context); Feign.Builder builder = feign(context);
if (!StringUtils.hasText(url)) { if (!StringUtils.hasText(url)) {
if (url != null && LOG.isWarnEnabled()) {
LOG.warn( if (LOG.isInfoEnabled()) {
"The provided URL is empty. Will try picking an instance via load-balancing."); LOG.info("For '" + name
} + "' URL not provided. Will try picking an instance via load-balancing.");
else if (LOG.isDebugEnabled()) {
LOG.debug("URL not provided. Will use LoadBalancer.");
} }
if (!name.startsWith("http")) { if (!name.startsWith("http")) {
url = "http://" + name; url = "http://" + name;
@@ -75,7 +75,7 @@ public class MatrixVariableParameterProcessor implements AnnotatedParameterProce
private String expandMap(Object object) { private String expandMap(Object object) {
Map<String, Object> paramMap = (Map) object; Map<String, Object> paramMap = (Map) object;
return paramMap.keySet().stream() return paramMap.keySet().stream().filter(key -> paramMap.get(key) != null)
.map(key -> ";" + key + "=" + paramMap.get(key).toString()) .map(key -> ";" + key + "=" + paramMap.get(key).toString())
.collect(Collectors.joining()); .collect(Collectors.joining());
} }
@@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,37 +16,30 @@
package org.springframework.cloud.openfeign.hateoas; package org.springframework.cloud.openfeign.hateoas;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; 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.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration; 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.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.hateoas.RepresentationModel; import org.springframework.hateoas.RepresentationModel;
import org.springframework.hateoas.mediatype.MessageResolver; import org.springframework.hateoas.mediatype.hal.HalMediaTypeConfiguration;
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.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter; import org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter;
import static org.springframework.hateoas.MediaTypes.HAL_JSON; import static org.springframework.hateoas.MediaTypes.HAL_JSON;
/** /**
* @author Hector Espert * @author Hector Espert
* @author Olga Maciaszek-Sharma
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication @ConditionalOnWebApplication
@@ -54,41 +47,19 @@ import static org.springframework.hateoas.MediaTypes.HAL_JSON;
@AutoConfigureAfter({ JacksonAutoConfiguration.class, @AutoConfigureAfter({ JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
RepositoryRestMvcAutoConfiguration.class }) RepositoryRestMvcAutoConfiguration.class })
@AutoConfigureBefore(HypermediaAutoConfiguration.class)
public class FeignHalAutoConfiguration { public class FeignHalAutoConfiguration {
@Bean @Bean
@ConditionalOnBean(HalMediaTypeConfiguration.class)
@ConditionalOnMissingBean @ConditionalOnMissingBean
public TypeConstrainedMappingJackson2HttpMessageConverter halJacksonHttpMessageConverter( public TypeConstrainedMappingJackson2HttpMessageConverter halJacksonHttpMessageConverter(
ObjectProvider<ObjectMapper> objectMapper, ObjectProvider<ObjectMapper> objectMapper,
ObjectProvider<HalConfiguration> halConfiguration, HalMediaTypeConfiguration halConfiguration) {
ObjectProvider<MessageResolver> messageResolver,
ObjectProvider<CurieProvider> curieProvider,
ObjectProvider<LinkRelationProvider> linkRelationProvider) {
ObjectMapper mapper = objectMapper.getIfAvailable(ObjectMapper::new).copy(); ObjectMapper mapper = objectMapper.getIfAvailable(ObjectMapper::new).copy();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); halConfiguration.configureObjectMapper(mapper);
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);
}
TypeConstrainedMappingJackson2HttpMessageConverter converter = new TypeConstrainedMappingJackson2HttpMessageConverter( TypeConstrainedMappingJackson2HttpMessageConverter converter = new TypeConstrainedMappingJackson2HttpMessageConverter(
RepresentationModel.class); RepresentationModel.class);
converter.setSupportedMediaTypes(Arrays.asList(HAL_JSON)); converter.setSupportedMediaTypes(Collections.singletonList(HAL_JSON));
converter.setObjectMapper(mapper); converter.setObjectMapper(mapper);
return converter; return converter;
} }
@@ -0,0 +1,50 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.loadbalancer;
import java.io.ByteArrayInputStream;
import java.net.URI;
import feign.Response;
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
/**
* A {@link RetryableStatusCodeException} for {@link Response}s.
*
* @author Ryan Baxter
*/
public class LoadBalancerResponseStatusCodeException
extends RetryableStatusCodeException {
private final Response response;
public LoadBalancerResponseStatusCodeException(String serviceId, Response response,
byte[] body, URI uri) {
super(serviceId, response.status(), response, uri);
this.response = Response.builder()
.body(new ByteArrayInputStream(body), body.length)
.headers(response.headers()).reason(response.reason())
.status(response.status()).request(response.request()).build();
}
@Override
public Response getResponse() {
return this.response;
}
}
@@ -36,7 +36,6 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancedRecoveryCallbac
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient; import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@@ -46,6 +45,7 @@ import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.backoff.NoBackOffPolicy; import org.springframework.retry.backoff.NoBackOffPolicy;
import org.springframework.retry.policy.NeverRetryPolicy; import org.springframework.retry.policy.NeverRetryPolicy;
import org.springframework.retry.support.RetryTemplate; import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.StreamUtils;
/** /**
* A {@link Client} implementation that provides Spring Retry support for requests * A {@link Client} implementation that provides Spring Retry support for requests
@@ -112,9 +112,11 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
LOG.debug( LOG.debug(
String.format("Retrying on status code: %d", responseStatus)); String.format("Retrying on status code: %d", responseStatus));
} }
byte[] byteArray = response.body() == null ? new byte[] {}
: StreamUtils.copyToByteArray(response.body().asInputStream());
response.close(); response.close();
throw new RetryableStatusCodeException(serviceId, responseStatus, throw new LoadBalancerResponseStatusCodeException(serviceId, response,
response, URI.create(request.url())); byteArray, URI.create(request.url()));
} }
return response; return response;
}, new LoadBalancedRecoveryCallback<Response, Response>() { }, new LoadBalancedRecoveryCallback<Response, Response>() {
@@ -24,6 +24,7 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Objects;
import java.util.stream.Stream; import java.util.stream.Stream;
import feign.RequestTemplate; 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.getHeaders;
import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHeaders; 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 * @author Spencer Gibb
@@ -56,6 +61,7 @@ import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHead
* @author Aaron Whiteside * @author Aaron Whiteside
* @author Darren Foong * @author Darren Foong
* @author Olga Maciaszek-Sharma * @author Olga Maciaszek-Sharma
* @author Can Bezmen
*/ */
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public class SpringEncoder implements Encoder { public class SpringEncoder implements Encoder {
@@ -99,8 +105,8 @@ public class SpringEncoder implements Encoder {
requestContentType = MediaType.valueOf(type); requestContentType = MediaType.valueOf(type);
} }
if (isMultipartType(requestContentType)) { if (isFormRelatedContentType(requestContentType)) {
this.springFormEncoder.encode(requestBody, bodyType, request); springFormEncoder.encode(requestBody, bodyType, request);
return; return;
} }
else { else {
@@ -117,7 +123,7 @@ public class SpringEncoder implements Encoder {
private void encodeWithMessageConverter(Object requestBody, Type bodyType, private void encodeWithMessageConverter(Object requestBody, Type bodyType,
RequestTemplate request, MediaType requestContentType) { RequestTemplate request, MediaType requestContentType) {
for (HttpMessageConverter messageConverter : this.messageConverters.getObject() for (HttpMessageConverter messageConverter : messageConverters.getObject()
.getConverters()) { .getConverters()) {
FeignOutputMessage outputMessage; FeignOutputMessage outputMessage;
try { try {
@@ -223,9 +229,18 @@ public class SpringEncoder implements Encoder {
} }
} }
private boolean isFormRelatedContentType(MediaType requestContentType) {
return isMultipartType(requestContentType)
|| isFormUrlEncoded(requestContentType);
}
private boolean isMultipartType(MediaType requestContentType) { private boolean isMultipartType(MediaType requestContentType) {
return Arrays.asList(MediaType.MULTIPART_FORM_DATA, MediaType.MULTIPART_MIXED, return Arrays.asList(MULTIPART_FORM_DATA, MULTIPART_MIXED, MULTIPART_RELATED)
MediaType.MULTIPART_RELATED).contains(requestContentType); .contains(requestContentType);
}
private boolean isFormUrlEncoded(MediaType requestContentType) {
return Objects.equals(APPLICATION_FORM_URLENCODED, requestContentType);
} }
private boolean binaryContentType(FeignOutputMessage outputMessage) { private boolean binaryContentType(FeignOutputMessage outputMessage) {
@@ -244,21 +259,21 @@ public class SpringEncoder implements Encoder {
private final HttpHeaders httpHeaders; private final HttpHeaders httpHeaders;
private FeignOutputMessage(RequestTemplate request) { private FeignOutputMessage(RequestTemplate request) {
this.httpHeaders = getHttpHeaders(request.headers()); httpHeaders = getHttpHeaders(request.headers());
} }
@Override @Override
public OutputStream getBody() throws IOException { public OutputStream getBody() throws IOException {
return this.outputStream; return outputStream;
} }
@Override @Override
public HttpHeaders getHeaders() { public HttpHeaders getHeaders() {
return this.httpHeaders; return httpHeaders;
} }
public ByteArrayOutputStream getOutputStream() { public ByteArrayOutputStream getOutputStream() {
return this.outputStream; return outputStream;
} }
} }
@@ -35,6 +35,8 @@ import feign.Feign;
import feign.MethodMetadata; import feign.MethodMetadata;
import feign.Param; import feign.Param;
import feign.Request; import feign.Request;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor; import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
import org.springframework.cloud.openfeign.CollectionFormat; import org.springframework.cloud.openfeign.CollectionFormat;
@@ -83,6 +85,8 @@ import static org.springframework.core.annotation.AnnotatedElementUtils.findMerg
public class SpringMvcContract extends Contract.BaseContract public class SpringMvcContract extends Contract.BaseContract
implements ResourceLoaderAware { implements ResourceLoaderAware {
private static final Log LOG = LogFactory.getLog(SpringMvcContract.class);
private static final String ACCEPT = "Accept"; private static final String ACCEPT = "Accept";
private static final String CONTENT_TYPE = "Content-Type"; private static final String CONTENT_TYPE = "Content-Type";
@@ -181,49 +185,19 @@ public class SpringMvcContract extends Contract.BaseContract
@Override @Override
protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) { protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
if (clz.getInterfaces().length == 0) { RequestMapping classAnnotation = findMergedAnnotation(clz, RequestMapping.class);
RequestMapping classAnnotation = findMergedAnnotation(clz, if (classAnnotation != null) {
RequestMapping.class); LOG.error("Cannot process class: " + clz.getName()
if (classAnnotation != null) { + ". @RequestMapping annotation is not allowed on @FeignClient interfaces.");
// Prepend path from class annotation if specified throw new IllegalArgumentException(
if (classAnnotation.value().length > 0) { "@RequestMapping annotation not allowed on @FeignClient interfaces");
String pathValue = emptyToNull(classAnnotation.value()[0]);
pathValue = resolve(pathValue);
if (!pathValue.startsWith("/")) {
pathValue = "/" + pathValue;
}
data.template().uri(pathValue);
if (data.template().decodeSlash() != decodeSlash) {
data.template().decodeSlash(decodeSlash);
}
}
}
} }
} }
@Override @Override
public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) { public MethodMetadata parseAndValidateMetadata(Class<?> targetType, Method method) {
processedMethods.put(Feign.configKey(targetType, method), method); processedMethods.put(Feign.configKey(targetType, method), method);
MethodMetadata md = super.parseAndValidateMetadata(targetType, method); return super.parseAndValidateMetadata(targetType, method);
RequestMapping classAnnotation = findMergedAnnotation(targetType,
RequestMapping.class);
if (classAnnotation != null) {
// produces - use from class annotation only if method has not specified this
if (!md.template().headers().containsKey(ACCEPT)) {
parseProduces(md, method, classAnnotation);
}
// consumes -- use from class annotation only if method has not specified this
if (!md.template().headers().containsKey(CONTENT_TYPE)) {
parseConsumes(md, method, classAnnotation);
}
// headers -- class annotation is inherited to methods, always write these if
// present
parseHeaders(md, method, classAnnotation);
}
return md;
} }
@Override @Override
@@ -16,7 +16,7 @@
package org.springframework.cloud.openfeign.hateoas; package org.springframework.cloud.openfeign.hateoas;
import java.util.Arrays; import java.util.Collections;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test; import org.junit.Test;
@@ -26,9 +26,11 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.hateoas.mediatype.MessageResolver; import org.springframework.hateoas.mediatype.MessageResolver;
import org.springframework.hateoas.mediatype.hal.CurieProvider; import org.springframework.hateoas.mediatype.hal.CurieProvider;
import org.springframework.hateoas.mediatype.hal.HalConfiguration; 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.mediatype.hal.Jackson2HalModule;
import org.springframework.hateoas.server.LinkRelationProvider; import org.springframework.hateoas.server.LinkRelationProvider;
import org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter; import org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter;
@@ -45,20 +47,20 @@ import static org.springframework.hateoas.MediaTypes.HAL_JSON;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class FeignHalAutoConfigurationTests { public class FeignHalAutoConfigurationTests {
@Mock
private ObjectProvider<ObjectMapper> objectMapper;
@Mock @Mock
private ObjectProvider<HalConfiguration> halConfiguration; private ObjectProvider<HalConfiguration> halConfiguration;
@Mock @Mock
private ObjectProvider<LinkRelationProvider> relProvider; private ObjectProvider<ObjectMapper> objectMapper;
@Mock
private LinkRelationProvider relProvider;
@Mock @Mock
private ObjectProvider<CurieProvider> curieProvider; private ObjectProvider<CurieProvider> curieProvider;
@Mock @Mock
private ObjectProvider<MessageResolver> messageResolver; private MessageResolver messageResolver;
@InjectMocks @InjectMocks
private FeignHalAutoConfiguration feignHalAutoConfiguration; private FeignHalAutoConfiguration feignHalAutoConfiguration;
@@ -70,17 +72,19 @@ public class FeignHalAutoConfigurationTests {
when(halConfiguration.getIfAvailable(any())) when(halConfiguration.getIfAvailable(any()))
.thenReturn(mock(HalConfiguration.class)); .thenReturn(mock(HalConfiguration.class));
when(relProvider.getIfAvailable()).thenReturn(mock(LinkRelationProvider.class));
when(curieProvider.getIfAvailable(any())).thenReturn(mock(CurieProvider.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 TypeConstrainedMappingJackson2HttpMessageConverter converter = feignHalAutoConfiguration
.halJacksonHttpMessageConverter(objectMapper, halConfiguration, .halJacksonHttpMessageConverter(objectMapper, halMediaTypeConfiguration);
messageResolver, curieProvider, relProvider);
assertThat(converter).isNotNull(); assertThat(converter).isNotNull();
assertThat(converter.getObjectMapper()).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())) assertThat(Jackson2HalModule.isAlreadyRegisteredIn(converter.getObjectMapper()))
.isTrue(); .isTrue();
@@ -54,7 +54,7 @@ import static org.mockito.Mockito.when;
* {@link FeignBlockingLoadBalancerClient} and its delegates. * {@link FeignBlockingLoadBalancerClient} and its delegates.
* *
* @see <a href= * @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 * @author Olga Maciaszek-Sharma
*/ */
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
@@ -16,7 +16,10 @@
package org.springframework.cloud.openfeign.loadbalancer; package org.springframework.cloud.openfeign.loadbalancer;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Collection; import java.util.Collection;
@@ -27,6 +30,7 @@ import java.util.Map;
import feign.Client; import feign.Client;
import feign.Request; import feign.Request;
import feign.Response; import feign.Response;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@@ -58,7 +62,7 @@ import static org.mockito.Mockito.when;
* {@link RetryableFeignBlockingLoadBalancerClient} and its delegates. * {@link RetryableFeignBlockingLoadBalancerClient} and its delegates.
* *
* @see <a href= * @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 * @author Olga Maciaszek-Sharma
*/ */
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
@@ -113,6 +117,15 @@ class RetryableFeignBlockingLoadBalancerClientTests {
return Response.builder().request(testRequest()).status(status).build(); 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 @Test
void shouldExecuteOriginalRequestIfInstanceNotFound() throws IOException { void shouldExecuteOriginalRequestIfInstanceNotFound() throws IOException {
Request request = testRequest(); Request request = testRequest();
@@ -148,6 +161,27 @@ class RetryableFeignBlockingLoadBalancerClientTests {
verify(delegate, times(2)).execute(any(), any()); verify(delegate, times(2)).execute(any(), any());
} }
@Test
void shouldExposeResponseBodyOnRetry() throws IOException {
properties.getRetryableStatusCodes().add(503);
Request request = testRequest();
when(delegate.execute(any(), any())).thenReturn(testResponse(503, "foo"),
testResponse(503, "foo"));
when(retryFactory.createRetryPolicy(any(), eq(loadBalancerClient)))
.thenReturn(new BlockingLoadBalancedRetryPolicy("test",
loadBalancerClient, properties));
when(loadBalancerClient.reconstructURI(serviceInstance,
URI.create("http://test/path")))
.thenReturn(URI.create("http://testhost:80/path"));
Response response = feignBlockingLoadBalancerClient.execute(request,
new Request.Options());
String bodyContent = IOUtils
.toString(response.body().asReader(StandardCharsets.UTF_8));
assertThat(bodyContent).isEqualTo("foo");
}
@Test @Test
void shouldPassCorrectRequestToDelegate() throws IOException { void shouldPassCorrectRequestToDelegate() throws IOException {
Request request = testRequest(); Request request = testRequest();
@@ -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.ACCEPT;
import static org.springframework.http.HttpHeaders.CONTENT_LENGTH; import static org.springframework.http.HttpHeaders.CONTENT_LENGTH;
import static org.springframework.http.HttpHeaders.CONTENT_TYPE; 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.APPLICATION_OCTET_STREAM_VALUE;
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
import static org.springframework.http.MediaType.TEXT_PLAIN_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 Spencer Gibb
* @author Olga Maciaszek-Sharma * @author Olga Maciaszek-Sharma
* @author Ahmad Mozafarnia * @author Ahmad Mozafarnia
* @author Can Bezmen
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SpringEncoderTests.Application.class, @SpringBootTest(classes = SpringEncoderTests.Application.class,
@@ -192,6 +194,18 @@ public class SpringEncoderTests {
.as("Body content cannot be decoded").contains("hi"); .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 @Test
public void testNoCharsetForBinaryFiles() { public void testNoCharsetForBinaryFiles() {
Encoder encoder = context.getInstance("test", Encoder.class); Encoder encoder = context.getInstance("test", Encoder.class);
@@ -62,6 +62,7 @@ import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY;
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE;
import static feign.CollectionFormat.SSV; import static feign.CollectionFormat.SSV;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.junit.Assume.assumeTrue; import static org.junit.Assume.assumeTrue;
/** /**
@@ -182,18 +183,12 @@ public class SpringMvcContractTests {
} }
@Test @Test
public void testProcessAnnotations_Class_AnnotationsGetSpecificTest() public void testProcessAnnotations_Class_Annotations_RequestMapping() {
throws Exception { assertThatIllegalArgumentException().isThrownBy(() -> {
Method method = TestTemplate_Class_Annotations.class Method method = TestTemplate_Class_RequestMapping.class
.getDeclaredMethod("getSpecificTest", String.class, String.class); .getDeclaredMethod("getSpecificTest", String.class, String.class);
MethodMetadata data = contract contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
.parseAndValidateMetadata(method.getDeclaringClass(), method); });
assertThat(data.template().url()).isEqualTo("/prepend/{classId}/test/{testId}");
assertThat(data.template().method()).isEqualTo("GET");
assertThat(data.indexToName().get(0).iterator().next()).isEqualTo("classId");
assertThat(data.indexToName().get(1).iterator().next()).isEqualTo("testId");
} }
@Test @Test
@@ -203,7 +198,7 @@ public class SpringMvcContractTests {
MethodMetadata data = contract MethodMetadata data = contract
.parseAndValidateMetadata(method.getDeclaringClass(), method); .parseAndValidateMetadata(method.getDeclaringClass(), method);
assertThat(data.template().url()).isEqualTo("/prepend/{classId}"); assertThat(data.template().url()).isEqualTo("/");
assertThat(data.template().method()).isEqualTo("GET"); assertThat(data.template().method()).isEqualTo("GET");
assertThat(data.indexToName().get(0).iterator().next()).isEqualTo("classId"); assertThat(data.indexToName().get(0).iterator().next()).isEqualTo("classId");
@@ -211,22 +206,6 @@ public class SpringMvcContractTests {
assertThat(data.template().decodeSlash()).isTrue(); assertThat(data.template().decodeSlash()).isTrue();
} }
@Test
public void testProcessAnnotations_Class_AnnotationsGetAllTests_EncodeSlash()
throws Exception {
contract = new SpringMvcContract(Collections.emptyList(), getConversionService(),
false);
Method method = TestTemplate_Class_Annotations.class
.getDeclaredMethod("getAllTests", String.class);
MethodMetadata data = contract
.parseAndValidateMetadata(method.getDeclaringClass(), method);
assertThat(data.template().url()).isEqualTo("/prepend/{classId}");
assertThat(data.template().decodeSlash()).isFalse();
}
@Test @Test
public void testProcessAnnotations_ExtendedInterface() throws Exception { public void testProcessAnnotations_ExtendedInterface() throws Exception {
Method extendedMethod = TestTemplate_Extended.class.getMethod("getAllTests", Method extendedMethod = TestTemplate_Extended.class.getMethod("getAllTests",
@@ -249,27 +228,6 @@ public class SpringMvcContractTests {
assertThat(data.template().decodeSlash()).isTrue(); assertThat(data.template().decodeSlash()).isTrue();
} }
@Test
public void testProcessAnnotations_ExtendedInterface_EncodeSlash() throws Exception {
contract = new SpringMvcContract(Collections.emptyList(), getConversionService(),
false);
Method extendedMethod = TestTemplate_Extended.class.getMethod("getAllTests",
String.class);
MethodMetadata extendedData = contract.parseAndValidateMetadata(
extendedMethod.getDeclaringClass(), extendedMethod);
Method method = TestTemplate_Class_Annotations.class
.getDeclaredMethod("getAllTests", String.class);
MethodMetadata data = contract
.parseAndValidateMetadata(method.getDeclaringClass(), method);
assertThat(data.template().url()).isEqualTo(extendedData.template().url());
assertThat(data.template().method()).isEqualTo(extendedData.template().method());
assertThat(data.template().decodeSlash()).isFalse();
}
@Test @Test
public void testProcessAnnotations_SimplePost() throws Exception { public void testProcessAnnotations_SimplePost() throws Exception {
Method method = TestTemplate_Simple.class.getDeclaredMethod("postTest", Method method = TestTemplate_Simple.class.getDeclaredMethod("postTest",
@@ -305,8 +263,7 @@ public class SpringMvcContractTests {
MethodMetadata data = contract MethodMetadata data = contract
.parseAndValidateMetadata(method.getDeclaringClass(), method); .parseAndValidateMetadata(method.getDeclaringClass(), method);
assertThat(data.template().url()) assertThat(data.template().url()).isEqualTo("/test/{id}?amount=" + "{amount}");
.isEqualTo("/advanced/test/{id}?amount=" + "{amount}");
assertThat(data.template().method()).isEqualTo("PUT"); assertThat(data.template().method()).isEqualTo("PUT");
assertThat(data.template().headers().get("Accept").iterator().next()) assertThat(data.template().headers().get("Accept").iterator().next())
.isEqualTo(MediaType.APPLICATION_JSON_VALUE); .isEqualTo(MediaType.APPLICATION_JSON_VALUE);
@@ -341,8 +298,7 @@ public class SpringMvcContractTests {
MethodMetadata data = contract MethodMetadata data = contract
.parseAndValidateMetadata(method.getDeclaringClass(), method); .parseAndValidateMetadata(method.getDeclaringClass(), method);
assertThat(data.template().url()) assertThat(data.template().url()).isEqualTo("/test/{id}?amount=" + "{amount}");
.isEqualTo("/advanced/test/{id}?amount=" + "{amount}");
assertThat(data.template().method()).isEqualTo("PUT"); assertThat(data.template().method()).isEqualTo("PUT");
assertThat(data.template().headers().get("Accept").iterator().next()) assertThat(data.template().headers().get("Accept").iterator().next())
.isEqualTo(MediaType.APPLICATION_JSON_VALUE); .isEqualTo(MediaType.APPLICATION_JSON_VALUE);
@@ -366,8 +322,7 @@ public class SpringMvcContractTests {
MethodMetadata data = contract MethodMetadata data = contract
.parseAndValidateMetadata(method.getDeclaringClass(), method); .parseAndValidateMetadata(method.getDeclaringClass(), method);
assertThat(data.template().url()) assertThat(data.template().url()).isEqualTo("/test2?amount=" + "{amount}");
.isEqualTo("/advanced/test2?amount=" + "{amount}");
assertThat(data.template().method()).isEqualTo("PUT"); assertThat(data.template().method()).isEqualTo("PUT");
assertThat(data.template().headers().get("Accept").iterator().next()) assertThat(data.template().headers().get("Accept").iterator().next())
.isEqualTo(MediaType.APPLICATION_JSON_VALUE); .isEqualTo(MediaType.APPLICATION_JSON_VALUE);
@@ -429,7 +384,7 @@ public class SpringMvcContractTests {
MethodMetadata data = contract MethodMetadata data = contract
.parseAndValidateMetadata(method.getDeclaringClass(), method); .parseAndValidateMetadata(method.getDeclaringClass(), method);
assertThat(data.template().url()).isEqualTo("/advanced"); assertThat(data.template().url()).isEqualTo("/");
assertThat(data.template().method()).isEqualTo("GET"); assertThat(data.template().method()).isEqualTo("GET");
assertThat(data.template().headers().get("Accept").iterator().next()) assertThat(data.template().headers().get("Accept").iterator().next())
.isEqualTo(MediaType.APPLICATION_JSON_VALUE); .isEqualTo(MediaType.APPLICATION_JSON_VALUE);
@@ -539,7 +494,7 @@ public class SpringMvcContractTests {
.parseAndValidateMetadata(method.getDeclaringClass(), method); .parseAndValidateMetadata(method.getDeclaringClass(), method);
assertThat(data.template().url()) assertThat(data.template().url())
.isEqualTo("/advanced/testfallback/{id}?amount=" + "{amount}"); .isEqualTo("/testfallback/{id}?amount=" + "{amount}");
assertThat(data.template().method()).isEqualTo("PUT"); assertThat(data.template().method()).isEqualTo("PUT");
assertThat(data.template().headers().get("Accept").iterator().next()) assertThat(data.template().headers().get("Accept").iterator().next())
.isEqualTo(MediaType.APPLICATION_JSON_VALUE); .isEqualTo(MediaType.APPLICATION_JSON_VALUE);
@@ -708,6 +663,14 @@ public class SpringMvcContractTests {
} }
@RequestMapping("/prepend/{classId}") @RequestMapping("/prepend/{classId}")
public interface TestTemplate_Class_RequestMapping {
@RequestMapping(value = "/test/{testId}", method = RequestMethod.GET)
TestObject getSpecificTest(@PathVariable("classId") String classId,
@PathVariable("testId") String testId);
}
public interface TestTemplate_Class_Annotations { public interface TestTemplate_Class_Annotations {
@RequestMapping(value = "/test/{testId}", method = RequestMethod.GET) @RequestMapping(value = "/test/{testId}", method = RequestMethod.GET)
@@ -812,7 +775,6 @@ public class SpringMvcContractTests {
} }
@JsonAutoDetect @JsonAutoDetect
@RequestMapping("/advanced")
public interface TestTemplate_Advanced { public interface TestTemplate_Advanced {
@CollectionFormat(SSV) @CollectionFormat(SSV)
@@ -17,7 +17,6 @@
package org.springframework.cloud.openfeign.valid; package org.springframework.cloud.openfeign.valid;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.text.ParseException; import java.text.ParseException;
import java.time.LocalDate; import java.time.LocalDate;
@@ -49,7 +48,6 @@ import feign.Feign;
import feign.Logger; import feign.Logger;
import feign.RequestInterceptor; import feign.RequestInterceptor;
import feign.RequestTemplate; import feign.RequestTemplate;
import feign.Target;
import feign.codec.EncodeException; import feign.codec.EncodeException;
import feign.hystrix.FallbackFactory; import feign.hystrix.FallbackFactory;
import feign.hystrix.SetterFactory; import feign.hystrix.SetterFactory;
@@ -91,6 +89,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@@ -102,6 +101,7 @@ import org.springframework.web.multipart.MultipartFile;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
@@ -110,6 +110,7 @@ import static org.hamcrest.core.IsInstanceOf.instanceOf;
* @author Halvdan Hoem Grelland * @author Halvdan Hoem Grelland
* @author Aaron Whiteside * @author Aaron Whiteside
* @author Darren Foong * @author Darren Foong
* @author Can Bezmen
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = FeignClientTests.Application.class, @SpringBootTest(classes = FeignClientTests.Application.class,
@@ -178,23 +179,23 @@ public class FeignClientTests {
@Test @Test
public void testClient() { public void testClient() {
assertThat(this.testClient).as("testClient was null").isNotNull(); assertThat(testClient).as("testClient was null").isNotNull();
assertThat(Proxy.isProxyClass(this.testClient.getClass())) assertThat(Proxy.isProxyClass(testClient.getClass()))
.as("testClient is not a java Proxy").isTrue(); .as("testClient is not a java Proxy").isTrue();
InvocationHandler invocationHandler = Proxy.getInvocationHandler(this.testClient); InvocationHandler invocationHandler = Proxy.getInvocationHandler(testClient);
assertThat(invocationHandler).as("invocationHandler was null").isNotNull(); assertThat(invocationHandler).as("invocationHandler was null").isNotNull();
} }
@Test @Test
public void testRequestMappingClassLevelPropertyReplacement() { public void testRequestMappingClassLevelPropertyReplacement() {
Hello hello = this.testClient.getHelloUsingPropertyPlaceHolder(); Hello hello = testClient.getHelloUsingPropertyPlaceHolder();
assertThat(hello).as("hello was null").isNotNull(); assertThat(hello).as("hello was null").isNotNull();
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello(OI_TERRA_2)); assertThat(hello).as("first hello didn't match").isEqualTo(new Hello(OI_TERRA_2));
} }
@Test @Test
public void testSimpleType() { public void testSimpleType() {
Hello hello = this.testClient.getHello(); Hello hello = testClient.getHello();
assertThat(hello).as("hello was null").isNotNull(); assertThat(hello).as("hello was null").isNotNull();
assertThat(hello).as("first hello didn't match") assertThat(hello).as("first hello didn't match")
.isEqualTo(new Hello(HELLO_WORLD_1)); .isEqualTo(new Hello(HELLO_WORLD_1));
@@ -202,20 +203,20 @@ public class FeignClientTests {
@Test @Test
public void testOptional() { public void testOptional() {
Optional<Hello> hello = this.testClient.getOptionalHello(); Optional<Hello> hello = testClient.getOptionalHello();
assertThat(hello).isNotNull().isPresent().contains(new Hello(HELLO_WORLD_1)); assertThat(hello).isNotNull().isPresent().contains(new Hello(HELLO_WORLD_1));
} }
@Test @Test
public void testGenericType() { public void testGenericType() {
List<Hello> hellos = this.testClient.getHellos(); List<Hello> hellos = testClient.getHellos();
assertThat(hellos).as("hellos was null").isNotNull(); assertThat(hellos).as("hellos was null").isNotNull();
assertThat(getHelloList()).as("hellos didn't match").isEqualTo(hellos); assertThat(getHelloList()).as("hellos didn't match").isEqualTo(hellos);
} }
@Test @Test
public void testRequestInterceptors() { public void testRequestInterceptors() {
List<String> headers = this.testClient.getHelloHeaders(); List<String> headers = testClient.getHelloHeaders();
assertThat(headers).as("headers was null").isNotNull(); assertThat(headers).as("headers was null").isNotNull();
assertThat(headers.contains("myheader1value")) assertThat(headers.contains("myheader1value"))
.as("headers didn't contain myheader1value").isTrue(); .as("headers didn't contain myheader1value").isTrue();
@@ -225,24 +226,23 @@ public class FeignClientTests {
@Test @Test
public void testHeaderPlaceholders() { public void testHeaderPlaceholders() {
String header = this.testClient.getHelloHeadersPlaceholders(); String header = testClient.getHelloHeadersPlaceholders();
assertThat(header).as("header was null").isNotNull(); assertThat(header).as("header was null").isNotNull();
assertThat(header).as("header was wrong").isEqualTo("myPlaceholderHeaderValue"); assertThat(header).as("header was wrong").isEqualTo("myPlaceholderHeaderValue");
} }
@Test @Test
public void testFeignClientType() throws IllegalAccessException { public void testFeignClientType() {
assertThat(this.feignClient).isInstanceOf(LoadBalancerFeignClient.class); assertThat(feignClient).isInstanceOf(LoadBalancerFeignClient.class);
LoadBalancerFeignClient client = (LoadBalancerFeignClient) this.feignClient; LoadBalancerFeignClient client = (LoadBalancerFeignClient) feignClient;
Client delegate = client.getDelegate(); Client delegate = client.getDelegate();
assertThat(delegate).isInstanceOf(Client.Default.class); assertThat(delegate).isInstanceOf(Client.Default.class);
} }
@Test @Test
public void testServiceId() { public void testServiceId() {
assertThat(this.testClientServiceId).as("testClientServiceId was null") assertThat(testClientServiceId).as("testClientServiceId was null").isNotNull();
.isNotNull(); final Hello hello = testClientServiceId.getHello();
final Hello hello = this.testClientServiceId.getHello();
assertThat(hello).as("The hello response was null").isNotNull(); assertThat(hello).as("The hello response was null").isNotNull();
assertThat(hello).as("first hello didn't match") assertThat(hello).as("first hello didn't match")
.isEqualTo(new Hello(HELLO_WORLD_1)); .isEqualTo(new Hello(HELLO_WORLD_1));
@@ -251,7 +251,7 @@ public class FeignClientTests {
@Test @Test
public void testParams() { public void testParams() {
List<String> list = Arrays.asList("a", "1", "test"); List<String> list = Arrays.asList("a", "1", "test");
List<String> params = this.testClient.getParams(list); List<String> params = testClient.getParams(list);
assertThat(params).as("params was null").isNotNull(); assertThat(params).as("params was null").isNotNull();
assertThat(params.size()).as("params size was wrong").isEqualTo(list.size()); assertThat(params.size()).as("params size was wrong").isEqualTo(list.size());
} }
@@ -260,14 +260,14 @@ public class FeignClientTests {
public void testFormattedParams() { public void testFormattedParams() {
List<LocalDate> list = Arrays.asList(LocalDate.of(2001, 1, 1), List<LocalDate> list = Arrays.asList(LocalDate.of(2001, 1, 1),
LocalDate.of(2018, 6, 10)); LocalDate.of(2018, 6, 10));
List<LocalDate> params = this.testClient.getFormattedParams(list); List<LocalDate> params = testClient.getFormattedParams(list);
assertThat(params).as("params was null").isNotNull(); assertThat(params).as("params was null").isNotNull();
assertThat(params).as("params not converted correctly").isEqualTo(list); assertThat(params).as("params not converted correctly").isEqualTo(list);
} }
@Test @Test
public void testHystrixCommand() throws NoSuchMethodException { public void testHystrixCommand() throws NoSuchMethodException {
HystrixCommand<List<Hello>> command = this.testClient.getHellosHystrix(); HystrixCommand<List<Hello>> command = testClient.getHellosHystrix();
assertThat(command).as("command was null").isNotNull(); assertThat(command).as("command was null").isNotNull();
assertThat(command.getCommandGroup().name()).as( assertThat(command.getCommandGroup().name()).as(
"Hystrix command group name should match the name of the feign client") "Hystrix command group name should match the name of the feign client")
@@ -284,7 +284,7 @@ public class FeignClientTests {
@Test @Test
public void testSingle() { public void testSingle() {
Single<Hello> single = this.testClient.getHelloSingle(); Single<Hello> single = testClient.getHelloSingle();
assertThat(single).as("single was null").isNotNull(); assertThat(single).as("single was null").isNotNull();
Hello hello = single.toBlocking().value(); Hello hello = single.toBlocking().value();
assertThat(hello).as("hello was null").isNotNull(); assertThat(hello).as("hello was null").isNotNull();
@@ -294,7 +294,7 @@ public class FeignClientTests {
@Test @Test
public void testNoContentResponse() { public void testNoContentResponse() {
ResponseEntity<Void> response = this.testClient.noContent(); ResponseEntity<Void> response = testClient.noContent();
assertThat(response).as("response was null").isNotNull(); assertThat(response).as("response was null").isNotNull();
assertThat(response.getStatusCode()).as("status code was wrong") assertThat(response.getStatusCode()).as("status code was wrong")
.isEqualTo(HttpStatus.NO_CONTENT); .isEqualTo(HttpStatus.NO_CONTENT);
@@ -302,7 +302,7 @@ public class FeignClientTests {
@Test @Test
public void testHeadResponse() { public void testHeadResponse() {
ResponseEntity<Void> response = this.testClient.head(); ResponseEntity<Void> response = testClient.head();
assertThat(response).as("response was null").isNotNull(); assertThat(response).as("response was null").isNotNull();
assertThat(response.getStatusCode()).as("status code was wrong") assertThat(response.getStatusCode()).as("status code was wrong")
.isEqualTo(HttpStatus.OK); .isEqualTo(HttpStatus.OK);
@@ -310,7 +310,7 @@ public class FeignClientTests {
@Test @Test
public void testHttpEntity() { public void testHttpEntity() {
HttpEntity<Hello> entity = this.testClient.getHelloEntity(); HttpEntity<Hello> entity = testClient.getHelloEntity();
assertThat(entity).as("entity was null").isNotNull(); assertThat(entity).as("entity was null").isNotNull();
Hello hello = entity.getBody(); Hello hello = entity.getBody();
assertThat(hello).as("hello was null").isNotNull(); assertThat(hello).as("hello was null").isNotNull();
@@ -320,7 +320,7 @@ public class FeignClientTests {
@Test @Test
public void testMoreComplexHeader() { public void testMoreComplexHeader() {
String response = this.testClient.moreComplexContentType("{\"value\":\"OK\"}"); String response = testClient.moreComplexContentType("{\"value\":\"OK\"}");
assertThat(response).as("response was null").isNotNull(); assertThat(response).as("response was null").isNotNull();
assertThat(response).as("didn't respond with {\"value\":\"OK\"}") assertThat(response).as("didn't respond with {\"value\":\"OK\"}")
.isEqualTo("{\"value\":\"OK\"}"); .isEqualTo("{\"value\":\"OK\"}");
@@ -328,7 +328,7 @@ public class FeignClientTests {
@Test @Test
public void testDecodeNotFound() { public void testDecodeNotFound() {
ResponseEntity<String> response = this.decodingTestClient.notFound(); ResponseEntity<String> response = decodingTestClient.notFound();
assertThat(response).as("response was null").isNotNull(); assertThat(response).as("response was null").isNotNull();
assertThat(response.getStatusCode()).as("status code was wrong") assertThat(response.getStatusCode()).as("status code was wrong")
.isEqualTo(HttpStatus.NOT_FOUND); .isEqualTo(HttpStatus.NOT_FOUND);
@@ -337,35 +337,35 @@ public class FeignClientTests {
@Test @Test
public void testOptionalNotFound() { public void testOptionalNotFound() {
Optional<String> s = this.decodingTestClient.optional(); Optional<String> s = decodingTestClient.optional();
assertThat(s).isNotPresent(); assertThat(s).isNotPresent();
} }
@Test @Test
public void testConvertingExpander() { public void testConvertingExpander() {
assertThat(this.testClient.getToString(Arg.A)).isEqualTo(Arg.A.toString()); assertThat(testClient.getToString(Arg.A)).isEqualTo(Arg.A.toString());
assertThat(this.testClient.getToString(Arg.B)).isEqualTo(Arg.B.toString()); assertThat(testClient.getToString(Arg.B)).isEqualTo(Arg.B.toString());
assertThat(this.testClient.getToString(new OtherArg("foo"))).isEqualTo("bar"); assertThat(testClient.getToString(new OtherArg("foo"))).isEqualTo("bar");
List<OtherArg> args = new ArrayList<>(); List<OtherArg> args = new ArrayList<>();
args.add(new OtherArg("foo")); args.add(new OtherArg("foo"));
args.add(new OtherArg("goo")); args.add(new OtherArg("goo"));
List<String> expectedResult = new ArrayList<>(); List<String> expectedResult = new ArrayList<>();
expectedResult.add("bar"); expectedResult.add("bar");
expectedResult.add("goo"); expectedResult.add("goo");
assertThat(this.testClient.getToString(args)).isEqualTo(expectedResult); assertThat(testClient.getToString(args)).isEqualTo(expectedResult);
} }
@Test @Test
public void testHystrixFallbackWorks() { public void testHystrixFallbackWorks() {
Hello hello = this.hystrixClient.fail(); Hello hello = hystrixClient.fail();
assertThat(hello).as("hello was null").isNotNull(); assertThat(hello).as("hello was null").isNotNull();
assertThat(hello.getMessage()).as("message was wrong").isEqualTo("fallback"); assertThat(hello.getMessage()).as("message was wrong").isEqualTo("fallback");
} }
@Test @Test
public void testHystrixFallbackSingle() { public void testHystrixFallbackSingle() {
Single<Hello> single = this.hystrixClient.failSingle(); Single<Hello> single = hystrixClient.failSingle();
assertThat(single).as("single was null").isNotNull(); assertThat(single).as("single was null").isNotNull();
Hello hello = single.toBlocking().value(); Hello hello = single.toBlocking().value();
assertThat(hello).as("hello was null").isNotNull(); assertThat(hello).as("hello was null").isNotNull();
@@ -375,7 +375,7 @@ public class FeignClientTests {
@Test @Test
public void testHystrixFallbackCommand() { public void testHystrixFallbackCommand() {
HystrixCommand<Hello> command = this.hystrixClient.failCommand(); HystrixCommand<Hello> command = hystrixClient.failCommand();
assertThat(command).as("command was null").isNotNull(); assertThat(command).as("command was null").isNotNull();
Hello hello = command.execute(); Hello hello = command.execute();
assertThat(hello).as("hello was null").isNotNull(); assertThat(hello).as("hello was null").isNotNull();
@@ -385,7 +385,7 @@ public class FeignClientTests {
@Test @Test
public void testHystrixFallbackObservable() { public void testHystrixFallbackObservable() {
Observable<Hello> observable = this.hystrixClient.failObservable(); Observable<Hello> observable = hystrixClient.failObservable();
assertThat(observable).as("observable was null").isNotNull(); assertThat(observable).as("observable was null").isNotNull();
Hello hello = observable.toBlocking().first(); Hello hello = observable.toBlocking().first();
assertThat(hello).as("hello was null").isNotNull(); assertThat(hello).as("hello was null").isNotNull();
@@ -395,7 +395,7 @@ public class FeignClientTests {
@Test @Test
public void testHystrixFallbackFuture() throws Exception { public void testHystrixFallbackFuture() throws Exception {
Future<Hello> future = this.hystrixClient.failFuture(); Future<Hello> future = hystrixClient.failFuture();
assertThat(future).as("future was null").isNotNull(); assertThat(future).as("future was null").isNotNull();
Hello hello = future.get(1, TimeUnit.SECONDS); Hello hello = future.get(1, TimeUnit.SECONDS);
assertThat(hello).as("hello was null").isNotNull(); assertThat(hello).as("hello was null").isNotNull();
@@ -405,7 +405,7 @@ public class FeignClientTests {
@Test @Test
public void testHystrixClientWithFallBackFactory() throws Exception { public void testHystrixClientWithFallBackFactory() throws Exception {
Hello hello = this.hystrixClientWithFallBackFactory.fail(); Hello hello = hystrixClientWithFallBackFactory.fail();
assertThat(hello).as("hello was null").isNotNull(); assertThat(hello).as("hello was null").isNotNull();
assertThat(hello.getMessage()).as("hello#message was null").isNotNull(); assertThat(hello.getMessage()).as("hello#message was null").isNotNull();
assertThat(hello.getMessage().contains("500")).as( assertThat(hello.getMessage().contains("500")).as(
@@ -415,22 +415,29 @@ public class FeignClientTests {
@Test(expected = HystrixRuntimeException.class) @Test(expected = HystrixRuntimeException.class)
public void testInvalidTypeHystrixFallbackFactory() throws Exception { public void testInvalidTypeHystrixFallbackFactory() throws Exception {
this.invalidTypeHystrixClientWithFallBackFactory.fail(); invalidTypeHystrixClientWithFallBackFactory.fail();
} }
@Test(expected = HystrixRuntimeException.class) @Test(expected = HystrixRuntimeException.class)
public void testNullHystrixFallbackFactory() throws Exception { public void testNullHystrixFallbackFactory() throws Exception {
this.nullHystrixClientWithFallBackFactory.fail(); nullHystrixClientWithFallBackFactory.fail();
}
@Test
public void testFormURLEncoded() {
Hello hello = new Hello(HELLO_WORLD_1);
Hello response = testClient.postFormUrlEncoded(hello);
assertThat(response).isEqualTo(hello);
} }
@Test @Test
public void namedFeignClientWorks() { public void namedFeignClientWorks() {
assertThat(this.namedHystrixClient).as("namedHystrixClient was null").isNotNull(); assertThat(namedHystrixClient).as("namedHystrixClient was null").isNotNull();
} }
@Test @Test
public void testHystrixSetterFactory() { public void testHystrixSetterFactory() {
HystrixCommand<List<Hello>> command = this.hystrixSetterFactoryClient HystrixCommand<List<Hello>> command = hystrixSetterFactoryClient
.getHellosHystrix(); .getHellosHystrix();
assertThat(command).as("command was null").isNotNull(); assertThat(command).as("command was null").isNotNull();
String setterPrefix = TestHystrixSetterFactoryClientConfig.SETTER_PREFIX; String setterPrefix = TestHystrixSetterFactoryClientConfig.SETTER_PREFIX;
@@ -449,13 +456,13 @@ public class FeignClientTests {
@Test @Test
public void testSingleRequestPart() { public void testSingleRequestPart() {
String response = this.multipartClient.singlePart("abc"); String response = multipartClient.singlePart("abc");
assertThat(response).isEqualTo("abc"); assertThat(response).isEqualTo("abc");
} }
@Test @Test
public void testSinglePojoRequestPart() { public void testSinglePojoRequestPart() {
String response = this.multipartClient.singlePojoPart(new Hello(HELLO_WORLD_1)); String response = multipartClient.singlePojoPart(new Hello(HELLO_WORLD_1));
assertThat(response).isEqualTo(HELLO_WORLD_1); assertThat(response).isEqualTo(HELLO_WORLD_1);
} }
@@ -463,7 +470,7 @@ public class FeignClientTests {
public void testMultipleRequestParts() { public void testMultipleRequestParts() {
MockMultipartFile file = new MockMultipartFile("file", "hello.bin", null, MockMultipartFile file = new MockMultipartFile("file", "hello.bin", null,
"hello".getBytes()); "hello".getBytes());
String response = this.multipartClient.multipart("abc", "123", file); String response = multipartClient.multipart("abc", "123", file);
assertThat(response).isEqualTo("abc123hello.bin"); assertThat(response).isEqualTo("abc123hello.bin");
} }
@@ -473,8 +480,7 @@ public class FeignClientTests {
Hello pojo2 = new Hello(OI_TERRA_2); Hello pojo2 = new Hello(OI_TERRA_2);
MockMultipartFile file = new MockMultipartFile("file", "hello.bin", null, MockMultipartFile file = new MockMultipartFile("file", "hello.bin", null,
"hello".getBytes()); "hello".getBytes());
String response = this.multipartClient.multipartPojo("abc", "123", pojo1, pojo2, String response = multipartClient.multipartPojo("abc", "123", pojo1, pojo2, file);
file);
assertThat(response).isEqualTo("abc123hello world 1oi terra 2hello.bin"); assertThat(response).isEqualTo("abc123hello world 1oi terra 2hello.bin");
} }
@@ -483,10 +489,10 @@ public class FeignClientTests {
List<MultipartFile> multipartFiles = Arrays.asList( List<MultipartFile> multipartFiles = Arrays.asList(
new MockMultipartFile("file1", "hello1.bin", null, "hello".getBytes()), new MockMultipartFile("file1", "hello1.bin", null, "hello".getBytes()),
new MockMultipartFile("file2", "hello2.bin", null, "hello".getBytes())); new MockMultipartFile("file2", "hello2.bin", null, "hello".getBytes()));
String partNames = this.multipartClient String partNames = multipartClient
.requestPartListOfMultipartFilesReturnsPartNames(multipartFiles); .requestPartListOfMultipartFilesReturnsPartNames(multipartFiles);
assertThat(partNames).isEqualTo("files,files"); assertThat(partNames).isEqualTo("files,files");
String fileNames = this.multipartClient String fileNames = multipartClient
.requestPartListOfMultipartFilesReturnsFileNames(multipartFiles); .requestPartListOfMultipartFilesReturnsFileNames(multipartFiles);
assertThat(fileNames).contains("hello1.bin", "hello2.bin"); assertThat(fileNames).contains("hello1.bin", "hello2.bin");
} }
@@ -499,9 +505,8 @@ public class FeignClientTests {
"hello".getBytes()); "hello".getBytes());
MockMultipartFile file2 = new MockMultipartFile("file2", "hello2.bin", null, MockMultipartFile file2 = new MockMultipartFile("file2", "hello2.bin", null,
"hello".getBytes()); "hello".getBytes());
String response = this.multipartClient String response = multipartClient.requestPartListOfPojosAndListOfMultipartFiles(
.requestPartListOfPojosAndListOfMultipartFiles( Arrays.asList(pojo1, pojo2), Arrays.asList(file1, file2));
Arrays.asList(pojo1, pojo2), Arrays.asList(file1, file2));
assertThat(response).isEqualTo("hello world 1oi terra 2hello1.binhello2.bin"); assertThat(response).isEqualTo("hello world 1oi terra 2hello1.binhello2.bin");
} }
@@ -510,7 +515,7 @@ public class FeignClientTests {
String partName = UUID.randomUUID().toString(); String partName = UUID.randomUUID().toString();
MockMultipartFile file1 = new MockMultipartFile(partName, "hello1.bin", null, MockMultipartFile file1 = new MockMultipartFile(partName, "hello1.bin", null,
"hello".getBytes()); "hello".getBytes());
String response = this.multipartClient.requestBodySingleMultipartFile(file1); String response = multipartClient.requestBodySingleMultipartFile(file1);
assertThat(response).isEqualTo(partName); assertThat(response).isEqualTo(partName);
} }
@@ -520,7 +525,7 @@ public class FeignClientTests {
"hello".getBytes()); "hello".getBytes());
MockMultipartFile file2 = new MockMultipartFile("file2", "hello2.bin", null, MockMultipartFile file2 = new MockMultipartFile("file2", "hello2.bin", null,
"hello".getBytes()); "hello".getBytes());
String response = this.multipartClient String response = multipartClient
.requestBodyListOfMultipartFiles(Arrays.asList(file1, file2)); .requestBodyListOfMultipartFiles(Arrays.asList(file1, file2));
assertThat(response).contains("file1", "file2"); assertThat(response).contains("file1", "file2");
} }
@@ -535,7 +540,7 @@ public class FeignClientTests {
form.put("file1", file1); form.put("file1", file1);
form.put("file2", file2); form.put("file2", file2);
form.put("hello", "world"); form.put("hello", "world");
String response = this.multipartClient.requestBodyMap(form); String response = multipartClient.requestBodyMap(form);
assertThat(response).contains("file1", "file2", "hello"); assertThat(response).contains("file1", "file2", "hello");
} }
@@ -544,7 +549,7 @@ public class FeignClientTests {
MockMultipartFile file = new MockMultipartFile("file1", "hello1.bin", null, MockMultipartFile file = new MockMultipartFile("file1", "hello1.bin", null,
"hello".getBytes()); "hello".getBytes());
expected.expectCause(instanceOf(EncodeException.class)); expected.expectCause(instanceOf(EncodeException.class));
this.multipartClient.invalid(file); multipartClient.invalid(file);
} }
protected enum Arg { protected enum Arg {
@@ -615,6 +620,10 @@ public class FeignClientTests {
@RequestMapping(method = RequestMethod.GET, path = "/tostring") @RequestMapping(method = RequestMethod.GET, path = "/tostring")
String getToString(@RequestParam("arg") Arg arg); String getToString(@RequestParam("arg") Arg arg);
@PostMapping(path = "/form-urlencoded",
consumes = APPLICATION_FORM_URLENCODED_VALUE)
Hello postFormUrlEncoded(Hello hello);
@RequestMapping(method = RequestMethod.GET, path = "/tostring2") @RequestMapping(method = RequestMethod.GET, path = "/tostring2")
String getToString(@RequestParam("arg") OtherArg arg); String getToString(@RequestParam("arg") OtherArg arg);
@@ -776,7 +785,7 @@ public class FeignClientTests {
@Override @Override
public String toString() { public String toString() {
return this.value; return value;
} }
} }
@@ -810,13 +819,9 @@ public class FeignClientTests {
@Override @Override
public HystrixClientWithFallBackFactory create(final Throwable cause) { public HystrixClientWithFallBackFactory create(final Throwable cause) {
return new HystrixClientWithFallBackFactory() { return () -> {
@Override assertThat(cause).isNotNull().as("Cause was null");
public Hello fail() { return new Hello("Hello from the fallback side: " + cause.getMessage());
assertThat(cause).isNotNull().as("Cause was null");
return new Hello(
"Hello from the fallback side: " + cause.getMessage());
}
}; };
} }
@@ -876,18 +881,15 @@ public class FeignClientTests {
@Bean @Bean
public SetterFactory commandKeyIsRequestLineSetterFactory() { public SetterFactory commandKeyIsRequestLineSetterFactory() {
return new SetterFactory() { return (target, method) -> {
@Override String groupKey = SETTER_PREFIX + target.name();
public HystrixCommand.Setter create(Target<?> target, Method method) { RequestMapping requestMapping = method
String groupKey = SETTER_PREFIX + target.name(); .getAnnotation(RequestMapping.class);
RequestMapping requestMapping = method String commandKey = SETTER_PREFIX + requestMapping.method()[0] + " "
.getAnnotation(RequestMapping.class); + requestMapping.path()[0];
String commandKey = SETTER_PREFIX + requestMapping.method()[0] + " " return HystrixCommand.Setter
+ requestMapping.path()[0]; .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
return HystrixCommand.Setter .andCommandKey(HystrixCommandKey.Factory.asKey(commandKey));
.withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
.andCommandKey(HystrixCommandKey.Factory.asKey(commandKey));
}
}; };
} }
@@ -1154,6 +1156,12 @@ public class FeignClientTests {
return result.toString(); return result.toString();
} }
@PostMapping(path = "/form-urlencoded",
consumes = APPLICATION_FORM_URLENCODED_VALUE)
Hello postFormUrlEncoded(Hello hello) {
return hello;
}
} }
public static class Hello { public static class Hello {
@@ -1168,7 +1176,7 @@ public class FeignClientTests {
} }
public String getMessage() { public String getMessage() {
return this.message; return message;
} }
public void setMessage(String message) { public void setMessage(String message) {
@@ -1184,12 +1192,12 @@ public class FeignClientTests {
return false; return false;
} }
Hello that = (Hello) o; Hello that = (Hello) o;
return Objects.equals(this.message, that.message); return Objects.equals(message, that.message);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(this.message); return Objects.hash(message);
} }
} }
@@ -1213,7 +1221,7 @@ public class FeignClientTests {
@Bean @Bean
public ServerList<Server> ribbonServerList() { public ServerList<Server> ribbonServerList() {
return new StaticServerList<>(new Server("localhost", this.port)); return new StaticServerList<>(new Server("localhost", port));
} }
} }
+2 -2
View File
@@ -6,11 +6,11 @@
<parent> <parent>
<artifactId>spring-cloud-dependencies-parent</artifactId> <artifactId>spring-cloud-dependencies-parent</artifactId>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<version>2.3.4.RELEASE</version> <version>2.3.6.BUILD-SNAPSHOT</version>
<relativePath/> <relativePath/>
</parent> </parent>
<artifactId>spring-cloud-openfeign-dependencies</artifactId> <artifactId>spring-cloud-openfeign-dependencies</artifactId>
<version>2.2.8.RELEASE</version> <version>2.2.11.BUILD-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>spring-cloud-openfeign-dependencies</name> <name>spring-cloud-openfeign-dependencies</name>
<description>Spring Cloud OpenFeign Dependencies</description> <description>Spring Cloud OpenFeign Dependencies</description>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId> <artifactId>spring-cloud-openfeign</artifactId>
<version>2.2.8.RELEASE</version> <version>2.2.11.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath> <relativePath>..</relativePath>
</parent> </parent>
<artifactId>spring-cloud-starter-openfeign</artifactId> <artifactId>spring-cloud-starter-openfeign</artifactId>