Compare commits

..
47 Commits
Author SHA1 Message Date
buildmaster 7af0efd569 Update SNAPSHOT to 3.0.6 2021-12-15 17:01:03 +00:00
Been24 70a9002002 Update RetryableFeignBlockingLoadBalancerClient.java
fix log  class
2021-11-29 16:45:40 +01:00
Nikita Konev ad892a23f0 Support size = 0 in PageJacksonModule 2021-11-29 16:30:20 +01:00
Hyeonmin Park c0f314a5f0 GitHub workflows: Use Java version in matrix, cache functionality of actions/setup-java@v2 (#614)
* Fix GitHub workflows not using Java version in matrix

* Use cache functionality of actions/setup-java@v2
2021-10-28 18:15:02 +02:00
buildmaster 56dc4e07e4 Bumping versions to 3.0.6-SNAPSHOT after release 2021-10-21 11:48:48 +00:00
buildmaster 9167752bfb Going back to snapshots 2021-10-21 11:48:47 +00:00
buildmaster 127c866616 Update SNAPSHOT to 3.0.5 2021-10-21 11:46:52 +00:00
Olga MaciaszekSharma 0e6b33f480 Merge remote-tracking branch 'origin/2.2.x' into 3.0.x 2021-10-21 13:04:57 +02: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 548e4d55ef Revert default circuit breaker name prefix to target.type(). Fixes gh-606. 2021-10-11 18:40:21 +02:00
buildmaster 55ceafae4d Bumping versions to 3.0.5-SNAPSHOT after release 2021-09-21 14:39:31 +00:00
buildmaster 0c87527ea5 Going back to snapshots 2021-09-21 14:39:31 +00:00
buildmaster a2c473db2a Update SNAPSHOT to 3.0.4 2021-09-21 14:37:38 +00:00
Can Bezmen ae33802a4b FIX gh-601 2021-09-20 15:35:28 +02:00
Olga MaciaszekSharma c761919bc9 Only add default query params and default headers if not already in template. Fixes gh-590. 2021-09-09 17:59:08 +02:00
Olga MaciaszekSharma 998baed0d3 Merge remote-tracking branch 'origin/3.0.x' into 3.0.x 2021-09-08 18:52:18 +02:00
Olga MaciaszekSharma 4fc80e6b01 Update docs. Fixes gh-585. 2021-09-08 18:52:11 +02:00
Kwangyong Kim c101fd2c6e Add CircuitBreakerNameResolver (#575) 2021-07-27 10:59:32 +02:00
Olga MaciaszekSharma 71930fd9f0 Do not reset RequestAttributes. Fixes gh-572. 2021-07-26 17:03:33 +02:00
Olga MaciaszekSharma b5283e2f88 Fix Github workflow setup. 2021-07-21 13:21:08 +02:00
ManasviGoyal 6aeb1ff676 Added Tip (#580) 2021-07-21 12:21:21 +02:00
spencergibb 44c63798b0 rollback to 3.0.x versions 2021-07-09 14:47:28 -04:00
Jonathan SchneiderandModerne 769b38c5c1 Refactor: replace @RequestMapping annotations
Co-authored-by: Moderne <team@moderne.io>
2021-07-09 12:16:27 +02:00
Can Bezmen 3c402201ec Codecov for main branch (#554) 2021-07-09 11:51:57 +02:00
buildmaster deb150ed88 Bumping versions 2021-07-09 00:24:22 +00: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
Olga MaciaszekSharma 6de9fdd3e1 Merge remote-tracking branch 'origin/2.2.x' 2021-07-05 13:19:39 +02: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 45861dd657 Merge remote-tracking branch 'origin/2.2.x'. Refactor. 2021-07-05 12:09:23 +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 1f5bdd4224 Merge remote-tracking branch 'origin/2.2.x' 2021-06-10 16:08:39 +02:00
Olga MaciaszekSharma 2f43715f6f Change log level. Fixes gh-552. 2021-06-10 16:04:17 +02:00
buildmaster 280291ec93 Bumping versions 2021-05-28 00:22:29 +00:00
buildmaster 0de9dd614d Going back to snapshots 2021-05-27 21:20:38 +00:00
49 changed files with 659 additions and 411 deletions
+11 -13
View File
@@ -5,9 +5,9 @@ name: Build
on:
push:
branches: [ main ]
branches: [ 3.0.x ]
pull_request:
branches: [ main ]
branches: [ 3.0.x ]
jobs:
build:
@@ -20,16 +20,14 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v2
with:
java-version: 1.8
- name: Cache local Maven repository
uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
distribution: 'zulu'
java-version: ${{ matrix.java }}
cache: 'maven'
- name: Build with Maven
run: ./mvnw clean install -B -U
run: ./mvnw clean install -B -U -P sonar
- uses: codecov/codecov-action@v1
with:
fail_ci_if_error: true
+53 -16
View File
@@ -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
@@ -317,6 +303,57 @@ Go to `File` -> `Settings` -> `Other settings` -> `Checkstyle`. There click on t
IMPORTANT: Remember to set the `Scan Scope` to `All sources` since we apply checkstyle rules for production and test sources.
=== Duplicate Finder
Spring Cloud Build brings along the `basepom:duplicate-finder-maven-plugin`, that enables flagging duplicate and conflicting classes and resources on the java classpath.
==== Duplicate Finder configuration
Duplicate finder is *enabled by default* and will run in the `verify` phase of your Maven build, but it will only take effect in your project if you add the `duplicate-finder-maven-plugin` to the `build` section of the projecst's `pom.xml`.
.pom.xml
[source,xml]
----
<build>
<plugins>
<plugin>
<groupId>org.basepom.maven</groupId>
<artifactId>duplicate-finder-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
----
For other properties, we have set defaults as listed in the https://github.com/basepom/duplicate-finder-maven-plugin/wiki[plugin documentation].
You can easily override them but setting the value of the selected property prefixed with `duplicate-finder-maven-plugin`. For example, set `duplicate-finder-maven-plugin.skip` to `true` in order to skip duplicates check in your build.
If you need to add `ignoredClassPatterns` or `ignoredResourcePatterns` to your setup, make sure to add them in the plugin configuration section of your project:
[source,xml]
----
<build>
<plugins>
<plugin>
<groupId>org.basepom.maven</groupId>
<artifactId>duplicate-finder-maven-plugin</artifactId>
<configuration>
<ignoredClassPatterns>
<ignoredClassPattern>org.joda.time.base.BaseDateTime</ignoredClassPattern>
<ignoredClassPattern>.*module-info</ignoredClassPattern>
</ignoredClassPatterns>
<ignoredResourcePatterns>
<ignoredResourcePattern>changelog.txt</ignoredResourcePattern>
</ignoredResourcePatterns>
</configuration>
</plugin>
</plugins>
</build>
----
== License
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>3.0.3</version>
<version>3.0.6</version>
</parent>
<artifactId>spring-cloud-openfeign-docs</artifactId>
<packaging>jar</packaging>
@@ -66,10 +66,15 @@ The load-balancer client above will want to discover the physical addresses
for the "stores" service. If your application is a Eureka client then
it will resolve the service in the Eureka service registry. If you
don't want to use Eureka, you can configure a list of servers
in your external configuration using https://cloud.spring.io/spring-cloud-static/spring-cloud-commons/current/reference/html/#simplediscoveryclient[`SimpleDiscoveryClient`].
in your external configuration using https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#simplediscoveryclient[`SimpleDiscoveryClient`].
Spring Cloud OpenFeign supports all the features available for the blocking mode of Spring Cloud LoadBalancer. You can read more about them in the https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#spring-cloud-loadbalancer[project documentation].
TIP: To use `@EnableFeignClients` annotation on `@Configuration`-annotated-classes, make sure to specify where the clients are located, for example:
`@EnableFeignClients(basePackages = "com.example.clients")`
or list them explicitly:
`@EnableFeignClients(clients = InventoryServiceFeignClient.class)`
[[spring-cloud-feign-overriding-defaults]]
=== Overriding Feign Defaults
@@ -343,7 +348,22 @@ public class FooConfiguration {
}
----
The circuit breaker name follows this pattern `<feignClientName>#<calledMethod>`. When calling a `@FeignClient` with name `foo` and the called interface method is `bar` then the circuit breaker name will be `foo_bar`.
The circuit breaker name follows this pattern `<feignClientClassName>#<calledMethod>(<parameterTypes>)`. When calling a `@FeignClient` with `FooClient` interface and the called interface method that has no parameters is `bar` then the circuit breaker name will be `FooClient#bar()`.
NOTE: As of 2020.0.2, the circuit breaker name pattern has changed from `<feignClientName>_<calledMethod>`.
Using `CircuitBreakerNameResolver` introduced in 2020.0.4, circuit breaker names can retain the old pattern.
Providing a bean of `CircuitBreakerNameResolver`, you can change the circuit breaker name pattern.
[source,java,indent=0]
----
@Configuration
public class FooConfiguration {
@Bean
public CircuitBreakerNameResolver circuitBreakerNameResolver() {
return (String feignClientName, Target<?> target, Method method) -> feignClientName + "_" + method.getName();
}
}
----
To enable Spring Cloud CircuitBreaker group set the `feign.circuitbreaker.group.enabled` property to `true` (by default `false`).
@@ -471,10 +491,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
+3 -3
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>3.0.3</version>
<version>3.0.6</version>
<packaging>pom</packaging>
<name>Spring Cloud OpenFeign</name>
<description>Spring Cloud OpenFeign</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
<version>3.0.3</version>
<version>3.0.5</version>
<relativePath/>
</parent>
<scm>
@@ -26,7 +26,7 @@
<properties>
<main.basedir>${basedir}</main.basedir>
<jackson.version>2.11.3</jackson.version>
<spring-cloud-commons.version>3.0.3</spring-cloud-commons.version>
<spring-cloud-commons.version>3.0.5</spring-cloud-commons.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>3.0.3</version>
<version>3.0.6</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-openfeign-core</artifactId>
@@ -0,0 +1,34 @@
/*
* Copyright 2013-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign;
import java.lang.reflect.Method;
import feign.Target;
/**
* Used to resolve a circuitbreaker name which will be used in
* {@link org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory}.
*
* @author Kwangyong Kim
* @since 2020.0.4
*/
public interface CircuitBreakerNameResolver {
String resolveCircuitBreakerName(String feignClientName, Target<?> target, Method method);
}
@@ -17,6 +17,7 @@
package org.springframework.cloud.openfeign;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
@@ -29,6 +30,7 @@ import com.fasterxml.jackson.databind.Module;
import feign.Client;
import feign.Feign;
import feign.RequestInterceptor;
import feign.Target;
import feign.hc5.ApacheHttp5Client;
import feign.httpclient.ApacheHttpClient;
import feign.okhttp.OkHttpClient;
@@ -79,6 +81,7 @@ import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResour
* @author Olga Maciaszek-Sharma
* @author Nguyen Ky Thanh
* @author Andrii Bohutskyi
* @author Kwangyong Kim
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@@ -146,12 +149,29 @@ public class FeignAutoConfiguration {
return new DefaultTargeter();
}
@Bean
@ConditionalOnMissingBean(CircuitBreakerNameResolver.class)
public CircuitBreakerNameResolver circuitBreakerNameResolver() {
return new DefaultCircuitBreakerNameResolver();
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(CircuitBreakerFactory.class)
public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory,
@Value("${feign.circuitbreaker.group.enabled:false}") boolean circuitBreakerGroupEnabled) {
return new FeignCircuitBreakerTargeter(circuitBreakerFactory, circuitBreakerGroupEnabled);
@Value("${feign.circuitbreaker.group.enabled:false}") boolean circuitBreakerGroupEnabled,
CircuitBreakerNameResolver circuitBreakerNameResolver) {
return new FeignCircuitBreakerTargeter(circuitBreakerFactory, circuitBreakerGroupEnabled,
circuitBreakerNameResolver);
}
static class DefaultCircuitBreakerNameResolver implements CircuitBreakerNameResolver {
@Override
public String resolveCircuitBreakerName(String feignClientName, Target<?> target, Method method) {
return Feign.configKey(target.type(), method);
}
}
}
@@ -27,6 +27,7 @@ import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
*
* @author Marcin Grzejszczak
* @author Andrii Bohutskyi
* @author Kwangyong Kim
* @since 3.0.0
*/
public final class FeignCircuitBreaker {
@@ -53,6 +54,8 @@ public final class FeignCircuitBreaker {
private boolean circuitBreakerGroupEnabled;
private CircuitBreakerNameResolver circuitBreakerNameResolver;
Builder circuitBreakerFactory(CircuitBreakerFactory circuitBreakerFactory) {
this.circuitBreakerFactory = circuitBreakerFactory;
return this;
@@ -68,6 +71,11 @@ public final class FeignCircuitBreaker {
return this;
}
Builder circuitBreakerNameResolver(CircuitBreakerNameResolver circuitBreakerNameResolver) {
this.circuitBreakerNameResolver = circuitBreakerNameResolver;
return this;
}
public <T> T target(Target<T> target, T fallback) {
return build(fallback != null ? new FallbackFactory.Default<T>(fallback) : null).newInstance(target);
}
@@ -82,9 +90,9 @@ public final class FeignCircuitBreaker {
}
public Feign build(final FallbackFactory<?> nullableFallbackFactory) {
super.invocationHandlerFactory(
(target, dispatch) -> new FeignCircuitBreakerInvocationHandler(circuitBreakerFactory,
feignClientName, target, dispatch, nullableFallbackFactory, circuitBreakerGroupEnabled));
super.invocationHandlerFactory((target, dispatch) -> new FeignCircuitBreakerInvocationHandler(
circuitBreakerFactory, feignClientName, target, dispatch, nullableFallbackFactory,
circuitBreakerGroupEnabled, circuitBreakerNameResolver));
return super.build();
}
@@ -24,7 +24,6 @@ import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import feign.Feign;
import feign.InvocationHandlerFactory;
import feign.Target;
@@ -51,9 +50,11 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
private final boolean circuitBreakerGroupEnabled;
private final CircuitBreakerNameResolver circuitBreakerNameResolver;
FeignCircuitBreakerInvocationHandler(CircuitBreakerFactory factory, String feignClientName, Target<?> target,
Map<Method, InvocationHandlerFactory.MethodHandler> dispatch, FallbackFactory<?> nullableFallbackFactory,
boolean circuitBreakerGroupEnabled) {
boolean circuitBreakerGroupEnabled, CircuitBreakerNameResolver circuitBreakerNameResolver) {
this.factory = factory;
this.feignClientName = feignClientName;
this.target = checkNotNull(target, "target");
@@ -61,6 +62,7 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
this.fallbackMethodMap = toFallbackMethod(dispatch);
this.nullableFallbackFactory = nullableFallbackFactory;
this.circuitBreakerGroupEnabled = circuitBreakerGroupEnabled;
this.circuitBreakerNameResolver = circuitBreakerNameResolver;
}
@Override
@@ -82,7 +84,8 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
else if ("toString".equals(method.getName())) {
return toString();
}
String circuitName = Feign.configKey(target.type(), method);
String circuitName = circuitBreakerNameResolver.resolveCircuitBreakerName(feignClientName, target, method);
CircuitBreaker circuitBreaker = circuitBreakerGroupEnabled ? factory.create(circuitName, feignClientName)
: factory.create(circuitName);
Supplier<Object> supplier = asSupplier(method, args);
@@ -106,7 +109,7 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
return () -> {
try {
RequestContextHolder.setRequestAttributes(requestAttributes);
return this.dispatch.get(method).invoke(args);
return dispatch.get(method).invoke(args);
}
catch (RuntimeException throwable) {
throw throwable;
@@ -114,9 +117,6 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
finally {
RequestContextHolder.resetRequestAttributes();
}
};
}
@@ -29,9 +29,13 @@ class FeignCircuitBreakerTargeter implements Targeter {
private final boolean circuitBreakerGroupEnabled;
FeignCircuitBreakerTargeter(CircuitBreakerFactory circuitBreakerFactory, boolean circuitBreakerGroupEnabled) {
private final CircuitBreakerNameResolver circuitBreakerNameResolver;
FeignCircuitBreakerTargeter(CircuitBreakerFactory circuitBreakerFactory, boolean circuitBreakerGroupEnabled,
CircuitBreakerNameResolver circuitBreakerNameResolver) {
this.circuitBreakerFactory = circuitBreakerFactory;
this.circuitBreakerGroupEnabled = circuitBreakerGroupEnabled;
this.circuitBreakerNameResolver = circuitBreakerNameResolver;
}
@Override
@@ -85,7 +89,8 @@ class FeignCircuitBreakerTargeter implements Targeter {
private FeignCircuitBreaker.Builder builder(String feignClientName, FeignCircuitBreaker.Builder builder) {
return builder.circuitBreakerFactory(circuitBreakerFactory).feignClientName(feignClientName)
.circuitBreakerGroupEnabled(circuitBreakerGroupEnabled);
.circuitBreakerGroupEnabled(circuitBreakerGroupEnabled)
.circuitBreakerNameResolver(circuitBreakerNameResolver);
}
}
@@ -17,6 +17,7 @@
package org.springframework.cloud.openfeign;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -276,13 +277,8 @@ public class FeignClientFactoryBean
builder.encoder(getOrInstantiate(config.getEncoder()));
}
if (Objects.nonNull(config.getDefaultRequestHeaders())) {
builder.requestInterceptor(requestTemplate -> requestTemplate.headers(config.getDefaultRequestHeaders()));
}
if (Objects.nonNull(config.getDefaultQueryParameters())) {
builder.requestInterceptor(requestTemplate -> requestTemplate.queries(config.getDefaultQueryParameters()));
}
addDefaultRequestHeaders(config, builder);
addDefaultQueryParams(config, builder);
if (Objects.nonNull(config.getDecoder())) {
builder.decoder(getOrInstantiate(config.getDecoder()));
@@ -301,6 +297,35 @@ public class FeignClientFactoryBean
}
}
private void addDefaultQueryParams(FeignClientProperties.FeignClientConfiguration config, Feign.Builder builder) {
Map<String, Collection<String>> defaultQueryParameters = config.getDefaultQueryParameters();
if (Objects.nonNull(defaultQueryParameters)) {
builder.requestInterceptor(requestTemplate -> {
Map<String, Collection<String>> queries = requestTemplate.queries();
defaultQueryParameters.keySet().forEach(key -> {
if (!queries.containsKey(key)) {
requestTemplate.query(key, defaultQueryParameters.get(key));
}
});
});
}
}
private void addDefaultRequestHeaders(FeignClientProperties.FeignClientConfiguration config,
Feign.Builder builder) {
Map<String, Collection<String>> defaultRequestHeaders = config.getDefaultRequestHeaders();
if (Objects.nonNull(defaultRequestHeaders)) {
builder.requestInterceptor(requestTemplate -> {
Map<String, Collection<String>> headers = requestTemplate.headers();
defaultRequestHeaders.keySet().forEach(key -> {
if (!headers.containsKey(key)) {
requestTemplate.header(key, defaultRequestHeaders.get(key));
}
});
});
}
}
private <T> T getOrInstantiate(Class<T> tClass) {
try {
return beanFactory != null ? beanFactory.getBean(tClass) : applicationContext.getBean(tClass);
@@ -382,11 +407,9 @@ public class FeignClientFactoryBean
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;
@@ -0,0 +1,46 @@
/*
* Copyright 2013-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign.loadbalancer;
import java.io.ByteArrayInputStream;
import java.net.URI;
import feign.Response;
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
/**
* A {@link RetryableStatusCodeException} for {@link Response}s.
*
* @author Ryan Baxter
*/
public class LoadBalancerResponseStatusCodeException extends RetryableStatusCodeException {
private final Response response;
public LoadBalancerResponseStatusCodeException(String serviceId, Response response, byte[] body, URI uri) {
super(serviceId, response.status(), response, uri);
this.response = Response.builder().body(new ByteArrayInputStream(body), body.length).headers(response.headers())
.reason(response.reason()).status(response.status()).request(response.request()).build();
}
@Override
public Response getResponse() {
return this.response;
}
}
@@ -46,7 +46,6 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValida
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.client.loadbalancer.ResponseData;
import org.springframework.cloud.client.loadbalancer.RetryableRequestContext;
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@@ -57,6 +56,7 @@ import org.springframework.retry.backoff.NoBackOffPolicy;
import org.springframework.retry.policy.NeverRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils.buildRequestData;
@@ -70,7 +70,7 @@ import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils
@SuppressWarnings({ "rawtypes", "unchecked" })
public class RetryableFeignBlockingLoadBalancerClient implements Client {
private static final Log LOG = LogFactory.getLog(FeignBlockingLoadBalancerClient.class);
private static final Log LOG = LogFactory.getLog(RetryableFeignBlockingLoadBalancerClient.class);
private final Client delegate;
@@ -161,8 +161,11 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Retrying on status code: %d", responseStatus));
}
byte[] byteArray = response.body() == null ? new byte[] {}
: StreamUtils.copyToByteArray(response.body().asInputStream());
response.close();
throw new RetryableStatusCodeException(serviceId, responseStatus, response, URI.create(request.url()));
throw new LoadBalancerResponseStatusCodeException(serviceId, response, byteArray,
URI.create(request.url()));
}
return response;
}, new LoadBalancedRecoveryCallback<Response, Response>() {
@@ -38,6 +38,8 @@ import org.springframework.data.domain.Sort;
*
* @author Pascal Büttiker
* @author Olga Maciaszek-Sharma
* @author Pedro Mendes
* @author Nikita Konev
*/
public class PageJacksonModule extends Module {
@@ -69,18 +71,22 @@ public class PageJacksonModule extends Module {
@JsonProperty("size") int size, @JsonProperty("totalElements") @JsonAlias({ "total-elements",
"total_elements", "totalelements", "TotalElements" }) long totalElements,
@JsonProperty("sort") Sort sort) {
PageRequest pageRequest;
if (sort != null) {
pageRequest = PageRequest.of(number, size, sort);
if (size > 0) {
PageRequest pageRequest;
if (sort != null) {
pageRequest = PageRequest.of(number, size, sort);
}
else {
pageRequest = PageRequest.of(number, size);
}
delegate = new PageImpl<>(content, pageRequest, totalElements);
}
else {
pageRequest = PageRequest.of(number, size);
delegate = new PageImpl<>(content);
}
delegate = new PageImpl<>(content, pageRequest, totalElements);
}
@JsonProperty
@JsonIgnore
@Override
public int getTotalPages() {
return delegate.getTotalPages();
@@ -104,7 +110,7 @@ public class PageJacksonModule extends Module {
return delegate.getSize();
}
@JsonProperty
@JsonIgnore
@Override
public int getNumberOfElements() {
return delegate.getNumberOfElements();
@@ -128,13 +134,13 @@ public class PageJacksonModule extends Module {
return delegate.getSort();
}
@JsonProperty
@JsonIgnore
@Override
public boolean isFirst() {
return delegate.isFirst();
}
@JsonProperty
@JsonIgnore
@Override
public boolean isLast() {
return delegate.isLast();
@@ -176,6 +182,18 @@ public class PageJacksonModule extends Module {
return delegate.iterator();
}
@JsonIgnore
@Override
public Pageable getPageable() {
return delegate.getPageable();
}
@JsonIgnore
@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
}
}
@@ -24,6 +24,7 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.stream.Stream;
import feign.RequestTemplate;
@@ -48,6 +49,10 @@ import org.springframework.web.multipart.MultipartFile;
import static org.springframework.cloud.openfeign.support.FeignUtils.getHeaders;
import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHeaders;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA;
import static org.springframework.http.MediaType.MULTIPART_MIXED;
import static org.springframework.http.MediaType.MULTIPART_RELATED;
/**
* @author Spencer Gibb
@@ -56,6 +61,7 @@ import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHead
* @author Aaron Whiteside
* @author Darren Foong
* @author Olga Maciaszek-Sharma
* @author Can Bezmen
*/
@SuppressWarnings("rawtypes")
public class SpringEncoder implements Encoder {
@@ -95,8 +101,8 @@ public class SpringEncoder implements Encoder {
requestContentType = MediaType.valueOf(type);
}
if (isMultipartType(requestContentType)) {
this.springFormEncoder.encode(requestBody, bodyType, request);
if (isFormRelatedContentType(requestContentType)) {
springFormEncoder.encode(requestBody, bodyType, request);
return;
}
else {
@@ -207,9 +213,16 @@ public class SpringEncoder implements Encoder {
}
}
private boolean isFormRelatedContentType(MediaType requestContentType) {
return isMultipartType(requestContentType) || isFormUrlEncoded(requestContentType);
}
private boolean isMultipartType(MediaType requestContentType) {
return Arrays.asList(MediaType.MULTIPART_FORM_DATA, MediaType.MULTIPART_MIXED, MediaType.MULTIPART_RELATED)
.contains(requestContentType);
return Arrays.asList(MULTIPART_FORM_DATA, MULTIPART_MIXED, MULTIPART_RELATED).contains(requestContentType);
}
private boolean isFormUrlEncoded(MediaType requestContentType) {
return Objects.equals(APPLICATION_FORM_URLENCODED, requestContentType);
}
private boolean binaryContentType(FeignOutputMessage outputMessage) {
@@ -227,21 +240,21 @@ public class SpringEncoder implements Encoder {
private final HttpHeaders httpHeaders;
private FeignOutputMessage(RequestTemplate request) {
this.httpHeaders = getHttpHeaders(request.headers());
httpHeaders = getHttpHeaders(request.headers());
}
@Override
public OutputStream getBody() throws IOException {
return this.outputStream;
return outputStream;
}
@Override
public HttpHeaders getHeaders() {
return this.httpHeaders;
return httpHeaders;
}
public ByteArrayOutputStream getOutputStream() {
return this.outputStream;
return outputStream;
}
}
@@ -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;
@@ -82,6 +84,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";
@@ -169,47 +173,18 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
@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,12 +16,16 @@
package org.springframework.cloud.openfeign;
import java.lang.reflect.Method;
import feign.Target;
import org.assertj.core.api.Condition;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
import org.springframework.cloud.openfeign.FeignAutoConfiguration.CircuitBreakerPresentFeignTargeterConfiguration.DefaultCircuitBreakerNameResolver;
import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
@@ -31,6 +35,7 @@ import static org.mockito.Mockito.mock;
* @author Tim Peeters
* @author Olga Maciaszek-Sharma
* @author Andrii Bohutskyi
* @author Kwangyong Kim
*/
class FeignAutoConfigurationTests {
@@ -50,6 +55,8 @@ class FeignAutoConfigurationTests {
.withPropertyValues("feign.circuitbreaker.enabled=true").run(ctx -> {
assertOnlyOneTargeterPresent(ctx, FeignCircuitBreakerTargeter.class);
assertThatFeignCircuitBreakerTargeterHasGroupEnabledPropertyWithValue(ctx, false);
assertThatFeignCircuitBreakerTargeterHasSameCircuitBreakerNameResolver(ctx,
DefaultCircuitBreakerNameResolver.class);
});
}
@@ -63,6 +70,17 @@ class FeignAutoConfigurationTests {
});
}
@Test
void shouldInstantiateFeignCircuitBreakerTargeterWhenEnabledWithCustomCircuitBreakerNameResolver() {
runner.withBean(CircuitBreakerFactory.class, () -> mock(CircuitBreakerFactory.class))
.withBean(CircuitBreakerNameResolver.class, CustomCircuitBreakerNameResolver::new)
.withPropertyValues("feign.circuitbreaker.enabled=true").run(ctx -> {
assertOnlyOneTargeterPresent(ctx, FeignCircuitBreakerTargeter.class);
assertThatFeignCircuitBreakerTargeterHasSameCircuitBreakerNameResolver(ctx,
CustomCircuitBreakerNameResolver.class);
});
}
private void assertOnlyOneTargeterPresent(ConfigurableApplicationContext ctx, Class<?> beanClass) {
assertThat(ctx.getBeansOfType(Targeter.class)).hasSize(1).hasValueSatisfying(new Condition<>(
beanClass::isInstance, String.format("Targeter should be an instance of %s", beanClass)));
@@ -75,4 +93,19 @@ class FeignAutoConfigurationTests {
assertThat(bean).hasFieldOrPropertyWithValue("circuitBreakerGroupEnabled", expectedValue);
}
private void assertThatFeignCircuitBreakerTargeterHasSameCircuitBreakerNameResolver(
ConfigurableApplicationContext ctx, Class<?> beanClass) {
final CircuitBreakerNameResolver bean = ctx.getBean(CircuitBreakerNameResolver.class);
assertThat(bean).isExactlyInstanceOf(beanClass);
}
static class CustomCircuitBreakerNameResolver implements CircuitBreakerNameResolver {
@Override
public String resolveCircuitBreakerName(String feignClientName, Target<?> target, Method method) {
return feignClientName + "_" + method.getName();
}
}
}
@@ -32,8 +32,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.GetMapping;
import static org.assertj.core.api.Assertions.assertThat;
@@ -83,7 +82,7 @@ class FeignClientDisabledClientLevelFeaturesTests {
@FeignClient(name = "bar", url = "https://bar", configuration = BarConfiguration.class)
interface BarClient {
@RequestMapping(value = "/", method = RequestMethod.GET)
@GetMapping("/")
String get();
}
@@ -32,8 +32,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.GetMapping;
import static org.assertj.core.api.Assertions.assertThat;
@@ -82,7 +81,7 @@ class FeignClientDisabledFeaturesTests {
@FeignClient(name = "bar", url = "https://bar", configuration = BarConfiguration.class)
interface BarClient {
@RequestMapping(value = "/", method = RequestMethod.GET)
@GetMapping("/")
String get();
}
@@ -37,8 +37,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.GetMapping;
import static org.assertj.core.api.Assertions.assertThat;
@@ -133,7 +132,7 @@ public class FeignClientErrorDecoderTests {
@FeignClient(name = "bar", url = "http://bar", configuration = BarConfiguration.class)
interface BarClient {
@RequestMapping(value = "/", method = RequestMethod.GET)
@GetMapping("/")
String get();
}
@@ -39,10 +39,9 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
/**
* @author Spencer Gibb
@@ -90,7 +89,7 @@ public class FeignClientFactoryTests {
interface TestType {
@RequestMapping(value = "/", method = GET)
@GetMapping("/")
String hello();
}
@@ -66,8 +66,6 @@ import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@@ -295,8 +293,7 @@ public class FeignClientUsingPropertiesTests {
protected interface FormClient {
@RequestMapping(value = "/form", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
@PostMapping(value = "/form", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
String form(Map<String, String> form);
}
@@ -328,7 +325,7 @@ public class FeignClientUsingPropertiesTests {
@Import(NoSecurityConfiguration.class)
protected static class Application {
@RequestMapping(method = RequestMethod.GET, value = "/foo")
@GetMapping("/foo")
public String foo(HttpServletRequest request) throws IllegalAccessException {
if ("Foo".equals(request.getHeader("Foo")) && "Bar".equals(request.getHeader("Bar"))) {
return "OK";
@@ -25,8 +25,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.GetMapping;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
@@ -105,7 +104,7 @@ public class FeignClientsRegistrarTests {
@FeignClient(name = "fallbackTestClient", url = "http://localhost:8080/", fallback = FallbackClient.class)
protected interface FallbackClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
String fallbackTest();
}
@@ -114,7 +113,7 @@ public class FeignClientsRegistrarTests {
fallbackFactory = FallbackFactoryClient.class)
protected interface FallbackFactoryClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
String fallbackFactoryTest();
}
@@ -37,8 +37,7 @@ import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.SocketUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
@@ -102,7 +101,7 @@ class FeignHttpClientUrlTests {
@FeignClient(name = "localappurl", url = "http://localhost:${server.port}/")
protected interface UrlClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
Hello getHello();
}
@@ -110,7 +109,7 @@ class FeignHttpClientUrlTests {
@FeignClient(name = "beanappurl", url = "#{SERVER_URL}path")
protected interface BeanUrlClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
Hello getHello();
}
@@ -118,7 +117,7 @@ class FeignHttpClientUrlTests {
@FeignClient(name = "beanappurlnoprotocol", url = "#{SERVER_URL_NO_PROTOCOL}path")
protected interface BeanUrlClientNoProtocol {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
Hello getHello();
}
@@ -130,12 +129,12 @@ class FeignHttpClientUrlTests {
@Import(NoSecurityConfiguration.class)
protected static class TestConfig {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
public Hello getHello() {
return new Hello("hello world 1");
}
@RequestMapping(method = RequestMethod.GET, value = "/path/hello")
@GetMapping("/path/hello")
public Hello getHelloWithPath() {
return getHello();
}
@@ -38,8 +38,6 @@ import org.springframework.test.annotation.DirtiesContext;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.SocketUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
@@ -104,7 +102,7 @@ class FeignHttpClientUrlTestsWithRetryableLoadBalancer {
@FeignClient(name = "localappurl", url = "http://localhost:${server.port}/")
protected interface UrlClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
Hello getHello();
}
@@ -112,7 +110,7 @@ class FeignHttpClientUrlTestsWithRetryableLoadBalancer {
@FeignClient(name = "beanappurl", url = "#{SERVER_URL}path")
protected interface BeanUrlClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
Hello getHello();
}
@@ -120,7 +118,7 @@ class FeignHttpClientUrlTestsWithRetryableLoadBalancer {
@FeignClient(name = "beanappurlnoprotocol", url = "#{SERVER_URL_NO_PROTOCOL}path")
protected interface BeanUrlClientNoProtocol {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
Hello getHello();
}
@@ -39,8 +39,6 @@ import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
@@ -154,22 +152,22 @@ public class SpringDecoderTests extends FeignClientFactoryBean {
protected interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/helloresponse")
@GetMapping("/helloresponse")
ResponseEntity<Hello> getHelloResponse();
@RequestMapping(method = RequestMethod.GET, value = "/hellovoid")
@GetMapping("/hellovoid")
ResponseEntity<Void> getHelloVoid();
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "/hellos")
@GetMapping("/hellos")
List<Hello> getHellos();
@RequestMapping(method = RequestMethod.GET, value = "/hellostrings")
@GetMapping("/hellostrings")
List<String> getHelloStrings();
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
@GetMapping("/hellonotfound")
ResponseEntity<String> getNotFound();
@GetMapping("/helloWildcard")
@@ -39,8 +39,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
@@ -122,7 +121,7 @@ public class BeansFeignClientTests {
return feignClientBuilder.forType(TestClient.class, "builderapp").build();
}
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
public Hello getHello() {
return new Hello("hello world 1");
}
@@ -19,14 +19,13 @@ package org.springframework.cloud.openfeign.beans;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.beans.BeansFeignClientTests.Hello;
import org.springframework.context.annotation.Primary;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.GetMapping;
@Primary
@FeignClient("localapp")
public interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
Hello getHello();
}
@@ -18,13 +18,12 @@ package org.springframework.cloud.openfeign.beans.extra;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.beans.BeansFeignClientTests.Hello;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(value = "otherapp", qualifier = "uniquequalifier")
public interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
Hello getHello();
}
@@ -45,8 +45,7 @@ import org.springframework.stereotype.Component;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.SocketUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
@@ -115,10 +114,10 @@ public class CircuitBreakerTests {
@FeignClient(name = "test", url = "http://localhost:${server.port}/", fallback = Fallback.class)
protected interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
@GetMapping("/hellonotfound")
String getException();
}
@@ -142,10 +141,10 @@ public class CircuitBreakerTests {
fallbackFactory = TestFallbackFactory.class)
protected interface TestClientWithFactory {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
@GetMapping("/hellonotfound")
String getException();
}
@@ -40,8 +40,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.util.SocketUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
@@ -97,10 +96,10 @@ public class CircuitBreakerWithNoFallbackTests {
@FeignClient(name = "test", url = "http://localhost:${server.port}/")
protected interface CircuitBreakerTestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
Hello getHello();
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
@GetMapping("/hellonotfound")
String getException();
}
@@ -24,9 +24,9 @@ import org.springframework.cloud.openfeign.encoding.app.domain.Invoice;
import org.springframework.data.domain.Page;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Simple Feign client for retrieving the invoice list.
@@ -37,23 +37,23 @@ import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient("local")
public interface InvoiceClient {
@RequestMapping(value = "invoicesPaged", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@GetMapping(value = "invoicesPaged", produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Page<Invoice>> getInvoicesPaged(org.springframework.data.domain.Pageable pageable);
@RequestMapping(value = "invoicesPagedWithBody", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(value = "invoicesPagedWithBody", consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Page<Invoice>> getInvoicesPagedWithBody(
@SpringQueryMap org.springframework.data.domain.Pageable pageable, @RequestBody String titlePrefix);
@RequestMapping(value = "invoicesSortedWithBody", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(value = "invoicesSortedWithBody", consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Page<Invoice>> getInvoicesSortedWithBody(@SpringQueryMap org.springframework.data.domain.Sort sort,
@RequestBody String titlePrefix);
@RequestMapping(value = "invoices", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@GetMapping(value = "invoices", produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<List<Invoice>> getInvoices();
@RequestMapping(value = "invoices", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE,
@PostMapping(value = "invoices", consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<List<Invoice>> saveInvoices(List<Invoice> invoices);
@@ -28,9 +28,9 @@ import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
@@ -42,28 +42,28 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
public class InvoiceResource {
@RequestMapping(value = "invoices", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@GetMapping(value = "invoices", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Invoice>> getInvoices() {
return ResponseEntity.ok(createInvoiceList(null, 100, null));
}
@RequestMapping(value = "invoices", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE,
@PostMapping(value = "invoices", consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<List<Invoice>> saveInvoices(@RequestBody List<Invoice> invoices) {
return ResponseEntity.ok(invoices);
}
@RequestMapping(value = "invoicesPaged", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@GetMapping(value = "invoicesPaged", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Page<Invoice>> getInvoicesPaged(org.springframework.data.domain.Pageable pageable) {
Page<Invoice> page = new PageImpl<>(createInvoiceList(null, pageable.getPageSize(), pageable.getSort()),
pageable, 100);
return ResponseEntity.ok(page);
}
@RequestMapping(value = "invoicesPagedWithBody", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(value = "invoicesPagedWithBody", consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Page<Invoice>> getInvoicesPagedWithBody(org.springframework.data.domain.Pageable pageable,
@RequestBody String titlePrefix) {
Page<Invoice> page = new PageImpl<>(createInvoiceList(titlePrefix, pageable.getPageSize(), pageable.getSort()),
@@ -71,8 +71,8 @@ public class InvoiceResource {
return ResponseEntity.ok(page);
}
@RequestMapping(value = "invoicesSortedWithBody", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(value = "invoicesSortedWithBody", consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Page<Invoice>> getInvoicesSortedWithBody(org.springframework.data.domain.Sort sort,
@RequestBody String titlePrefix) {
Page<Invoice> page = new PageImpl<>(createInvoiceList(titlePrefix, 100, sort), PageRequest.of(0, 100, sort),
@@ -28,8 +28,7 @@ import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.GetMapping;
import static org.assertj.core.api.Assertions.assertThat;
@@ -74,7 +73,7 @@ public class FeignClientValidationTests {
@FeignClient(name = "bar")
interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/")
@GetMapping("/")
String get();
}
@@ -90,7 +89,7 @@ public class FeignClientValidationTests {
@FeignClient(contextId = "foo", name = "bar")
interface FooClient {
@RequestMapping(method = RequestMethod.GET, value = "/")
@GetMapping("/")
String get();
}
@@ -98,7 +97,7 @@ public class FeignClientValidationTests {
@FeignClient(name = "bar")
interface BarClient {
@RequestMapping(method = RequestMethod.GET, value = "/")
@GetMapping("/")
String get();
}
@@ -113,7 +112,7 @@ public class FeignClientValidationTests {
@FeignClient("foo_bar")
interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/")
@GetMapping("/")
String get();
}
@@ -16,7 +16,10 @@
package org.springframework.cloud.openfeign.loadbalancer;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
@@ -29,6 +32,7 @@ import java.util.concurrent.ConcurrentHashMap;
import feign.Client;
import feign.Request;
import feign.Response;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -117,6 +121,13 @@ class RetryableFeignBlockingLoadBalancerClientTests {
return Response.builder().request(testRequest()).status(status).build();
}
private Response testResponse(int status, String body) {
// ByteArrayInputStream ignores close() and must be wrapped
InputStream reallyCloseable = new BufferedInputStream(
new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)));
return Response.builder().request(testRequest()).status(status).body(reallyCloseable, null).build();
}
@Test
void shouldExecuteOriginalRequestIfInstanceNotFound() throws IOException {
Request request = testRequest();
@@ -148,6 +159,22 @@ class RetryableFeignBlockingLoadBalancerClientTests {
verify(delegate, times(2)).execute(any(), any());
}
@Test
void shouldExposeResponseBodyOnRetry() throws IOException {
properties.getRetry().getRetryableStatusCodes().add(503);
Request request = testRequest();
when(delegate.execute(any(), any())).thenReturn(testResponse(503, "foo"), testResponse(503, "foo"));
when(retryFactory.createRetryPolicy(any(), eq(loadBalancerClient)))
.thenReturn(new BlockingLoadBalancedRetryPolicy(properties));
when(loadBalancerClient.reconstructURI(serviceInstance, URI.create("http://test/path")))
.thenReturn(URI.create("http://testhost:80/path"));
Response response = feignBlockingLoadBalancerClient.execute(request, new Request.Options());
String bodyContent = IOUtils.toString(response.body().asReader(StandardCharsets.UTF_8));
assertThat(bodyContent).isEqualTo("foo");
}
@Test
void shouldPassCorrectRequestToDelegate() throws IOException {
Request request = testRequest();
@@ -16,13 +16,18 @@
package org.springframework.cloud.openfeign.support;
import java.util.ArrayList;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import static org.assertj.core.api.Assertions.assertThat;
@@ -31,6 +36,8 @@ import static org.assertj.core.api.Assertions.assertThat;
*
* @author Ruben Vervaeke
* @author Olga Maciaszek-Sharma
* @author Pedro Mendes
* @author Nikita Konev
*/
public class PageJacksonModuleTests {
@@ -40,6 +47,7 @@ public class PageJacksonModuleTests {
public static void initialize() {
objectMapper = new ObjectMapper();
objectMapper.registerModule(new PageJacksonModule());
objectMapper.registerModule(new SortJacksonModule());
}
@ParameterizedTest
@@ -58,4 +66,40 @@ public class PageJacksonModuleTests {
assertThat(result.getPageable().getPageNumber()).isEqualTo(1);
}
@Test
public void serializeAndDeserializeEmpty() throws JsonProcessingException {
// Given
PageImpl<Object> objects = new PageImpl<>(new ArrayList<>());
String pageJson = objectMapper.writeValueAsString(objects);
// When
Page<?> result = objectMapper.readValue(pageJson, Page.class);
// Then
assertThat(result).isNotNull();
assertThat(result.getTotalElements()).isEqualTo(0);
assertThat(result.getContent()).hasSize(0);
}
@Test
public void serializeAndDeserializeFilledMultiple() throws JsonProcessingException {
// Given
ArrayList<Object> strings0 = new ArrayList<>();
strings0.add("first element");
strings0.add("second element");
PageImpl<Object> objects = new PageImpl<>(strings0, PageRequest.of(6, 2), 100);
assertThat(objects.getContent()).hasSize(2);
assertThat(objects.getPageable().getPageSize()).isEqualTo(2);
String pageJson = objectMapper.writeValueAsString(objects);
// When
Page<?> result = objectMapper.readValue(pageJson, Page.class);
// Then
assertThat(result).isNotNull();
assertThat(result.getTotalElements()).isEqualTo(100);
assertThat(result.getContent()).hasSize(2);
assertThat(result.getContent().get(0)).isEqualTo("first element");
assertThat(result.getContent().get(1)).isEqualTo("second element");
assertThat(result.getPageable().getPageSize()).isEqualTo(2);
assertThat(result.getPageable().getPageNumber()).isEqualTo(6);
}
}
@@ -60,6 +60,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.http.HttpHeaders.ACCEPT;
import static org.springframework.http.HttpHeaders.CONTENT_LENGTH;
import static org.springframework.http.HttpHeaders.CONTENT_TYPE;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE;
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;
@@ -68,6 +69,7 @@ import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;
* @author Spencer Gibb
* @author Olga Maciaszek-Sharma
* @author Ahmad Mozafarnia
* @author Can Bezmen
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SpringEncoderTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT,
@@ -179,6 +181,17 @@ public class SpringEncoderTests {
assertThat(new String(request.requestBody().asBytes())).as("Body content cannot be decoded").contains("hi");
}
@Test
public void testFromURLEncodedValue() {
Encoder encoder = context.getInstance("formUrlEncoded", Encoder.class);
assertThat(encoder).isNotNull();
RequestTemplate request = new RequestTemplate();
request.header(CONTENT_TYPE, APPLICATION_FORM_URLENCODED_VALUE);
String body = "test";
encoder.encode(body, String.class, request);
assertThat(new String(request.requestBody().asBytes())).as("Body content cannot be decoded").contains(body);
}
@Test
public void testNoCharsetForBinaryFiles() {
Encoder encoder = context.getInstance("test", Encoder.class);
@@ -50,6 +50,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.MatrixVariable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -62,6 +63,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;
/**
@@ -168,16 +170,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
@@ -185,25 +183,13 @@ public class SpringMvcContractTests {
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().url()).isEqualTo("/");
assertThat(data.template().method()).isEqualTo("GET");
assertThat(data.indexToName().get(0).iterator().next()).isEqualTo("classId");
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", String.class);
@@ -220,22 +206,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", TestObject.class);
@@ -266,7 +236,7 @@ public class SpringMvcContractTests {
Integer.class);
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);
@@ -296,7 +266,7 @@ public class SpringMvcContractTests {
Integer.class);
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);
@@ -315,7 +285,7 @@ public class SpringMvcContractTests {
Method method = TestTemplate_Advanced.class.getDeclaredMethod("getTest2", String.class, Integer.class);
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);
@@ -367,7 +337,7 @@ public class SpringMvcContractTests {
Method method = TestTemplate_Advanced.class.getDeclaredMethod("getTest");
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);
@@ -459,7 +429,7 @@ public class SpringMvcContractTests {
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
assertThat(data.template().url()).isEqualTo("/advanced/testfallback/{id}?amount=" + "{amount}");
assertThat(data.template().url()).isEqualTo("/testfallback/{id}?amount=" + "{amount}");
assertThat(data.template().method()).isEqualTo("PUT");
assertThat(data.template().headers().get("Accept").iterator().next())
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
@@ -587,13 +557,13 @@ public class SpringMvcContractTests {
@RequestMapping(value = "/test/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<TestObject> getTest(@PathVariable("id") String id);
@RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
TestObject getTest();
@GetMapping(value = "/test/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<TestObject> getMappingTest(@PathVariable("id") String id);
@RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
TestObject postTest(@RequestBody TestObject object);
@PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
@@ -602,11 +572,18 @@ public class SpringMvcContractTests {
}
@RequestMapping("/prepend/{classId}")
public interface TestTemplate_Class_Annotations {
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 {
@GetMapping("/test/{testId}")
TestObject getSpecificTest(@PathVariable("classId") String classId, @PathVariable("testId") String testId);
@RequestMapping(method = RequestMethod.GET)
TestObject getAllTests(@PathVariable("classId") String classId);
@@ -618,47 +595,46 @@ public class SpringMvcContractTests {
public interface TestTemplate_Headers {
@RequestMapping(value = "/test/{id}", method = RequestMethod.GET, headers = "X-Foo=bar")
@GetMapping(value = "/test/{id}", headers = "X-Foo=bar")
ResponseEntity<TestObject> getTest(@PathVariable("id") String id);
}
public interface TestTemplate_HeadersWithoutValues {
@RequestMapping(value = "/test/{id}", method = RequestMethod.GET,
headers = { "X-Foo", "!X-Bar", "X-Baz!=fooBar" })
@GetMapping(value = "/test/{id}", headers = { "X-Foo", "!X-Bar", "X-Baz!=fooBar" })
ResponseEntity<TestObject> getTest(@PathVariable("id") String id);
}
public interface TestTemplate_ListParams {
@RequestMapping(value = "/test", method = RequestMethod.GET)
@GetMapping("/test")
ResponseEntity<TestObject> getTest(@RequestParam("id") List<String> id);
}
public interface TestTemplate_ListParamsWithoutName {
@RequestMapping(value = "/test", method = RequestMethod.GET)
@GetMapping("/test")
ResponseEntity<TestObject> getTest(@RequestParam List<String> id);
}
public interface TestTemplate_MapParams {
@RequestMapping(value = "/test", method = RequestMethod.GET)
@GetMapping("/test")
ResponseEntity<TestObject> getTest(@RequestParam Map<String, String> params);
}
public interface TestTemplate_HeaderMap {
@RequestMapping(path = "/headerMap")
@GetMapping("/headerMap")
String headerMap(@RequestHeader MultiValueMap<String, String> headerMap,
@RequestHeader(name = "aHeader") String aHeader);
@RequestMapping(path = "/headerMapMoreThanOnce")
@GetMapping("/headerMapMoreThanOnce")
String headerMapMoreThanOnce(@RequestHeader MultiValueMap<String, String> headerMap1,
@RequestHeader MultiValueMap<String, String> headerMap2);
@@ -666,42 +642,40 @@ public class SpringMvcContractTests {
public interface TestTemplate_QueryMap {
@RequestMapping(path = "/queryMap")
@GetMapping("/queryMap")
String queryMap(@RequestParam MultiValueMap<String, String> queryMap,
@RequestParam(name = "aParam") String aParam);
@RequestMapping(path = "/queryMapMoreThanOnce")
@GetMapping("/queryMapMoreThanOnce")
String queryMapMoreThanOnce(@RequestParam MultiValueMap<String, String> queryMap1,
@RequestParam MultiValueMap<String, String> queryMap2);
@RequestMapping(path = "/queryMapObject")
@GetMapping("/queryMapObject")
String queryMapObject(@SpringQueryMap TestObject queryMap, @RequestParam(name = "aParam") String aParam);
}
public interface TestTemplate_RequestPart {
@RequestMapping(path = "/requestPart", method = RequestMethod.POST,
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@PostMapping(path = "/requestPart", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
void requestWithMultipleParts(@RequestPart("file") MultipartFile file, @RequestPart("id") String identifier);
}
public interface TestTemplate_MatrixVariable {
@RequestMapping(path = "/matrixVariable/{params}")
@GetMapping("/matrixVariable/{params}")
String matrixVariable(@MatrixVariable("params") Map<String, Object> params);
@RequestMapping(path = "/matrixVariableObject/{param}")
@GetMapping("/matrixVariableObject/{param}")
String matrixVariableObject(@MatrixVariable("param") Object object);
@RequestMapping(path = "/matrixVariable/{params}")
@GetMapping("/matrixVariable/{params}")
String matrixVariableNotNamed(@MatrixVariable Map<String, Object> params);
}
@JsonAutoDetect
@RequestMapping("/advanced")
public interface TestTemplate_Advanced {
@CollectionFormat(SSV)
@@ -709,21 +683,20 @@ public class SpringMvcContractTests {
ResponseEntity<TestObject> getWithCollectionFormat();
@ExceptionHandler
@RequestMapping(path = "/test/{id}", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
@PutMapping(path = "/test/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<TestObject> getTest(@RequestHeader("Authorization") String auth, @PathVariable("id") String id,
@RequestParam("amount") Integer amount);
@RequestMapping(path = "/test2", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
@PutMapping(path = "/test2", produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<TestObject> getTest2(@RequestHeader(name = "Authorization") String auth,
@RequestParam(name = "amount") Integer amount);
@ExceptionHandler
@RequestMapping(path = "/testfallback/{id}", method = RequestMethod.PUT,
produces = MediaType.APPLICATION_JSON_VALUE)
@PutMapping(path = "/testfallback/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<TestObject> getTestFallback(@RequestHeader String Authorization, @PathVariable String id,
@RequestParam Integer amount);
@RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
TestObject getTest();
@GetMapping(produces = "application/json")
@@ -735,7 +708,7 @@ public class SpringMvcContractTests {
String CUSTOM_PATTERN = "dd-MM-yyyy HH:mm";
@RequestMapping(method = RequestMethod.GET)
@GetMapping
String getTest(@RequestParam(name = "localDateTime") @DateTimeFormat(
pattern = CUSTOM_PATTERN) LocalDateTime localDateTime);
@@ -745,7 +718,7 @@ public class SpringMvcContractTests {
String CUSTOM_PATTERN = "$###,###.###";
@RequestMapping(method = RequestMethod.GET)
@GetMapping
String getTest(@RequestParam("amount") @NumberFormat(pattern = CUSTOM_PATTERN) BigDecimal amount);
}
@@ -17,8 +17,7 @@
package org.springframework.cloud.openfeign.testclients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.GetMapping;
/**
* @author Ryan Baxter
@@ -26,7 +25,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(name = "localapp")
public interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
String getHello();
}
@@ -37,8 +37,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
@@ -83,7 +82,7 @@ public class FeignClientNotPrimaryTests {
@FeignClient(name = "localapp", primary = false)
protected interface TestClient {
@RequestMapping(method = RequestMethod.GET, path = "/hello")
@GetMapping("/hello")
Hello getHello();
}
@@ -101,7 +100,7 @@ public class FeignClientNotPrimaryTests {
return new PrimaryTestClient();
}
@RequestMapping(method = RequestMethod.GET, path = "/hello")
@GetMapping("/hello")
public Hello getHello() {
return new Hello(HELLO_WORLD_1);
}
@@ -27,8 +27,7 @@ import org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoCon
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.GetMapping;
import static org.assertj.core.api.Assertions.assertThat;
@@ -70,7 +69,7 @@ public class FeignClientValidationTests {
@FeignClient(name = "example", url = "https://example.com")
interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/")
@GetMapping("/")
@Deprecated
String get();
@@ -86,7 +85,7 @@ public class FeignClientValidationTests {
@FeignClient(name = "example", url = "${feignClient.url:https://example.com}")
interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/")
@GetMapping("/")
@Deprecated
String get();
@@ -102,7 +101,7 @@ public class FeignClientValidationTests {
@FeignClient("foo")
interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/")
@GetMapping("/")
@Deprecated
String get();
@@ -41,11 +41,11 @@ import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
@@ -107,17 +107,17 @@ class FeignHttpClientTests {
protected interface BaseTestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello", produces = MediaType.APPLICATION_JSON_VALUE)
@GetMapping(value = "/hello", produces = MediaType.APPLICATION_JSON_VALUE)
Hello getHello();
@RequestMapping(method = RequestMethod.PATCH, value = "/hellop", consumes = "application/json")
@PatchMapping(value = "/hellop", consumes = "application/json")
ResponseEntity<Void> patchHello(Hello hello);
}
protected interface UserService {
@RequestMapping(method = RequestMethod.GET, value = "/users/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
@GetMapping(value = "/users/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
User getUser(@PathVariable("id") long id);
}
@@ -136,12 +136,12 @@ class FeignHttpClientTests {
@Import(NoSecurityConfiguration.class)
protected static class Application implements UserService {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
public Hello getHello() {
return new Hello("hello world 1");
}
@RequestMapping(method = RequestMethod.PATCH, value = "/hellop")
@PatchMapping("/hellop")
public ResponseEntity<Void> patchHello(@RequestBody Hello hello,
@RequestHeader("Content-Length") int contentLength) {
if (contentLength <= 0) {
@@ -40,11 +40,11 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
@@ -107,17 +107,17 @@ class FeignOkHttpTests {
protected interface BaseTestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
Hello getHello();
@RequestMapping(method = RequestMethod.PATCH, value = "/hellop", consumes = "application/json")
@PatchMapping(value = "/hellop", consumes = "application/json")
ResponseEntity<Void> patchHello(Hello hello);
}
protected interface UserService {
@RequestMapping(method = RequestMethod.GET, value = "/users/{id}")
@GetMapping("/users/{id}")
User getUser(@PathVariable("id") long id);
}
@@ -138,12 +138,12 @@ class FeignOkHttpTests {
@Import(NoSecurityConfiguration.class)
protected static class Application implements UserService {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
public Hello getHello() {
return new Hello("hello world 1");
}
@RequestMapping(method = RequestMethod.PATCH, value = "/hellop")
@PatchMapping("/hellop")
public ResponseEntity<Void> patchHello(@RequestBody Hello hello,
@RequestHeader("Content-Length") int contentLength) {
if (contentLength <= 0) {
@@ -38,7 +38,6 @@ import javax.servlet.http.Part;
import feign.Client;
import feign.Logger;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.codec.EncodeException;
import org.junit.jupiter.api.Test;
@@ -65,7 +64,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistry;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
@@ -73,6 +71,8 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -84,6 +84,9 @@ import org.springframework.web.multipart.MultipartFile;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;
/**
* @author Spencer Gibb
@@ -277,6 +280,13 @@ class ValidFeignClientTests {
assertThat(testClient.getToString(args)).isEqualTo(expectedResult);
}
@Test
void testFormURLEncoded() {
Hello hello = new Hello(HELLO_WORLD_1);
Hello response = testClient.postFormUrlEncoded(hello);
assertThat(response).isEqualTo(hello);
}
@Test
void namedFeignClientWorks() {
assertThat(namedFeignClient).as("namedFeignClient was null").isNotNull();
@@ -382,52 +392,42 @@ class ValidFeignClientTests {
@FeignClient(name = "localapp8")
protected interface MultipartClient {
@RequestMapping(method = RequestMethod.POST, path = "/singlePart",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@PostMapping(path = "/singlePart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
String singlePart(@RequestPart("hello") String hello);
@RequestMapping(method = RequestMethod.POST, path = "/singlePojoPart",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@PostMapping(path = "/singlePojoPart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
String singlePojoPart(@RequestPart("hello") Hello hello);
@RequestMapping(method = RequestMethod.POST, path = "/multipart",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@PostMapping(path = "/multipart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
String multipart(@RequestPart("hello") String hello, @RequestPart("world") String world,
@RequestPart("file") MultipartFile file);
@RequestMapping(method = RequestMethod.POST, path = "/multipartPojo",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@PostMapping(path = "/multipartPojo", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
String multipartPojo(@RequestPart("hello") String hello, @RequestPart("world") String world,
@RequestPart("pojo1") Hello pojo1, @RequestPart("pojo2") Hello pojo2,
@RequestPart("file") MultipartFile file);
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
String requestPartListOfMultipartFilesReturnsPartNames(@RequestPart("files") List<MultipartFile> files);
@RequestMapping(method = RequestMethod.POST, path = "/multipartFilenames",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@PostMapping(path = "/multipartFilenames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
String requestPartListOfMultipartFilesReturnsFileNames(@RequestPart("files") List<MultipartFile> files);
@RequestMapping(method = RequestMethod.POST, path = "/multipartPojosFiles",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@PostMapping(path = "/multipartPojosFiles", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
String requestPartListOfPojosAndListOfMultipartFiles(@RequestPart("pojos") List<Hello> pojos,
@RequestPart("files") List<MultipartFile> files);
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
String requestBodyListOfMultipartFiles(@RequestBody List<MultipartFile> files);
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
String requestBodySingleMultipartFile(@RequestBody MultipartFile file);
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
String requestBodyMap(@RequestBody Map<String, ?> form);
@RequestMapping(method = RequestMethod.POST, path = "/invalid",
consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@PostMapping(path = "/invalid", consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE,
produces = TEXT_PLAIN_VALUE)
String invalid(@RequestBody MultipartFile file);
}
@@ -435,36 +435,36 @@ class ValidFeignClientTests {
@FeignClient(name = "localapp", configuration = TestClientConfig.class)
protected interface TestClient {
@RequestMapping(method = RequestMethod.GET, path = "/hello")
@GetMapping("/hello")
Hello getHello();
@RequestMapping(method = RequestMethod.GET, path = "/hello")
@GetMapping("/hello")
Optional<Hello> getOptionalHello();
@RequestMapping(method = RequestMethod.GET, path = "${feignClient.methodLevelRequestMappingPath}")
@GetMapping("${feignClient.methodLevelRequestMappingPath}")
Hello getHelloUsingPropertyPlaceHolder();
@RequestMapping(method = RequestMethod.GET, path = "/hellos")
@GetMapping("/hellos")
List<Hello> getHellos();
@RequestMapping(method = RequestMethod.GET, path = "/hellostrings")
@GetMapping("/hellostrings")
List<String> getHelloStrings();
@RequestMapping(method = RequestMethod.GET, path = "/helloheaders")
@GetMapping("/helloheaders")
List<String> getHelloHeaders();
@RequestMapping(method = RequestMethod.GET, path = "/helloheadersplaceholders",
@GetMapping(path = "/helloheadersplaceholders",
headers = "myPlaceholderHeader=${feignClient.myPlaceholderHeader}")
String getHelloHeadersPlaceholders();
@RequestMapping(method = RequestMethod.GET, path = "/helloparams")
@GetMapping("/helloparams")
List<String> getParams(@RequestParam("params") List<String> params);
@RequestMapping(method = RequestMethod.GET, path = "/formattedparams")
@GetMapping("/formattedparams")
List<LocalDate> getFormattedParams(
@RequestParam("params") @DateTimeFormat(pattern = "dd-MM-yyyy") List<LocalDate> params);
@RequestMapping(method = RequestMethod.GET, path = "/noContent")
@GetMapping("/noContent")
ResponseEntity<Void> noContent();
@RequestMapping(method = RequestMethod.HEAD, path = "/head")
@@ -477,21 +477,24 @@ class ValidFeignClientTests {
produces = "application/vnd.io.spring.cloud.test.v1+json", path = "/complex")
String moreComplexContentType(String body);
@RequestMapping(method = RequestMethod.GET, path = "/tostring")
@GetMapping("/tostring")
String getToString(@RequestParam("arg") Arg arg);
@RequestMapping(method = RequestMethod.GET, path = "/tostring2")
@GetMapping("/tostring2")
String getToString(@RequestParam("arg") OtherArg arg);
@RequestMapping(method = RequestMethod.GET, path = "/tostringcollection")
@GetMapping("/tostringcollection")
Collection<String> getToString(@RequestParam("arg") Collection<OtherArg> args);
@PostMapping(path = "/form-urlencoded", consumes = APPLICATION_FORM_URLENCODED_VALUE)
Hello postFormUrlEncoded(Hello hello);
}
@FeignClient(name = "localapp1")
protected interface TestClientServiceId {
@RequestMapping(method = RequestMethod.GET, path = "/hello")
@GetMapping("/hello")
Hello getHello();
}
@@ -499,10 +502,10 @@ class ValidFeignClientTests {
@FeignClient(name = "localapp2", decode404 = true)
protected interface DecodingTestClient {
@RequestMapping(method = RequestMethod.GET, path = "/notFound")
@GetMapping("/notFound")
ResponseEntity<String> notFound();
@RequestMapping(method = RequestMethod.GET, path = "/notFound")
@GetMapping("/notFound")
Optional<String> optional();
}
@@ -526,22 +529,12 @@ class ValidFeignClientTests {
@Bean
public RequestInterceptor interceptor1() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
template.header(MYHEADER1, "myheader1value");
}
};
return template -> template.header(MYHEADER1, "myheader1value");
}
@Bean
public RequestInterceptor interceptor2() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
template.header(MYHEADER2, "myheader2value");
}
};
return template -> template.header(MYHEADER2, "myheader2value");
}
}
@@ -570,27 +563,21 @@ class ValidFeignClientTests {
@Bean
FeignFormatterRegistrar feignFormatterRegistrar() {
return new FeignFormatterRegistrar() {
return registry -> registry.addFormatter(new Formatter<OtherArg>() {
@Override
public void registerFormatters(FormatterRegistry registry) {
registry.addFormatter(new Formatter<OtherArg>() {
@Override
public String print(OtherArg object, Locale locale) {
if ("foo".equals(object.value)) {
return "bar";
}
return object.value;
}
@Override
public OtherArg parse(String text, Locale locale) throws ParseException {
return new OtherArg(text);
}
});
public String print(OtherArg object, Locale locale) {
if ("foo".equals(object.value)) {
return "bar";
}
return object.value;
}
};
@Override
public OtherArg parse(String text, Locale locale) throws ParseException {
return new OtherArg(text);
}
});
}
@Bean
@@ -598,23 +585,23 @@ class ValidFeignClientTests {
return new JsonFormWriter();
}
@RequestMapping(method = RequestMethod.GET, path = "/hello")
@GetMapping("/hello")
public Hello getHello() {
return new Hello(HELLO_WORLD_1);
}
@RequestMapping(method = RequestMethod.GET, path = "/hello2")
@GetMapping("/hello2")
public Hello getHello2() {
return new Hello(OI_TERRA_2);
}
@RequestMapping(method = RequestMethod.GET, path = "/hellos")
@GetMapping("/hellos")
public List<Hello> getHellos() {
ArrayList<Hello> hellos = getHelloList();
return hellos;
}
@RequestMapping(method = RequestMethod.GET, path = "/hellostrings")
@GetMapping("/hellostrings")
public List<String> getHelloStrings() {
ArrayList<String> hellos = new ArrayList<>();
hellos.add(HELLO_WORLD_1);
@@ -622,7 +609,7 @@ class ValidFeignClientTests {
return hellos;
}
@RequestMapping(method = RequestMethod.GET, path = "/helloheaders")
@GetMapping("/helloheaders")
public List<String> getHelloHeaders(@RequestHeader(MYHEADER1) String myheader1,
@RequestHeader(MYHEADER2) String myheader2) {
ArrayList<String> headers = new ArrayList<>();
@@ -631,23 +618,23 @@ class ValidFeignClientTests {
return headers;
}
@RequestMapping(method = RequestMethod.GET, path = "/helloheadersplaceholders")
@GetMapping("/helloheadersplaceholders")
public String getHelloHeadersPlaceholders(@RequestHeader("myPlaceholderHeader") String myPlaceholderHeader) {
return myPlaceholderHeader;
}
@RequestMapping(method = RequestMethod.GET, path = "/helloparams")
@GetMapping("/helloparams")
public List<String> getParams(@RequestParam("params") List<String> params) {
return params;
}
@RequestMapping(method = RequestMethod.GET, path = "/formattedparams")
@GetMapping("/formattedparams")
public List<LocalDate> getFormattedParams(
@RequestParam("params") @DateTimeFormat(pattern = "dd-MM-yyyy") List<LocalDate> params) {
return params;
}
@RequestMapping(method = RequestMethod.GET, path = "/noContent")
@GetMapping("/noContent")
ResponseEntity<Void> noContent() {
return ResponseEntity.noContent().build();
}
@@ -662,7 +649,7 @@ class ValidFeignClientTests {
throw new RuntimeException("always fails");
}
@RequestMapping(method = RequestMethod.GET, path = "/notFound")
@GetMapping("/notFound")
ResponseEntity<String> notFound() {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body((String) null);
}
@@ -676,17 +663,17 @@ class ValidFeignClientTests {
return body;
}
@RequestMapping(method = RequestMethod.GET, path = "/tostring")
@GetMapping("/tostring")
String getToString(@RequestParam("arg") Arg arg) {
return arg.toString();
}
@RequestMapping(method = RequestMethod.GET, path = "/tostring2")
@GetMapping("/tostring2")
String getToString(@RequestParam("arg") OtherArg arg) {
return arg.value;
}
@RequestMapping(method = RequestMethod.GET, path = "/tostringcollection")
@GetMapping("/tostringcollection")
Collection<String> getToString(@RequestParam("arg") Collection<OtherArg> args) {
List<String> result = new ArrayList<>();
for (OtherArg arg : args) {
@@ -695,47 +682,40 @@ class ValidFeignClientTests {
return result;
}
@RequestMapping(method = RequestMethod.POST, path = "/singlePart",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@PostMapping(path = "/singlePart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
String singlePart(@RequestPart("hello") String hello) {
return hello;
}
@RequestMapping(method = RequestMethod.POST, path = "/singlePojoPart",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@PostMapping(path = "/singlePojoPart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
String singlePojoPart(@RequestPart("hello") Hello hello) {
return hello.getMessage();
}
@RequestMapping(method = RequestMethod.POST, path = "/multipart",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@PostMapping(path = "/multipart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
String multipart(@RequestPart("hello") String hello, @RequestPart("world") String world,
@RequestPart("file") MultipartFile file) {
return hello + world + file.getOriginalFilename();
}
@RequestMapping(method = RequestMethod.POST, path = "/multipartPojo",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@PostMapping(path = "/multipartPojo", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
String multipartPojo(@RequestPart("hello") String hello, @RequestPart("world") String world,
@RequestPart("pojo1") Hello pojo1, @RequestPart("pojo2") Hello pojo2,
@RequestPart("file") MultipartFile file) {
return hello + world + pojo1.getMessage() + pojo2.getMessage() + file.getOriginalFilename();
}
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
String multipartNames(HttpServletRequest request) throws Exception {
return request.getParts().stream().map(Part::getName).collect(Collectors.joining(","));
}
@RequestMapping(method = RequestMethod.POST, path = "/multipartFilenames",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@PostMapping(path = "/multipartFilenames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
String multipartFilenames(HttpServletRequest request) throws Exception {
return request.getParts().stream().map(Part::getSubmittedFileName).collect(Collectors.joining(","));
}
@RequestMapping(method = RequestMethod.POST, path = "/multipartPojosFiles",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
@PostMapping(path = "/multipartPojosFiles", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
String requestPartListOfPojosAndListOfMultipartFiles(@RequestPart("pojos") List<Hello> pojos,
@RequestPart("files") List<MultipartFile> files) {
StringBuilder result = new StringBuilder();
@@ -751,6 +731,11 @@ class ValidFeignClientTests {
return result.toString();
}
@PostMapping(path = "/form-urlencoded", consumes = APPLICATION_FORM_URLENCODED_VALUE)
Hello postFormUrlEncoded(Hello hello) {
return hello;
}
}
public static class Hello {
@@ -35,8 +35,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
@@ -70,7 +69,7 @@ public class FeignClientEnvVarTests {
@Import(NoSecurityConfiguration.class)
protected static class Application {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
public String getHello() {
return "hello world 1";
}
@@ -37,8 +37,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
@@ -83,7 +82,7 @@ public class FeignClientScanningTests {
@FeignClient("localapp123")
protected interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
String getHello();
}
@@ -91,7 +90,7 @@ public class FeignClientScanningTests {
@FeignClient("${feignClient.localappName}")
protected interface TestClientByKey {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
String getHello();
}
@@ -104,7 +103,7 @@ public class FeignClientScanningTests {
@Import(NoSecurityConfiguration.class)
protected static class Application {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@GetMapping("/hello")
public String getHello() {
return "hello world 1";
}
+2 -2
View File
@@ -6,11 +6,11 @@
<parent>
<artifactId>spring-cloud-dependencies-parent</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>3.0.3</version>
<version>3.0.5</version>
<relativePath/>
</parent>
<artifactId>spring-cloud-openfeign-dependencies</artifactId>
<version>3.0.3</version>
<version>3.0.6</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>3.0.3</version>
<version>3.0.6</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-cloud-starter-openfeign</artifactId>