Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
127c866616 | ||
|
|
0e6b33f480 | ||
|
|
17d0161faf | ||
|
|
d6783a6f1e | ||
|
|
548e4d55ef | ||
|
|
55ceafae4d | ||
|
|
0c87527ea5 | ||
|
|
a2c473db2a | ||
|
|
ae33802a4b | ||
|
|
c761919bc9 | ||
|
|
998baed0d3 | ||
|
|
4fc80e6b01 | ||
|
|
c101fd2c6e | ||
|
|
71930fd9f0 | ||
|
|
b5283e2f88 | ||
|
|
6aeb1ff676 | ||
|
|
44c63798b0 | ||
|
|
769b38c5c1 | ||
|
|
3c402201ec | ||
|
|
deb150ed88 | ||
|
|
1275875ddb | ||
|
|
c4b9492a9b | ||
|
|
3acf5f9e92 | ||
|
|
e9e38d0274 | ||
|
|
28b50401c5 | ||
|
|
6479db0a54 | ||
|
|
f7e490c466 | ||
|
|
d60cda824d | ||
|
|
190f5a9083 | ||
|
|
1005749eb5 | ||
|
|
6de9fdd3e1 | ||
|
|
df1f982046 | ||
|
|
baf7cf95f2 | ||
|
|
45861dd657 | ||
|
|
00ea88c67f | ||
|
|
d897b18e16 | ||
|
|
8c08204c78 | ||
|
|
1f5bdd4224 | ||
|
|
2f43715f6f | ||
|
|
280291ec93 | ||
|
|
0de9dd614d |
@@ -5,9 +5,9 @@ name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches: [ 3.0.x ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
branches: [ 3.0.x ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -32,4 +32,7 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-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
|
||||
|
||||
+2
-16
@@ -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
|
||||
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>3.0.3</version>
|
||||
<version>3.0.5</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
|
||||
|
||||
|
||||
@@ -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.5</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.4</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<scm>
|
||||
@@ -26,7 +26,7 @@
|
||||
<properties>
|
||||
<main.basedir>${basedir}</main.basedir>
|
||||
<jackson.version>2.11.3</jackson.version>
|
||||
<spring-cloud-commons.version>3.0.3</spring-cloud-commons.version>
|
||||
<spring-cloud-commons.version>3.0.4</spring-cloud-commons.version>
|
||||
|
||||
<!-- Plugin versions -->
|
||||
<maven-eclipse-plugin.version>2.10</maven-eclipse-plugin.version>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>3.0.3</version>
|
||||
<version>3.0.5</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-openfeign-core</artifactId>
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import feign.Target;
|
||||
|
||||
/**
|
||||
* Used to resolve a circuitbreaker name which will be used in
|
||||
* {@link org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory}.
|
||||
*
|
||||
* @author Kwangyong Kim
|
||||
* @since 2020.0.4
|
||||
*/
|
||||
public interface CircuitBreakerNameResolver {
|
||||
|
||||
String resolveCircuitBreakerName(String feignClientName, Target<?> target, Method method);
|
||||
|
||||
}
|
||||
+22
-2
@@ -17,6 +17,7 @@
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
@@ -29,6 +30,7 @@ import com.fasterxml.jackson.databind.Module;
|
||||
import feign.Client;
|
||||
import feign.Feign;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.Target;
|
||||
import feign.hc5.ApacheHttp5Client;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
import feign.okhttp.OkHttpClient;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+11
-3
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+7
-7
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+7
-2
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+35
-12
@@ -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;
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign.loadbalancer;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.net.URI;
|
||||
|
||||
import feign.Response;
|
||||
|
||||
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
|
||||
|
||||
/**
|
||||
* A {@link RetryableStatusCodeException} for {@link Response}s.
|
||||
*
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class LoadBalancerResponseStatusCodeException extends RetryableStatusCodeException {
|
||||
|
||||
private final Response response;
|
||||
|
||||
public LoadBalancerResponseStatusCodeException(String serviceId, Response response, byte[] body, URI uri) {
|
||||
super(serviceId, response.status(), response, uri);
|
||||
this.response = Response.builder().body(new ByteArrayInputStream(body), body.length).headers(response.headers())
|
||||
.reason(response.reason()).status(response.status()).request(response.request()).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response getResponse() {
|
||||
return this.response;
|
||||
}
|
||||
|
||||
}
|
||||
+5
-2
@@ -46,7 +46,6 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValida
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.ResponseData;
|
||||
import org.springframework.cloud.client.loadbalancer.RetryableRequestContext;
|
||||
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@@ -57,6 +56,7 @@ import org.springframework.retry.backoff.NoBackOffPolicy;
|
||||
import org.springframework.retry.policy.NeverRetryPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils.buildRequestData;
|
||||
|
||||
@@ -161,8 +161,11 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(String.format("Retrying on status code: %d", responseStatus));
|
||||
}
|
||||
byte[] byteArray = response.body() == null ? new byte[] {}
|
||||
: StreamUtils.copyToByteArray(response.body().asInputStream());
|
||||
response.close();
|
||||
throw new RetryableStatusCodeException(serviceId, responseStatus, response, URI.create(request.url()));
|
||||
throw new LoadBalancerResponseStatusCodeException(serviceId, response, byteArray,
|
||||
URI.create(request.url()));
|
||||
}
|
||||
return response;
|
||||
}, new LoadBalancedRecoveryCallback<Response, Response>() {
|
||||
|
||||
+21
-8
@@ -24,6 +24,7 @@ import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import feign.RequestTemplate;
|
||||
@@ -48,6 +49,10 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import static org.springframework.cloud.openfeign.support.FeignUtils.getHeaders;
|
||||
import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHeaders;
|
||||
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;
|
||||
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA;
|
||||
import static org.springframework.http.MediaType.MULTIPART_MIXED;
|
||||
import static org.springframework.http.MediaType.MULTIPART_RELATED;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
@@ -56,6 +61,7 @@ import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHead
|
||||
* @author Aaron Whiteside
|
||||
* @author Darren Foong
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Can Bezmen
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class SpringEncoder implements Encoder {
|
||||
@@ -95,8 +101,8 @@ public class SpringEncoder implements Encoder {
|
||||
requestContentType = MediaType.valueOf(type);
|
||||
}
|
||||
|
||||
if (isMultipartType(requestContentType)) {
|
||||
this.springFormEncoder.encode(requestBody, bodyType, request);
|
||||
if (isFormRelatedContentType(requestContentType)) {
|
||||
springFormEncoder.encode(requestBody, bodyType, request);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
@@ -207,9 +213,16 @@ public class SpringEncoder implements Encoder {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFormRelatedContentType(MediaType requestContentType) {
|
||||
return isMultipartType(requestContentType) || isFormUrlEncoded(requestContentType);
|
||||
}
|
||||
|
||||
private boolean isMultipartType(MediaType requestContentType) {
|
||||
return Arrays.asList(MediaType.MULTIPART_FORM_DATA, MediaType.MULTIPART_MIXED, MediaType.MULTIPART_RELATED)
|
||||
.contains(requestContentType);
|
||||
return Arrays.asList(MULTIPART_FORM_DATA, MULTIPART_MIXED, MULTIPART_RELATED).contains(requestContentType);
|
||||
}
|
||||
|
||||
private boolean isFormUrlEncoded(MediaType requestContentType) {
|
||||
return Objects.equals(APPLICATION_FORM_URLENCODED, requestContentType);
|
||||
}
|
||||
|
||||
private boolean binaryContentType(FeignOutputMessage outputMessage) {
|
||||
@@ -227,21 +240,21 @@ public class SpringEncoder implements Encoder {
|
||||
private final HttpHeaders httpHeaders;
|
||||
|
||||
private FeignOutputMessage(RequestTemplate request) {
|
||||
this.httpHeaders = getHttpHeaders(request.headers());
|
||||
httpHeaders = getHttpHeaders(request.headers());
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getBody() throws IOException {
|
||||
return this.outputStream;
|
||||
return outputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
return this.httpHeaders;
|
||||
return httpHeaders;
|
||||
}
|
||||
|
||||
public ByteArrayOutputStream getOutputStream() {
|
||||
return this.outputStream;
|
||||
return outputStream;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+10
-35
@@ -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
|
||||
|
||||
+33
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-3
@@ -32,8 +32,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -83,7 +82,7 @@ class FeignClientDisabledClientLevelFeaturesTests {
|
||||
@FeignClient(name = "bar", url = "https://bar", configuration = BarConfiguration.class)
|
||||
interface BarClient {
|
||||
|
||||
@RequestMapping(value = "/", method = RequestMethod.GET)
|
||||
@GetMapping("/")
|
||||
String get();
|
||||
|
||||
}
|
||||
|
||||
+2
-3
@@ -32,8 +32,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -82,7 +81,7 @@ class FeignClientDisabledFeaturesTests {
|
||||
@FeignClient(name = "bar", url = "https://bar", configuration = BarConfiguration.class)
|
||||
interface BarClient {
|
||||
|
||||
@RequestMapping(value = "/", method = RequestMethod.GET)
|
||||
@GetMapping("/")
|
||||
String get();
|
||||
|
||||
}
|
||||
|
||||
+2
-3
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
+2
-3
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
+2
-5
@@ -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";
|
||||
|
||||
+3
-4
@@ -25,8 +25,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
@@ -105,7 +104,7 @@ public class FeignClientsRegistrarTests {
|
||||
@FeignClient(name = "fallbackTestClient", url = "http://localhost:8080/", fallback = FallbackClient.class)
|
||||
protected interface FallbackClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
String fallbackTest();
|
||||
|
||||
}
|
||||
@@ -114,7 +113,7 @@ public class FeignClientsRegistrarTests {
|
||||
fallbackFactory = FallbackFactoryClient.class)
|
||||
protected interface FallbackFactoryClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
String fallbackFactoryTest();
|
||||
|
||||
}
|
||||
|
||||
+6
-7
@@ -37,8 +37,7 @@ import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.SocketUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -102,7 +101,7 @@ class FeignHttpClientUrlTests {
|
||||
@FeignClient(name = "localappurl", url = "http://localhost:${server.port}/")
|
||||
protected interface UrlClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
@@ -110,7 +109,7 @@ class FeignHttpClientUrlTests {
|
||||
@FeignClient(name = "beanappurl", url = "#{SERVER_URL}path")
|
||||
protected interface BeanUrlClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
@@ -118,7 +117,7 @@ class FeignHttpClientUrlTests {
|
||||
@FeignClient(name = "beanappurlnoprotocol", url = "#{SERVER_URL_NO_PROTOCOL}path")
|
||||
protected interface BeanUrlClientNoProtocol {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
@@ -130,12 +129,12 @@ class FeignHttpClientUrlTests {
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
protected static class TestConfig {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
public Hello getHello() {
|
||||
return new Hello("hello world 1");
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/path/hello")
|
||||
@GetMapping("/path/hello")
|
||||
public Hello getHelloWithPath() {
|
||||
return getHello();
|
||||
}
|
||||
|
||||
+3
-5
@@ -38,8 +38,6 @@ import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.SocketUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -104,7 +102,7 @@ class FeignHttpClientUrlTestsWithRetryableLoadBalancer {
|
||||
@FeignClient(name = "localappurl", url = "http://localhost:${server.port}/")
|
||||
protected interface UrlClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
@@ -112,7 +110,7 @@ class FeignHttpClientUrlTestsWithRetryableLoadBalancer {
|
||||
@FeignClient(name = "beanappurl", url = "#{SERVER_URL}path")
|
||||
protected interface BeanUrlClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
@@ -120,7 +118,7 @@ class FeignHttpClientUrlTestsWithRetryableLoadBalancer {
|
||||
@FeignClient(name = "beanappurlnoprotocol", url = "#{SERVER_URL_NO_PROTOCOL}path")
|
||||
protected interface BeanUrlClientNoProtocol {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
|
||||
+6
-8
@@ -39,8 +39,6 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -154,22 +152,22 @@ public class SpringDecoderTests extends FeignClientFactoryBean {
|
||||
|
||||
protected interface TestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/helloresponse")
|
||||
@GetMapping("/helloresponse")
|
||||
ResponseEntity<Hello> getHelloResponse();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hellovoid")
|
||||
@GetMapping("/hellovoid")
|
||||
ResponseEntity<Void> getHelloVoid();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hellos")
|
||||
@GetMapping("/hellos")
|
||||
List<Hello> getHellos();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hellostrings")
|
||||
@GetMapping("/hellostrings")
|
||||
List<String> getHelloStrings();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
|
||||
@GetMapping("/hellonotfound")
|
||||
ResponseEntity<String> getNotFound();
|
||||
|
||||
@GetMapping("/helloWildcard")
|
||||
|
||||
+2
-3
@@ -39,8 +39,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -122,7 +121,7 @@ public class BeansFeignClientTests {
|
||||
return feignClientBuilder.forType(TestClient.class, "builderapp").build();
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
public Hello getHello() {
|
||||
return new Hello("hello world 1");
|
||||
}
|
||||
|
||||
+2
-3
@@ -19,14 +19,13 @@ package org.springframework.cloud.openfeign.beans;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.cloud.openfeign.beans.BeansFeignClientTests.Hello;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Primary
|
||||
@FeignClient("localapp")
|
||||
public interface TestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
|
||||
+2
-3
@@ -18,13 +18,12 @@ package org.springframework.cloud.openfeign.beans.extra;
|
||||
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.cloud.openfeign.beans.BeansFeignClientTests.Hello;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@FeignClient(value = "otherapp", qualifier = "uniquequalifier")
|
||||
public interface TestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
|
||||
+5
-6
@@ -45,8 +45,7 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.SocketUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -115,10 +114,10 @@ public class CircuitBreakerTests {
|
||||
@FeignClient(name = "test", url = "http://localhost:${server.port}/", fallback = Fallback.class)
|
||||
protected interface TestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
|
||||
@GetMapping("/hellonotfound")
|
||||
String getException();
|
||||
|
||||
}
|
||||
@@ -142,10 +141,10 @@ public class CircuitBreakerTests {
|
||||
fallbackFactory = TestFallbackFactory.class)
|
||||
protected interface TestClientWithFactory {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
|
||||
@GetMapping("/hellonotfound")
|
||||
String getException();
|
||||
|
||||
}
|
||||
|
||||
+3
-4
@@ -40,8 +40,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.util.SocketUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -97,10 +96,10 @@ public class CircuitBreakerWithNoFallbackTests {
|
||||
@FeignClient(name = "test", url = "http://localhost:${server.port}/")
|
||||
protected interface CircuitBreakerTestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
|
||||
@GetMapping("/hellonotfound")
|
||||
String getException();
|
||||
|
||||
}
|
||||
|
||||
+9
-9
@@ -24,9 +24,9 @@ import org.springframework.cloud.openfeign.encoding.app.domain.Invoice;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
/**
|
||||
* Simple Feign client for retrieving the invoice list.
|
||||
@@ -37,23 +37,23 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||
@FeignClient("local")
|
||||
public interface InvoiceClient {
|
||||
|
||||
@RequestMapping(value = "invoicesPaged", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@GetMapping(value = "invoicesPaged", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
ResponseEntity<Page<Invoice>> getInvoicesPaged(org.springframework.data.domain.Pageable pageable);
|
||||
|
||||
@RequestMapping(value = "invoicesPagedWithBody", method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@PostMapping(value = "invoicesPagedWithBody", consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
ResponseEntity<Page<Invoice>> getInvoicesPagedWithBody(
|
||||
@SpringQueryMap org.springframework.data.domain.Pageable pageable, @RequestBody String titlePrefix);
|
||||
|
||||
@RequestMapping(value = "invoicesSortedWithBody", method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@PostMapping(value = "invoicesSortedWithBody", consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
ResponseEntity<Page<Invoice>> getInvoicesSortedWithBody(@SpringQueryMap org.springframework.data.domain.Sort sort,
|
||||
@RequestBody String titlePrefix);
|
||||
|
||||
@RequestMapping(value = "invoices", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@GetMapping(value = "invoices", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
ResponseEntity<List<Invoice>> getInvoices();
|
||||
|
||||
@RequestMapping(value = "invoices", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
@PostMapping(value = "invoices", consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
ResponseEntity<List<Invoice>> saveInvoices(List<Invoice> invoices);
|
||||
|
||||
|
||||
+9
-9
@@ -28,9 +28,9 @@ import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
@@ -42,28 +42,28 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RestController
|
||||
public class InvoiceResource {
|
||||
|
||||
@RequestMapping(value = "invoices", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@GetMapping(value = "invoices", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<List<Invoice>> getInvoices() {
|
||||
|
||||
return ResponseEntity.ok(createInvoiceList(null, 100, null));
|
||||
}
|
||||
|
||||
@RequestMapping(value = "invoices", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
@PostMapping(value = "invoices", consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
ResponseEntity<List<Invoice>> saveInvoices(@RequestBody List<Invoice> invoices) {
|
||||
|
||||
return ResponseEntity.ok(invoices);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "invoicesPaged", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@GetMapping(value = "invoicesPaged", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<Page<Invoice>> getInvoicesPaged(org.springframework.data.domain.Pageable pageable) {
|
||||
Page<Invoice> page = new PageImpl<>(createInvoiceList(null, pageable.getPageSize(), pageable.getSort()),
|
||||
pageable, 100);
|
||||
return ResponseEntity.ok(page);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "invoicesPagedWithBody", method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@PostMapping(value = "invoicesPagedWithBody", consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<Page<Invoice>> getInvoicesPagedWithBody(org.springframework.data.domain.Pageable pageable,
|
||||
@RequestBody String titlePrefix) {
|
||||
Page<Invoice> page = new PageImpl<>(createInvoiceList(titlePrefix, pageable.getPageSize(), pageable.getSort()),
|
||||
@@ -71,8 +71,8 @@ public class InvoiceResource {
|
||||
return ResponseEntity.ok(page);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "invoicesSortedWithBody", method = RequestMethod.POST,
|
||||
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@PostMapping(value = "invoicesSortedWithBody", consumes = MediaType.APPLICATION_JSON_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public ResponseEntity<Page<Invoice>> getInvoicesSortedWithBody(org.springframework.data.domain.Sort sort,
|
||||
@RequestBody String titlePrefix) {
|
||||
Page<Invoice> page = new PageImpl<>(createInvoiceList(titlePrefix, 100, sort), PageRequest.of(0, 100, sort),
|
||||
|
||||
+5
-6
@@ -28,8 +28,7 @@ import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -74,7 +73,7 @@ public class FeignClientValidationTests {
|
||||
@FeignClient(name = "bar")
|
||||
interface Client {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/")
|
||||
@GetMapping("/")
|
||||
String get();
|
||||
|
||||
}
|
||||
@@ -90,7 +89,7 @@ public class FeignClientValidationTests {
|
||||
@FeignClient(contextId = "foo", name = "bar")
|
||||
interface FooClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/")
|
||||
@GetMapping("/")
|
||||
String get();
|
||||
|
||||
}
|
||||
@@ -98,7 +97,7 @@ public class FeignClientValidationTests {
|
||||
@FeignClient(name = "bar")
|
||||
interface BarClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/")
|
||||
@GetMapping("/")
|
||||
String get();
|
||||
|
||||
}
|
||||
@@ -113,7 +112,7 @@ public class FeignClientValidationTests {
|
||||
@FeignClient("foo_bar")
|
||||
interface Client {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/")
|
||||
@GetMapping("/")
|
||||
String get();
|
||||
|
||||
}
|
||||
|
||||
+27
@@ -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();
|
||||
|
||||
+13
@@ -60,6 +60,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.http.HttpHeaders.ACCEPT;
|
||||
import static org.springframework.http.HttpHeaders.CONTENT_LENGTH;
|
||||
import static org.springframework.http.HttpHeaders.CONTENT_TYPE;
|
||||
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
|
||||
import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE;
|
||||
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
|
||||
import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;
|
||||
@@ -68,6 +69,7 @@ import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;
|
||||
* @author Spencer Gibb
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Ahmad Mozafarnia
|
||||
* @author Can Bezmen
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = SpringEncoderTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT,
|
||||
@@ -179,6 +181,17 @@ public class SpringEncoderTests {
|
||||
assertThat(new String(request.requestBody().asBytes())).as("Body content cannot be decoded").contains("hi");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromURLEncodedValue() {
|
||||
Encoder encoder = context.getInstance("formUrlEncoded", Encoder.class);
|
||||
assertThat(encoder).isNotNull();
|
||||
RequestTemplate request = new RequestTemplate();
|
||||
request.header(CONTENT_TYPE, APPLICATION_FORM_URLENCODED_VALUE);
|
||||
String body = "test";
|
||||
encoder.encode(body, String.class, request);
|
||||
assertThat(new String(request.requestBody().asBytes())).as("Body content cannot be decoded").contains(body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoCharsetForBinaryFiles() {
|
||||
Encoder encoder = context.getInstance("test", Encoder.class);
|
||||
|
||||
+44
-71
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
+2
-3
@@ -17,8 +17,7 @@
|
||||
package org.springframework.cloud.openfeign.testclients;
|
||||
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
@@ -26,7 +25,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||
@FeignClient(name = "localapp")
|
||||
public interface TestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
String getHello();
|
||||
|
||||
}
|
||||
|
||||
+3
-4
@@ -37,8 +37,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -83,7 +82,7 @@ public class FeignClientNotPrimaryTests {
|
||||
@FeignClient(name = "localapp", primary = false)
|
||||
protected interface TestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
@@ -101,7 +100,7 @@ public class FeignClientNotPrimaryTests {
|
||||
return new PrimaryTestClient();
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hello")
|
||||
@GetMapping("/hello")
|
||||
public Hello getHello() {
|
||||
return new Hello(HELLO_WORLD_1);
|
||||
}
|
||||
|
||||
+4
-5
@@ -27,8 +27,7 @@ import org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoCon
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -70,7 +69,7 @@ public class FeignClientValidationTests {
|
||||
@FeignClient(name = "example", url = "https://example.com")
|
||||
interface Client {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/")
|
||||
@GetMapping("/")
|
||||
@Deprecated
|
||||
String get();
|
||||
|
||||
@@ -86,7 +85,7 @@ public class FeignClientValidationTests {
|
||||
@FeignClient(name = "example", url = "${feignClient.url:https://example.com}")
|
||||
interface Client {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/")
|
||||
@GetMapping("/")
|
||||
@Deprecated
|
||||
String get();
|
||||
|
||||
@@ -102,7 +101,7 @@ public class FeignClientValidationTests {
|
||||
@FeignClient("foo")
|
||||
interface Client {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/")
|
||||
@GetMapping("/")
|
||||
@Deprecated
|
||||
String get();
|
||||
|
||||
|
||||
+7
-7
@@ -41,11 +41,11 @@ import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -107,17 +107,17 @@ class FeignHttpClientTests {
|
||||
|
||||
protected interface BaseTestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@GetMapping(value = "/hello", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
Hello getHello();
|
||||
|
||||
@RequestMapping(method = RequestMethod.PATCH, value = "/hellop", consumes = "application/json")
|
||||
@PatchMapping(value = "/hellop", consumes = "application/json")
|
||||
ResponseEntity<Void> patchHello(Hello hello);
|
||||
|
||||
}
|
||||
|
||||
protected interface UserService {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/users/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@GetMapping(value = "/users/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
User getUser(@PathVariable("id") long id);
|
||||
|
||||
}
|
||||
@@ -136,12 +136,12 @@ class FeignHttpClientTests {
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
protected static class Application implements UserService {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
public Hello getHello() {
|
||||
return new Hello("hello world 1");
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.PATCH, value = "/hellop")
|
||||
@PatchMapping("/hellop")
|
||||
public ResponseEntity<Void> patchHello(@RequestBody Hello hello,
|
||||
@RequestHeader("Content-Length") int contentLength) {
|
||||
if (contentLength <= 0) {
|
||||
|
||||
+7
-7
@@ -40,11 +40,11 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -107,17 +107,17 @@ class FeignOkHttpTests {
|
||||
|
||||
protected interface BaseTestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
@RequestMapping(method = RequestMethod.PATCH, value = "/hellop", consumes = "application/json")
|
||||
@PatchMapping(value = "/hellop", consumes = "application/json")
|
||||
ResponseEntity<Void> patchHello(Hello hello);
|
||||
|
||||
}
|
||||
|
||||
protected interface UserService {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/users/{id}")
|
||||
@GetMapping("/users/{id}")
|
||||
User getUser(@PathVariable("id") long id);
|
||||
|
||||
}
|
||||
@@ -138,12 +138,12 @@ class FeignOkHttpTests {
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
protected static class Application implements UserService {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
public Hello getHello() {
|
||||
return new Hello("hello world 1");
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.PATCH, value = "/hellop")
|
||||
@PatchMapping("/hellop")
|
||||
public ResponseEntity<Void> patchHello(@RequestBody Hello hello,
|
||||
@RequestHeader("Content-Length") int contentLength) {
|
||||
if (contentLength <= 0) {
|
||||
|
||||
+82
-97
@@ -38,7 +38,6 @@ import javax.servlet.http.Part;
|
||||
import feign.Client;
|
||||
import feign.Logger;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import feign.codec.EncodeException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -65,7 +64,6 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.format.Formatter;
|
||||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -73,6 +71,8 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -84,6 +84,9 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
|
||||
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
|
||||
import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
@@ -277,6 +280,13 @@ class ValidFeignClientTests {
|
||||
assertThat(testClient.getToString(args)).isEqualTo(expectedResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFormURLEncoded() {
|
||||
Hello hello = new Hello(HELLO_WORLD_1);
|
||||
Hello response = testClient.postFormUrlEncoded(hello);
|
||||
assertThat(response).isEqualTo(hello);
|
||||
}
|
||||
|
||||
@Test
|
||||
void namedFeignClientWorks() {
|
||||
assertThat(namedFeignClient).as("namedFeignClient was null").isNotNull();
|
||||
@@ -382,52 +392,42 @@ class ValidFeignClientTests {
|
||||
@FeignClient(name = "localapp8")
|
||||
protected interface MultipartClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/singlePart",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/singlePart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String singlePart(@RequestPart("hello") String hello);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/singlePojoPart",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/singlePojoPart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String singlePojoPart(@RequestPart("hello") Hello hello);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipart",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String multipart(@RequestPart("hello") String hello, @RequestPart("world") String world,
|
||||
@RequestPart("file") MultipartFile file);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartPojo",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartPojo", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String multipartPojo(@RequestPart("hello") String hello, @RequestPart("world") String world,
|
||||
@RequestPart("pojo1") Hello pojo1, @RequestPart("pojo2") Hello pojo2,
|
||||
@RequestPart("file") MultipartFile file);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String requestPartListOfMultipartFilesReturnsPartNames(@RequestPart("files") List<MultipartFile> files);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartFilenames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartFilenames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String requestPartListOfMultipartFilesReturnsFileNames(@RequestPart("files") List<MultipartFile> files);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartPojosFiles",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartPojosFiles", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String requestPartListOfPojosAndListOfMultipartFiles(@RequestPart("pojos") List<Hello> pojos,
|
||||
@RequestPart("files") List<MultipartFile> files);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String requestBodyListOfMultipartFiles(@RequestBody List<MultipartFile> files);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String requestBodySingleMultipartFile(@RequestBody MultipartFile file);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String requestBodyMap(@RequestBody Map<String, ?> form);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/invalid",
|
||||
consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/invalid", consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE,
|
||||
produces = TEXT_PLAIN_VALUE)
|
||||
String invalid(@RequestBody MultipartFile file);
|
||||
|
||||
}
|
||||
@@ -435,36 +435,36 @@ class ValidFeignClientTests {
|
||||
@FeignClient(name = "localapp", configuration = TestClientConfig.class)
|
||||
protected interface TestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Optional<Hello> getOptionalHello();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "${feignClient.methodLevelRequestMappingPath}")
|
||||
@GetMapping("${feignClient.methodLevelRequestMappingPath}")
|
||||
Hello getHelloUsingPropertyPlaceHolder();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hellos")
|
||||
@GetMapping("/hellos")
|
||||
List<Hello> getHellos();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hellostrings")
|
||||
@GetMapping("/hellostrings")
|
||||
List<String> getHelloStrings();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/helloheaders")
|
||||
@GetMapping("/helloheaders")
|
||||
List<String> getHelloHeaders();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/helloheadersplaceholders",
|
||||
@GetMapping(path = "/helloheadersplaceholders",
|
||||
headers = "myPlaceholderHeader=${feignClient.myPlaceholderHeader}")
|
||||
String getHelloHeadersPlaceholders();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/helloparams")
|
||||
@GetMapping("/helloparams")
|
||||
List<String> getParams(@RequestParam("params") List<String> params);
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/formattedparams")
|
||||
@GetMapping("/formattedparams")
|
||||
List<LocalDate> getFormattedParams(
|
||||
@RequestParam("params") @DateTimeFormat(pattern = "dd-MM-yyyy") List<LocalDate> params);
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/noContent")
|
||||
@GetMapping("/noContent")
|
||||
ResponseEntity<Void> noContent();
|
||||
|
||||
@RequestMapping(method = RequestMethod.HEAD, path = "/head")
|
||||
@@ -477,21 +477,24 @@ class ValidFeignClientTests {
|
||||
produces = "application/vnd.io.spring.cloud.test.v1+json", path = "/complex")
|
||||
String moreComplexContentType(String body);
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/tostring")
|
||||
@GetMapping("/tostring")
|
||||
String getToString(@RequestParam("arg") Arg arg);
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/tostring2")
|
||||
@GetMapping("/tostring2")
|
||||
String getToString(@RequestParam("arg") OtherArg arg);
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/tostringcollection")
|
||||
@GetMapping("/tostringcollection")
|
||||
Collection<String> getToString(@RequestParam("arg") Collection<OtherArg> args);
|
||||
|
||||
@PostMapping(path = "/form-urlencoded", consumes = APPLICATION_FORM_URLENCODED_VALUE)
|
||||
Hello postFormUrlEncoded(Hello hello);
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "localapp1")
|
||||
protected interface TestClientServiceId {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hello")
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
}
|
||||
@@ -499,10 +502,10 @@ class ValidFeignClientTests {
|
||||
@FeignClient(name = "localapp2", decode404 = true)
|
||||
protected interface DecodingTestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/notFound")
|
||||
@GetMapping("/notFound")
|
||||
ResponseEntity<String> notFound();
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/notFound")
|
||||
@GetMapping("/notFound")
|
||||
Optional<String> optional();
|
||||
|
||||
}
|
||||
@@ -526,22 +529,12 @@ class ValidFeignClientTests {
|
||||
|
||||
@Bean
|
||||
public RequestInterceptor interceptor1() {
|
||||
return new RequestInterceptor() {
|
||||
@Override
|
||||
public void apply(RequestTemplate template) {
|
||||
template.header(MYHEADER1, "myheader1value");
|
||||
}
|
||||
};
|
||||
return template -> template.header(MYHEADER1, "myheader1value");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RequestInterceptor interceptor2() {
|
||||
return new RequestInterceptor() {
|
||||
@Override
|
||||
public void apply(RequestTemplate template) {
|
||||
template.header(MYHEADER2, "myheader2value");
|
||||
}
|
||||
};
|
||||
return template -> template.header(MYHEADER2, "myheader2value");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -570,27 +563,21 @@ class ValidFeignClientTests {
|
||||
|
||||
@Bean
|
||||
FeignFormatterRegistrar feignFormatterRegistrar() {
|
||||
return new FeignFormatterRegistrar() {
|
||||
return registry -> registry.addFormatter(new Formatter<OtherArg>() {
|
||||
|
||||
@Override
|
||||
public void registerFormatters(FormatterRegistry registry) {
|
||||
registry.addFormatter(new Formatter<OtherArg>() {
|
||||
|
||||
@Override
|
||||
public String print(OtherArg object, Locale locale) {
|
||||
if ("foo".equals(object.value)) {
|
||||
return "bar";
|
||||
}
|
||||
return object.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OtherArg parse(String text, Locale locale) throws ParseException {
|
||||
return new OtherArg(text);
|
||||
}
|
||||
});
|
||||
public String print(OtherArg object, Locale locale) {
|
||||
if ("foo".equals(object.value)) {
|
||||
return "bar";
|
||||
}
|
||||
return object.value;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public OtherArg parse(String text, Locale locale) throws ParseException {
|
||||
return new OtherArg(text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -598,23 +585,23 @@ class ValidFeignClientTests {
|
||||
return new JsonFormWriter();
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hello")
|
||||
@GetMapping("/hello")
|
||||
public Hello getHello() {
|
||||
return new Hello(HELLO_WORLD_1);
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hello2")
|
||||
@GetMapping("/hello2")
|
||||
public Hello getHello2() {
|
||||
return new Hello(OI_TERRA_2);
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hellos")
|
||||
@GetMapping("/hellos")
|
||||
public List<Hello> getHellos() {
|
||||
ArrayList<Hello> hellos = getHelloList();
|
||||
return hellos;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/hellostrings")
|
||||
@GetMapping("/hellostrings")
|
||||
public List<String> getHelloStrings() {
|
||||
ArrayList<String> hellos = new ArrayList<>();
|
||||
hellos.add(HELLO_WORLD_1);
|
||||
@@ -622,7 +609,7 @@ class ValidFeignClientTests {
|
||||
return hellos;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/helloheaders")
|
||||
@GetMapping("/helloheaders")
|
||||
public List<String> getHelloHeaders(@RequestHeader(MYHEADER1) String myheader1,
|
||||
@RequestHeader(MYHEADER2) String myheader2) {
|
||||
ArrayList<String> headers = new ArrayList<>();
|
||||
@@ -631,23 +618,23 @@ class ValidFeignClientTests {
|
||||
return headers;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/helloheadersplaceholders")
|
||||
@GetMapping("/helloheadersplaceholders")
|
||||
public String getHelloHeadersPlaceholders(@RequestHeader("myPlaceholderHeader") String myPlaceholderHeader) {
|
||||
return myPlaceholderHeader;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/helloparams")
|
||||
@GetMapping("/helloparams")
|
||||
public List<String> getParams(@RequestParam("params") List<String> params) {
|
||||
return params;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/formattedparams")
|
||||
@GetMapping("/formattedparams")
|
||||
public List<LocalDate> getFormattedParams(
|
||||
@RequestParam("params") @DateTimeFormat(pattern = "dd-MM-yyyy") List<LocalDate> params) {
|
||||
return params;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/noContent")
|
||||
@GetMapping("/noContent")
|
||||
ResponseEntity<Void> noContent() {
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
@@ -662,7 +649,7 @@ class ValidFeignClientTests {
|
||||
throw new RuntimeException("always fails");
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/notFound")
|
||||
@GetMapping("/notFound")
|
||||
ResponseEntity<String> notFound() {
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body((String) null);
|
||||
}
|
||||
@@ -676,17 +663,17 @@ class ValidFeignClientTests {
|
||||
return body;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/tostring")
|
||||
@GetMapping("/tostring")
|
||||
String getToString(@RequestParam("arg") Arg arg) {
|
||||
return arg.toString();
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/tostring2")
|
||||
@GetMapping("/tostring2")
|
||||
String getToString(@RequestParam("arg") OtherArg arg) {
|
||||
return arg.value;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "/tostringcollection")
|
||||
@GetMapping("/tostringcollection")
|
||||
Collection<String> getToString(@RequestParam("arg") Collection<OtherArg> args) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (OtherArg arg : args) {
|
||||
@@ -695,47 +682,40 @@ class ValidFeignClientTests {
|
||||
return result;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/singlePart",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/singlePart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String singlePart(@RequestPart("hello") String hello) {
|
||||
return hello;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/singlePojoPart",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/singlePojoPart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String singlePojoPart(@RequestPart("hello") Hello hello) {
|
||||
return hello.getMessage();
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipart",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipart", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String multipart(@RequestPart("hello") String hello, @RequestPart("world") String world,
|
||||
@RequestPart("file") MultipartFile file) {
|
||||
return hello + world + file.getOriginalFilename();
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartPojo",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartPojo", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String multipartPojo(@RequestPart("hello") String hello, @RequestPart("world") String world,
|
||||
@RequestPart("pojo1") Hello pojo1, @RequestPart("pojo2") Hello pojo2,
|
||||
@RequestPart("file") MultipartFile file) {
|
||||
return hello + world + pojo1.getMessage() + pojo2.getMessage() + file.getOriginalFilename();
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartNames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String multipartNames(HttpServletRequest request) throws Exception {
|
||||
return request.getParts().stream().map(Part::getName).collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartFilenames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartFilenames", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String multipartFilenames(HttpServletRequest request) throws Exception {
|
||||
return request.getParts().stream().map(Part::getSubmittedFileName).collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartPojosFiles",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
@PostMapping(path = "/multipartPojosFiles", consumes = MULTIPART_FORM_DATA_VALUE, produces = TEXT_PLAIN_VALUE)
|
||||
String requestPartListOfPojosAndListOfMultipartFiles(@RequestPart("pojos") List<Hello> pojos,
|
||||
@RequestPart("files") List<MultipartFile> files) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
@@ -751,6 +731,11 @@ class ValidFeignClientTests {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@PostMapping(path = "/form-urlencoded", consumes = APPLICATION_FORM_URLENCODED_VALUE)
|
||||
Hello postFormUrlEncoded(Hello hello) {
|
||||
return hello;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Hello {
|
||||
|
||||
+2
-3
@@ -35,8 +35,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -70,7 +69,7 @@ public class FeignClientEnvVarTests {
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
protected static class Application {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
public String getHello() {
|
||||
return "hello world 1";
|
||||
}
|
||||
|
||||
+4
-5
@@ -37,8 +37,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -83,7 +82,7 @@ public class FeignClientScanningTests {
|
||||
@FeignClient("localapp123")
|
||||
protected interface TestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
String getHello();
|
||||
|
||||
}
|
||||
@@ -91,7 +90,7 @@ public class FeignClientScanningTests {
|
||||
@FeignClient("${feignClient.localappName}")
|
||||
protected interface TestClientByKey {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
String getHello();
|
||||
|
||||
}
|
||||
@@ -104,7 +103,7 @@ public class FeignClientScanningTests {
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
protected static class Application {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@GetMapping("/hello")
|
||||
public String getHello() {
|
||||
return "hello world 1";
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
<parent>
|
||||
<artifactId>spring-cloud-dependencies-parent</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>3.0.3</version>
|
||||
<version>3.0.4</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-openfeign-dependencies</artifactId>
|
||||
<version>3.0.3</version>
|
||||
<version>3.0.5</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>spring-cloud-openfeign-dependencies</name>
|
||||
<description>Spring Cloud OpenFeign Dependencies</description>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>3.0.3</version>
|
||||
<version>3.0.5</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user