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