Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10c6992935 | ||
|
|
d87375e835 | ||
|
|
cc9af41dfe | ||
|
|
9bb8b43d58 | ||
|
|
60558c46ef | ||
|
|
89db04d7d0 | ||
|
|
27a3967975 | ||
|
|
0c4c19bfcd |
+2
-8
@@ -6,10 +6,10 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<version>2.2.7.RELEASE</version>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-openfeign-docs</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<packaging>pom</packaging>
|
||||
<name>Spring Cloud OpenFeign Docs</name>
|
||||
<description>Spring Cloud Docs</description>
|
||||
<properties>
|
||||
@@ -24,9 +24,6 @@
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<sourceDirectory>src/main/asciidoc</sourceDirectory>
|
||||
</build>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>docs</id>
|
||||
@@ -37,11 +34,9 @@
|
||||
<artifactId>git-commit-id-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
@@ -53,7 +48,6 @@
|
||||
<artifactId>asciidoctor-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-antrun-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|===
|
||||
|Name | Default | Description
|
||||
|
||||
|feign.autoconfiguration.jackson.enabled | `false` | If true, PageJacksonModule and SortJacksonModule bean will be provided for Jackson page decoding.
|
||||
|feign.circuitbreaker.enabled | `false` | If true, an OpenFeign client will be wrapped with a Spring Cloud CircuitBreaker circuit breaker.
|
||||
|feign.client.config | |
|
||||
|feign.client.decode-slash | `true` | Feign clients do not encode slash `/` characters by default. To change this behavior, set the `decodeSlash` to `false`.
|
||||
@@ -20,6 +21,7 @@
|
||||
|feign.httpclient.max-connections-per-route | `50` |
|
||||
|feign.httpclient.time-to-live | `900` |
|
||||
|feign.httpclient.time-to-live-unit | |
|
||||
|feign.hystrix.enabled | `false` | If true, an OpenFeign client will be wrapped with a Hystrix circuit breaker.
|
||||
|feign.okhttp.enabled | `false` | Enables the use of the OK HTTP Client by Feign.
|
||||
|
||||
|===
|
||||
@@ -15,7 +15,7 @@ To use Feign create an interface and annotate it.
|
||||
It has pluggable annotation support including Feign annotations and JAX-RS annotations.
|
||||
Feign also supports pluggable encoders and decoders.
|
||||
Spring Cloud adds support for Spring MVC annotations and for using the same `HttpMessageConverters` used by default in Spring Web.
|
||||
Spring Cloud integrates Eureka, Spring Cloud CircuitBreaker, as well as Spring Cloud LoadBalancer to provide a load-balanced http client when using Feign.
|
||||
Spring Cloud integrates Ribbon and Eureka, Spring Cloud CircuitBreaker, as well as Spring Cloud LoadBalancer to provide a load-balanced http client when using Feign.
|
||||
|
||||
[[netflix-feign-starter]]
|
||||
=== How to Include Feign
|
||||
@@ -55,7 +55,7 @@ public interface StoreClient {
|
||||
}
|
||||
----
|
||||
|
||||
In the `@FeignClient` annotation the String value ("stores" above) is an arbitrary client name, which is used to create a https://github.com/spring-cloud/spring-cloud-commons/blob/master/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/blocking/client/BlockingLoadBalancerClient.java[Spring Cloud LoadBalancer client].
|
||||
In the `@FeignClient` annotation the String value ("stores" above) is an arbitrary client name, which is used to create either a https://github.com/Netflix/ribbon[Ribbon] load-balancer (see <<spring-cloud-ribbon,below for details of Ribbon support>> and <<spring-cloud-circuitbreaker,below for details of Spring Cloud CircuitBreaker support>>) or https://github.com/spring-cloud/spring-cloud-commons/blob/master/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancer.java[Spring Cloud LoadBalancer].
|
||||
You can also specify a URL using the `url` attribute
|
||||
(absolute value or just a hostname). The name of the bean in the
|
||||
application context is the fully qualified name of the interface.
|
||||
@@ -68,7 +68,9 @@ it will resolve the service in the Eureka service registry. If you
|
||||
don't want to use Eureka, you can simply 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`].
|
||||
|
||||
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].
|
||||
WARNING: In order to maintain backward compatibility, is used as the default load-balancer implementation.
|
||||
However, Spring Cloud Netflix Ribbon is now in maintenance mode, so we recommend using Spring Cloud LoadBalancer instead.
|
||||
To do this, set the value of `spring.cloud.loadbalancer.ribbon.enabled` to `false`.
|
||||
|
||||
[[spring-cloud-feign-overriding-defaults]]
|
||||
=== Overriding Feign Defaults
|
||||
@@ -92,6 +94,8 @@ In this case the client is composed from the components already in `FeignClients
|
||||
|
||||
NOTE: `FooConfiguration` does not need to be annotated with `@Configuration`. However, if it is, then take care to exclude it from any `@ComponentScan` that would otherwise include this configuration as it will become the default source for `feign.Decoder`, `feign.Encoder`, `feign.Contract`, etc., when specified. This can be avoided by putting it in a separate, non-overlapping package from any `@ComponentScan` or `@SpringBootApplication`, or it can be explicitly excluded in `@ComponentScan`.
|
||||
|
||||
NOTE: The `serviceId` attribute is now deprecated in favor of the `name` attribute.
|
||||
|
||||
NOTE: Using `contextId` attribute of the `@FeignClient` annotation in addition to changing the name of
|
||||
the `ApplicationContext` ensemble, it will override the alias of the client name
|
||||
and it will be used as part of the name of the configuration bean created for that client.
|
||||
@@ -114,11 +118,12 @@ Spring Cloud OpenFeign provides the following beans by default for feign (`BeanT
|
||||
* `Encoder` feignEncoder: `SpringEncoder`
|
||||
* `Logger` feignLogger: `Slf4jLogger`
|
||||
* `Contract` feignContract: `SpringMvcContract`
|
||||
* `Feign.Builder` feignBuilder: `HystrixFeign.Builder`
|
||||
* `Feign.Builder` feignBuilder: `FeignCircuitBreaker.Builder`
|
||||
* `Client` feignClient: if Spring Cloud LoadBalancer is in the classpath, `FeignBlockingLoadBalancerClient` is used.
|
||||
* `Client` feignClient: if Ribbon is in the classpath and is enabled it is a `LoadBalancerFeignClient`, otherwise if Spring Cloud LoadBalancer is in the classpath, `FeignBlockingLoadBalancerClient` is used.
|
||||
If none of them is in the classpath, the default feign client is used.
|
||||
|
||||
NOTE: `spring-cloud-starter-openfeign` supports `spring-cloud-starter-loadbalancer`. However, as is an optional dependency, you need to make sure it been added to your project if you want to use it.
|
||||
NOTE: `spring-cloud-starter-openfeign` supports both `spring-cloud-starter-netflix-ribbon` and `spring-cloud-starter-loadbalancer`. However, as they are optional dependencies, you need to make sure the one you want to use has been added to your project.
|
||||
|
||||
The OkHttpClient and ApacheHttpClient feign clients can be used by setting `feign.okhttp.enabled` or `feign.httpclient.enabled` to `true`, respectively, and having them on the classpath.
|
||||
You can customize the HTTP client used by providing a bean of either `org.apache.http.impl.client.CloseableHttpClient` when using Apache or `okhttp3.OkHttpClient` when using OK HTTP.
|
||||
@@ -206,6 +211,26 @@ If we create both `@Configuration` bean and configuration properties, configurat
|
||||
It will override `@Configuration` values. But if you want to change the priority to `@Configuration`,
|
||||
you can change `feign.client.default-to-properties` to `false`.
|
||||
|
||||
NOTE: If you need to use `ThreadLocal` bound variables in your `RequestInterceptor`s you will need to either set the
|
||||
thread isolation strategy for Hystrix to `SEMAPHORE` or disable Hystrix in Feign.
|
||||
|
||||
application.yml
|
||||
[source,yaml]
|
||||
----
|
||||
# To disable Hystrix in Feign
|
||||
feign:
|
||||
hystrix:
|
||||
enabled: false
|
||||
|
||||
# To set thread isolation to SEMAPHORE
|
||||
hystrix:
|
||||
command:
|
||||
default:
|
||||
execution:
|
||||
isolation:
|
||||
strategy: SEMAPHORE
|
||||
----
|
||||
|
||||
If we want to create multiple feign clients with the same name or url
|
||||
so that they would point to the same server but each with a different custom configuration then
|
||||
we have to use `contextId` attribute of the `@FeignClient` in order to avoid name
|
||||
@@ -262,6 +287,32 @@ We can configure timeouts on both the default and the named client. OpenFeign wo
|
||||
|
||||
NOTE: In case the server is not running or available a packet results in _connection refused_. The communication ends either with an error message or in a fallback. This can happen _before_ the `connectTimeout` if it is set very low. The time taken to perform a lookup and to receive such a packet causes a significant part of this delay. It is subject to change based on the remote host that involves a DNS lookup.
|
||||
|
||||
When Hystrix <<spring-cloud-openfeign#spring-cloud-feign-hystrix,is enabled>>, its timeout configuration link:https://github.com/Netflix/Hystrix/wiki/Configuration#execution.isolation.thread.timeoutInMilliseconds[defaults] to 1000 milliseconds. Hence, it might occur before the client timeout that we configured earlier. Increasing this timeout prevents it from happening.
|
||||
|
||||
[source,yaml]
|
||||
----
|
||||
feign:
|
||||
client:
|
||||
config:
|
||||
default:
|
||||
connectTimeout: 5000
|
||||
readTimeout: 5000
|
||||
hystrix:
|
||||
enabled: true
|
||||
|
||||
hystrix:
|
||||
command:
|
||||
default:
|
||||
execution:
|
||||
timeout:
|
||||
enabled: true
|
||||
isolation:
|
||||
thread:
|
||||
timeoutInMilliseconds: 60000
|
||||
----
|
||||
|
||||
NOTE: When the Hystrix timeout is enabled and its timeout is set longer than that of a feign client, `HystrixTimeoutException` wraps a feign exception. Otherwise, the only difference is the cause of the exception. The purpose of `HystrixTimeoutException` is to wrap any runtime exception that occurs first and throw an instance of itself.
|
||||
|
||||
=== Creating Feign Clients Manually
|
||||
|
||||
In some cases it might be necessary to customize your Feign Clients in a way that is not
|
||||
@@ -310,6 +361,76 @@ the default Feign native annotations.
|
||||
You can also use the `Builder`to configure FeignClient not to inherit beans from the parent context.
|
||||
You can do this by overriding calling `inheritParentContext(false)` on the `Builder`.
|
||||
|
||||
[[spring-cloud-feign-hystrix]]
|
||||
=== Feign Hystrix Support
|
||||
|
||||
If Hystrix is on the classpath and `feign.hystrix.enabled=true`, Feign will wrap all methods with a circuit breaker. Returning a `com.netflix.hystrix.HystrixCommand` is also available. This lets you use reactive patterns (with a call to `.toObservable()` or `.observe()` or asynchronous use (with a call to `.queue()`).
|
||||
|
||||
To disable Hystrix support on a per-client basis create a vanilla `Feign.Builder` with the "prototype" scope, e.g.:
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@Configuration
|
||||
public class FooConfiguration {
|
||||
@Bean
|
||||
@Scope("prototype")
|
||||
public Feign.Builder feignBuilder() {
|
||||
return Feign.builder();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
WARNING: Prior to the Spring Cloud Dalston release, if Hystrix was on the classpath Feign would have wrapped
|
||||
all methods in a circuit breaker by default. This default behavior was changed in Spring Cloud Dalston in
|
||||
favor for an opt-in approach.
|
||||
|
||||
[[spring-cloud-feign-hystrix-fallback]]
|
||||
=== Feign Hystrix Fallbacks
|
||||
|
||||
Hystrix supports the notion of a fallback: a default code path that is executed when they circuit is open or there is an error. To enable fallbacks for a given `@FeignClient` set the `fallback` attribute to the class name that implements the fallback. You also need to declare your implementation as a Spring bean.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@FeignClient(name = "hello", fallback = HystrixClientFallback.class)
|
||||
protected interface HystrixClient {
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
Hello iFailSometimes();
|
||||
}
|
||||
|
||||
static class HystrixClientFallback implements HystrixClient {
|
||||
@Override
|
||||
public Hello iFailSometimes() {
|
||||
return new Hello("fallback");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
If one needs access to the cause that made the fallback trigger, one can use the `fallbackFactory` attribute inside `@FeignClient`.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@FeignClient(name = "hello", fallbackFactory = HystrixClientFallbackFactory.class)
|
||||
protected interface HystrixClient {
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
Hello iFailSometimes();
|
||||
}
|
||||
|
||||
@Component
|
||||
static class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> {
|
||||
@Override
|
||||
public HystrixClient create(Throwable cause) {
|
||||
return new HystrixClient() {
|
||||
@Override
|
||||
public Hello iFailSometimes() {
|
||||
return new Hello("fallback; reason was: " + cause.getMessage());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
WARNING: There is a limitation with the implementation of fallbacks in Feign and how Hystrix fallbacks work. Fallbacks are currently not supported for methods that return `com.netflix.hystrix.HystrixCommand` and `rx.Observable`.
|
||||
|
||||
[[spring-cloud-feign-circuitbreaker]]
|
||||
=== Feign Spring Cloud CircuitBreaker Support
|
||||
|
||||
@@ -350,7 +471,7 @@ include::{core_path}/src/test/java/org/springframework/cloud/openfeign/circuitbr
|
||||
|
||||
=== Feign and `@Primary`
|
||||
|
||||
When using Feign with Spring Cloud CircuitBreaker fallbacks, there are multiple beans in the `ApplicationContext` of the same type. This will cause `@Autowired` to not work because there isn't exactly one bean, or one marked as primary. To work around this, Spring Cloud OpenFeign marks all Feign instances as `@Primary`, so Spring Framework will know which bean to inject. In some cases, this may not be desirable. To turn off this behavior set the `primary` attribute of `@FeignClient` to false.
|
||||
When using Feign with Hystrix fallbacks, there are multiple beans in the `ApplicationContext` of the same type. This will cause `@Autowired` to not work because there isn't exactly one bean, or one marked as primary. To work around this, Spring Cloud OpenFeign marks all Feign instances as `@Primary`, so Spring Framework will know which bean to inject. In some cases, this may not be desirable. To turn off this behavior set the `primary` attribute of `@FeignClient` to false.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@@ -549,7 +670,7 @@ public interface DemoTemplate {
|
||||
----
|
||||
|
||||
=== Feign `CollectionFormat` support
|
||||
We support `feign.CollectionFormat` by providing the `@CollectionFormat` annotation.You can annotate a Feign client method with it by passing the desired `feign.CollectionFormat` as annotation value.
|
||||
We support `feign.CollectionFormat` by providing the `@CollectionFormat` annotation. You can annotate a Feign client method with it by passing the desired `feign.CollectionFormat` as annotation value.
|
||||
|
||||
In the following example, the `CSV` format is used instead of the default `EXPLODED` to process the method.
|
||||
|
||||
@@ -568,7 +689,7 @@ In the following example, the `CSV` format is used instead of the default `EXPLO
|
||||
TIP: Set the `CSV` format while sending `Pageable` as a query parameter in order for it to be encoded correctly.
|
||||
|
||||
=== Reactive Support
|
||||
As the https://github.com/OpenFeign/feign[OpenFeign project] does not currently support reactive clients, such as https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/function/client/WebClient.html[Spring WebClient], neither does Spring Cloud OpenFeign.We will add support for it here as soon as it becomes available in the core project.
|
||||
As the https://github.com/OpenFeign/feign[OpenFeign project] does not currently support reactive clients, such as https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/function/client/WebClient.html[Spring WebClient], neither does Spring Cloud OpenFeign. We will add support for it here as soon as it becomes available in the core project.
|
||||
|
||||
Until that is done, we recommend using https://github.com/Playtika/feign-reactive[feign-reactive] for Spring WebClient support.
|
||||
|
||||
@@ -584,7 +705,6 @@ ObjectProvider<TestFeginClient> testFeginClient;
|
||||
----
|
||||
|
||||
=== Spring Data Support
|
||||
|
||||
You may consider enabling Jackson Modules for the support `org.springframework.data.domain.Page` and `org.springframework.data.domain.Sort` decoding.
|
||||
|
||||
[source,java]
|
||||
|
||||
@@ -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.1</version>
|
||||
<version>2.2.7.RELEASE</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.1</version>
|
||||
<version>2.3.2.RELEASE</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<scm>
|
||||
@@ -26,7 +26,8 @@
|
||||
<properties>
|
||||
<main.basedir>${basedir}</main.basedir>
|
||||
<jackson.version>2.11.3</jackson.version>
|
||||
<spring-cloud-commons.version>3.0.1</spring-cloud-commons.version>
|
||||
<spring-cloud-commons.version>2.2.7.RELEASE</spring-cloud-commons.version>
|
||||
<spring-cloud-netflix.version>2.2.7.RELEASE</spring-cloud-netflix.version>
|
||||
|
||||
<!-- Plugin versions -->
|
||||
<maven-eclipse-plugin.version>2.10</maven-eclipse-plugin.version>
|
||||
@@ -93,6 +94,13 @@
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix-dependencies</artifactId>
|
||||
<version>${spring-cloud-netflix.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons-dependencies</artifactId>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<version>2.2.7.RELEASE</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-openfeign-core</artifactId>
|
||||
@@ -21,6 +21,10 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix-ribbon</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
@@ -85,6 +89,11 @@
|
||||
<artifactId>spring-cloud-context</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.ribbon</groupId>
|
||||
<artifactId>ribbon-loadbalancer</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-core</artifactId>
|
||||
@@ -104,11 +113,46 @@
|
||||
<artifactId>feign-httpclient</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-hystrix</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-okhttp</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.hystrix</groupId>
|
||||
<artifactId>hystrix-core</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.hystrix</groupId>
|
||||
<artifactId>hystrix-serialization</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.hystrix</groupId>
|
||||
<artifactId>hystrix-metrics-event-stream</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.hystrix</groupId>
|
||||
<artifactId>hystrix-javanica</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.ribbon</groupId>
|
||||
<artifactId>ribbon-core</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.ribbon</groupId>
|
||||
<artifactId>ribbon-httpclient</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.reactivex</groupId>
|
||||
<artifactId>rxjava</artifactId>
|
||||
@@ -119,11 +163,6 @@
|
||||
<artifactId>okhttp</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security.oauth.boot</groupId>
|
||||
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure-processor</artifactId>
|
||||
@@ -139,11 +178,6 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.vintage</groupId>
|
||||
<artifactId>junit-vintage-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-test-support</artifactId>
|
||||
@@ -159,6 +193,16 @@
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.ribbon</groupId>
|
||||
<artifactId>ribbon</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-rest</artifactId>
|
||||
|
||||
+2
-1
@@ -43,7 +43,8 @@ public interface AnnotatedParameterProcessor {
|
||||
* @param method the method that contains the annotation
|
||||
* @return whether the parameter is http
|
||||
*/
|
||||
boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method);
|
||||
boolean processArgument(AnnotatedParameterContext context, Annotation annotation,
|
||||
Method method);
|
||||
|
||||
/**
|
||||
* Specifies the parameter context.
|
||||
|
||||
+2
-2
@@ -25,8 +25,8 @@ import feign.Target;
|
||||
class DefaultTargeter implements Targeter {
|
||||
|
||||
@Override
|
||||
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
|
||||
Target.HardCodedTarget<T> target) {
|
||||
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
|
||||
FeignContext context, Target.HardCodedTarget<T> target) {
|
||||
return feign.target(target);
|
||||
}
|
||||
|
||||
|
||||
+60
-48
@@ -28,7 +28,6 @@ import javax.annotation.PreDestroy;
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
import feign.Client;
|
||||
import feign.Feign;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
import feign.okhttp.OkHttpClient;
|
||||
import okhttp3.ConnectionPool;
|
||||
@@ -44,6 +43,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.actuator.HasFeatures;
|
||||
@@ -53,17 +53,15 @@ import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionMa
|
||||
import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
|
||||
import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
|
||||
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
|
||||
import org.springframework.cloud.openfeign.security.OAuth2FeignRequestInterceptor;
|
||||
import org.springframework.cloud.openfeign.support.DefaultGzipDecoderConfiguration;
|
||||
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
|
||||
import org.springframework.cloud.openfeign.support.PageJacksonModule;
|
||||
import org.springframework.cloud.openfeign.support.SortJacksonModule;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.security.oauth2.client.OAuth2ClientContext;
|
||||
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
@@ -73,7 +71,8 @@ import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResour
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(Feign.class)
|
||||
@EnableConfigurationProperties({ FeignClientProperties.class, FeignHttpClientProperties.class })
|
||||
@EnableConfigurationProperties({ FeignClientProperties.class,
|
||||
FeignHttpClientProperties.class })
|
||||
@Import(DefaultGzipDecoderConfiguration.class)
|
||||
public class FeignAutoConfiguration {
|
||||
|
||||
@@ -94,24 +93,29 @@ public class FeignAutoConfiguration {
|
||||
return context;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(PageJacksonModule.class)
|
||||
@ConditionalOnClass(name = "org.springframework.data.domain.Page")
|
||||
@ConditionalOnProperty(value = "feign.autoconfiguration.jackson.enabled", havingValue = "true")
|
||||
public Module pageJacksonModule() {
|
||||
return new PageJacksonModule();
|
||||
}
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass({ Module.class, Page.class, Sort.class })
|
||||
@ConditionalOnProperty(value = "feign.autoconfiguration.jackson.enabled",
|
||||
havingValue = "true")
|
||||
protected static class FeignJacksonConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(PageJacksonModule.class)
|
||||
public PageJacksonModule pageJacksonModule() {
|
||||
return new PageJacksonModule();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(SortJacksonModule.class)
|
||||
public SortJacksonModule sortModule() {
|
||||
return new SortJacksonModule();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(SortJacksonModule.class)
|
||||
@ConditionalOnClass(name = "org.springframework.data.domain.Sort")
|
||||
@ConditionalOnProperty(value = "feign.autoconfiguration.jackson.enabled", havingValue = "true")
|
||||
public Module sortModule() {
|
||||
return new SortJacksonModule();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Conditional(FeignCircuitBreakerDisabledConditions.class)
|
||||
@ConditionalOnMissingClass({ "feign.hystrix.HystrixFeign",
|
||||
"org.springframework.cloud.client.circuitbreaker.CircuitBreaker" })
|
||||
protected static class DefaultFeignTargeterConfiguration {
|
||||
|
||||
@Bean
|
||||
@@ -122,6 +126,18 @@ public class FeignAutoConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
|
||||
protected static class HystrixFeignTargeterConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Targeter feignTargeter() {
|
||||
return new HystrixTargeter();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(CircuitBreaker.class)
|
||||
@ConditionalOnProperty("feign.circuitbreaker.enabled")
|
||||
@@ -136,18 +152,20 @@ public class FeignAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnBean(CircuitBreakerFactory.class)
|
||||
public Targeter circuitBreakerFeignTargeter(CircuitBreakerFactory circuitBreakerFactory) {
|
||||
public Targeter circuitBreakerFeignTargeter(
|
||||
CircuitBreakerFactory circuitBreakerFactory) {
|
||||
return new FeignCircuitBreakerTargeter(circuitBreakerFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// the following configuration is for alternate feign clients if
|
||||
// SC loadbalancer is not on the class path.
|
||||
// ribbon is not on the class path.
|
||||
// see corresponding configurations in FeignRibbonClientAutoConfiguration
|
||||
// for load-balanced clients.
|
||||
// for load balanced ribbon clients.
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(ApacheHttpClient.class)
|
||||
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
|
||||
@ConditionalOnMissingBean(CloseableHttpClient.class)
|
||||
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
|
||||
protected static class HttpClientFeignConfiguration {
|
||||
@@ -165,10 +183,13 @@ public class FeignAutoConfiguration {
|
||||
public HttpClientConnectionManager connectionManager(
|
||||
ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
|
||||
FeignHttpClientProperties httpClientProperties) {
|
||||
final HttpClientConnectionManager connectionManager = connectionManagerFactory.newConnectionManager(
|
||||
httpClientProperties.isDisableSslValidation(), httpClientProperties.getMaxConnections(),
|
||||
httpClientProperties.getMaxConnectionsPerRoute(), httpClientProperties.getTimeToLive(),
|
||||
httpClientProperties.getTimeToLiveUnit(), this.registryBuilder);
|
||||
final HttpClientConnectionManager connectionManager = connectionManagerFactory
|
||||
.newConnectionManager(httpClientProperties.isDisableSslValidation(),
|
||||
httpClientProperties.getMaxConnections(),
|
||||
httpClientProperties.getMaxConnectionsPerRoute(),
|
||||
httpClientProperties.getTimeToLive(),
|
||||
httpClientProperties.getTimeToLiveUnit(),
|
||||
this.registryBuilder);
|
||||
this.connectionManagerTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -184,8 +205,10 @@ public class FeignAutoConfiguration {
|
||||
FeignHttpClientProperties httpClientProperties) {
|
||||
RequestConfig defaultRequestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(httpClientProperties.getConnectionTimeout())
|
||||
.setRedirectsEnabled(httpClientProperties.isFollowRedirects()).build();
|
||||
this.httpClient = httpClientFactory.createBuilder().setConnectionManager(httpClientConnectionManager)
|
||||
.setRedirectsEnabled(httpClientProperties.isFollowRedirects())
|
||||
.build();
|
||||
this.httpClient = httpClientFactory.createBuilder()
|
||||
.setConnectionManager(httpClientConnectionManager)
|
||||
.setDefaultRequestConfig(defaultRequestConfig).build();
|
||||
return this.httpClient;
|
||||
}
|
||||
@@ -215,6 +238,7 @@ public class FeignAutoConfiguration {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(OkHttpClient.class)
|
||||
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
|
||||
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
|
||||
@ConditionalOnProperty("feign.okhttp.enabled")
|
||||
protected static class OkHttpFeignConfiguration {
|
||||
@@ -223,7 +247,8 @@ public class FeignAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ConnectionPool.class)
|
||||
public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,
|
||||
public ConnectionPool httpClientConnectionPool(
|
||||
FeignHttpClientProperties httpClientProperties,
|
||||
OkHttpClientConnectionPoolFactory connectionPoolFactory) {
|
||||
Integer maxTotalConnections = httpClientProperties.getMaxConnections();
|
||||
Long timeToLive = httpClientProperties.getTimeToLive();
|
||||
@@ -232,14 +257,16 @@ public class FeignAutoConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool,
|
||||
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
|
||||
ConnectionPool connectionPool,
|
||||
FeignHttpClientProperties httpClientProperties) {
|
||||
Boolean followRedirects = httpClientProperties.isFollowRedirects();
|
||||
Integer connectTimeout = httpClientProperties.getConnectionTimeout();
|
||||
Boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
|
||||
this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation)
|
||||
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).followRedirects(followRedirects)
|
||||
.connectionPool(connectionPool).build();
|
||||
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
|
||||
.followRedirects(followRedirects).connectionPool(connectionPool)
|
||||
.build();
|
||||
return this.okHttpClient;
|
||||
}
|
||||
|
||||
@@ -259,19 +286,4 @@ public class FeignAutoConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(OAuth2ClientContext.class)
|
||||
@ConditionalOnProperty("feign.oauth2.enabled")
|
||||
protected static class Oauth2FeignConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(OAuth2FeignRequestInterceptor.class)
|
||||
@ConditionalOnBean({ OAuth2ClientContext.class, OAuth2ProtectedResourceDetails.class })
|
||||
public RequestInterceptor oauth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext,
|
||||
OAuth2ProtectedResourceDetails resource) {
|
||||
return new OAuth2FeignRequestInterceptor(oAuth2ClientContext, resource);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2019 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.
|
||||
|
||||
+10
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2021 the original author or authors.
|
||||
* 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.
|
||||
@@ -61,10 +61,13 @@ public final class FeignCircuitBreaker {
|
||||
}
|
||||
|
||||
public <T> T target(Target<T> target, T fallback) {
|
||||
return build(fallback != null ? new FallbackFactory.Default<T>(fallback) : null).newInstance(target);
|
||||
return build(
|
||||
fallback != null ? new FallbackFactory.Default<T>(fallback) : null)
|
||||
.newInstance(target);
|
||||
}
|
||||
|
||||
public <T> T target(Target<T> target, FallbackFactory<? extends T> fallbackFactory) {
|
||||
public <T> T target(Target<T> target,
|
||||
FallbackFactory<? extends T> fallbackFactory) {
|
||||
return build(fallbackFactory).newInstance(target);
|
||||
}
|
||||
|
||||
@@ -74,8 +77,10 @@ public final class FeignCircuitBreaker {
|
||||
}
|
||||
|
||||
public Feign build(final FallbackFactory<?> nullableFallbackFactory) {
|
||||
super.invocationHandlerFactory((target, dispatch) -> new FeignCircuitBreakerInvocationHandler(
|
||||
circuitBreakerFactory, feignClientName, target, dispatch, nullableFallbackFactory));
|
||||
super.invocationHandlerFactory(
|
||||
(target, dispatch) -> new FeignCircuitBreakerInvocationHandler(
|
||||
circuitBreakerFactory, feignClientName, target, dispatch,
|
||||
nullableFallbackFactory));
|
||||
return super.build();
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -31,7 +31,8 @@ class FeignCircuitBreakerDisabledConditions extends AnyNestedCondition {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(value = "feign.circuitbreaker.enabled", havingValue = "false", matchIfMissing = true)
|
||||
@ConditionalOnProperty(value = "feign.circuitbreaker.enabled", havingValue = "false",
|
||||
matchIfMissing = true)
|
||||
static class CircuitBreakerDisabled {
|
||||
|
||||
}
|
||||
|
||||
+10
-5
@@ -46,8 +46,10 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
|
||||
|
||||
private final Map<Method, Method> fallbackMethodMap;
|
||||
|
||||
FeignCircuitBreakerInvocationHandler(CircuitBreakerFactory factory, String feignClientName, Target<?> target,
|
||||
Map<Method, InvocationHandlerFactory.MethodHandler> dispatch, FallbackFactory<?> nullableFallbackFactory) {
|
||||
FeignCircuitBreakerInvocationHandler(CircuitBreakerFactory factory,
|
||||
String feignClientName, Target<?> target,
|
||||
Map<Method, InvocationHandlerFactory.MethodHandler> dispatch,
|
||||
FallbackFactory<?> nullableFallbackFactory) {
|
||||
this.factory = factory;
|
||||
this.feignClientName = feignClientName;
|
||||
this.target = checkNotNull(target, "target");
|
||||
@@ -57,12 +59,14 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
|
||||
public Object invoke(final Object proxy, final Method method, final Object[] args)
|
||||
throws Throwable {
|
||||
// early exit if the invoked method is from java.lang.Object
|
||||
// code is the same as ReflectiveFeign.FeignInvocationHandler
|
||||
if ("equals".equals(method.getName())) {
|
||||
try {
|
||||
Object otherHandler = args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
|
||||
Object otherHandler = args.length > 0 && args[0] != null
|
||||
? Proxy.getInvocationHandler(args[0]) : null;
|
||||
return equals(otherHandler);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
@@ -116,7 +120,8 @@ class FeignCircuitBreakerInvocationHandler implements InvocationHandler {
|
||||
* reflection calls.
|
||||
* @return cached methods map for fallback invoking
|
||||
*/
|
||||
static Map<Method, Method> toFallbackMethod(Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
|
||||
static Map<Method, Method> toFallbackMethod(
|
||||
Map<Method, InvocationHandlerFactory.MethodHandler> dispatch) {
|
||||
Map<Method, Method> result = new LinkedHashMap<Method, Method>();
|
||||
for (Method method : dispatch.keySet()) {
|
||||
method.setAccessible(true);
|
||||
|
||||
+28
-18
@@ -32,56 +32,66 @@ class FeignCircuitBreakerTargeter implements Targeter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
|
||||
Target.HardCodedTarget<T> target) {
|
||||
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
|
||||
FeignContext context, Target.HardCodedTarget<T> target) {
|
||||
if (!(feign instanceof FeignCircuitBreaker.Builder)) {
|
||||
return feign.target(target);
|
||||
}
|
||||
FeignCircuitBreaker.Builder builder = (FeignCircuitBreaker.Builder) feign;
|
||||
String name = !StringUtils.hasText(factory.getContextId()) ? factory.getName() : factory.getContextId();
|
||||
String name = !StringUtils.hasText(factory.getContextId()) ? factory.getName()
|
||||
: factory.getContextId();
|
||||
Class<?> fallback = factory.getFallback();
|
||||
if (fallback != void.class) {
|
||||
return targetWithFallback(name, context, target, builder, fallback);
|
||||
}
|
||||
Class<?> fallbackFactory = factory.getFallbackFactory();
|
||||
if (fallbackFactory != void.class) {
|
||||
return targetWithFallbackFactory(name, context, target, builder, fallbackFactory);
|
||||
return targetWithFallbackFactory(name, context, target, builder,
|
||||
fallbackFactory);
|
||||
}
|
||||
return builder(name, builder).target(target);
|
||||
}
|
||||
|
||||
private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context,
|
||||
Target.HardCodedTarget<T> target, FeignCircuitBreaker.Builder builder, Class<?> fallbackFactoryClass) {
|
||||
FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>) getFromContext("fallbackFactory",
|
||||
feignClientName, context, fallbackFactoryClass, FallbackFactory.class);
|
||||
Target.HardCodedTarget<T> target, FeignCircuitBreaker.Builder builder,
|
||||
Class<?> fallbackFactoryClass) {
|
||||
FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>) getFromContext(
|
||||
"fallbackFactory", feignClientName, context, fallbackFactoryClass,
|
||||
FallbackFactory.class);
|
||||
return builder(feignClientName, builder).target(target, fallbackFactory);
|
||||
}
|
||||
|
||||
private <T> T targetWithFallback(String feignClientName, FeignContext context, Target.HardCodedTarget<T> target,
|
||||
FeignCircuitBreaker.Builder builder, Class<?> fallback) {
|
||||
T fallbackInstance = getFromContext("fallback", feignClientName, context, fallback, target.type());
|
||||
private <T> T targetWithFallback(String feignClientName, FeignContext context,
|
||||
Target.HardCodedTarget<T> target, FeignCircuitBreaker.Builder builder,
|
||||
Class<?> fallback) {
|
||||
T fallbackInstance = getFromContext("fallback", feignClientName, context,
|
||||
fallback, target.type());
|
||||
return builder(feignClientName, builder).target(target, fallbackInstance);
|
||||
}
|
||||
|
||||
private <T> T getFromContext(String fallbackMechanism, String feignClientName, FeignContext context,
|
||||
Class<?> beanType, Class<T> targetType) {
|
||||
private <T> T getFromContext(String fallbackMechanism, String feignClientName,
|
||||
FeignContext context, Class<?> beanType, Class<T> targetType) {
|
||||
Object fallbackInstance = context.getInstance(feignClientName, beanType);
|
||||
if (fallbackInstance == null) {
|
||||
throw new IllegalStateException(
|
||||
String.format("No " + fallbackMechanism + " instance of type %s found for feign client %s",
|
||||
beanType, feignClientName));
|
||||
throw new IllegalStateException(String.format(
|
||||
"No " + fallbackMechanism
|
||||
+ " instance of type %s found for feign client %s",
|
||||
beanType, feignClientName));
|
||||
}
|
||||
|
||||
if (!targetType.isAssignableFrom(beanType)) {
|
||||
throw new IllegalStateException(String.format("Incompatible " + fallbackMechanism
|
||||
throw new IllegalStateException(String.format("Incompatible "
|
||||
+ fallbackMechanism
|
||||
+ " instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
|
||||
beanType, targetType, feignClientName));
|
||||
}
|
||||
return (T) fallbackInstance;
|
||||
}
|
||||
|
||||
private FeignCircuitBreaker.Builder builder(String feignClientName, FeignCircuitBreaker.Builder builder) {
|
||||
return builder.circuitBreakerFactory(this.circuitBreakerFactory).feignClientName(feignClientName);
|
||||
private FeignCircuitBreaker.Builder builder(String feignClientName,
|
||||
FeignCircuitBreaker.Builder builder) {
|
||||
return builder.circuitBreakerFactory(this.circuitBreakerFactory)
|
||||
.feignClientName(feignClientName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
-4
@@ -27,9 +27,9 @@ import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
/**
|
||||
* Annotation for interfaces declaring that a REST client with that interface should be
|
||||
* created (e.g. for autowiring into another component). If SC LoadBalancer is available
|
||||
* it will be used to load balance the backend requests, and the load balancer can be
|
||||
* configured using the same name (i.e. value) as the feign client.
|
||||
* created (e.g. for autowiring into another component). If ribbon is available it will be
|
||||
* used to load balance the backend requests, and the load balancer can be configured
|
||||
* using a <code>@RibbonClient</code> with the same name (i.e. value) as the feign client.
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @author Venil Noronha
|
||||
@@ -49,6 +49,14 @@ public @interface FeignClient {
|
||||
@AliasFor("name")
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* The service id with optional protocol prefix. Synonym for {@link #value() value}.
|
||||
* @deprecated use {@link #name() name} instead
|
||||
* @return the service id with optional protocol prefix
|
||||
*/
|
||||
@Deprecated
|
||||
String serviceId() default "";
|
||||
|
||||
/**
|
||||
* This will be used as the bean name instead of name if present, but will not be used
|
||||
* as a service id.
|
||||
@@ -100,13 +108,15 @@ public @interface FeignClient {
|
||||
* factory must produce instances of fallback classes that implement the interface
|
||||
* annotated by {@link FeignClient}. The fallback factory must be a valid spring bean.
|
||||
*
|
||||
* @see feign.hystrix.FallbackFactory for details.
|
||||
* @see FallbackFactory for details.
|
||||
* @return fallback factory for the specified Feign client interface
|
||||
*/
|
||||
Class<?> fallbackFactory() default void.class;
|
||||
|
||||
/**
|
||||
* @return path prefix to be used by all method-level mappings.
|
||||
* @return path prefix to be used by all method-level mappings. Can be used with or
|
||||
* without <code>@RibbonClient</code>.
|
||||
*/
|
||||
String path() default "";
|
||||
|
||||
|
||||
+12
-12
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import feign.hystrix.FallbackFactory;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
/**
|
||||
@@ -39,11 +41,6 @@ public class FeignClientBuilder {
|
||||
return new Builder<>(this.applicationContext, type, name);
|
||||
}
|
||||
|
||||
public <T> Builder<T> forType(final Class<T> type, final FeignClientFactoryBean clientFactoryBean,
|
||||
final String name) {
|
||||
return new Builder<>(this.applicationContext, clientFactoryBean, type, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder of feign targets.
|
||||
*
|
||||
@@ -53,13 +50,9 @@ public class FeignClientBuilder {
|
||||
|
||||
private FeignClientFactoryBean feignClientFactoryBean;
|
||||
|
||||
private Builder(final ApplicationContext applicationContext, final Class<T> type, final String name) {
|
||||
this(applicationContext, new FeignClientFactoryBean(), type, name);
|
||||
}
|
||||
|
||||
private Builder(final ApplicationContext applicationContext, final FeignClientFactoryBean clientFactoryBean,
|
||||
final Class<T> type, final String name) {
|
||||
this.feignClientFactoryBean = clientFactoryBean;
|
||||
private Builder(final ApplicationContext applicationContext, final Class<T> type,
|
||||
final String name) {
|
||||
this.feignClientFactoryBean = new FeignClientFactoryBean();
|
||||
|
||||
this.feignClientFactoryBean.setApplicationContext(applicationContext);
|
||||
this.feignClientFactoryBean.setType(type);
|
||||
@@ -102,6 +95,13 @@ public class FeignClientBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> fallbackFactory(
|
||||
final Class<? extends FallbackFactory<? extends T>> fallbackFactory) {
|
||||
FeignClientsRegistrar.validateFallbackFactory(fallbackFactory);
|
||||
this.feignClientFactoryBean.setFallbackFactory(fallbackFactory);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the created Feign client
|
||||
*/
|
||||
|
||||
+87
-48
@@ -45,6 +45,7 @@ import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.cloud.openfeign.clientconfig.FeignClientConfigurer;
|
||||
import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient;
|
||||
import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
@@ -61,8 +62,8 @@ import org.springframework.util.StringUtils;
|
||||
* @author Ilia Ilinykh
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
public class FeignClientFactoryBean
|
||||
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware, BeanFactoryAware {
|
||||
public class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
|
||||
ApplicationContextAware, BeanFactoryAware {
|
||||
|
||||
/***********************************
|
||||
* WARNING! Nothing in this class should be @Autowired. It causes NPEs because of some
|
||||
@@ -121,30 +122,38 @@ public class FeignClientFactoryBean
|
||||
}
|
||||
|
||||
private void applyBuildCustomizers(FeignContext context, Feign.Builder builder) {
|
||||
Map<String, FeignBuilderCustomizer> customizerMap = context.getInstances(contextId,
|
||||
FeignBuilderCustomizer.class);
|
||||
Map<String, FeignBuilderCustomizer> customizerMap = context
|
||||
.getInstances(contextId, FeignBuilderCustomizer.class);
|
||||
|
||||
if (customizerMap != null) {
|
||||
customizerMap.values().stream().sorted(AnnotationAwareOrderComparator.INSTANCE)
|
||||
.forEach(feignBuilderCustomizer -> feignBuilderCustomizer.customize(builder));
|
||||
customizerMap.values().stream()
|
||||
.sorted(AnnotationAwareOrderComparator.INSTANCE)
|
||||
.forEach(feignBuilderCustomizer -> feignBuilderCustomizer
|
||||
.customize(builder));
|
||||
}
|
||||
}
|
||||
|
||||
protected void configureFeign(FeignContext context, Feign.Builder builder) {
|
||||
FeignClientProperties properties = beanFactory != null ? beanFactory.getBean(FeignClientProperties.class)
|
||||
FeignClientProperties properties = beanFactory != null
|
||||
? beanFactory.getBean(FeignClientProperties.class)
|
||||
: applicationContext.getBean(FeignClientProperties.class);
|
||||
|
||||
FeignClientConfigurer feignClientConfigurer = getOptional(context, FeignClientConfigurer.class);
|
||||
FeignClientConfigurer feignClientConfigurer = getOptional(context,
|
||||
FeignClientConfigurer.class);
|
||||
setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());
|
||||
|
||||
if (properties != null && inheritParentContext) {
|
||||
if (properties.isDefaultToProperties()) {
|
||||
configureUsingConfiguration(context, builder);
|
||||
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
|
||||
configureUsingProperties(
|
||||
properties.getConfig().get(properties.getDefaultConfig()),
|
||||
builder);
|
||||
configureUsingProperties(properties.getConfig().get(contextId), builder);
|
||||
}
|
||||
else {
|
||||
configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
|
||||
configureUsingProperties(
|
||||
properties.getConfig().get(properties.getDefaultConfig()),
|
||||
builder);
|
||||
configureUsingProperties(properties.getConfig().get(contextId), builder);
|
||||
configureUsingConfiguration(context, builder);
|
||||
}
|
||||
@@ -154,7 +163,8 @@ public class FeignClientFactoryBean
|
||||
}
|
||||
}
|
||||
|
||||
protected void configureUsingConfiguration(FeignContext context, Feign.Builder builder) {
|
||||
protected void configureUsingConfiguration(FeignContext context,
|
||||
Feign.Builder builder) {
|
||||
Logger.Level level = getInheritedAwareOptional(context, Logger.Level.class);
|
||||
if (level != null) {
|
||||
builder.logLevel(level);
|
||||
@@ -163,45 +173,51 @@ public class FeignClientFactoryBean
|
||||
if (retryer != null) {
|
||||
builder.retryer(retryer);
|
||||
}
|
||||
ErrorDecoder errorDecoder = getInheritedAwareOptional(context, ErrorDecoder.class);
|
||||
ErrorDecoder errorDecoder = getInheritedAwareOptional(context,
|
||||
ErrorDecoder.class);
|
||||
if (errorDecoder != null) {
|
||||
builder.errorDecoder(errorDecoder);
|
||||
}
|
||||
else {
|
||||
FeignErrorDecoderFactory errorDecoderFactory = getOptional(context, FeignErrorDecoderFactory.class);
|
||||
FeignErrorDecoderFactory errorDecoderFactory = getOptional(context,
|
||||
FeignErrorDecoderFactory.class);
|
||||
if (errorDecoderFactory != null) {
|
||||
ErrorDecoder factoryErrorDecoder = errorDecoderFactory.create(type);
|
||||
builder.errorDecoder(factoryErrorDecoder);
|
||||
}
|
||||
}
|
||||
Request.Options options = getInheritedAwareOptional(context, Request.Options.class);
|
||||
Request.Options options = getInheritedAwareOptional(context,
|
||||
Request.Options.class);
|
||||
if (options != null) {
|
||||
builder.options(options);
|
||||
readTimeoutMillis = options.readTimeoutMillis();
|
||||
connectTimeoutMillis = options.connectTimeoutMillis();
|
||||
}
|
||||
Map<String, RequestInterceptor> requestInterceptors = getInheritedAwareInstances(context,
|
||||
RequestInterceptor.class);
|
||||
Map<String, RequestInterceptor> requestInterceptors = getInheritedAwareInstances(
|
||||
context, RequestInterceptor.class);
|
||||
if (requestInterceptors != null) {
|
||||
List<RequestInterceptor> interceptors = new ArrayList<>(requestInterceptors.values());
|
||||
List<RequestInterceptor> interceptors = new ArrayList<>(
|
||||
requestInterceptors.values());
|
||||
AnnotationAwareOrderComparator.sort(interceptors);
|
||||
builder.requestInterceptors(interceptors);
|
||||
}
|
||||
QueryMapEncoder queryMapEncoder = getInheritedAwareOptional(context, QueryMapEncoder.class);
|
||||
QueryMapEncoder queryMapEncoder = getInheritedAwareOptional(context,
|
||||
QueryMapEncoder.class);
|
||||
if (queryMapEncoder != null) {
|
||||
builder.queryMapEncoder(queryMapEncoder);
|
||||
}
|
||||
if (decode404) {
|
||||
builder.decode404();
|
||||
}
|
||||
ExceptionPropagationPolicy exceptionPropagationPolicy = getInheritedAwareOptional(context,
|
||||
ExceptionPropagationPolicy.class);
|
||||
ExceptionPropagationPolicy exceptionPropagationPolicy = getInheritedAwareOptional(
|
||||
context, ExceptionPropagationPolicy.class);
|
||||
if (exceptionPropagationPolicy != null) {
|
||||
builder.exceptionPropagationPolicy(exceptionPropagationPolicy);
|
||||
}
|
||||
}
|
||||
|
||||
protected void configureUsingProperties(FeignClientProperties.FeignClientConfiguration config,
|
||||
protected void configureUsingProperties(
|
||||
FeignClientProperties.FeignClientConfiguration config,
|
||||
Feign.Builder builder) {
|
||||
if (config == null) {
|
||||
return;
|
||||
@@ -211,11 +227,13 @@ public class FeignClientFactoryBean
|
||||
builder.logLevel(config.getLoggerLevel());
|
||||
}
|
||||
|
||||
connectTimeoutMillis = config.getConnectTimeout() != null ? config.getConnectTimeout() : connectTimeoutMillis;
|
||||
readTimeoutMillis = config.getReadTimeout() != null ? config.getReadTimeout() : readTimeoutMillis;
|
||||
connectTimeoutMillis = config.getConnectTimeout() != null
|
||||
? config.getConnectTimeout() : connectTimeoutMillis;
|
||||
readTimeoutMillis = config.getReadTimeout() != null ? config.getReadTimeout()
|
||||
: readTimeoutMillis;
|
||||
|
||||
builder.options(new Request.Options(connectTimeoutMillis, TimeUnit.MILLISECONDS, readTimeoutMillis,
|
||||
TimeUnit.MILLISECONDS, true));
|
||||
builder.options(new Request.Options(connectTimeoutMillis, TimeUnit.MILLISECONDS,
|
||||
readTimeoutMillis, TimeUnit.MILLISECONDS, true));
|
||||
|
||||
if (config.getRetryer() != null) {
|
||||
Retryer retryer = getOrInstantiate(config.getRetryer());
|
||||
@@ -227,7 +245,8 @@ public class FeignClientFactoryBean
|
||||
builder.errorDecoder(errorDecoder);
|
||||
}
|
||||
|
||||
if (config.getRequestInterceptors() != null && !config.getRequestInterceptors().isEmpty()) {
|
||||
if (config.getRequestInterceptors() != null
|
||||
&& !config.getRequestInterceptors().isEmpty()) {
|
||||
// this will add request interceptor to builder, not replace existing
|
||||
for (Class<RequestInterceptor> bean : config.getRequestInterceptors()) {
|
||||
RequestInterceptor interceptor = getOrInstantiate(bean);
|
||||
@@ -246,11 +265,13 @@ public class FeignClientFactoryBean
|
||||
}
|
||||
|
||||
if (Objects.nonNull(config.getDefaultRequestHeaders())) {
|
||||
builder.requestInterceptor(requestTemplate -> requestTemplate.headers(config.getDefaultRequestHeaders()));
|
||||
builder.requestInterceptor(requestTemplate -> requestTemplate
|
||||
.headers(config.getDefaultRequestHeaders()));
|
||||
}
|
||||
|
||||
if (Objects.nonNull(config.getDefaultQueryParameters())) {
|
||||
builder.requestInterceptor(requestTemplate -> requestTemplate.queries(config.getDefaultQueryParameters()));
|
||||
builder.requestInterceptor(requestTemplate -> requestTemplate
|
||||
.queries(config.getDefaultQueryParameters()));
|
||||
}
|
||||
|
||||
if (Objects.nonNull(config.getDecoder())) {
|
||||
@@ -268,7 +289,8 @@ public class FeignClientFactoryBean
|
||||
|
||||
private <T> T getOrInstantiate(Class<T> tClass) {
|
||||
try {
|
||||
return beanFactory != null ? beanFactory.getBean(tClass) : applicationContext.getBean(tClass);
|
||||
return beanFactory != null ? beanFactory.getBean(tClass)
|
||||
: applicationContext.getBean(tClass);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException e) {
|
||||
return BeanUtils.instantiateClass(tClass);
|
||||
@@ -278,7 +300,8 @@ public class FeignClientFactoryBean
|
||||
protected <T> T get(FeignContext context, Class<T> type) {
|
||||
T instance = context.getInstance(contextId, type);
|
||||
if (instance == null) {
|
||||
throw new IllegalStateException("No bean found of type " + type + " for " + contextId);
|
||||
throw new IllegalStateException(
|
||||
"No bean found of type " + type + " for " + contextId);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
@@ -296,7 +319,8 @@ public class FeignClientFactoryBean
|
||||
}
|
||||
}
|
||||
|
||||
protected <T> Map<String, T> getInheritedAwareInstances(FeignContext context, Class<T> type) {
|
||||
protected <T> Map<String, T> getInheritedAwareInstances(FeignContext context,
|
||||
Class<T> type) {
|
||||
if (inheritParentContext) {
|
||||
return context.getInstances(contextId, type);
|
||||
}
|
||||
@@ -305,7 +329,8 @@ public class FeignClientFactoryBean
|
||||
}
|
||||
}
|
||||
|
||||
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, HardCodedTarget<T> target) {
|
||||
protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
|
||||
HardCodedTarget<T> target) {
|
||||
Client client = getOptional(context, Client.class);
|
||||
if (client != null) {
|
||||
builder.client(client);
|
||||
@@ -314,7 +339,7 @@ public class FeignClientFactoryBean
|
||||
}
|
||||
|
||||
throw new IllegalStateException(
|
||||
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?");
|
||||
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -328,7 +353,8 @@ public class FeignClientFactoryBean
|
||||
* information
|
||||
*/
|
||||
<T> T getTarget() {
|
||||
FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
|
||||
FeignContext context = beanFactory != null
|
||||
? beanFactory.getBean(FeignContext.class)
|
||||
: applicationContext.getBean(FeignContext.class);
|
||||
Feign.Builder builder = feign(context);
|
||||
|
||||
@@ -340,7 +366,8 @@ public class FeignClientFactoryBean
|
||||
url = name;
|
||||
}
|
||||
url += cleanPath();
|
||||
return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
|
||||
return (T) loadBalance(builder, context,
|
||||
new HardCodedTarget<>(type, name, url));
|
||||
}
|
||||
if (StringUtils.hasText(url) && !url.startsWith("http")) {
|
||||
url = "http://" + url;
|
||||
@@ -348,6 +375,11 @@ public class FeignClientFactoryBean
|
||||
String url = this.url + cleanPath();
|
||||
Client client = getOptional(context, Client.class);
|
||||
if (client != null) {
|
||||
if (client instanceof LoadBalancerFeignClient) {
|
||||
// not load balancing because we have a url,
|
||||
// but ribbon is on the classpath, so unwrap
|
||||
client = ((LoadBalancerFeignClient) client).getDelegate();
|
||||
}
|
||||
if (client instanceof FeignBlockingLoadBalancerClient) {
|
||||
// not load balancing because we have a url,
|
||||
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
|
||||
@@ -356,7 +388,8 @@ public class FeignClientFactoryBean
|
||||
builder.client(client);
|
||||
}
|
||||
Targeter targeter = get(context, Targeter.class);
|
||||
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
|
||||
return (T) targeter.target(this, builder, context,
|
||||
new HardCodedTarget<>(type, name, url));
|
||||
}
|
||||
|
||||
private String cleanPath() {
|
||||
@@ -474,26 +507,32 @@ public class FeignClientFactoryBean
|
||||
}
|
||||
FeignClientFactoryBean that = (FeignClientFactoryBean) o;
|
||||
return Objects.equals(applicationContext, that.applicationContext)
|
||||
&& Objects.equals(beanFactory, that.beanFactory) && decode404 == that.decode404
|
||||
&& inheritParentContext == that.inheritParentContext && Objects.equals(fallback, that.fallback)
|
||||
&& Objects.equals(fallbackFactory, that.fallbackFactory) && Objects.equals(name, that.name)
|
||||
&& Objects.equals(path, that.path) && Objects.equals(type, that.type) && Objects.equals(url, that.url);
|
||||
&& Objects.equals(beanFactory, that.beanFactory)
|
||||
&& decode404 == that.decode404
|
||||
&& inheritParentContext == that.inheritParentContext
|
||||
&& Objects.equals(fallback, that.fallback)
|
||||
&& Objects.equals(fallbackFactory, that.fallbackFactory)
|
||||
&& Objects.equals(name, that.name) && Objects.equals(path, that.path)
|
||||
&& Objects.equals(type, that.type) && Objects.equals(url, that.url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(applicationContext, beanFactory, decode404, inheritParentContext, fallback, fallbackFactory,
|
||||
name, path, type, url);
|
||||
return Objects.hash(applicationContext, beanFactory, decode404,
|
||||
inheritParentContext, fallback, fallbackFactory, name, path, type, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder("FeignClientFactoryBean{").append("type=").append(type).append(", ").append("name='")
|
||||
.append(name).append("', ").append("url='").append(url).append("', ").append("path='").append(path)
|
||||
.append("', ").append("decode404=").append(decode404).append(", ").append("inheritParentContext=")
|
||||
.append(inheritParentContext).append(", ").append("applicationContext=").append(applicationContext)
|
||||
.append(", ").append("beanFactory=").append(beanFactory).append(", ").append("fallback=")
|
||||
.append(fallback).append(", ").append("fallbackFactory=").append(fallbackFactory).append("}")
|
||||
return new StringBuilder("FeignClientFactoryBean{").append("type=").append(type)
|
||||
.append(", ").append("name='").append(name).append("', ").append("url='")
|
||||
.append(url).append("', ").append("path='").append(path).append("', ")
|
||||
.append("decode404=").append(decode404).append(", ")
|
||||
.append("inheritParentContext=").append(inheritParentContext).append(", ")
|
||||
.append("applicationContext=").append(applicationContext).append(", ")
|
||||
.append("beanFactory=").append(beanFactory).append(", ")
|
||||
.append("fallback=").append(fallback).append(", ")
|
||||
.append("fallbackFactory=").append(fallbackFactory).append("}")
|
||||
.toString();
|
||||
}
|
||||
|
||||
|
||||
+27
-14
@@ -94,8 +94,10 @@ public class FeignClientProperties {
|
||||
return false;
|
||||
}
|
||||
FeignClientProperties that = (FeignClientProperties) o;
|
||||
return defaultToProperties == that.defaultToProperties && Objects.equals(defaultConfig, that.defaultConfig)
|
||||
&& Objects.equals(config, that.config) && Objects.equals(decodeSlash, that.decodeSlash);
|
||||
return defaultToProperties == that.defaultToProperties
|
||||
&& Objects.equals(defaultConfig, that.defaultConfig)
|
||||
&& Objects.equals(config, that.config)
|
||||
&& Objects.equals(decodeSlash, that.decodeSlash);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -178,7 +180,8 @@ public class FeignClientProperties {
|
||||
return requestInterceptors;
|
||||
}
|
||||
|
||||
public void setRequestInterceptors(List<Class<RequestInterceptor>> requestInterceptors) {
|
||||
public void setRequestInterceptors(
|
||||
List<Class<RequestInterceptor>> requestInterceptors) {
|
||||
this.requestInterceptors = requestInterceptors;
|
||||
}
|
||||
|
||||
@@ -186,7 +189,8 @@ public class FeignClientProperties {
|
||||
return defaultRequestHeaders;
|
||||
}
|
||||
|
||||
public void setDefaultRequestHeaders(Map<String, Collection<String>> defaultRequestHeaders) {
|
||||
public void setDefaultRequestHeaders(
|
||||
Map<String, Collection<String>> defaultRequestHeaders) {
|
||||
this.defaultRequestHeaders = defaultRequestHeaders;
|
||||
}
|
||||
|
||||
@@ -194,7 +198,8 @@ public class FeignClientProperties {
|
||||
return defaultQueryParameters;
|
||||
}
|
||||
|
||||
public void setDefaultQueryParameters(Map<String, Collection<String>> defaultQueryParameters) {
|
||||
public void setDefaultQueryParameters(
|
||||
Map<String, Collection<String>> defaultQueryParameters) {
|
||||
this.defaultQueryParameters = defaultQueryParameters;
|
||||
}
|
||||
|
||||
@@ -234,7 +239,8 @@ public class FeignClientProperties {
|
||||
return exceptionPropagationPolicy;
|
||||
}
|
||||
|
||||
public void setExceptionPropagationPolicy(ExceptionPropagationPolicy exceptionPropagationPolicy) {
|
||||
public void setExceptionPropagationPolicy(
|
||||
ExceptionPropagationPolicy exceptionPropagationPolicy) {
|
||||
this.exceptionPropagationPolicy = exceptionPropagationPolicy;
|
||||
}
|
||||
|
||||
@@ -247,21 +253,28 @@ public class FeignClientProperties {
|
||||
return false;
|
||||
}
|
||||
FeignClientConfiguration that = (FeignClientConfiguration) o;
|
||||
return loggerLevel == that.loggerLevel && Objects.equals(connectTimeout, that.connectTimeout)
|
||||
&& Objects.equals(readTimeout, that.readTimeout) && Objects.equals(retryer, that.retryer)
|
||||
return loggerLevel == that.loggerLevel
|
||||
&& Objects.equals(connectTimeout, that.connectTimeout)
|
||||
&& Objects.equals(readTimeout, that.readTimeout)
|
||||
&& Objects.equals(retryer, that.retryer)
|
||||
&& Objects.equals(errorDecoder, that.errorDecoder)
|
||||
&& Objects.equals(requestInterceptors, that.requestInterceptors)
|
||||
&& Objects.equals(decode404, that.decode404) && Objects.equals(encoder, that.encoder)
|
||||
&& Objects.equals(decoder, that.decoder) && Objects.equals(contract, that.contract)
|
||||
&& Objects.equals(exceptionPropagationPolicy, that.exceptionPropagationPolicy)
|
||||
&& Objects.equals(decode404, that.decode404)
|
||||
&& Objects.equals(encoder, that.encoder)
|
||||
&& Objects.equals(decoder, that.decoder)
|
||||
&& Objects.equals(contract, that.contract)
|
||||
&& Objects.equals(exceptionPropagationPolicy,
|
||||
that.exceptionPropagationPolicy)
|
||||
&& Objects.equals(defaultRequestHeaders, that.defaultRequestHeaders)
|
||||
&& Objects.equals(defaultQueryParameters, that.defaultQueryParameters);
|
||||
&& Objects.equals(defaultQueryParameters,
|
||||
that.defaultQueryParameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(loggerLevel, connectTimeout, readTimeout, retryer, errorDecoder, requestInterceptors,
|
||||
decode404, encoder, decoder, contract, exceptionPropagationPolicy, defaultQueryParameters,
|
||||
return Objects.hash(loggerLevel, connectTimeout, readTimeout, retryer,
|
||||
errorDecoder, requestInterceptors, decode404, encoder, decoder,
|
||||
contract, exceptionPropagationPolicy, defaultQueryParameters,
|
||||
defaultRequestHeaders);
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -64,7 +64,8 @@ class FeignClientSpecification implements NamedContextFactory.Specification {
|
||||
return false;
|
||||
}
|
||||
FeignClientSpecification that = (FeignClientSpecification) o;
|
||||
return Objects.equals(this.name, that.name) && Arrays.equals(this.configuration, that.configuration);
|
||||
return Objects.equals(this.name, that.name)
|
||||
&& Arrays.equals(this.configuration, that.configuration);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -74,8 +75,9 @@ class FeignClientSpecification implements NamedContextFactory.Specification {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder("FeignClientSpecification{").append("name='").append(this.name).append("', ")
|
||||
.append("configuration=").append(Arrays.toString(this.configuration)).append("}").toString();
|
||||
return new StringBuilder("FeignClientSpecification{").append("name='")
|
||||
.append(this.name).append("', ").append("configuration=")
|
||||
.append(Arrays.toString(this.configuration)).append("}").toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+43
-24
@@ -19,6 +19,7 @@ package org.springframework.cloud.openfeign;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.netflix.hystrix.HystrixCommand;
|
||||
import feign.Contract;
|
||||
import feign.Feign;
|
||||
import feign.Logger;
|
||||
@@ -27,6 +28,7 @@ import feign.codec.Decoder;
|
||||
import feign.codec.Encoder;
|
||||
import feign.form.MultipartFormContentProcessor;
|
||||
import feign.form.spring.SpringFormEncoder;
|
||||
import feign.hystrix.HystrixFeign;
|
||||
import feign.optionals.OptionalDecoder;
|
||||
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
@@ -49,7 +51,6 @@ import org.springframework.cloud.openfeign.support.SpringDecoder;
|
||||
import org.springframework.cloud.openfeign.support.SpringEncoder;
|
||||
import org.springframework.cloud.openfeign.support.SpringMvcContract;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
@@ -87,7 +88,8 @@ public class FeignClientsConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Decoder feignDecoder() {
|
||||
return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
|
||||
return new OptionalDecoder(
|
||||
new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -100,13 +102,18 @@ public class FeignClientsConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnClass(name = "org.springframework.data.domain.Pageable")
|
||||
@ConditionalOnMissingBean
|
||||
public Encoder feignEncoderPageable(ObjectProvider<AbstractFormWriter> formWriterProvider) {
|
||||
PageableSpringEncoder encoder = new PageableSpringEncoder(springEncoder(formWriterProvider));
|
||||
public Encoder feignEncoderPageable(
|
||||
ObjectProvider<AbstractFormWriter> formWriterProvider) {
|
||||
PageableSpringEncoder encoder = new PageableSpringEncoder(
|
||||
springEncoder(formWriterProvider));
|
||||
|
||||
if (springDataWebProperties != null) {
|
||||
encoder.setPageParameter(springDataWebProperties.getPageable().getPageParameter());
|
||||
encoder.setSizeParameter(springDataWebProperties.getPageable().getSizeParameter());
|
||||
encoder.setSortParameter(springDataWebProperties.getSort().getSortParameter());
|
||||
encoder.setPageParameter(
|
||||
springDataWebProperties.getPageable().getPageParameter());
|
||||
encoder.setSizeParameter(
|
||||
springDataWebProperties.getPageable().getSizeParameter());
|
||||
encoder.setSortParameter(
|
||||
springDataWebProperties.getSort().getSortParameter());
|
||||
}
|
||||
return encoder;
|
||||
}
|
||||
@@ -114,8 +121,10 @@ public class FeignClientsConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Contract feignContract(ConversionService feignConversionService) {
|
||||
boolean decodeSlash = feignClientProperties == null || feignClientProperties.isDecodeSlash();
|
||||
return new SpringMvcContract(this.parameterProcessors, feignConversionService, decodeSlash);
|
||||
boolean decodeSlash = feignClientProperties == null
|
||||
|| feignClientProperties.isDecodeSlash();
|
||||
return new SpringMvcContract(this.parameterProcessors, feignConversionService,
|
||||
decodeSlash);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -133,6 +142,13 @@ public class FeignClientsConfiguration {
|
||||
return Retryer.NEVER_RETRY;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Scope("prototype")
|
||||
@ConditionalOnMissingBean
|
||||
public Feign.Builder feignBuilder(Retryer retryer) {
|
||||
return Feign.builder().retryer(retryer);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(FeignLoggerFactory.class)
|
||||
public FeignLoggerFactory feignLoggerFactory() {
|
||||
@@ -150,37 +166,40 @@ public class FeignClientsConfiguration {
|
||||
AbstractFormWriter formWriter = formWriterProvider.getIfAvailable();
|
||||
|
||||
if (formWriter != null) {
|
||||
return new SpringEncoder(new SpringPojoFormEncoder(formWriter), this.messageConverters);
|
||||
return new SpringEncoder(new SpringPojoFormEncoder(formWriter),
|
||||
this.messageConverters);
|
||||
}
|
||||
else {
|
||||
return new SpringEncoder(new SpringFormEncoder(), this.messageConverters);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass({ HystrixCommand.class, HystrixFeign.class })
|
||||
protected static class HystrixFeignConfiguration {
|
||||
|
||||
@Bean
|
||||
@Scope("prototype")
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(name = "feign.hystrix.enabled")
|
||||
public Feign.Builder feignHystrixBuilder() {
|
||||
return HystrixFeign.builder();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class SpringPojoFormEncoder extends SpringFormEncoder {
|
||||
|
||||
SpringPojoFormEncoder(AbstractFormWriter formWriter) {
|
||||
super();
|
||||
|
||||
MultipartFormContentProcessor processor = (MultipartFormContentProcessor) getContentProcessor(MULTIPART);
|
||||
MultipartFormContentProcessor processor = (MultipartFormContentProcessor) getContentProcessor(
|
||||
MULTIPART);
|
||||
processor.addFirstWriter(formWriter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Conditional(FeignCircuitBreakerDisabledConditions.class)
|
||||
protected static class DefaultFeignBuilderConfiguration {
|
||||
|
||||
@Bean
|
||||
@Scope("prototype")
|
||||
@ConditionalOnMissingBean
|
||||
public Feign.Builder feignBuilder(Retryer retryer) {
|
||||
return Feign.builder().retryer(retryer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(CircuitBreaker.class)
|
||||
@ConditionalOnProperty("feign.circuitbreaker.enabled")
|
||||
|
||||
+67
-41
@@ -58,7 +58,8 @@ import org.springframework.util.StringUtils;
|
||||
* @author Michal Domagala
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
|
||||
class FeignClientsRegistrar
|
||||
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
|
||||
|
||||
// patterned after Spring Integration IntegrationComponentScanRegistrar
|
||||
// and RibbonClientsConfigurationRegistgrar
|
||||
@@ -71,7 +72,8 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
|
||||
}
|
||||
|
||||
static void validateFallback(final Class clazz) {
|
||||
Assert.isTrue(!clazz.isInterface(), "Fallback class must implement the interface annotated by @FeignClient");
|
||||
Assert.isTrue(!clazz.isInterface(),
|
||||
"Fallback class must implement the interface annotated by @FeignClient");
|
||||
}
|
||||
|
||||
static void validateFallbackFactory(final Class clazz) {
|
||||
@@ -136,13 +138,16 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
|
||||
public void registerBeanDefinitions(AnnotationMetadata metadata,
|
||||
BeanDefinitionRegistry registry) {
|
||||
registerDefaultConfiguration(metadata, registry);
|
||||
registerFeignClients(metadata, registry);
|
||||
}
|
||||
|
||||
private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
|
||||
Map<String, Object> defaultAttrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
|
||||
private void registerDefaultConfiguration(AnnotationMetadata metadata,
|
||||
BeanDefinitionRegistry registry) {
|
||||
Map<String, Object> defaultAttrs = metadata
|
||||
.getAnnotationAttributes(EnableFeignClients.class.getName(), true);
|
||||
|
||||
if (defaultAttrs != null && defaultAttrs.containsKey("defaultConfiguration")) {
|
||||
String name;
|
||||
@@ -152,15 +157,19 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
|
||||
else {
|
||||
name = "default." + metadata.getClassName();
|
||||
}
|
||||
registerClientConfiguration(registry, name, defaultAttrs.get("defaultConfiguration"));
|
||||
registerClientConfiguration(registry, name,
|
||||
defaultAttrs.get("defaultConfiguration"));
|
||||
}
|
||||
}
|
||||
|
||||
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
|
||||
public void registerFeignClients(AnnotationMetadata metadata,
|
||||
BeanDefinitionRegistry registry) {
|
||||
|
||||
LinkedHashSet<BeanDefinition> candidateComponents = new LinkedHashSet<>();
|
||||
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
|
||||
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
|
||||
Map<String, Object> attrs = metadata
|
||||
.getAnnotationAttributes(EnableFeignClients.class.getName());
|
||||
final Class<?>[] clients = attrs == null ? null
|
||||
: (Class<?>[]) attrs.get("clients");
|
||||
if (clients == null || clients.length == 0) {
|
||||
ClassPathScanningCandidateComponentProvider scanner = getScanner();
|
||||
scanner.setResourceLoader(this.resourceLoader);
|
||||
@@ -181,21 +190,23 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
|
||||
// verify annotated class is an interface
|
||||
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
|
||||
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
|
||||
Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
|
||||
Assert.isTrue(annotationMetadata.isInterface(),
|
||||
"@FeignClient can only be specified on an interface");
|
||||
|
||||
Map<String, Object> attributes = annotationMetadata
|
||||
.getAnnotationAttributes(FeignClient.class.getCanonicalName());
|
||||
|
||||
String name = getClientName(attributes);
|
||||
registerClientConfiguration(registry, name, attributes.get("configuration"));
|
||||
registerClientConfiguration(registry, name,
|
||||
attributes.get("configuration"));
|
||||
|
||||
registerFeignClient(registry, annotationMetadata, attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
|
||||
Map<String, Object> attributes) {
|
||||
private void registerFeignClient(BeanDefinitionRegistry registry,
|
||||
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
|
||||
String className = annotationMetadata.getClassName();
|
||||
Class clazz = ClassUtils.resolveClassName(className, null);
|
||||
ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
|
||||
@@ -207,22 +218,27 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
|
||||
factoryBean.setName(name);
|
||||
factoryBean.setContextId(contextId);
|
||||
factoryBean.setType(clazz);
|
||||
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
|
||||
factoryBean.setUrl(getUrl(beanFactory, attributes));
|
||||
factoryBean.setPath(getPath(beanFactory, attributes));
|
||||
factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
|
||||
Object fallback = attributes.get("fallback");
|
||||
if (fallback != null) {
|
||||
factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
|
||||
: ClassUtils.resolveClassName(fallback.toString(), null));
|
||||
}
|
||||
Object fallbackFactory = attributes.get("fallbackFactory");
|
||||
if (fallbackFactory != null) {
|
||||
factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
|
||||
: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
|
||||
}
|
||||
return factoryBean.getObject();
|
||||
});
|
||||
BeanDefinitionBuilder definition = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(clazz, () -> {
|
||||
factoryBean.setUrl(getUrl(beanFactory, attributes));
|
||||
factoryBean.setPath(getPath(beanFactory, attributes));
|
||||
factoryBean.setDecode404(Boolean
|
||||
.parseBoolean(String.valueOf(attributes.get("decode404"))));
|
||||
Object fallback = attributes.get("fallback");
|
||||
if (fallback != null) {
|
||||
factoryBean.setFallback(fallback instanceof Class
|
||||
? (Class<?>) fallback
|
||||
: ClassUtils.resolveClassName(fallback.toString(), null));
|
||||
}
|
||||
Object fallbackFactory = attributes.get("fallbackFactory");
|
||||
if (fallbackFactory != null) {
|
||||
factoryBean.setFallbackFactory(fallbackFactory instanceof Class
|
||||
? (Class<?>) fallbackFactory
|
||||
: ClassUtils.resolveClassName(fallbackFactory.toString(),
|
||||
null));
|
||||
}
|
||||
return factoryBean.getObject();
|
||||
});
|
||||
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
|
||||
definition.setLazyInit(true);
|
||||
validate(attributes);
|
||||
@@ -242,7 +258,8 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
|
||||
alias = qualifier;
|
||||
}
|
||||
|
||||
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
|
||||
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
|
||||
new String[] { alias });
|
||||
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
|
||||
}
|
||||
|
||||
@@ -270,7 +287,8 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
|
||||
return getName(name);
|
||||
}
|
||||
|
||||
private String getContextId(ConfigurableBeanFactory beanFactory, Map<String, Object> attributes) {
|
||||
private String getContextId(ConfigurableBeanFactory beanFactory,
|
||||
Map<String, Object> attributes) {
|
||||
String contextId = (String) attributes.get("contextId");
|
||||
if (!StringUtils.hasText(contextId)) {
|
||||
return getName(attributes);
|
||||
@@ -290,17 +308,20 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
|
||||
if (resolver == null) {
|
||||
return resolved;
|
||||
}
|
||||
return String.valueOf(resolver.evaluate(resolved, new BeanExpressionContext(beanFactory, null)));
|
||||
return String.valueOf(resolver.evaluate(resolved,
|
||||
new BeanExpressionContext(beanFactory, null)));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private String getUrl(ConfigurableBeanFactory beanFactory, Map<String, Object> attributes) {
|
||||
private String getUrl(ConfigurableBeanFactory beanFactory,
|
||||
Map<String, Object> attributes) {
|
||||
String url = resolve(beanFactory, (String) attributes.get("url"));
|
||||
return getUrl(url);
|
||||
}
|
||||
|
||||
private String getPath(ConfigurableBeanFactory beanFactory, Map<String, Object> attributes) {
|
||||
private String getPath(ConfigurableBeanFactory beanFactory,
|
||||
Map<String, Object> attributes) {
|
||||
String path = resolve(beanFactory, (String) attributes.get("path"));
|
||||
return getPath(path);
|
||||
}
|
||||
@@ -308,7 +329,8 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
|
||||
protected ClassPathScanningCandidateComponentProvider getScanner() {
|
||||
return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
|
||||
@Override
|
||||
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
|
||||
protected boolean isCandidateComponent(
|
||||
AnnotatedBeanDefinition beanDefinition) {
|
||||
boolean isCandidate = false;
|
||||
if (beanDefinition.getMetadata().isIndependent()) {
|
||||
if (!beanDefinition.getMetadata().isAnnotation()) {
|
||||
@@ -340,7 +362,8 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
|
||||
}
|
||||
|
||||
if (basePackages.isEmpty()) {
|
||||
basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
|
||||
basePackages.add(
|
||||
ClassUtils.getPackageName(importingClassMetadata.getClassName()));
|
||||
}
|
||||
return basePackages;
|
||||
}
|
||||
@@ -374,15 +397,18 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLo
|
||||
return value;
|
||||
}
|
||||
|
||||
throw new IllegalStateException(
|
||||
"Either 'name' or 'value' must be provided in @" + FeignClient.class.getSimpleName());
|
||||
throw new IllegalStateException("Either 'name' or 'value' must be provided in @"
|
||||
+ FeignClient.class.getSimpleName());
|
||||
}
|
||||
|
||||
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
|
||||
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
|
||||
Object configuration) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(FeignClientSpecification.class);
|
||||
builder.addConstructorArgValue(name);
|
||||
builder.addConstructorArgValue(configuration);
|
||||
registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),
|
||||
registry.registerBeanDefinition(
|
||||
name + "." + FeignClientSpecification.class.getSimpleName(),
|
||||
builder.getBeanDefinition());
|
||||
}
|
||||
|
||||
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import feign.Feign;
|
||||
import feign.Target;
|
||||
import feign.hystrix.FallbackFactory;
|
||||
import feign.hystrix.HystrixFeign;
|
||||
import feign.hystrix.SetterFactory;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Erik Kringen
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
class HystrixTargeter implements Targeter {
|
||||
|
||||
@Override
|
||||
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
|
||||
FeignContext context, Target.HardCodedTarget<T> target) {
|
||||
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
|
||||
return feign.target(target);
|
||||
}
|
||||
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
|
||||
String name = StringUtils.isEmpty(factory.getContextId()) ? factory.getName()
|
||||
: factory.getContextId();
|
||||
SetterFactory setterFactory = getOptional(name, context, SetterFactory.class);
|
||||
if (setterFactory != null) {
|
||||
builder.setterFactory(setterFactory);
|
||||
}
|
||||
Class<?> fallback = factory.getFallback();
|
||||
if (fallback != void.class) {
|
||||
return targetWithFallback(name, context, target, builder, fallback);
|
||||
}
|
||||
Class<?> fallbackFactory = factory.getFallbackFactory();
|
||||
if (fallbackFactory != void.class) {
|
||||
return targetWithFallbackFactory(name, context, target, builder,
|
||||
fallbackFactory);
|
||||
}
|
||||
|
||||
return feign.target(target);
|
||||
}
|
||||
|
||||
private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context,
|
||||
Target.HardCodedTarget<T> target, HystrixFeign.Builder builder,
|
||||
Class<?> fallbackFactoryClass) {
|
||||
FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>) getFromContext(
|
||||
"fallbackFactory", feignClientName, context, fallbackFactoryClass,
|
||||
FallbackFactory.class);
|
||||
return builder.target(target, fallbackFactory);
|
||||
}
|
||||
|
||||
private <T> T targetWithFallback(String feignClientName, FeignContext context,
|
||||
Target.HardCodedTarget<T> target, HystrixFeign.Builder builder,
|
||||
Class<?> fallback) {
|
||||
T fallbackInstance = getFromContext("fallback", feignClientName, context,
|
||||
fallback, target.type());
|
||||
return builder.target(target, fallbackInstance);
|
||||
}
|
||||
|
||||
private <T> T getFromContext(String fallbackMechanism, String feignClientName,
|
||||
FeignContext context, Class<?> beanType, Class<T> targetType) {
|
||||
Object fallbackInstance = context.getInstance(feignClientName, beanType);
|
||||
if (fallbackInstance == null) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"No " + fallbackMechanism
|
||||
+ " instance of type %s found for feign client %s",
|
||||
beanType, feignClientName));
|
||||
}
|
||||
|
||||
if (!targetType.isAssignableFrom(beanType)) {
|
||||
throw new IllegalStateException(String.format("Incompatible "
|
||||
+ fallbackMechanism
|
||||
+ " instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
|
||||
beanType, targetType, feignClientName));
|
||||
}
|
||||
return (T) fallbackInstance;
|
||||
}
|
||||
|
||||
private <T> T getOptional(String feignClientName, FeignContext context,
|
||||
Class<T> beanType) {
|
||||
return context.getInstance(feignClientName, beanType);
|
||||
}
|
||||
|
||||
}
|
||||
+3
-3
@@ -22,9 +22,9 @@ import feign.Target;
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public interface Targeter {
|
||||
interface Targeter {
|
||||
|
||||
<T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
|
||||
Target.HardCodedTarget<T> target);
|
||||
<T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
|
||||
FeignContext context, Target.HardCodedTarget<T> target);
|
||||
|
||||
}
|
||||
|
||||
+8
-4
@@ -48,13 +48,15 @@ public class MatrixVariableParameterProcessor implements AnnotatedParameterProce
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
|
||||
public boolean processArgument(AnnotatedParameterContext context,
|
||||
Annotation annotation, Method method) {
|
||||
int parameterIndex = context.getParameterIndex();
|
||||
Class<?> parameterType = method.getParameterTypes()[parameterIndex];
|
||||
MethodMetadata data = context.getMethodMetadata();
|
||||
String name = ANNOTATION.cast(annotation).value();
|
||||
|
||||
checkState(emptyToNull(name) != null, "MatrixVariable annotation was empty on param %s.",
|
||||
checkState(emptyToNull(name) != null,
|
||||
"MatrixVariable annotation was empty on param %s.",
|
||||
context.getParameterIndex());
|
||||
|
||||
context.setParameterName(name);
|
||||
@@ -63,7 +65,8 @@ public class MatrixVariableParameterProcessor implements AnnotatedParameterProce
|
||||
data.indexToExpander().put(parameterIndex, this::expandMap);
|
||||
}
|
||||
else {
|
||||
data.indexToExpander().put(parameterIndex, object -> ";" + name + "=" + object.toString());
|
||||
data.indexToExpander().put(parameterIndex,
|
||||
object -> ";" + name + "=" + object.toString());
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -72,7 +75,8 @@ public class MatrixVariableParameterProcessor implements AnnotatedParameterProce
|
||||
private String expandMap(Object object) {
|
||||
Map<String, Object> paramMap = (Map) object;
|
||||
|
||||
return paramMap.keySet().stream().map(key -> ";" + key + "=" + paramMap.get(key).toString())
|
||||
return paramMap.keySet().stream()
|
||||
.map(key -> ";" + key + "=" + paramMap.get(key).toString())
|
||||
.collect(Collectors.joining());
|
||||
}
|
||||
|
||||
|
||||
+6
-3
@@ -46,15 +46,18 @@ public class PathVariableParameterProcessor implements AnnotatedParameterProcess
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
|
||||
public boolean processArgument(AnnotatedParameterContext context,
|
||||
Annotation annotation, Method method) {
|
||||
String name = ANNOTATION.cast(annotation).value();
|
||||
checkState(emptyToNull(name) != null, "PathVariable annotation was empty on param %s.",
|
||||
checkState(emptyToNull(name) != null,
|
||||
"PathVariable annotation was empty on param %s.",
|
||||
context.getParameterIndex());
|
||||
context.setParameterName(name);
|
||||
|
||||
MethodMetadata data = context.getMethodMetadata();
|
||||
String varName = '{' + name + '}';
|
||||
if (!data.template().url().contains(varName) && !searchMapValues(data.template().queries(), varName)
|
||||
if (!data.template().url().contains(varName)
|
||||
&& !searchMapValues(data.template().queries(), varName)
|
||||
&& !searchMapValues(data.template().headers(), varName)) {
|
||||
data.formParams().add(name);
|
||||
}
|
||||
|
||||
+2
-1
@@ -40,7 +40,8 @@ public class QueryMapParameterProcessor implements AnnotatedParameterProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
|
||||
public boolean processArgument(AnnotatedParameterContext context,
|
||||
Annotation annotation, Method method) {
|
||||
int paramIndex = context.getParameterIndex();
|
||||
MethodMetadata metadata = context.getMethodMetadata();
|
||||
if (metadata.queryMapIndex() == null) {
|
||||
|
||||
+8
-4
@@ -46,23 +46,27 @@ public class RequestHeaderParameterProcessor implements AnnotatedParameterProces
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
|
||||
public boolean processArgument(AnnotatedParameterContext context,
|
||||
Annotation annotation, Method method) {
|
||||
int parameterIndex = context.getParameterIndex();
|
||||
Class<?> parameterType = method.getParameterTypes()[parameterIndex];
|
||||
MethodMetadata data = context.getMethodMetadata();
|
||||
|
||||
if (Map.class.isAssignableFrom(parameterType)) {
|
||||
checkState(data.headerMapIndex() == null, "Header map can only be present once.");
|
||||
checkState(data.headerMapIndex() == null,
|
||||
"Header map can only be present once.");
|
||||
data.headerMapIndex(parameterIndex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
String name = ANNOTATION.cast(annotation).value();
|
||||
checkState(emptyToNull(name) != null, "RequestHeader.value() was empty on parameter %s", parameterIndex);
|
||||
checkState(emptyToNull(name) != null,
|
||||
"RequestHeader.value() was empty on parameter %s", parameterIndex);
|
||||
context.setParameterName(name);
|
||||
|
||||
Collection<String> header = context.setTemplateParameter(name, data.template().headers().get(name));
|
||||
Collection<String> header = context.setTemplateParameter(name,
|
||||
data.template().headers().get(name));
|
||||
data.template().header(name, header);
|
||||
return true;
|
||||
}
|
||||
|
||||
+8
-4
@@ -46,13 +46,15 @@ public class RequestParamParameterProcessor implements AnnotatedParameterProcess
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
|
||||
public boolean processArgument(AnnotatedParameterContext context,
|
||||
Annotation annotation, Method method) {
|
||||
int parameterIndex = context.getParameterIndex();
|
||||
Class<?> parameterType = method.getParameterTypes()[parameterIndex];
|
||||
MethodMetadata data = context.getMethodMetadata();
|
||||
|
||||
if (Map.class.isAssignableFrom(parameterType)) {
|
||||
checkState(data.queryMapIndex() == null, "Query map can only be present once.");
|
||||
checkState(data.queryMapIndex() == null,
|
||||
"Query map can only be present once.");
|
||||
data.queryMapIndex(parameterIndex);
|
||||
|
||||
return true;
|
||||
@@ -60,10 +62,12 @@ public class RequestParamParameterProcessor implements AnnotatedParameterProcess
|
||||
|
||||
RequestParam requestParam = ANNOTATION.cast(annotation);
|
||||
String name = requestParam.value();
|
||||
checkState(emptyToNull(name) != null, "RequestParam.value() was empty on parameter %s", parameterIndex);
|
||||
checkState(emptyToNull(name) != null,
|
||||
"RequestParam.value() was empty on parameter %s", parameterIndex);
|
||||
context.setParameterName(name);
|
||||
|
||||
Collection<String> query = context.setTemplateParameter(name, data.template().queries().get(name));
|
||||
Collection<String> query = context.setTemplateParameter(name,
|
||||
data.template().queries().get(name));
|
||||
data.template().query(name, query);
|
||||
return true;
|
||||
}
|
||||
|
||||
+6
-3
@@ -44,16 +44,19 @@ public class RequestPartParameterProcessor implements AnnotatedParameterProcesso
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
|
||||
public boolean processArgument(AnnotatedParameterContext context,
|
||||
Annotation annotation, Method method) {
|
||||
int parameterIndex = context.getParameterIndex();
|
||||
MethodMetadata data = context.getMethodMetadata();
|
||||
|
||||
String name = ANNOTATION.cast(annotation).value();
|
||||
checkState(emptyToNull(name) != null, "RequestPart.value() was empty on parameter %s", parameterIndex);
|
||||
checkState(emptyToNull(name) != null,
|
||||
"RequestPart.value() was empty on parameter %s", parameterIndex);
|
||||
context.setParameterName(name);
|
||||
|
||||
data.formParams().add(name);
|
||||
Collection<String> names = context.setTemplateParameter(name, data.indexToName().get(parameterIndex));
|
||||
Collection<String> names = context.setTemplateParameter(name,
|
||||
data.indexToName().get(parameterIndex));
|
||||
data.indexToName().put(parameterIndex, names);
|
||||
return true;
|
||||
}
|
||||
|
||||
+30
-20
@@ -53,8 +53,8 @@ public class HttpClientFeignConfiguration {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(HttpClientFeignConfiguration.class);
|
||||
|
||||
private final Timer connectionManagerTimer = new Timer("FeignApacheHttpClientConfiguration.connectionManagerTimer",
|
||||
true);
|
||||
private final Timer connectionManagerTimer = new Timer(
|
||||
"FeignApacheHttpClientConfiguration.connectionManagerTimer", true);
|
||||
|
||||
private CloseableHttpClient httpClient;
|
||||
|
||||
@@ -66,10 +66,12 @@ public class HttpClientFeignConfiguration {
|
||||
public HttpClientConnectionManager connectionManager(
|
||||
ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
|
||||
FeignHttpClientProperties httpClientProperties) {
|
||||
final HttpClientConnectionManager connectionManager = connectionManagerFactory.newConnectionManager(
|
||||
httpClientProperties.isDisableSslValidation(), httpClientProperties.getMaxConnections(),
|
||||
httpClientProperties.getMaxConnectionsPerRoute(), httpClientProperties.getTimeToLive(),
|
||||
httpClientProperties.getTimeToLiveUnit(), this.registryBuilder);
|
||||
final HttpClientConnectionManager connectionManager = connectionManagerFactory
|
||||
.newConnectionManager(httpClientProperties.isDisableSslValidation(),
|
||||
httpClientProperties.getMaxConnections(),
|
||||
httpClientProperties.getMaxConnectionsPerRoute(),
|
||||
httpClientProperties.getTimeToLive(),
|
||||
httpClientProperties.getTimeToLiveUnit(), this.registryBuilder);
|
||||
this.connectionManagerTimer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -80,29 +82,37 @@ public class HttpClientFeignConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "feign.compression.response.enabled", havingValue = "true")
|
||||
public CloseableHttpClient customHttpClient(HttpClientConnectionManager httpClientConnectionManager,
|
||||
@ConditionalOnProperty(value = "feign.compression.response.enabled",
|
||||
havingValue = "true")
|
||||
public CloseableHttpClient customHttpClient(
|
||||
HttpClientConnectionManager httpClientConnectionManager,
|
||||
FeignHttpClientProperties httpClientProperties) {
|
||||
HttpClientBuilder builder = HttpClientBuilder.create().disableCookieManagement().useSystemProperties();
|
||||
this.httpClient = createClient(builder, httpClientConnectionManager, httpClientProperties);
|
||||
return this.httpClient;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "feign.compression.response.enabled", havingValue = "false", matchIfMissing = true)
|
||||
public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
|
||||
HttpClientConnectionManager httpClientConnectionManager, FeignHttpClientProperties httpClientProperties) {
|
||||
this.httpClient = createClient(httpClientFactory.createBuilder(), httpClientConnectionManager,
|
||||
HttpClientBuilder builder = HttpClientBuilder.create().disableCookieManagement()
|
||||
.useSystemProperties();
|
||||
this.httpClient = createClient(builder, httpClientConnectionManager,
|
||||
httpClientProperties);
|
||||
return this.httpClient;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "feign.compression.response.enabled",
|
||||
havingValue = "false", matchIfMissing = true)
|
||||
public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,
|
||||
HttpClientConnectionManager httpClientConnectionManager,
|
||||
FeignHttpClientProperties httpClientProperties) {
|
||||
this.httpClient = createClient(httpClientFactory.createBuilder(),
|
||||
httpClientConnectionManager, httpClientProperties);
|
||||
return this.httpClient;
|
||||
}
|
||||
|
||||
private CloseableHttpClient createClient(HttpClientBuilder builder,
|
||||
HttpClientConnectionManager httpClientConnectionManager, FeignHttpClientProperties httpClientProperties) {
|
||||
HttpClientConnectionManager httpClientConnectionManager,
|
||||
FeignHttpClientProperties httpClientProperties) {
|
||||
RequestConfig defaultRequestConfig = RequestConfig.custom()
|
||||
.setConnectTimeout(httpClientProperties.getConnectionTimeout())
|
||||
.setRedirectsEnabled(httpClientProperties.isFollowRedirects()).build();
|
||||
CloseableHttpClient httpClient = builder.setDefaultRequestConfig(defaultRequestConfig)
|
||||
CloseableHttpClient httpClient = builder
|
||||
.setDefaultRequestConfig(defaultRequestConfig)
|
||||
.setConnectionManager(httpClientConnectionManager).build();
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
+8
-5
@@ -46,7 +46,8 @@ public class OkHttpFeignConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(ConnectionPool.class)
|
||||
public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,
|
||||
public ConnectionPool httpClientConnectionPool(
|
||||
FeignHttpClientProperties httpClientProperties,
|
||||
OkHttpClientConnectionPoolFactory connectionPoolFactory) {
|
||||
Integer maxTotalConnections = httpClientProperties.getMaxConnections();
|
||||
Long timeToLive = httpClientProperties.getTimeToLive();
|
||||
@@ -55,13 +56,15 @@ public class OkHttpFeignConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, ConnectionPool connectionPool,
|
||||
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
|
||||
ConnectionPool connectionPool,
|
||||
FeignHttpClientProperties httpClientProperties) {
|
||||
Boolean followRedirects = httpClientProperties.isFollowRedirects();
|
||||
Integer connectTimeout = httpClientProperties.getConnectionTimeout();
|
||||
this.okHttpClient = httpClientFactory.createBuilder(httpClientProperties.isDisableSslValidation())
|
||||
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).followRedirects(followRedirects)
|
||||
.connectionPool(connectionPool).build();
|
||||
this.okHttpClient = httpClientFactory
|
||||
.createBuilder(httpClientProperties.isDisableSslValidation())
|
||||
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
|
||||
.followRedirects(followRedirects).connectionPool(connectionPool).build();
|
||||
return this.okHttpClient;
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -48,7 +48,8 @@ public abstract class BaseRequestInterceptor implements RequestInterceptor {
|
||||
* @param name the header name
|
||||
* @param values the header values
|
||||
*/
|
||||
protected void addHeader(RequestTemplate requestTemplate, String name, String... values) {
|
||||
protected void addHeader(RequestTemplate requestTemplate, String name,
|
||||
String... values) {
|
||||
|
||||
if (!requestTemplate.headers().containsKey(name)) {
|
||||
requestTemplate.header(name, values);
|
||||
|
||||
+2
-1
@@ -39,7 +39,8 @@ import org.springframework.context.annotation.Configuration;
|
||||
@EnableConfigurationProperties(FeignClientEncodingProperties.class)
|
||||
@ConditionalOnClass(Feign.class)
|
||||
@ConditionalOnBean(Client.class)
|
||||
@ConditionalOnProperty(value = "feign.compression.response.enabled", matchIfMissing = false)
|
||||
@ConditionalOnProperty(value = "feign.compression.response.enabled",
|
||||
matchIfMissing = false)
|
||||
// The OK HTTP client uses "transparent" compression.
|
||||
// If the accept-encoding header is present it disable transparent compression
|
||||
@ConditionalOnMissingBean(type = "okhttp3.OkHttpClient")
|
||||
|
||||
+4
-3
@@ -33,7 +33,8 @@ public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor {
|
||||
* Creates new instance of {@link FeignAcceptGzipEncodingInterceptor}.
|
||||
* @param properties the encoding properties
|
||||
*/
|
||||
protected FeignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
|
||||
protected FeignAcceptGzipEncodingInterceptor(
|
||||
FeignClientEncodingProperties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@@ -43,8 +44,8 @@ public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor {
|
||||
@Override
|
||||
public void apply(RequestTemplate template) {
|
||||
|
||||
addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING,
|
||||
HttpEncoding.DEFLATE_ENCODING);
|
||||
addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER,
|
||||
HttpEncoding.GZIP_ENCODING, HttpEncoding.DEFLATE_ENCODING);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-3
@@ -32,7 +32,8 @@ public class FeignClientEncodingProperties {
|
||||
/**
|
||||
* The list of supported mime types.
|
||||
*/
|
||||
private String[] mimeTypes = new String[] { "text/xml", "application/xml", "application/json" };
|
||||
private String[] mimeTypes = new String[] { "text/xml", "application/xml",
|
||||
"application/json" };
|
||||
|
||||
/**
|
||||
* The minimum threshold content size.
|
||||
@@ -76,8 +77,9 @@ public class FeignClientEncodingProperties {
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder("FeignClientEncodingProperties{").append("mimeTypes=")
|
||||
.append(Arrays.toString(this.mimeTypes)).append(", ").append("minRequestSize=")
|
||||
.append(this.minRequestSize).append("}").toString();
|
||||
.append(Arrays.toString(this.mimeTypes)).append(", ")
|
||||
.append("minRequestSize=").append(this.minRequestSize).append("}")
|
||||
.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-4
@@ -33,7 +33,8 @@ public class FeignContentGzipEncodingInterceptor extends BaseRequestInterceptor
|
||||
* Creates new instance of {@link FeignContentGzipEncodingInterceptor}.
|
||||
* @param properties the encoding properties
|
||||
*/
|
||||
protected FeignContentGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
|
||||
protected FeignContentGzipEncodingInterceptor(
|
||||
FeignClientEncodingProperties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@@ -44,8 +45,8 @@ public class FeignContentGzipEncodingInterceptor extends BaseRequestInterceptor
|
||||
public void apply(RequestTemplate template) {
|
||||
|
||||
if (requiresCompression(template)) {
|
||||
addHeader(template, HttpEncoding.CONTENT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING,
|
||||
HttpEncoding.DEFLATE_ENCODING);
|
||||
addHeader(template, HttpEncoding.CONTENT_ENCODING_HEADER,
|
||||
HttpEncoding.GZIP_ENCODING, HttpEncoding.DEFLATE_ENCODING);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +93,8 @@ public class FeignContentGzipEncodingInterceptor extends BaseRequestInterceptor
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getProperties().getMimeTypes() == null || getProperties().getMimeTypes().length == 0) {
|
||||
if (getProperties().getMimeTypes() == null
|
||||
|| getProperties().getMimeTypes().length == 0) {
|
||||
// no specific mime types has been set - matching everything
|
||||
return true;
|
||||
}
|
||||
|
||||
+10
-6
@@ -51,7 +51,8 @@ import static org.springframework.hateoas.MediaTypes.HAL_JSON;
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnWebApplication
|
||||
@ConditionalOnClass(RepresentationModel.class)
|
||||
@AutoConfigureAfter({ JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
|
||||
@AutoConfigureAfter({ JacksonAutoConfiguration.class,
|
||||
HttpMessageConvertersAutoConfiguration.class,
|
||||
RepositoryRestMvcAutoConfiguration.class })
|
||||
@AutoConfigureBefore(HypermediaAutoConfiguration.class)
|
||||
public class FeignHalAutoConfiguration {
|
||||
@@ -59,21 +60,24 @@ public class FeignHalAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TypeConstrainedMappingJackson2HttpMessageConverter halJacksonHttpMessageConverter(
|
||||
ObjectProvider<ObjectMapper> objectMapper, ObjectProvider<HalConfiguration> halConfiguration,
|
||||
ObjectProvider<MessageResolver> messageResolver, ObjectProvider<CurieProvider> curieProvider,
|
||||
ObjectProvider<ObjectMapper> objectMapper,
|
||||
ObjectProvider<HalConfiguration> halConfiguration,
|
||||
ObjectProvider<MessageResolver> messageResolver,
|
||||
ObjectProvider<CurieProvider> curieProvider,
|
||||
ObjectProvider<LinkRelationProvider> linkRelationProvider) {
|
||||
|
||||
ObjectMapper mapper = objectMapper.getIfAvailable(ObjectMapper::new).copy();
|
||||
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
|
||||
HalConfiguration configuration = halConfiguration.getIfAvailable(HalConfiguration::new);
|
||||
HalConfiguration configuration = halConfiguration
|
||||
.getIfAvailable(HalConfiguration::new);
|
||||
|
||||
CurieProvider curieProviderInstance = curieProvider
|
||||
.getIfAvailable(() -> new DefaultCurieProvider(Collections.emptyMap()));
|
||||
|
||||
Jackson2HalModule.HalHandlerInstantiator halHandlerInstantiator = new Jackson2HalModule.HalHandlerInstantiator(
|
||||
linkRelationProvider.getIfAvailable(), curieProviderInstance, messageResolver.getIfAvailable(),
|
||||
configuration);
|
||||
linkRelationProvider.getIfAvailable(), curieProviderInstance,
|
||||
messageResolver.getIfAvailable(), configuration);
|
||||
|
||||
mapper.setHandlerInstantiator(halHandlerInstantiator);
|
||||
|
||||
|
||||
+17
-18
@@ -16,52 +16,51 @@
|
||||
|
||||
package org.springframework.cloud.openfeign.loadbalancer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import feign.Client;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
|
||||
/**
|
||||
* Configuration instantiating a {@link LoadBalancerClient}-based {@link Client} object
|
||||
* that uses {@link Client.Default} under the hood.
|
||||
* Configuration instantiating a {@link BlockingLoadBalancerClient}-based {@link Client}
|
||||
* object that uses {@link Client.Default} under the hood.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(LoadBalancerProperties.class)
|
||||
class DefaultFeignLoadBalancerConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@Conditional(OnRetryNotEnabledCondition.class)
|
||||
public Client feignClient(LoadBalancerClient loadBalancerClient, LoadBalancerProperties properties,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
return new FeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient, properties,
|
||||
loadBalancerClientFactory);
|
||||
public Client feignClient(BlockingLoadBalancerClient loadBalancerClient) {
|
||||
return new FeignBlockingLoadBalancerClient(new Client.Default(null, null),
|
||||
loadBalancerClient);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
|
||||
@ConditionalOnBean(LoadBalancedRetryFactory.class)
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public Client feignRetryClient(LoadBalancerClient loadBalancerClient,
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
return new RetryableFeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient,
|
||||
loadBalancedRetryFactory, properties, loadBalancerClientFactory);
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled",
|
||||
havingValue = "true", matchIfMissing = true)
|
||||
public Client feignRetryClient(BlockingLoadBalancerClient loadBalancerClient,
|
||||
List<LoadBalancedRetryFactory> loadBalancedRetryFactories) {
|
||||
AnnotationAwareOrderComparator.sort(loadBalancedRetryFactories);
|
||||
return new RetryableFeignBlockingLoadBalancerClient(
|
||||
new Client.Default(null, null), loadBalancerClient,
|
||||
loadBalancedRetryFactories.get(0));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+20
-55
@@ -19,7 +19,6 @@ package org.springframework.cloud.openfeign.loadbalancer;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Set;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Request;
|
||||
@@ -28,86 +27,58 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.CompletionContext;
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultRequest;
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValidator;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.RequestDataContext;
|
||||
import org.springframework.cloud.client.loadbalancer.ResponseData;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils.buildRequestData;
|
||||
import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils.executeWithLoadBalancerLifecycleProcessing;
|
||||
|
||||
/**
|
||||
* A {@link Client} implementation that uses {@link LoadBalancerClient} to select a
|
||||
* {@link ServiceInstance} to use while resolving the request host.
|
||||
* A {@link Client} implementation that uses {@link BlockingLoadBalancerClient} to select
|
||||
* a {@link ServiceInstance} to use while resolving the request host.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public class FeignBlockingLoadBalancerClient implements Client {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(FeignBlockingLoadBalancerClient.class);
|
||||
private static final Log LOG = LogFactory
|
||||
.getLog(FeignBlockingLoadBalancerClient.class);
|
||||
|
||||
private final Client delegate;
|
||||
|
||||
private final LoadBalancerClient loadBalancerClient;
|
||||
private final BlockingLoadBalancerClient loadBalancerClient;
|
||||
|
||||
private final LoadBalancerProperties properties;
|
||||
|
||||
private final LoadBalancerClientFactory loadBalancerClientFactory;
|
||||
|
||||
public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
|
||||
LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
public FeignBlockingLoadBalancerClient(Client delegate,
|
||||
BlockingLoadBalancerClient loadBalancerClient) {
|
||||
this.delegate = delegate;
|
||||
this.loadBalancerClient = loadBalancerClient;
|
||||
this.properties = properties;
|
||||
this.loadBalancerClientFactory = loadBalancerClientFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response execute(Request request, Request.Options options) throws IOException {
|
||||
final URI originalUri = URI.create(request.url());
|
||||
String serviceId = originalUri.getHost();
|
||||
Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);
|
||||
String hint = getHint(serviceId);
|
||||
DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(
|
||||
new RequestDataContext(buildRequestData(request), hint));
|
||||
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
|
||||
.getSupportedLifecycleProcessors(
|
||||
loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
|
||||
RequestDataContext.class, ResponseData.class, ServiceInstance.class);
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
|
||||
ServiceInstance instance = loadBalancerClient.choose(serviceId, lbRequest);
|
||||
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
|
||||
instance);
|
||||
Assert.state(serviceId != null,
|
||||
"Request URI does not contain a valid hostname: " + originalUri);
|
||||
ServiceInstance instance = loadBalancerClient.choose(serviceId);
|
||||
if (instance == null) {
|
||||
String message = "Load balancer does not contain an instance for the service " + serviceId;
|
||||
String message = "Load balancer does not contain an instance for the service "
|
||||
+ serviceId;
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn(message);
|
||||
}
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
|
||||
.onComplete(new CompletionContext<ResponseData, ServiceInstance, RequestDataContext>(
|
||||
CompletionContext.Status.DISCARD, lbRequest, lbResponse)));
|
||||
return Response.builder().request(request).status(HttpStatus.SERVICE_UNAVAILABLE.value())
|
||||
return Response.builder().request(request)
|
||||
.status(HttpStatus.SERVICE_UNAVAILABLE.value())
|
||||
.body(message, StandardCharsets.UTF_8).build();
|
||||
}
|
||||
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
|
||||
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri)
|
||||
.toString();
|
||||
Request newRequest = buildRequest(request, reconstructedUrl);
|
||||
return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, lbResponse,
|
||||
supportedLifecycleProcessors);
|
||||
return delegate.execute(newRequest, options);
|
||||
}
|
||||
|
||||
protected Request buildRequest(Request request, String reconstructedUrl) {
|
||||
return Request.create(request.httpMethod(), reconstructedUrl, request.headers(), request.body(),
|
||||
request.charset(), request.requestTemplate());
|
||||
return Request.create(request.httpMethod(), reconstructedUrl, request.headers(),
|
||||
request.body(), request.charset(), request.requestTemplate());
|
||||
}
|
||||
|
||||
// Visible for Sleuth instrumentation
|
||||
@@ -115,10 +86,4 @@ public class FeignBlockingLoadBalancerClient implements Client {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
private String getHint(String serviceId) {
|
||||
String defaultHint = properties.getHint().getOrDefault("default", "default");
|
||||
String hintPropertyValue = properties.getHint().get(serviceId);
|
||||
return hintPropertyValue != null ? hintPropertyValue : defaultHint;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+10
-9
@@ -24,32 +24,33 @@ import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientAutoConfiguration;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
|
||||
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* An autoconfiguration that instantiates {@link LoadBalancerClient}-based implementations
|
||||
* of {@link Client}.
|
||||
* An autoconfiguration that instantiates {@link BlockingLoadBalancerClient}-based
|
||||
* implementations of {@link Client}. In order to use this load-balancing mechanism, the
|
||||
* Ribbon-based implementation has to be disabled by setting
|
||||
* <code>spring.cloud.loadbalancer.ribbon.enabled</code> to <code>true</code>.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@ConditionalOnClass(Feign.class)
|
||||
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
|
||||
@ConditionalOnBean(BlockingLoadBalancerClient.class)
|
||||
@AutoConfigureBefore(FeignAutoConfiguration.class)
|
||||
@AutoConfigureAfter({ BlockingLoadBalancerClientAutoConfiguration.class, LoadBalancerAutoConfiguration.class })
|
||||
@AutoConfigureAfter(FeignRibbonClientAutoConfiguration.class)
|
||||
@EnableConfigurationProperties(FeignHttpClientProperties.class)
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
// Order is important here, last should be the default, first should be optional
|
||||
// see
|
||||
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
|
||||
@Import({ HttpClientFeignLoadBalancerConfiguration.class, OkHttpFeignLoadBalancerConfiguration.class,
|
||||
@Import({ HttpClientFeignLoadBalancerConfiguration.class,
|
||||
OkHttpFeignLoadBalancerConfiguration.class,
|
||||
DefaultFeignLoadBalancerConfiguration.class })
|
||||
public class FeignLoadBalancerAutoConfiguration {
|
||||
|
||||
|
||||
+18
-18
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.openfeign.loadbalancer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import feign.Client;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
import org.apache.http.client.HttpClient;
|
||||
@@ -24,53 +26,51 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
|
||||
import org.springframework.cloud.openfeign.clientconfig.HttpClientFeignConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
|
||||
/**
|
||||
* Configuration instantiating a {@link LoadBalancerClient}-based {@link Client} object
|
||||
* that uses {@link ApacheHttpClient} under the hood.
|
||||
* Configuration instantiating a {@link BlockingLoadBalancerClient}-based {@link Client}
|
||||
* object that uses {@link ApacheHttpClient} under the hood.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(ApacheHttpClient.class)
|
||||
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
|
||||
@ConditionalOnBean(BlockingLoadBalancerClient.class)
|
||||
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
|
||||
@Import(HttpClientFeignConfiguration.class)
|
||||
@EnableConfigurationProperties(LoadBalancerProperties.class)
|
||||
class HttpClientFeignLoadBalancerConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@Conditional(OnRetryNotEnabledCondition.class)
|
||||
public Client feignClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
|
||||
LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
public Client feignClient(BlockingLoadBalancerClient loadBalancerClient,
|
||||
HttpClient httpClient) {
|
||||
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
|
||||
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, properties, loadBalancerClientFactory);
|
||||
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
|
||||
@ConditionalOnBean(LoadBalancedRetryFactory.class)
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public Client feignRetryClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled",
|
||||
havingValue = "true", matchIfMissing = true)
|
||||
public Client feignRetryClient(BlockingLoadBalancerClient loadBalancerClient,
|
||||
HttpClient httpClient,
|
||||
List<LoadBalancedRetryFactory> loadBalancedRetryFactories) {
|
||||
AnnotationAwareOrderComparator.sort(loadBalancedRetryFactories);
|
||||
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
|
||||
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
|
||||
properties, loadBalancerClientFactory);
|
||||
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient,
|
||||
loadBalancedRetryFactories.get(0));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* 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.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Request;
|
||||
import feign.Response;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.CompletionContext;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle;
|
||||
import org.springframework.cloud.client.loadbalancer.RequestData;
|
||||
import org.springframework.cloud.client.loadbalancer.ResponseData;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*
|
||||
* A utility class for handling {@link LoadBalancerLifecycle} calls.
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
final class LoadBalancerUtils {
|
||||
|
||||
private LoadBalancerUtils() {
|
||||
throw new IllegalStateException("Can't instantiate a utility class");
|
||||
}
|
||||
|
||||
static Response executeWithLoadBalancerLifecycleProcessing(Client feignClient, Request.Options options,
|
||||
Request feignRequest, org.springframework.cloud.client.loadbalancer.Request lbRequest,
|
||||
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse,
|
||||
Set<LoadBalancerLifecycle> supportedLifecycleProcessors, boolean loadBalanced) throws IOException {
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, lbResponse));
|
||||
try {
|
||||
Response response = feignClient.execute(feignRequest, options);
|
||||
if (loadBalanced) {
|
||||
supportedLifecycleProcessors.forEach(
|
||||
lifecycle -> lifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS,
|
||||
lbRequest, lbResponse, buildResponseData(response))));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
catch (Exception exception) {
|
||||
if (loadBalanced) {
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
|
||||
new CompletionContext<>(CompletionContext.Status.FAILED, exception, lbRequest, lbResponse)));
|
||||
}
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
static ResponseData buildResponseData(Response response) {
|
||||
HttpHeaders responseHeaders = new HttpHeaders();
|
||||
response.headers().forEach((key, value) -> responseHeaders.put(key, new ArrayList<>(value)));
|
||||
return new ResponseData(HttpStatus.resolve(response.status()), responseHeaders, null,
|
||||
buildRequestData(response.request()));
|
||||
}
|
||||
|
||||
static RequestData buildRequestData(Request request) {
|
||||
HttpHeaders requestHeaders = new HttpHeaders();
|
||||
request.headers().forEach((key, value) -> requestHeaders.put(key, new ArrayList<>(value)));
|
||||
return new RequestData(HttpMethod.resolve(request.httpMethod().name()), URI.create(request.url()),
|
||||
requestHeaders, null, new HashMap<>());
|
||||
}
|
||||
|
||||
static Response executeWithLoadBalancerLifecycleProcessing(Client feignClient, Request.Options options,
|
||||
Request feignRequest, org.springframework.cloud.client.loadbalancer.Request lbRequest,
|
||||
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse,
|
||||
Set<LoadBalancerLifecycle> supportedLifecycleProcessors) throws IOException {
|
||||
return executeWithLoadBalancerLifecycleProcessing(feignClient, options, feignRequest, lbRequest, lbResponse,
|
||||
supportedLifecycleProcessors, true);
|
||||
}
|
||||
|
||||
}
|
||||
+18
-18
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.cloud.openfeign.loadbalancer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import feign.Client;
|
||||
import feign.okhttp.OkHttpClient;
|
||||
|
||||
@@ -23,20 +25,18 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
|
||||
import org.springframework.cloud.openfeign.clientconfig.OkHttpFeignConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
|
||||
/**
|
||||
* Configuration instantiating a {@link LoadBalancerClient}-based {@link Client} object
|
||||
* that uses {@link OkHttpClient} under the hood.
|
||||
* Configuration instantiating a {@link BlockingLoadBalancerClient}-based {@link Client}
|
||||
* object that uses {@link OkHttpClient} under the hood.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 2.2.0
|
||||
@@ -44,32 +44,32 @@ import org.springframework.context.annotation.Import;
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(OkHttpClient.class)
|
||||
@ConditionalOnProperty("feign.okhttp.enabled")
|
||||
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
|
||||
@ConditionalOnBean(BlockingLoadBalancerClient.class)
|
||||
@Import(OkHttpFeignConfiguration.class)
|
||||
@EnableConfigurationProperties(LoadBalancerProperties.class)
|
||||
class OkHttpFeignLoadBalancerConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@Conditional(OnRetryNotEnabledCondition.class)
|
||||
public Client feignClient(okhttp3.OkHttpClient okHttpClient, LoadBalancerClient loadBalancerClient,
|
||||
LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
public Client feignClient(okhttp3.OkHttpClient okHttpClient,
|
||||
BlockingLoadBalancerClient loadBalancerClient) {
|
||||
OkHttpClient delegate = new OkHttpClient(okHttpClient);
|
||||
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, properties, loadBalancerClientFactory);
|
||||
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
|
||||
@ConditionalOnBean(LoadBalancedRetryFactory.class)
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public Client feignRetryClient(LoadBalancerClient loadBalancerClient, okhttp3.OkHttpClient okHttpClient,
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled",
|
||||
havingValue = "true", matchIfMissing = true)
|
||||
public Client feignRetryClient(BlockingLoadBalancerClient loadBalancerClient,
|
||||
okhttp3.OkHttpClient okHttpClient,
|
||||
List<LoadBalancedRetryFactory> loadBalancedRetryFactories) {
|
||||
AnnotationAwareOrderComparator.sort(loadBalancedRetryFactories);
|
||||
OkHttpClient delegate = new OkHttpClient(okHttpClient);
|
||||
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
|
||||
properties, loadBalancerClientFactory);
|
||||
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient,
|
||||
loadBalancedRetryFactories.get(0));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -47,7 +47,8 @@ public class OnRetryNotEnabledCondition extends AnyNestedCondition {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "false")
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled",
|
||||
havingValue = "false")
|
||||
static class OnLoadBalancerRetryEnabledCondition {
|
||||
|
||||
}
|
||||
|
||||
+41
-90
@@ -23,7 +23,6 @@ import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Request;
|
||||
@@ -32,22 +31,13 @@ import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.CompletionContext;
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultRequest;
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
|
||||
import org.springframework.cloud.client.loadbalancer.InterceptorRetryPolicy;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRecoveryCallback;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValidator;
|
||||
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.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRequest;
|
||||
@@ -56,9 +46,6 @@ import org.springframework.retry.backoff.BackOffPolicy;
|
||||
import org.springframework.retry.backoff.NoBackOffPolicy;
|
||||
import org.springframework.retry.policy.NeverRetryPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils.buildRequestData;
|
||||
|
||||
/**
|
||||
* A {@link Client} implementation that provides Spring Retry support for requests
|
||||
@@ -67,102 +54,67 @@ import static org.springframework.cloud.openfeign.loadbalancer.LoadBalancerUtils
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 2.2.6
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public class RetryableFeignBlockingLoadBalancerClient implements Client {
|
||||
|
||||
private static final Log LOG = LogFactory.getLog(FeignBlockingLoadBalancerClient.class);
|
||||
private static final Log LOG = LogFactory
|
||||
.getLog(FeignBlockingLoadBalancerClient.class);
|
||||
|
||||
private final Client delegate;
|
||||
|
||||
private final LoadBalancerClient loadBalancerClient;
|
||||
private final BlockingLoadBalancerClient loadBalancerClient;
|
||||
|
||||
private final LoadBalancedRetryFactory loadBalancedRetryFactory;
|
||||
|
||||
private final LoadBalancerProperties properties;
|
||||
|
||||
private final LoadBalancerClientFactory loadBalancerClientFactory;
|
||||
|
||||
public RetryableFeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory) {
|
||||
public RetryableFeignBlockingLoadBalancerClient(Client delegate,
|
||||
BlockingLoadBalancerClient loadBalancerClient,
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory) {
|
||||
this.delegate = delegate;
|
||||
this.loadBalancerClient = loadBalancerClient;
|
||||
this.loadBalancedRetryFactory = loadBalancedRetryFactory;
|
||||
this.properties = properties;
|
||||
this.loadBalancerClientFactory = loadBalancerClientFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response execute(Request request, Request.Options options) throws IOException {
|
||||
final URI originalUri = URI.create(request.url());
|
||||
String serviceId = originalUri.getHost();
|
||||
Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);
|
||||
final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryFactory.createRetryPolicy(serviceId,
|
||||
loadBalancerClient);
|
||||
final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryFactory
|
||||
.createRetryPolicy(serviceId, loadBalancerClient);
|
||||
RetryTemplate retryTemplate = buildRetryTemplate(serviceId, request, retryPolicy);
|
||||
return retryTemplate.execute(context -> {
|
||||
Request feignRequest = null;
|
||||
ServiceInstance retrievedServiceInstance = null;
|
||||
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
|
||||
.getSupportedLifecycleProcessors(
|
||||
loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
|
||||
RetryableRequestContext.class, ResponseData.class, ServiceInstance.class);
|
||||
String hint = getHint(serviceId);
|
||||
DefaultRequest<RetryableRequestContext> lbRequest = new DefaultRequest<>(
|
||||
new RetryableRequestContext(null, buildRequestData(request), hint));
|
||||
// On retries the policy will choose the server and set it in the context
|
||||
// and extract the server and update the request being made
|
||||
if (context instanceof LoadBalancedRetryContext) {
|
||||
LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
|
||||
ServiceInstance serviceInstance = lbContext.getServiceInstance();
|
||||
if (serviceInstance == null) {
|
||||
ServiceInstance serviceInstance = ((LoadBalancedRetryContext) context)
|
||||
.getServiceInstance();
|
||||
if (serviceInstance != null) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Service instance retrieved from LoadBalancedRetryContext: was null. "
|
||||
+ "Reattempting service instance selection");
|
||||
LOG.debug(String.format(
|
||||
"Using service instance from LoadBalancedRetryContext: %s",
|
||||
serviceInstance));
|
||||
}
|
||||
ServiceInstance previousServiceInstance = lbContext.getPreviousServiceInstance();
|
||||
lbRequest.getContext().setPreviousServiceInstance(previousServiceInstance);
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
|
||||
retrievedServiceInstance = loadBalancerClient.choose(serviceId, lbRequest);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(String.format("Selected service instance: %s", retrievedServiceInstance));
|
||||
}
|
||||
lbContext.setServiceInstance(retrievedServiceInstance);
|
||||
}
|
||||
|
||||
if (retrievedServiceInstance == null) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn("Service instance was not resolved, executing the original request");
|
||||
}
|
||||
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
|
||||
retrievedServiceInstance);
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
|
||||
.onComplete(new CompletionContext<ResponseData, ServiceInstance, RetryableRequestContext>(
|
||||
CompletionContext.Status.DISCARD, lbRequest, lbResponse)));
|
||||
feignRequest = request;
|
||||
}
|
||||
else {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(String.format("Using service instance from LoadBalancedRetryContext: %s",
|
||||
retrievedServiceInstance));
|
||||
}
|
||||
String reconstructedUrl = loadBalancerClient.reconstructURI(retrievedServiceInstance, originalUri)
|
||||
.toString();
|
||||
String reconstructedUrl = loadBalancerClient
|
||||
.reconstructURI(serviceInstance, originalUri).toString();
|
||||
feignRequest = buildRequest(request, reconstructedUrl);
|
||||
}
|
||||
}
|
||||
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
|
||||
retrievedServiceInstance);
|
||||
Response response = LoadBalancerUtils.executeWithLoadBalancerLifecycleProcessing(delegate, options,
|
||||
feignRequest, lbRequest, lbResponse, supportedLifecycleProcessors,
|
||||
retrievedServiceInstance != null);
|
||||
if (feignRequest == null) {
|
||||
if (LOG.isWarnEnabled()) {
|
||||
LOG.warn(
|
||||
"Service instance was not resolved, executing the original request");
|
||||
}
|
||||
feignRequest = request;
|
||||
}
|
||||
Response response = delegate.execute(feignRequest, options);
|
||||
int responseStatus = response.status();
|
||||
if (retryPolicy != null && retryPolicy.retryableStatusCode(responseStatus)) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(String.format("Retrying on status code: %d", responseStatus));
|
||||
LOG.debug(
|
||||
String.format("Retrying on status code: %d", responseStatus));
|
||||
}
|
||||
response.close();
|
||||
throw new RetryableStatusCodeException(serviceId, responseStatus, response, URI.create(request.url()));
|
||||
throw new RetryableStatusCodeException(serviceId, responseStatus,
|
||||
response, URI.create(request.url()));
|
||||
}
|
||||
return response;
|
||||
}, new LoadBalancedRecoveryCallback<Response, Response>() {
|
||||
@@ -174,21 +126,26 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
|
||||
}
|
||||
|
||||
protected Request buildRequest(Request request, String reconstructedUrl) {
|
||||
return Request.create(request.httpMethod(), reconstructedUrl, request.headers(), request.body(),
|
||||
request.charset(), request.requestTemplate());
|
||||
return Request.create(request.httpMethod(), reconstructedUrl, request.headers(),
|
||||
request.body(), request.charset(), request.requestTemplate());
|
||||
}
|
||||
|
||||
private RetryTemplate buildRetryTemplate(String serviceId, Request request, LoadBalancedRetryPolicy retryPolicy) {
|
||||
private RetryTemplate buildRetryTemplate(String serviceId, Request request,
|
||||
LoadBalancedRetryPolicy retryPolicy) {
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
BackOffPolicy backOffPolicy = this.loadBalancedRetryFactory.createBackOffPolicy(serviceId);
|
||||
retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
|
||||
RetryListener[] retryListeners = this.loadBalancedRetryFactory.createRetryListeners(serviceId);
|
||||
BackOffPolicy backOffPolicy = this.loadBalancedRetryFactory
|
||||
.createBackOffPolicy(serviceId);
|
||||
retryTemplate.setBackOffPolicy(
|
||||
backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
|
||||
RetryListener[] retryListeners = this.loadBalancedRetryFactory
|
||||
.createRetryListeners(serviceId);
|
||||
if (retryListeners != null && retryListeners.length != 0) {
|
||||
retryTemplate.setListeners(retryListeners);
|
||||
}
|
||||
|
||||
retryTemplate.setRetryPolicy(retryPolicy == null ? new NeverRetryPolicy()
|
||||
: new InterceptorRetryPolicy(toHttpRequest(request), retryPolicy, loadBalancerClient, serviceId));
|
||||
: new InterceptorRetryPolicy(toHttpRequest(request), retryPolicy,
|
||||
loadBalancerClient, serviceId));
|
||||
return retryTemplate;
|
||||
}
|
||||
|
||||
@@ -228,10 +185,4 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
|
||||
};
|
||||
}
|
||||
|
||||
private String getHint(String serviceId) {
|
||||
String defaultHint = properties.getHint().getOrDefault("default", "default");
|
||||
String hintPropertyValue = properties.getHint().get(serviceId);
|
||||
return hintPropertyValue != null ? hintPropertyValue : defaultHint;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.ribbon;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
|
||||
/**
|
||||
* Factory for SpringLoadBalancer instances that caches the entries created.
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @author Dave Syer
|
||||
* @author Ryan Baxter
|
||||
* @author Gang Li
|
||||
*/
|
||||
public class CachingSpringLoadBalancerFactory {
|
||||
|
||||
protected final SpringClientFactory factory;
|
||||
|
||||
protected LoadBalancedRetryFactory loadBalancedRetryFactory = null;
|
||||
|
||||
private volatile Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap<>();
|
||||
|
||||
public CachingSpringLoadBalancerFactory(SpringClientFactory factory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
public CachingSpringLoadBalancerFactory(SpringClientFactory factory,
|
||||
LoadBalancedRetryFactory loadBalancedRetryPolicyFactory) {
|
||||
this.factory = factory;
|
||||
this.loadBalancedRetryFactory = loadBalancedRetryPolicyFactory;
|
||||
}
|
||||
|
||||
public FeignLoadBalancer create(String clientName) {
|
||||
FeignLoadBalancer client = this.cache.get(clientName);
|
||||
if (client != null) {
|
||||
return client;
|
||||
}
|
||||
IClientConfig config = this.factory.getClientConfig(clientName);
|
||||
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
|
||||
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName,
|
||||
ServerIntrospector.class);
|
||||
client = this.loadBalancedRetryFactory != null
|
||||
? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
|
||||
this.loadBalancedRetryFactory)
|
||||
: new FeignLoadBalancer(lb, config, serverIntrospector);
|
||||
this.cache.put(clientName, client);
|
||||
return client;
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.ribbon;
|
||||
|
||||
import feign.Client;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class DefaultFeignLoadBalancedConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
|
||||
SpringClientFactory clientFactory) {
|
||||
return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
|
||||
clientFactory);
|
||||
}
|
||||
|
||||
}
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* 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.ribbon;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.netflix.client.AbstractLoadBalancerAwareClient;
|
||||
import com.netflix.client.ClientRequest;
|
||||
import com.netflix.client.IResponse;
|
||||
import com.netflix.client.RequestSpecificRetryHandler;
|
||||
import com.netflix.client.RetryHandler;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import feign.Client;
|
||||
import feign.Request;
|
||||
import feign.Response;
|
||||
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonProperties;
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpRequest;
|
||||
|
||||
import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToSecureConnectionIfNeeded;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @author Spencer Gibb
|
||||
* @author Ryan Baxter
|
||||
* @author Tim Ysewyn
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
public class FeignLoadBalancer extends
|
||||
AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> {
|
||||
|
||||
private final RibbonProperties ribbon;
|
||||
|
||||
protected int connectTimeout;
|
||||
|
||||
protected int readTimeout;
|
||||
|
||||
protected IClientConfig clientConfig;
|
||||
|
||||
protected ServerIntrospector serverIntrospector;
|
||||
|
||||
public FeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig,
|
||||
ServerIntrospector serverIntrospector) {
|
||||
super(lb, clientConfig);
|
||||
this.setRetryHandler(RetryHandler.DEFAULT);
|
||||
this.clientConfig = clientConfig;
|
||||
this.ribbon = RibbonProperties.from(clientConfig);
|
||||
RibbonProperties ribbon = this.ribbon;
|
||||
this.connectTimeout = ribbon.getConnectTimeout();
|
||||
this.readTimeout = ribbon.getReadTimeout();
|
||||
this.serverIntrospector = serverIntrospector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride)
|
||||
throws IOException {
|
||||
Request.Options options;
|
||||
if (configOverride != null) {
|
||||
RibbonProperties override = RibbonProperties.from(configOverride);
|
||||
options = new Request.Options(override.connectTimeout(this.connectTimeout),
|
||||
override.readTimeout(this.readTimeout));
|
||||
}
|
||||
else {
|
||||
options = new Request.Options(this.connectTimeout, this.readTimeout);
|
||||
}
|
||||
Response response = request.client().execute(request.toRequest(), options);
|
||||
return new RibbonResponse(request.getUri(), response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
|
||||
RibbonRequest request, IClientConfig requestConfig) {
|
||||
if (this.ribbon.isOkToRetryOnAllOperations()) {
|
||||
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
|
||||
requestConfig);
|
||||
}
|
||||
if (!request.toRequest().httpMethod().name().equals("GET")) {
|
||||
return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(),
|
||||
requestConfig);
|
||||
}
|
||||
else {
|
||||
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
|
||||
requestConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI reconstructURIWithServer(Server server, URI original) {
|
||||
URI uri = updateToSecureConnectionIfNeeded(original, this.clientConfig,
|
||||
this.serverIntrospector, server);
|
||||
return super.reconstructURIWithServer(server, uri);
|
||||
}
|
||||
|
||||
protected static class RibbonRequest extends ClientRequest implements Cloneable {
|
||||
|
||||
private final Request request;
|
||||
|
||||
private final Client client;
|
||||
|
||||
protected RibbonRequest(Client client, Request request, URI uri) {
|
||||
this.client = client;
|
||||
setUri(uri);
|
||||
this.request = toRequest(request);
|
||||
}
|
||||
|
||||
private Request toRequest(Request request) {
|
||||
Map<String, Collection<String>> headers = new LinkedHashMap<>(
|
||||
request.headers());
|
||||
return Request.create(request.httpMethod(), getUri().toASCIIString(), headers,
|
||||
request.body(), request.charset(), request.requestTemplate());
|
||||
}
|
||||
|
||||
Request toRequest() {
|
||||
return toRequest(this.request);
|
||||
}
|
||||
|
||||
Client client() {
|
||||
return this.client;
|
||||
}
|
||||
|
||||
HttpRequest toHttpRequest() {
|
||||
return new HttpRequest() {
|
||||
@Override
|
||||
public HttpMethod getMethod() {
|
||||
return HttpMethod
|
||||
.resolve(RibbonRequest.this.toRequest().httpMethod().name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethodValue() {
|
||||
return getMethod().name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getURI() {
|
||||
return RibbonRequest.this.getUri();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
Map<String, List<String>> headers = new HashMap<>();
|
||||
Map<String, Collection<String>> feignHeaders = RibbonRequest.this
|
||||
.toRequest().headers();
|
||||
for (String key : feignHeaders.keySet()) {
|
||||
headers.put(key, new ArrayList<>(feignHeaders.get(key)));
|
||||
}
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
httpHeaders.putAll(headers);
|
||||
return httpHeaders;
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Request getRequest() {
|
||||
return this.request;
|
||||
}
|
||||
|
||||
public Client getClient() {
|
||||
return this.client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
return new RibbonRequest(this.client, this.request, getUri());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected static class RibbonResponse implements IResponse {
|
||||
|
||||
private final URI uri;
|
||||
|
||||
private final Response response;
|
||||
|
||||
protected RibbonResponse(URI uri, Response response) {
|
||||
this.uri = uri;
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPayload() {
|
||||
return this.response.body();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPayload() {
|
||||
return this.response.body() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuccess() {
|
||||
return this.response.status() == 200;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getRequestedURI() {
|
||||
return this.uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Collection<String>> getHeaders() {
|
||||
return this.response.headers();
|
||||
}
|
||||
|
||||
Response toResponse() {
|
||||
return this.response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (this.response != null && this.response.body() != null) {
|
||||
this.response.body().close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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.ribbon;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.InterceptorRetryPolicy;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
|
||||
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.retry.RetryContext;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
public class FeignRetryPolicy extends InterceptorRetryPolicy {
|
||||
|
||||
private HttpRequest request;
|
||||
|
||||
private String serviceId;
|
||||
|
||||
public FeignRetryPolicy(HttpRequest request, LoadBalancedRetryPolicy policy,
|
||||
ServiceInstanceChooser serviceInstanceChooser, String serviceName) {
|
||||
super(request, policy, serviceInstanceChooser, serviceName);
|
||||
this.request = request;
|
||||
this.serviceId = serviceName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRetry(RetryContext context) {
|
||||
/*
|
||||
* In InterceptorRetryPolicy.canRetry we ask the LoadBalancer to choose a server
|
||||
* if one is not set in the retry context and then return true. RetryTemplat calls
|
||||
* the canRetry method of the policy even on its first execution. So the fact that
|
||||
* we didnt have a service instance set in the RetryContext signaled that it was
|
||||
* the first execution and we should return true.
|
||||
*
|
||||
* In the Feign scenario, Feign as actually already queried the load balancer for
|
||||
* a service instance and we set that service instance in the context when we call
|
||||
* the open method of the policy. So in the Feign case we just return true if the
|
||||
* retry count is 0 indicating we haven't yet made a failed request.
|
||||
*/
|
||||
if (context.getRetryCount() == 0) {
|
||||
return true;
|
||||
}
|
||||
return super.canRetry(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RetryContext open(RetryContext parent) {
|
||||
/*
|
||||
* With Feign (unlike Ribbon) the request already has the URI for the service
|
||||
* instance we are going to make the request to, so extract that information and
|
||||
* set the service instance in the context. In the Ribbon scenario the URI in the
|
||||
* request object still has the service id so we choose and set the service
|
||||
* instance later on.
|
||||
*/
|
||||
LoadBalancedRetryContext context = new LoadBalancedRetryContext(parent,
|
||||
this.request);
|
||||
context.setServiceInstance(
|
||||
new FeignRetryPolicyServiceInstance(this.serviceId, this.request));
|
||||
return context;
|
||||
}
|
||||
|
||||
class FeignRetryPolicyServiceInstance implements ServiceInstance {
|
||||
|
||||
private String serviceId;
|
||||
|
||||
private HttpRequest request;
|
||||
|
||||
private Map<String, String> metadata;
|
||||
|
||||
FeignRetryPolicyServiceInstance(String serviceId, HttpRequest request) {
|
||||
this.serviceId = serviceId;
|
||||
this.request = request;
|
||||
this.metadata = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceId() {
|
||||
return this.serviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHost() {
|
||||
return this.request.getURI().getHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() {
|
||||
return this.request.getURI().getPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSecure() {
|
||||
return "https".equals(this.request.getURI().getScheme());
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getUri() {
|
||||
return this.request.getURI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getMetadata() {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.ribbon;
|
||||
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import feign.Feign;
|
||||
import feign.Request;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.cloud.openfeign.FeignAutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
/**
|
||||
* Autoconfiguration to be activated if Feign is in use and needs to be use Ribbon as a
|
||||
* load balancer.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@ConditionalOnClass({ ILoadBalancer.class, Feign.class })
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
|
||||
matchIfMissing = true)
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@AutoConfigureBefore(FeignAutoConfiguration.class)
|
||||
@EnableConfigurationProperties({ FeignHttpClientProperties.class })
|
||||
// Order is important here, last should be the default, first should be optional
|
||||
// see
|
||||
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
|
||||
@Import({ HttpClientFeignLoadBalancedConfiguration.class,
|
||||
OkHttpFeignLoadBalancedConfiguration.class,
|
||||
DefaultFeignLoadBalancedConfiguration.class })
|
||||
public class FeignRibbonClientAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
|
||||
public CachingSpringLoadBalancerFactory cachingLBClientFactory(
|
||||
SpringClientFactory factory) {
|
||||
return new CachingSpringLoadBalancerFactory(factory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
|
||||
public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
|
||||
SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) {
|
||||
return new CachingSpringLoadBalancerFactory(factory, retryFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Request.Options feignRequestOptions() {
|
||||
return LoadBalancerFeignClient.DEFAULT_OPTIONS;
|
||||
}
|
||||
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.openfeign.ribbon;
|
||||
|
||||
import feign.Client;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
import org.apache.http.client.HttpClient;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.cloud.openfeign.clientconfig.HttpClientFeignConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(ApacheHttpClient.class)
|
||||
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
|
||||
@Import(HttpClientFeignConfiguration.class)
|
||||
class HttpClientFeignLoadBalancedConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(Client.class)
|
||||
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
|
||||
SpringClientFactory clientFactory, HttpClient httpClient) {
|
||||
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
|
||||
return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
|
||||
}
|
||||
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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.ribbon;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import com.netflix.client.ClientException;
|
||||
import com.netflix.client.config.CommonClientConfigKey;
|
||||
import com.netflix.client.config.DefaultClientConfigImpl;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import feign.Client;
|
||||
import feign.Request;
|
||||
import feign.Response;
|
||||
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class LoadBalancerFeignClient implements Client {
|
||||
|
||||
static final Request.Options DEFAULT_OPTIONS = new Request.Options();
|
||||
|
||||
private final Client delegate;
|
||||
|
||||
private CachingSpringLoadBalancerFactory lbClientFactory;
|
||||
|
||||
private SpringClientFactory clientFactory;
|
||||
|
||||
public LoadBalancerFeignClient(Client delegate,
|
||||
CachingSpringLoadBalancerFactory lbClientFactory,
|
||||
SpringClientFactory clientFactory) {
|
||||
this.delegate = delegate;
|
||||
this.lbClientFactory = lbClientFactory;
|
||||
this.clientFactory = clientFactory;
|
||||
}
|
||||
|
||||
static URI cleanUrl(String originalUrl, String host) {
|
||||
String newUrl = originalUrl;
|
||||
if (originalUrl.startsWith("https://")) {
|
||||
newUrl = originalUrl.substring(0, 8)
|
||||
+ originalUrl.substring(8 + host.length());
|
||||
}
|
||||
else if (originalUrl.startsWith("http")) {
|
||||
newUrl = originalUrl.substring(0, 7)
|
||||
+ originalUrl.substring(7 + host.length());
|
||||
}
|
||||
StringBuffer buffer = new StringBuffer(newUrl);
|
||||
if ((newUrl.startsWith("https://") && newUrl.length() == 8)
|
||||
|| (newUrl.startsWith("http://") && newUrl.length() == 7)) {
|
||||
buffer.append("/");
|
||||
}
|
||||
return URI.create(buffer.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response execute(Request request, Request.Options options) throws IOException {
|
||||
try {
|
||||
URI asUri = URI.create(request.url());
|
||||
String clientName = asUri.getHost();
|
||||
URI uriWithoutHost = cleanUrl(request.url(), clientName);
|
||||
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
|
||||
this.delegate, request, uriWithoutHost);
|
||||
|
||||
IClientConfig requestConfig = getClientConfig(options, clientName);
|
||||
return lbClient(clientName)
|
||||
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
|
||||
}
|
||||
catch (ClientException e) {
|
||||
IOException io = findIOException(e);
|
||||
if (io != null) {
|
||||
throw io;
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
IClientConfig getClientConfig(Request.Options options, String clientName) {
|
||||
IClientConfig requestConfig;
|
||||
if (options == DEFAULT_OPTIONS) {
|
||||
requestConfig = this.clientFactory.getClientConfig(clientName);
|
||||
}
|
||||
else {
|
||||
requestConfig = new FeignOptionsClientConfig(options);
|
||||
}
|
||||
return requestConfig;
|
||||
}
|
||||
|
||||
protected IOException findIOException(Throwable t) {
|
||||
if (t == null) {
|
||||
return null;
|
||||
}
|
||||
if (t instanceof IOException) {
|
||||
return (IOException) t;
|
||||
}
|
||||
return findIOException(t.getCause());
|
||||
}
|
||||
|
||||
public Client getDelegate() {
|
||||
return this.delegate;
|
||||
}
|
||||
|
||||
private FeignLoadBalancer lbClient(String clientName) {
|
||||
return this.lbClientFactory.create(clientName);
|
||||
}
|
||||
|
||||
static class FeignOptionsClientConfig extends DefaultClientConfigImpl {
|
||||
|
||||
FeignOptionsClientConfig(Request.Options options) {
|
||||
setProperty(CommonClientConfigKey.ConnectTimeout,
|
||||
options.connectTimeoutMillis());
|
||||
setProperty(CommonClientConfigKey.ReadTimeout, options.readTimeoutMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadProperties(String clientName) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadDefaultValues() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.ribbon;
|
||||
|
||||
import feign.Client;
|
||||
import feign.okhttp.OkHttpClient;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
|
||||
import org.springframework.cloud.openfeign.clientconfig.OkHttpFeignConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(OkHttpClient.class)
|
||||
@ConditionalOnProperty("feign.okhttp.enabled")
|
||||
@Import(OkHttpFeignConfiguration.class)
|
||||
class OkHttpFeignLoadBalancedConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(Client.class)
|
||||
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
|
||||
SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) {
|
||||
OkHttpClient delegate = new OkHttpClient(okHttpClient);
|
||||
return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);
|
||||
}
|
||||
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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.ribbon;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
|
||||
import com.netflix.client.DefaultLoadBalancerRetryHandler;
|
||||
import com.netflix.client.RequestSpecificRetryHandler;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import feign.Request;
|
||||
import feign.Response;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRecoveryCallback;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicy;
|
||||
import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonProperties;
|
||||
import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
|
||||
import org.springframework.retry.RetryListener;
|
||||
import org.springframework.retry.backoff.BackOffPolicy;
|
||||
import org.springframework.retry.backoff.NoBackOffPolicy;
|
||||
import org.springframework.retry.policy.NeverRetryPolicy;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
/**
|
||||
* A {@link FeignLoadBalancer} that leverages Spring Retry to retry failed requests.
|
||||
*
|
||||
* @author Ryan Baxter
|
||||
* @author Gang Li
|
||||
*/
|
||||
public class RetryableFeignLoadBalancer extends FeignLoadBalancer
|
||||
implements ServiceInstanceChooser {
|
||||
|
||||
private final LoadBalancedRetryFactory loadBalancedRetryFactory;
|
||||
|
||||
public RetryableFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig,
|
||||
ServerIntrospector serverIntrospector,
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory) {
|
||||
super(lb, clientConfig, serverIntrospector);
|
||||
this.loadBalancedRetryFactory = loadBalancedRetryFactory;
|
||||
this.setRetryHandler(new DefaultLoadBalancerRetryHandler(clientConfig));
|
||||
}
|
||||
|
||||
@Override
|
||||
public RibbonResponse execute(final RibbonRequest request,
|
||||
IClientConfig configOverride) throws IOException {
|
||||
final Request.Options options;
|
||||
if (configOverride != null) {
|
||||
RibbonProperties ribbon = RibbonProperties.from(configOverride);
|
||||
options = new Request.Options(ribbon.connectTimeout(this.connectTimeout),
|
||||
ribbon.readTimeout(this.readTimeout));
|
||||
}
|
||||
else {
|
||||
options = new Request.Options(this.connectTimeout, this.readTimeout);
|
||||
}
|
||||
final LoadBalancedRetryPolicy retryPolicy = this.loadBalancedRetryFactory
|
||||
.createRetryPolicy(this.getClientName(), this);
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
BackOffPolicy backOffPolicy = this.loadBalancedRetryFactory
|
||||
.createBackOffPolicy(this.getClientName());
|
||||
retryTemplate.setBackOffPolicy(
|
||||
backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
|
||||
RetryListener[] retryListeners = this.loadBalancedRetryFactory
|
||||
.createRetryListeners(this.getClientName());
|
||||
if (retryListeners != null && retryListeners.length != 0) {
|
||||
retryTemplate.setListeners(retryListeners);
|
||||
}
|
||||
retryTemplate.setRetryPolicy(retryPolicy == null ? new NeverRetryPolicy()
|
||||
: new FeignRetryPolicy(request.toHttpRequest(), retryPolicy, this,
|
||||
this.getClientName()));
|
||||
return retryTemplate.execute(retryContext -> {
|
||||
Request feignRequest = null;
|
||||
// on retries the policy will choose the server and set it in the context
|
||||
// extract the server and update the request being made
|
||||
if (retryContext instanceof LoadBalancedRetryContext) {
|
||||
ServiceInstance service = ((LoadBalancedRetryContext) retryContext)
|
||||
.getServiceInstance();
|
||||
if (service != null) {
|
||||
feignRequest = ((RibbonRequest) request
|
||||
.replaceUri(reconstructURIWithServer(
|
||||
new Server(service.getHost(), service.getPort()),
|
||||
request.getUri()))).toRequest();
|
||||
}
|
||||
}
|
||||
if (feignRequest == null) {
|
||||
feignRequest = request.toRequest();
|
||||
}
|
||||
Response response = request.client().execute(feignRequest, options);
|
||||
if (retryPolicy != null
|
||||
&& retryPolicy.retryableStatusCode(response.status())) {
|
||||
byte[] byteArray = response.body() == null ? new byte[] {}
|
||||
: StreamUtils.copyToByteArray(response.body().asInputStream());
|
||||
response.close();
|
||||
throw new RibbonResponseStatusCodeException(
|
||||
RetryableFeignLoadBalancer.this.clientName, response, byteArray,
|
||||
request.getUri());
|
||||
}
|
||||
return new RibbonResponse(request.getUri(), response);
|
||||
}, new LoadBalancedRecoveryCallback<RibbonResponse, Response>() {
|
||||
@Override
|
||||
protected RibbonResponse createResponse(Response response, URI uri) {
|
||||
return new RibbonResponse(uri, response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
|
||||
FeignLoadBalancer.RibbonRequest request, IClientConfig requestConfig) {
|
||||
return new RequestSpecificRetryHandler(false, false, this.getRetryHandler(),
|
||||
requestConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceInstance choose(String serviceId) {
|
||||
return new RibbonLoadBalancerClient.RibbonServer(serviceId,
|
||||
this.getLoadBalancer().chooseServer(serviceId));
|
||||
}
|
||||
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.ribbon;
|
||||
|
||||
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 RibbonResponseStatusCodeException extends RetryableStatusCodeException {
|
||||
|
||||
private final Response response;
|
||||
|
||||
public RibbonResponseStatusCodeException(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;
|
||||
}
|
||||
|
||||
}
|
||||
-182
@@ -1,182 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015-2019 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.security;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
|
||||
import org.springframework.security.oauth2.client.OAuth2ClientContext;
|
||||
import org.springframework.security.oauth2.client.http.AccessTokenRequiredException;
|
||||
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
|
||||
import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
|
||||
import org.springframework.security.oauth2.client.token.AccessTokenProvider;
|
||||
import org.springframework.security.oauth2.client.token.AccessTokenProviderChain;
|
||||
import org.springframework.security.oauth2.client.token.AccessTokenRequest;
|
||||
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsAccessTokenProvider;
|
||||
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider;
|
||||
import org.springframework.security.oauth2.client.token.grant.implicit.ImplicitAccessTokenProvider;
|
||||
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
|
||||
/**
|
||||
* Pre-defined custom RequestInterceptor for Feign Requests. It uses the
|
||||
* {@link OAuth2ClientContext OAuth2ClientContext} provided from the environment and
|
||||
* construct a new header on the request before it is made by Feign.
|
||||
*
|
||||
* @author Joao Pedro Evangelista
|
||||
* @author Tim Ysewyn
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public class OAuth2FeignRequestInterceptor implements RequestInterceptor {
|
||||
|
||||
/**
|
||||
* The name of the token.
|
||||
*/
|
||||
public static final String BEARER = "Bearer";
|
||||
|
||||
/**
|
||||
* The name of the header.
|
||||
*/
|
||||
public static final String AUTHORIZATION = "Authorization";
|
||||
|
||||
private final OAuth2ClientContext oAuth2ClientContext;
|
||||
|
||||
private final OAuth2ProtectedResourceDetails resource;
|
||||
|
||||
private final String tokenType;
|
||||
|
||||
private final String header;
|
||||
|
||||
private AccessTokenProvider accessTokenProvider = new AccessTokenProviderChain(Arrays.<AccessTokenProvider>asList(
|
||||
new AuthorizationCodeAccessTokenProvider(), new ImplicitAccessTokenProvider(),
|
||||
new ResourceOwnerPasswordAccessTokenProvider(), new ClientCredentialsAccessTokenProvider()));
|
||||
|
||||
/**
|
||||
* Default constructor which uses the provided OAuth2ClientContext and Bearer tokens
|
||||
* within Authorization header.
|
||||
* @param oAuth2ClientContext provided context
|
||||
* @param resource type of resource to be accessed
|
||||
*/
|
||||
public OAuth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext,
|
||||
OAuth2ProtectedResourceDetails resource) {
|
||||
this(oAuth2ClientContext, resource, BEARER, AUTHORIZATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully customizable constructor for changing token type and header name, in cases of
|
||||
* Bearer and Authorization is not the default such as "bearer", "authorization".
|
||||
* @param oAuth2ClientContext current oAuth2 Context
|
||||
* @param resource type of resource to be accessed
|
||||
* @param tokenType type of token e.g. "token", "Bearer"
|
||||
* @param header name of the header e.g. "Authorization", "authorization"
|
||||
*/
|
||||
public OAuth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext,
|
||||
OAuth2ProtectedResourceDetails resource, String tokenType, String header) {
|
||||
this.oAuth2ClientContext = oAuth2ClientContext;
|
||||
this.resource = resource;
|
||||
this.tokenType = tokenType;
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a template with the header of provided name and extracted extract.
|
||||
*
|
||||
* @see RequestInterceptor#apply(RequestTemplate)
|
||||
*/
|
||||
@Override
|
||||
public void apply(RequestTemplate template) {
|
||||
template.header(header); // Clears out the header, no "clear" method available.
|
||||
template.header(header, extract(tokenType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the token extract id the access token exists or returning an empty extract
|
||||
* if there is no one on the context it may occasionally causes Unauthorized response
|
||||
* since the token extract is empty.
|
||||
* @param tokenType type name of token
|
||||
* @return token value from context if it exists otherwise empty String
|
||||
*/
|
||||
protected String extract(String tokenType) {
|
||||
OAuth2AccessToken accessToken = getToken();
|
||||
return String.format("%s %s", tokenType, accessToken.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the access token within the request or try to acquire a new one by
|
||||
* delegating it to {@link #acquireAccessToken()}.
|
||||
* @return valid token
|
||||
*/
|
||||
public OAuth2AccessToken getToken() {
|
||||
|
||||
OAuth2AccessToken accessToken = oAuth2ClientContext.getAccessToken();
|
||||
if (accessToken == null || accessToken.isExpired()) {
|
||||
try {
|
||||
accessToken = acquireAccessToken();
|
||||
}
|
||||
catch (UserRedirectRequiredException e) {
|
||||
oAuth2ClientContext.setAccessToken(null);
|
||||
String stateKey = e.getStateKey();
|
||||
if (stateKey != null) {
|
||||
Object stateToPreserve = e.getStateToPreserve();
|
||||
if (stateToPreserve == null) {
|
||||
stateToPreserve = "NONE";
|
||||
}
|
||||
oAuth2ClientContext.setPreservedState(stateKey, stateToPreserve);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to acquire the token using a access token provider.
|
||||
* @return valid access token
|
||||
* @throws UserRedirectRequiredException in case the user needs to be redirected to an
|
||||
* approval page or login page
|
||||
*/
|
||||
protected OAuth2AccessToken acquireAccessToken() throws UserRedirectRequiredException {
|
||||
AccessTokenRequest tokenRequest = oAuth2ClientContext.getAccessTokenRequest();
|
||||
if (tokenRequest == null) {
|
||||
throw new AccessTokenRequiredException(
|
||||
"Cannot find valid context on request for resource '" + resource.getId() + "'.", resource);
|
||||
}
|
||||
String stateKey = tokenRequest.getStateKey();
|
||||
if (stateKey != null) {
|
||||
tokenRequest.setPreservedState(oAuth2ClientContext.removePreservedState(stateKey));
|
||||
}
|
||||
OAuth2AccessToken existingToken = oAuth2ClientContext.getAccessToken();
|
||||
if (existingToken != null) {
|
||||
oAuth2ClientContext.setAccessToken(existingToken);
|
||||
}
|
||||
OAuth2AccessToken obtainableAccessToken;
|
||||
obtainableAccessToken = accessTokenProvider.obtainAccessToken(resource, tokenRequest);
|
||||
if (obtainableAccessToken == null || obtainableAccessToken.getValue() == null) {
|
||||
throw new IllegalStateException(
|
||||
" Access token provider returned a null token, which is illegal according to the contract.");
|
||||
}
|
||||
oAuth2ClientContext.setAccessToken(obtainableAccessToken);
|
||||
return obtainableAccessToken;
|
||||
}
|
||||
|
||||
public void setAccessTokenProvider(AccessTokenProvider accessTokenProvider) {
|
||||
this.accessTokenProvider = accessTokenProvider;
|
||||
}
|
||||
|
||||
}
|
||||
+7
-4
@@ -29,6 +29,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import static feign.form.ContentProcessor.CRLF;
|
||||
import static feign.form.util.PojoUtil.isUserPojo;
|
||||
|
||||
/**
|
||||
* @author Darren Foong
|
||||
@@ -44,10 +45,12 @@ public abstract class AbstractFormWriter extends AbstractWriter {
|
||||
@Override
|
||||
public void write(Output output, String key, Object object) throws EncodeException {
|
||||
try {
|
||||
String string = new StringBuilder().append("Content-Disposition: form-data; name=\"").append(key)
|
||||
.append('"').append(CRLF).append("Content-Type: ").append(getContentType()).append("; charset=")
|
||||
.append(output.getCharset().name()).append(CRLF).append(CRLF).append(writeAsString(object))
|
||||
.toString();
|
||||
String string = new StringBuilder()
|
||||
.append("Content-Disposition: form-data; name=\"").append(key)
|
||||
.append('"').append(CRLF).append("Content-Type: ")
|
||||
.append(getContentType()).append("; charset=")
|
||||
.append(output.getCharset().name()).append(CRLF).append(CRLF)
|
||||
.append(writeAsString(object)).toString();
|
||||
|
||||
output.write(string);
|
||||
}
|
||||
|
||||
+10
-5
@@ -45,15 +45,19 @@ public class DefaultGzipDecoder implements Decoder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object decode(final Response response, Type type) throws IOException, FeignException {
|
||||
Collection<String> encoding = response.headers().containsKey(HttpEncoding.CONTENT_ENCODING_HEADER)
|
||||
? response.headers().get(HttpEncoding.CONTENT_ENCODING_HEADER) : null;
|
||||
public Object decode(final Response response, Type type)
|
||||
throws IOException, FeignException {
|
||||
Collection<String> encoding = response.headers()
|
||||
.containsKey(HttpEncoding.CONTENT_ENCODING_HEADER)
|
||||
? response.headers().get(HttpEncoding.CONTENT_ENCODING_HEADER)
|
||||
: null;
|
||||
|
||||
if (encoding != null) {
|
||||
if (encoding.contains(HttpEncoding.GZIP_ENCODING)) {
|
||||
String decompressedBody = decompress(response);
|
||||
if (decompressedBody != null) {
|
||||
Response decompressedResponse = response.toBuilder().body(decompressedBody.getBytes()).build();
|
||||
Response decompressedResponse = response.toBuilder()
|
||||
.body(decompressedBody.getBytes()).build();
|
||||
return decoder.decode(decompressedResponse, type);
|
||||
}
|
||||
}
|
||||
@@ -65,7 +69,8 @@ public class DefaultGzipDecoder implements Decoder {
|
||||
if (response.body() == null) {
|
||||
return null;
|
||||
}
|
||||
try (GZIPInputStream gzipInputStream = new GZIPInputStream(response.body().asInputStream());
|
||||
try (GZIPInputStream gzipInputStream = new GZIPInputStream(
|
||||
response.body().asInputStream());
|
||||
BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(gzipInputStream, StandardCharsets.UTF_8))) {
|
||||
String outputString = "";
|
||||
|
||||
+4
-3
@@ -43,7 +43,8 @@ public class DefaultGzipDecoderConfiguration {
|
||||
|
||||
private ObjectFactory<HttpMessageConverters> messageConverters;
|
||||
|
||||
public DefaultGzipDecoderConfiguration(ObjectFactory<HttpMessageConverters> messageConverters) {
|
||||
public DefaultGzipDecoderConfiguration(
|
||||
ObjectFactory<HttpMessageConverters> messageConverters) {
|
||||
this.messageConverters = messageConverters;
|
||||
}
|
||||
|
||||
@@ -51,8 +52,8 @@ public class DefaultGzipDecoderConfiguration {
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty("feign.compression.response.useGzipDecoder")
|
||||
public Decoder defaultGzipDecoder() {
|
||||
return new OptionalDecoder(
|
||||
new ResponseEntityDecoder(new DefaultGzipDecoder(new SpringDecoder(messageConverters))));
|
||||
return new OptionalDecoder(new ResponseEntityDecoder(
|
||||
new DefaultGzipDecoder(new SpringDecoder(messageConverters))));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.support;
|
||||
|
||||
import com.netflix.hystrix.HystrixCommand;
|
||||
import com.netflix.hystrix.HystrixCommandGroupKey;
|
||||
import com.netflix.hystrix.HystrixThreadPoolKey;
|
||||
|
||||
/**
|
||||
* Convenience class for implementing feign fallbacks that return {@link HystrixCommand}.
|
||||
* Also useful for return types of {@link rx.Observable} and
|
||||
* {@link java.util.concurrent.Future}. For those return types, just call
|
||||
* {@link FallbackCommand#observe()} or {@link FallbackCommand#queue()} respectively.
|
||||
*
|
||||
* @param <T> result type
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class FallbackCommand<T> extends HystrixCommand<T> {
|
||||
|
||||
private T result;
|
||||
|
||||
public FallbackCommand(T result) {
|
||||
this(result, "fallback");
|
||||
}
|
||||
|
||||
protected FallbackCommand(T result, String groupname) {
|
||||
super(HystrixCommandGroupKey.Factory.asKey(groupname));
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public FallbackCommand(T result, HystrixCommandGroupKey group) {
|
||||
super(group);
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public FallbackCommand(T result, HystrixCommandGroupKey group,
|
||||
int executionIsolationThreadTimeoutInMilliseconds) {
|
||||
super(group, executionIsolationThreadTimeoutInMilliseconds);
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public FallbackCommand(T result, HystrixCommandGroupKey group,
|
||||
HystrixThreadPoolKey threadPool) {
|
||||
super(group, threadPool);
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public FallbackCommand(T result, HystrixCommandGroupKey group,
|
||||
HystrixThreadPoolKey threadPool,
|
||||
int executionIsolationThreadTimeoutInMilliseconds) {
|
||||
super(group, threadPool, executionIsolationThreadTimeoutInMilliseconds);
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public FallbackCommand(T result, Setter setter) {
|
||||
super(setter);
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T run() throws Exception {
|
||||
return this.result;
|
||||
}
|
||||
|
||||
}
|
||||
+4
-2
@@ -54,8 +54,10 @@ public final class FeignUtils {
|
||||
return headers;
|
||||
}
|
||||
|
||||
static Collection<String> addTemplateParameter(Collection<String> possiblyNull, String paramName) {
|
||||
Collection<String> params = ofNullable(possiblyNull).map(ArrayList::new).orElse(new ArrayList<>());
|
||||
static Collection<String> addTemplateParameter(Collection<String> possiblyNull,
|
||||
String paramName) {
|
||||
Collection<String> params = ofNullable(possiblyNull).map(ArrayList::new)
|
||||
.orElse(new ArrayList<>());
|
||||
params.add(String.format("{%s}", paramName));
|
||||
return params;
|
||||
}
|
||||
|
||||
+7
-2
@@ -20,6 +20,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAlias;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.Version;
|
||||
@@ -36,6 +37,7 @@ import org.springframework.data.domain.Sort;
|
||||
* This Jackson module provides support to deserialize Spring {@link Page} objects.
|
||||
*
|
||||
* @author Pascal Büttiker
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
public class PageJacksonModule extends Module {
|
||||
|
||||
@@ -63,8 +65,11 @@ public class PageJacksonModule extends Module {
|
||||
|
||||
private final Page<T> delegate;
|
||||
|
||||
SimplePageImpl(@JsonProperty("content") List<T> content, @JsonProperty("number") int number,
|
||||
@JsonProperty("size") int size, @JsonProperty("totalElements") long totalElements,
|
||||
SimplePageImpl(@JsonProperty("content") List<T> content,
|
||||
@JsonProperty("number") int number, @JsonProperty("size") int size,
|
||||
@JsonProperty("totalElements") @JsonAlias({ "total-elements",
|
||||
"total_elements", "totalelements",
|
||||
"TotalElements" }) long totalElements,
|
||||
@JsonProperty("sort") Sort sort) {
|
||||
PageRequest pageRequest;
|
||||
if (sort != null) {
|
||||
|
||||
+9
-5
@@ -75,7 +75,8 @@ public class PageableSpringEncoder implements Encoder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
|
||||
public void encode(Object object, Type bodyType, RequestTemplate template)
|
||||
throws EncodeException {
|
||||
|
||||
if (supports(object)) {
|
||||
if (object instanceof Pageable) {
|
||||
@@ -100,15 +101,18 @@ public class PageableSpringEncoder implements Encoder {
|
||||
delegate.encode(object, bodyType, template);
|
||||
}
|
||||
else {
|
||||
throw new EncodeException("PageableSpringEncoder does not support the given object " + object.getClass()
|
||||
+ " and no delegate was provided for fallback!");
|
||||
throw new EncodeException(
|
||||
"PageableSpringEncoder does not support the given object "
|
||||
+ object.getClass()
|
||||
+ " and no delegate was provided for fallback!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applySort(RequestTemplate template, Sort sort) {
|
||||
Collection<String> existingSorts = template.queries().get("sort");
|
||||
List<String> sortQueries = existingSorts != null ? new ArrayList<>(existingSorts) : new ArrayList<>();
|
||||
List<String> sortQueries = existingSorts != null ? new ArrayList<>(existingSorts)
|
||||
: new ArrayList<>();
|
||||
if (!sortParameter.equals("sort")) {
|
||||
existingSorts = template.queries().get(sortParameter);
|
||||
if (existingSorts != null) {
|
||||
@@ -116,7 +120,7 @@ public class PageableSpringEncoder implements Encoder {
|
||||
}
|
||||
}
|
||||
for (Sort.Order order : sort) {
|
||||
sortQueries.add(order.getProperty() + "," + order.getDirection());
|
||||
sortQueries.add(order.getProperty() + "%2C" + order.getDirection());
|
||||
}
|
||||
if (!sortQueries.isEmpty()) {
|
||||
template.query(sortParameter, sortQueries);
|
||||
|
||||
+4
-2
@@ -46,7 +46,8 @@ public class ResponseEntityDecoder implements Decoder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object decode(final Response response, Type type) throws IOException, FeignException {
|
||||
public Object decode(final Response response, Type type)
|
||||
throws IOException, FeignException {
|
||||
|
||||
if (isParameterizeHttpEntity(type)) {
|
||||
type = ((ParameterizedType) type).getActualTypeArguments()[0];
|
||||
@@ -85,7 +86,8 @@ public class ResponseEntityDecoder implements Decoder {
|
||||
headers.put(key, new LinkedList<>(response.headers().get(key)));
|
||||
}
|
||||
|
||||
return new ResponseEntity<>((T) instance, headers, HttpStatus.valueOf(response.status()));
|
||||
return new ResponseEntity<>((T) instance, headers,
|
||||
HttpStatus.valueOf(response.status()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -48,7 +48,8 @@ public class SortJacksonModule extends Module {
|
||||
context.addSerializers(serializers);
|
||||
|
||||
SimpleDeserializers deserializers = new SimpleDeserializers();
|
||||
deserializers.addDeserializer(Sort.class, new SortJsonComponent.SortDeserializer());
|
||||
deserializers.addDeserializer(Sort.class,
|
||||
new SortJsonComponent.SortDeserializer());
|
||||
context.addDeserializers(deserializers);
|
||||
}
|
||||
|
||||
|
||||
+6
-4
@@ -44,7 +44,8 @@ public class SortJsonComponent {
|
||||
public static class SortSerializer extends JsonSerializer<Sort> {
|
||||
|
||||
@Override
|
||||
public void serialize(Sort value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
public void serialize(Sort value, JsonGenerator gen,
|
||||
SerializerProvider serializers) throws IOException {
|
||||
gen.writeStartArray();
|
||||
value.iterator().forEachRemaining(v -> {
|
||||
try {
|
||||
@@ -67,14 +68,15 @@ public class SortJsonComponent {
|
||||
public static class SortDeserializer extends JsonDeserializer<Sort> {
|
||||
|
||||
@Override
|
||||
public Sort deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
|
||||
throws IOException {
|
||||
public Sort deserialize(JsonParser jsonParser,
|
||||
DeserializationContext deserializationContext) throws IOException {
|
||||
TreeNode treeNode = jsonParser.getCodec().readTree(jsonParser);
|
||||
if (treeNode.isArray()) {
|
||||
ArrayNode arrayNode = (ArrayNode) treeNode;
|
||||
List<Sort.Order> orders = new ArrayList<>();
|
||||
for (JsonNode jsonNode : arrayNode) {
|
||||
Sort.Order order = new Sort.Order(Sort.Direction.valueOf(jsonNode.get("direction").textValue()),
|
||||
Sort.Order order = new Sort.Order(
|
||||
Sort.Direction.valueOf(jsonNode.get("direction").textValue()),
|
||||
jsonNode.get("property").textValue());
|
||||
orders.add(order);
|
||||
}
|
||||
|
||||
+8
-5
@@ -48,15 +48,18 @@ public class SpringDecoder implements Decoder {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object decode(final Response response, Type type) throws IOException, FeignException {
|
||||
if (type instanceof Class || type instanceof ParameterizedType || type instanceof WildcardType) {
|
||||
public Object decode(final Response response, Type type)
|
||||
throws IOException, FeignException {
|
||||
if (type instanceof Class || type instanceof ParameterizedType
|
||||
|| type instanceof WildcardType) {
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
HttpMessageConverterExtractor<?> extractor = new HttpMessageConverterExtractor(type,
|
||||
this.messageConverters.getObject().getConverters());
|
||||
HttpMessageConverterExtractor<?> extractor = new HttpMessageConverterExtractor(
|
||||
type, this.messageConverters.getObject().getConverters());
|
||||
|
||||
return extractor.extractData(new FeignResponseAdapter(response));
|
||||
}
|
||||
throw new DecodeException(response.status(), "type is not an instance of Class or ParameterizedType: " + type,
|
||||
throw new DecodeException(response.status(),
|
||||
"type is not an instance of Class or ParameterizedType: " + type,
|
||||
response.request());
|
||||
}
|
||||
|
||||
|
||||
+30
-20
@@ -69,16 +69,19 @@ public class SpringEncoder implements Encoder {
|
||||
this.messageConverters = messageConverters;
|
||||
}
|
||||
|
||||
public SpringEncoder(SpringFormEncoder springFormEncoder, ObjectFactory<HttpMessageConverters> messageConverters) {
|
||||
public SpringEncoder(SpringFormEncoder springFormEncoder,
|
||||
ObjectFactory<HttpMessageConverters> messageConverters) {
|
||||
this.springFormEncoder = springFormEncoder;
|
||||
this.messageConverters = messageConverters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(Object requestBody, Type bodyType, RequestTemplate request) throws EncodeException {
|
||||
public void encode(Object requestBody, Type bodyType, RequestTemplate request)
|
||||
throws EncodeException {
|
||||
// template.body(conversionService.convert(object, String.class));
|
||||
if (requestBody != null) {
|
||||
Collection<String> contentTypes = request.headers().get(HttpEncoding.CONTENT_TYPE);
|
||||
Collection<String> contentTypes = request.headers()
|
||||
.get(HttpEncoding.CONTENT_TYPE);
|
||||
|
||||
MediaType requestContentType = null;
|
||||
if (contentTypes != null && !contentTypes.isEmpty()) {
|
||||
@@ -92,20 +95,24 @@ public class SpringEncoder implements Encoder {
|
||||
}
|
||||
else {
|
||||
if (bodyType == MultipartFile.class) {
|
||||
log.warn("For MultipartFile to be handled correctly, the 'consumes' parameter of @RequestMapping "
|
||||
+ "should be specified as MediaType.MULTIPART_FORM_DATA_VALUE");
|
||||
log.warn(
|
||||
"For MultipartFile to be handled correctly, the 'consumes' parameter of @RequestMapping "
|
||||
+ "should be specified as MediaType.MULTIPART_FORM_DATA_VALUE");
|
||||
}
|
||||
}
|
||||
|
||||
for (HttpMessageConverter messageConverter : this.messageConverters.getObject().getConverters()) {
|
||||
for (HttpMessageConverter messageConverter : this.messageConverters
|
||||
.getObject().getConverters()) {
|
||||
FeignOutputMessage outputMessage;
|
||||
try {
|
||||
if (messageConverter instanceof GenericHttpMessageConverter) {
|
||||
outputMessage = checkAndWrite(requestBody, bodyType, requestContentType,
|
||||
outputMessage = checkAndWrite(requestBody, bodyType,
|
||||
requestContentType,
|
||||
(GenericHttpMessageConverter) messageConverter, request);
|
||||
}
|
||||
else {
|
||||
outputMessage = checkAndWrite(requestBody, requestContentType, messageConverter, request);
|
||||
outputMessage = checkAndWrite(requestBody, requestContentType,
|
||||
messageConverter, request);
|
||||
}
|
||||
}
|
||||
catch (IOException | HttpMessageConversionException ex) {
|
||||
@@ -124,19 +131,20 @@ public class SpringEncoder implements Encoder {
|
||||
charset = null;
|
||||
}
|
||||
else if (messageConverter instanceof ProtobufHttpMessageConverter
|
||||
&& ProtobufHttpMessageConverter.PROTOBUF
|
||||
.isCompatibleWith(outputMessage.getHeaders().getContentType())) {
|
||||
&& ProtobufHttpMessageConverter.PROTOBUF.isCompatibleWith(
|
||||
outputMessage.getHeaders().getContentType())) {
|
||||
charset = null;
|
||||
}
|
||||
else {
|
||||
charset = StandardCharsets.UTF_8;
|
||||
}
|
||||
request.body(Request.Body.encoded(outputMessage.getOutputStream().toByteArray(), charset));
|
||||
request.body(Request.Body.encoded(
|
||||
outputMessage.getOutputStream().toByteArray(), charset));
|
||||
return;
|
||||
}
|
||||
}
|
||||
String message = "Could not write request: no suitable HttpMessageConverter " + "found for request type ["
|
||||
+ requestBody.getClass().getName() + "]";
|
||||
String message = "Could not write request: no suitable HttpMessageConverter "
|
||||
+ "found for request type [" + requestBody.getClass().getName() + "]";
|
||||
if (requestContentType != null) {
|
||||
message += " and content type [" + requestContentType + "]";
|
||||
}
|
||||
@@ -145,8 +153,8 @@ public class SpringEncoder implements Encoder {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private FeignOutputMessage checkAndWrite(Object body, MediaType contentType, HttpMessageConverter converter,
|
||||
RequestTemplate request) throws IOException {
|
||||
private FeignOutputMessage checkAndWrite(Object body, MediaType contentType,
|
||||
HttpMessageConverter converter, RequestTemplate request) throws IOException {
|
||||
if (converter.canWrite(body.getClass(), contentType)) {
|
||||
logBeforeWrite(body, contentType, converter);
|
||||
FeignOutputMessage outputMessage = new FeignOutputMessage(request);
|
||||
@@ -159,8 +167,9 @@ public class SpringEncoder implements Encoder {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private FeignOutputMessage checkAndWrite(Object body, Type genericType, MediaType contentType,
|
||||
GenericHttpMessageConverter converter, RequestTemplate request) throws IOException {
|
||||
private FeignOutputMessage checkAndWrite(Object body, Type genericType,
|
||||
MediaType contentType, GenericHttpMessageConverter converter,
|
||||
RequestTemplate request) throws IOException {
|
||||
if (converter.canWrite(genericType, body.getClass(), contentType)) {
|
||||
logBeforeWrite(body, contentType, converter);
|
||||
FeignOutputMessage outputMessage = new FeignOutputMessage(request);
|
||||
@@ -176,11 +185,12 @@ public class SpringEncoder implements Encoder {
|
||||
HttpMessageConverter messageConverter) {
|
||||
if (log.isDebugEnabled()) {
|
||||
if (requestContentType != null) {
|
||||
log.debug("Writing [" + requestBody + "] as \"" + requestContentType + "\" using [" + messageConverter
|
||||
+ "]");
|
||||
log.debug("Writing [" + requestBody + "] as \"" + requestContentType
|
||||
+ "\" using [" + messageConverter + "]");
|
||||
}
|
||||
else {
|
||||
log.debug("Writing [" + requestBody + "] using [" + messageConverter + "]");
|
||||
log.debug(
|
||||
"Writing [" + requestBody + "] using [" + messageConverter + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+108
-49
@@ -80,15 +80,18 @@ import static org.springframework.core.annotation.AnnotatedElementUtils.findMerg
|
||||
* @author Darren Foong
|
||||
* @author Ram Anaswara
|
||||
*/
|
||||
public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {
|
||||
public class SpringMvcContract extends Contract.BaseContract
|
||||
implements ResourceLoaderAware {
|
||||
|
||||
private static final String ACCEPT = "Accept";
|
||||
|
||||
private static final String CONTENT_TYPE = "Content-Type";
|
||||
|
||||
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
|
||||
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor
|
||||
.valueOf(String.class);
|
||||
|
||||
private static final TypeDescriptor ITERABLE_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Iterable.class);
|
||||
private static final TypeDescriptor ITERABLE_TYPE_DESCRIPTOR = TypeDescriptor
|
||||
.valueOf(Iterable.class);
|
||||
|
||||
private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
|
||||
|
||||
@@ -108,18 +111,22 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
|
||||
this(Collections.emptyList());
|
||||
}
|
||||
|
||||
public SpringMvcContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors) {
|
||||
public SpringMvcContract(
|
||||
List<AnnotatedParameterProcessor> annotatedParameterProcessors) {
|
||||
this(annotatedParameterProcessors, new DefaultConversionService());
|
||||
}
|
||||
|
||||
public SpringMvcContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors,
|
||||
public SpringMvcContract(
|
||||
List<AnnotatedParameterProcessor> annotatedParameterProcessors,
|
||||
ConversionService conversionService) {
|
||||
this(annotatedParameterProcessors, conversionService, true);
|
||||
}
|
||||
|
||||
public SpringMvcContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors,
|
||||
public SpringMvcContract(
|
||||
List<AnnotatedParameterProcessor> annotatedParameterProcessors,
|
||||
ConversionService conversionService, boolean decodeSlash) {
|
||||
Assert.notNull(annotatedParameterProcessors, "Parameter processors can not be null.");
|
||||
Assert.notNull(annotatedParameterProcessors,
|
||||
"Parameter processors can not be null.");
|
||||
Assert.notNull(conversionService, "ConversionService can not be null.");
|
||||
|
||||
List<AnnotatedParameterProcessor> processors = getDefaultAnnotatedArgumentsProcessors();
|
||||
@@ -139,21 +146,26 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
|
||||
// Feign applies the Param.Expander to each element of an Iterable, so in those
|
||||
// cases we need to provide a TypeDescriptor of the element.
|
||||
if (typeDescriptor.isAssignableTo(ITERABLE_TYPE_DESCRIPTOR)) {
|
||||
TypeDescriptor elementTypeDescriptor = getElementTypeDescriptor(typeDescriptor);
|
||||
TypeDescriptor elementTypeDescriptor = getElementTypeDescriptor(
|
||||
typeDescriptor);
|
||||
|
||||
checkState(elementTypeDescriptor != null,
|
||||
"Could not resolve element type of Iterable type %s. Not declared?", typeDescriptor);
|
||||
"Could not resolve element type of Iterable type %s. Not declared?",
|
||||
typeDescriptor);
|
||||
|
||||
typeDescriptor = elementTypeDescriptor;
|
||||
}
|
||||
return typeDescriptor;
|
||||
}
|
||||
|
||||
private static TypeDescriptor getElementTypeDescriptor(TypeDescriptor typeDescriptor) {
|
||||
private static TypeDescriptor getElementTypeDescriptor(
|
||||
TypeDescriptor typeDescriptor) {
|
||||
TypeDescriptor elementTypeDescriptor = typeDescriptor.getElementTypeDescriptor();
|
||||
// that means it's not a collection but it is iterable, gh-135
|
||||
if (elementTypeDescriptor == null && Iterable.class.isAssignableFrom(typeDescriptor.getType())) {
|
||||
ResolvableType type = typeDescriptor.getResolvableType().as(Iterable.class).getGeneric(0);
|
||||
if (elementTypeDescriptor == null
|
||||
&& Iterable.class.isAssignableFrom(typeDescriptor.getType())) {
|
||||
ResolvableType type = typeDescriptor.getResolvableType().as(Iterable.class)
|
||||
.getGeneric(0);
|
||||
if (type.resolve() == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -170,7 +182,8 @@ 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);
|
||||
RequestMapping classAnnotation = findMergedAnnotation(clz,
|
||||
RequestMapping.class);
|
||||
if (classAnnotation != null) {
|
||||
// Prepend path from class annotation if specified
|
||||
if (classAnnotation.value().length > 0) {
|
||||
@@ -193,7 +206,8 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
|
||||
processedMethods.put(Feign.configKey(targetType, method), method);
|
||||
MethodMetadata md = super.parseAndValidateMetadata(targetType, method);
|
||||
|
||||
RequestMapping classAnnotation = findMergedAnnotation(targetType, RequestMapping.class);
|
||||
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)) {
|
||||
@@ -213,14 +227,16 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
|
||||
protected void processAnnotationOnMethod(MethodMetadata data,
|
||||
Annotation methodAnnotation, Method method) {
|
||||
if (CollectionFormat.class.isInstance(methodAnnotation)) {
|
||||
CollectionFormat collectionFormat = findMergedAnnotation(method, CollectionFormat.class);
|
||||
CollectionFormat collectionFormat = findMergedAnnotation(method,
|
||||
CollectionFormat.class);
|
||||
data.template().collectionFormat(collectionFormat.value());
|
||||
}
|
||||
|
||||
if (!RequestMapping.class.isInstance(methodAnnotation)
|
||||
&& !methodAnnotation.annotationType().isAnnotationPresent(RequestMapping.class)) {
|
||||
if (!RequestMapping.class.isInstance(methodAnnotation) && !methodAnnotation
|
||||
.annotationType().isAnnotationPresent(RequestMapping.class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -263,29 +279,34 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
|
||||
}
|
||||
|
||||
private String resolve(String value) {
|
||||
if (StringUtils.hasText(value) && resourceLoader instanceof ConfigurableApplicationContext) {
|
||||
return ((ConfigurableApplicationContext) resourceLoader).getEnvironment().resolvePlaceholders(value);
|
||||
if (StringUtils.hasText(value)
|
||||
&& resourceLoader instanceof ConfigurableApplicationContext) {
|
||||
return ((ConfigurableApplicationContext) resourceLoader).getEnvironment()
|
||||
.resolvePlaceholders(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private void checkAtMostOne(Method method, Object[] values, String fieldName) {
|
||||
checkState(values != null && (values.length == 0 || values.length == 1),
|
||||
"Method %s can only contain at most 1 %s field. Found: %s", method.getName(), fieldName,
|
||||
"Method %s can only contain at most 1 %s field. Found: %s",
|
||||
method.getName(), fieldName,
|
||||
values == null ? null : Arrays.asList(values));
|
||||
}
|
||||
|
||||
private void checkOne(Method method, Object[] values, String fieldName) {
|
||||
checkState(values != null && values.length == 1, "Method %s can only contain 1 %s field. Found: %s",
|
||||
method.getName(), fieldName, values == null ? null : Arrays.asList(values));
|
||||
checkState(values != null && values.length == 1,
|
||||
"Method %s can only contain 1 %s field. Found: %s", method.getName(),
|
||||
fieldName, values == null ? null : Arrays.asList(values));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
|
||||
protected boolean processAnnotationsOnParameter(MethodMetadata data,
|
||||
Annotation[] annotations, int paramIndex) {
|
||||
boolean isHttpAnnotation = false;
|
||||
|
||||
AnnotatedParameterProcessor.AnnotatedParameterContext context = new SimpleAnnotatedParameterContext(data,
|
||||
paramIndex);
|
||||
AnnotatedParameterProcessor.AnnotatedParameterContext context = new SimpleAnnotatedParameterContext(
|
||||
data, paramIndex);
|
||||
Method method = processedMethods.get(data.configKey());
|
||||
for (Annotation parameterAnnotation : annotations) {
|
||||
AnnotatedParameterProcessor processor = annotatedArgumentProcessors
|
||||
@@ -294,16 +315,19 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
|
||||
Annotation processParameterAnnotation;
|
||||
// synthesize, handling @AliasFor, while falling back to parameter name on
|
||||
// missing String #value():
|
||||
processParameterAnnotation = synthesizeWithMethodParameterNameAsFallbackValue(parameterAnnotation,
|
||||
method, paramIndex);
|
||||
isHttpAnnotation |= processor.processArgument(context, processParameterAnnotation, method);
|
||||
processParameterAnnotation = synthesizeWithMethodParameterNameAsFallbackValue(
|
||||
parameterAnnotation, method, paramIndex);
|
||||
isHttpAnnotation |= processor.processArgument(context,
|
||||
processParameterAnnotation, method);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isMultipartFormData(data) && isHttpAnnotation && data.indexToExpander().get(paramIndex) == null) {
|
||||
if (!isMultipartFormData(data) && isHttpAnnotation
|
||||
&& data.indexToExpander().get(paramIndex) == null) {
|
||||
TypeDescriptor typeDescriptor = createTypeDescriptor(method, paramIndex);
|
||||
if (conversionService.canConvert(typeDescriptor, STRING_TYPE_DESCRIPTOR)) {
|
||||
Param.Expander expander = convertingExpanderFactory.getExpander(typeDescriptor);
|
||||
Param.Expander expander = convertingExpanderFactory
|
||||
.getExpander(typeDescriptor);
|
||||
if (expander != null) {
|
||||
data.indexToExpander().put(paramIndex, expander);
|
||||
}
|
||||
@@ -312,23 +336,28 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
|
||||
return isHttpAnnotation;
|
||||
}
|
||||
|
||||
private void parseProduces(MethodMetadata md, Method method, RequestMapping annotation) {
|
||||
private void parseProduces(MethodMetadata md, Method method,
|
||||
RequestMapping annotation) {
|
||||
String[] serverProduces = annotation.produces();
|
||||
String clientAccepts = serverProduces.length == 0 ? null : emptyToNull(serverProduces[0]);
|
||||
String clientAccepts = serverProduces.length == 0 ? null
|
||||
: emptyToNull(serverProduces[0]);
|
||||
if (clientAccepts != null) {
|
||||
md.template().header(ACCEPT, clientAccepts);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseConsumes(MethodMetadata md, Method method, RequestMapping annotation) {
|
||||
private void parseConsumes(MethodMetadata md, Method method,
|
||||
RequestMapping annotation) {
|
||||
String[] serverConsumes = annotation.consumes();
|
||||
String clientProduces = serverConsumes.length == 0 ? null : emptyToNull(serverConsumes[0]);
|
||||
String clientProduces = serverConsumes.length == 0 ? null
|
||||
: emptyToNull(serverConsumes[0]);
|
||||
if (clientProduces != null) {
|
||||
md.template().header(CONTENT_TYPE, clientProduces);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseHeaders(MethodMetadata md, Method method, RequestMapping annotation) {
|
||||
private void parseHeaders(MethodMetadata md, Method method,
|
||||
RequestMapping annotation) {
|
||||
// TODO: only supports one header value per key
|
||||
if (annotation.headers() != null && annotation.headers().length > 0) {
|
||||
for (String header : annotation.headers()) {
|
||||
@@ -364,21 +393,26 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
|
||||
return annotatedArgumentResolvers;
|
||||
}
|
||||
|
||||
private Annotation synthesizeWithMethodParameterNameAsFallbackValue(Annotation parameterAnnotation, Method method,
|
||||
int parameterIndex) {
|
||||
Map<String, Object> annotationAttributes = AnnotationUtils.getAnnotationAttributes(parameterAnnotation);
|
||||
private Annotation synthesizeWithMethodParameterNameAsFallbackValue(
|
||||
Annotation parameterAnnotation, Method method, int parameterIndex) {
|
||||
Map<String, Object> annotationAttributes = AnnotationUtils
|
||||
.getAnnotationAttributes(parameterAnnotation);
|
||||
Object defaultValue = AnnotationUtils.getDefaultValue(parameterAnnotation);
|
||||
if (defaultValue instanceof String && defaultValue.equals(annotationAttributes.get(AnnotationUtils.VALUE))) {
|
||||
if (defaultValue instanceof String
|
||||
&& defaultValue.equals(annotationAttributes.get(AnnotationUtils.VALUE))) {
|
||||
Type[] parameterTypes = method.getGenericParameterTypes();
|
||||
String[] parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method);
|
||||
if (shouldAddParameterName(parameterIndex, parameterTypes, parameterNames)) {
|
||||
annotationAttributes.put(AnnotationUtils.VALUE, parameterNames[parameterIndex]);
|
||||
annotationAttributes.put(AnnotationUtils.VALUE,
|
||||
parameterNames[parameterIndex]);
|
||||
}
|
||||
}
|
||||
return AnnotationUtils.synthesizeAnnotation(annotationAttributes, parameterAnnotation.annotationType(), null);
|
||||
return AnnotationUtils.synthesizeAnnotation(annotationAttributes,
|
||||
parameterAnnotation.annotationType(), null);
|
||||
}
|
||||
|
||||
private boolean shouldAddParameterName(int parameterIndex, Type[] parameterTypes, String[] parameterNames) {
|
||||
private boolean shouldAddParameterName(int parameterIndex, Type[] parameterTypes,
|
||||
String[] parameterNames) {
|
||||
// has a parameter name
|
||||
return parameterNames != null && parameterNames.length > parameterIndex
|
||||
// has a type
|
||||
@@ -386,12 +420,14 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
|
||||
}
|
||||
|
||||
private boolean isMultipartFormData(MethodMetadata data) {
|
||||
Collection<String> contentTypes = data.template().headers().get(HttpEncoding.CONTENT_TYPE);
|
||||
Collection<String> contentTypes = data.template().headers()
|
||||
.get(HttpEncoding.CONTENT_TYPE);
|
||||
|
||||
if (contentTypes != null && !contentTypes.isEmpty()) {
|
||||
String type = contentTypes.iterator().next();
|
||||
try {
|
||||
return Objects.equals(MediaType.valueOf(type), MediaType.MULTIPART_FORM_DATA);
|
||||
return Objects.equals(MediaType.valueOf(type),
|
||||
MediaType.MULTIPART_FORM_DATA);
|
||||
}
|
||||
catch (InvalidMediaTypeException ignored) {
|
||||
return false;
|
||||
@@ -401,6 +437,25 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Not used internally anymore. Will be removed in the future.
|
||||
*/
|
||||
@Deprecated
|
||||
public static class ConvertingExpander implements Param.Expander {
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
public ConvertingExpander(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String expand(Object value) {
|
||||
return conversionService.convert(value, String.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ConvertingExpanderFactory {
|
||||
|
||||
private final ConversionService conversionService;
|
||||
@@ -411,20 +466,23 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
|
||||
|
||||
Param.Expander getExpander(TypeDescriptor typeDescriptor) {
|
||||
return value -> {
|
||||
Object converted = conversionService.convert(value, typeDescriptor, STRING_TYPE_DESCRIPTOR);
|
||||
Object converted = conversionService.convert(value, typeDescriptor,
|
||||
STRING_TYPE_DESCRIPTOR);
|
||||
return (String) converted;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class SimpleAnnotatedParameterContext implements AnnotatedParameterProcessor.AnnotatedParameterContext {
|
||||
private class SimpleAnnotatedParameterContext
|
||||
implements AnnotatedParameterProcessor.AnnotatedParameterContext {
|
||||
|
||||
private final MethodMetadata methodMetadata;
|
||||
|
||||
private final int parameterIndex;
|
||||
|
||||
SimpleAnnotatedParameterContext(MethodMetadata methodMetadata, int parameterIndex) {
|
||||
SimpleAnnotatedParameterContext(MethodMetadata methodMetadata,
|
||||
int parameterIndex) {
|
||||
this.methodMetadata = methodMetadata;
|
||||
this.parameterIndex = parameterIndex;
|
||||
}
|
||||
@@ -445,7 +503,8 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> setTemplateParameter(String name, Collection<String> rest) {
|
||||
public Collection<String> setTemplateParameter(String name,
|
||||
Collection<String> rest) {
|
||||
return addTemplateParameter(rest, name);
|
||||
}
|
||||
|
||||
|
||||
+12
@@ -2,6 +2,18 @@
|
||||
"groups": [
|
||||
],
|
||||
"properties": [
|
||||
{
|
||||
"name": "feign.hystrix.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "If true, an OpenFeign client will be wrapped with a Hystrix circuit breaker.",
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"name": "feign.autoconfiguration.jackson.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "If true, PageJacksonModule and SortJacksonModule bean will be provided for Jackson page decoding.",
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"name": "feign.circuitbreaker.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\
|
||||
org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration,\
|
||||
org.springframework.cloud.openfeign.FeignAutoConfiguration,\
|
||||
org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\
|
||||
|
||||
+14
-7
@@ -43,8 +43,10 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = DefaultGzipDecoderTests.Application.class,
|
||||
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
|
||||
value = { "spring.application.name=defaultGzipDecoderTests", "feign.compression.response.enabled=true",
|
||||
"feign.compression.response.useGzipDecoder=true", "feign.client.config.default.loggerLevel=full",
|
||||
value = { "spring.application.name=defaultGzipDecoderTests",
|
||||
"feign.compression.response.enabled=true",
|
||||
"feign.compression.response.useGzipDecoder=true",
|
||||
"feign.client.config.default.loggerLevel=full",
|
||||
"logging.level.org.springframework.cloud.openfeign=DEBUG" })
|
||||
@DirtiesContext
|
||||
public class DefaultGzipDecoderTests extends FeignClientFactoryBean {
|
||||
@@ -69,17 +71,20 @@ public class DefaultGzipDecoderTests extends FeignClientFactoryBean {
|
||||
public void testBodyDecompress() {
|
||||
ResponseEntity<Hello> response = testClient().getGzipResponse();
|
||||
assertThat(response).as("response was null").isNotNull();
|
||||
assertThat(response.getStatusCode()).as("wrong status code").isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getStatusCode()).as("wrong status code")
|
||||
.isEqualTo(HttpStatus.OK);
|
||||
Hello hello = response.getBody();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello("hello world via response"));
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world via response"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullBodyDecompress() {
|
||||
ResponseEntity<Hello> response = testClient().getNullResponse();
|
||||
assertThat(response).as("response was null").isNotNull();
|
||||
assertThat(response.getStatusCode()).as("wrong status code").isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getStatusCode()).as("wrong status code")
|
||||
.isEqualTo(HttpStatus.OK);
|
||||
Hello hello = response.getBody();
|
||||
assertThat(hello).as("hello was not null").isNull();
|
||||
assertThat(hello).as("null hello didn't match").isEqualTo(null);
|
||||
@@ -89,10 +94,12 @@ public class DefaultGzipDecoderTests extends FeignClientFactoryBean {
|
||||
public void testCharsetDecompress() {
|
||||
ResponseEntity<Hello> response = testClient().getUtf8Response();
|
||||
assertThat(response).as("response was null").isNotNull();
|
||||
assertThat(response.getStatusCode()).as("wrong status code").isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getStatusCode()).as("wrong status code")
|
||||
.isEqualTo(HttpStatus.OK);
|
||||
Hello hello = response.getBody();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("utf8 hello didn't match").isEqualTo(new Hello("안녕하세요 means Hello in Korean"));
|
||||
assertThat(hello).as("utf8 hello didn't match")
|
||||
.isEqualTo(new Hello("안녕하세요 means Hello in Korean"));
|
||||
}
|
||||
|
||||
private static class Hello {
|
||||
|
||||
+7
-3
@@ -21,10 +21,12 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.support.PageableSpringEncoder;
|
||||
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;
|
||||
|
||||
@@ -42,11 +44,13 @@ public class EnableFeignClientsSpringDataTests {
|
||||
@Test
|
||||
public void encoderDefaultCorrect() {
|
||||
|
||||
PageableSpringEncoder.class.cast(this.feignContext.getInstance("foo", Encoder.class));
|
||||
PageableSpringEncoder.class
|
||||
.cast(this.feignContext.getInstance("foo", Encoder.class));
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableAutoConfiguration
|
||||
@Import({ PropertyPlaceholderAutoConfiguration.class, ArchaiusAutoConfiguration.class,
|
||||
FeignAutoConfiguration.class })
|
||||
protected static class PlainConfiguration {
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -30,6 +30,7 @@ import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.support.SpringEncoder;
|
||||
import org.springframework.cloud.openfeign.support.SpringMvcContract;
|
||||
import org.springframework.cloud.test.ClassPathExclusions;
|
||||
@@ -87,7 +88,7 @@ public class EnableFeignClientsTests {
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import({ FeignAutoConfiguration.class })
|
||||
@Import({ ArchaiusAutoConfiguration.class, FeignAutoConfiguration.class })
|
||||
protected static class PlainConfiguration {
|
||||
|
||||
}
|
||||
|
||||
+9
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2019 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.
|
||||
@@ -41,7 +41,8 @@ public class FeignBuilderCustomizerTests {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
FeignBuilderCustomizerTests.SampleConfiguration2.class);
|
||||
|
||||
FeignClientFactoryBean clientFactoryBean = context.getBean(FeignClientFactoryBean.class);
|
||||
FeignClientFactoryBean clientFactoryBean = context
|
||||
.getBean(FeignClientFactoryBean.class);
|
||||
FeignContext feignContext = context.getBean(FeignContext.class);
|
||||
|
||||
Feign.Builder builder = clientFactoryBean.feign(feignContext);
|
||||
@@ -51,12 +52,14 @@ public class FeignBuilderCustomizerTests {
|
||||
context.close();
|
||||
}
|
||||
|
||||
private void assertFeignBuilderField(Feign.Builder builder, String fieldName, Object expectedValue) {
|
||||
private void assertFeignBuilderField(Feign.Builder builder, String fieldName,
|
||||
Object expectedValue) {
|
||||
Field builderField = ReflectionUtils.findField(Feign.Builder.class, fieldName);
|
||||
ReflectionUtils.makeAccessible(builderField);
|
||||
|
||||
Object value = ReflectionUtils.getField(builderField, builder);
|
||||
assertThat(value).as("Expected value for the field '" + fieldName + "':").isEqualTo(expectedValue);
|
||||
assertThat(value).as("Expected value for the field '" + fieldName + "':")
|
||||
.isEqualTo(expectedValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -64,7 +67,8 @@ public class FeignBuilderCustomizerTests {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
FeignBuilderCustomizerTests.SampleConfiguration3.class);
|
||||
|
||||
FeignClientFactoryBean clientFactoryBean = context.getBean(FeignClientFactoryBean.class);
|
||||
FeignClientFactoryBean clientFactoryBean = context
|
||||
.getBean(FeignClientFactoryBean.class);
|
||||
FeignContext feignContext = context.getBean(FeignContext.class);
|
||||
|
||||
Feign.Builder builder = clientFactoryBean.feign(feignContext);
|
||||
|
||||
+59
-48
@@ -24,6 +24,7 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import feign.hystrix.FallbackFactory;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@@ -49,23 +50,26 @@ public class FeignClientBuilderTests {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private static Object getDefaultValueFromFeignClientAnnotation(final String methodName) {
|
||||
private static Object getDefaultValueFromFeignClientAnnotation(
|
||||
final String methodName) {
|
||||
final Method method = ReflectionUtils.findMethod(FeignClient.class, methodName);
|
||||
return method.getDefaultValue();
|
||||
}
|
||||
|
||||
private static void assertFactoryBeanField(final FeignClientBuilder.Builder builder, final String fieldName,
|
||||
final Object expectedValue) {
|
||||
final Field factoryBeanField = ReflectionUtils.findField(FeignClientBuilder.Builder.class,
|
||||
"feignClientFactoryBean");
|
||||
private static void assertFactoryBeanField(final FeignClientBuilder.Builder builder,
|
||||
final String fieldName, final Object expectedValue) {
|
||||
final Field factoryBeanField = ReflectionUtils
|
||||
.findField(FeignClientBuilder.Builder.class, "feignClientFactoryBean");
|
||||
ReflectionUtils.makeAccessible(factoryBeanField);
|
||||
final FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) ReflectionUtils.getField(factoryBeanField,
|
||||
builder);
|
||||
final FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) ReflectionUtils
|
||||
.getField(factoryBeanField, builder);
|
||||
|
||||
final Field field = ReflectionUtils.findField(FeignClientFactoryBean.class, fieldName);
|
||||
final Field field = ReflectionUtils.findField(FeignClientFactoryBean.class,
|
||||
fieldName);
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
final Object value = ReflectionUtils.getField(field, factoryBean);
|
||||
assertThat(value).as("Expected value for the field '" + fieldName + "':").isEqualTo(expectedValue);
|
||||
assertThat(value).as("Expected value for the field '" + fieldName + "':")
|
||||
.isEqualTo(expectedValue);
|
||||
}
|
||||
|
||||
@Before
|
||||
@@ -80,8 +84,9 @@ public class FeignClientBuilderTests {
|
||||
for (final Method method : FeignClient.class.getMethods()) {
|
||||
methodNames.add(method.getName());
|
||||
}
|
||||
methodNames.removeAll(Arrays.asList("annotationType", "value", "serviceId", "qualifier", "configuration",
|
||||
"primary", "equals", "hashCode", "toString"));
|
||||
methodNames.removeAll(
|
||||
Arrays.asList("annotationType", "value", "serviceId", "qualifier",
|
||||
"configuration", "primary", "equals", "hashCode", "toString"));
|
||||
Collections.sort(methodNames);
|
||||
// If this safety check fails the Builder has to be updated.
|
||||
// (1) Either a field was removed from the FeignClient annotation and so it has to
|
||||
@@ -89,54 +94,44 @@ public class FeignClientBuilderTests {
|
||||
// on this builder class.
|
||||
// (2) Or a new field was added and the builder class has to be extended with this
|
||||
// new field.
|
||||
assertThat(methodNames).containsExactly("contextId", "decode404", "fallback", "fallbackFactory", "name", "path",
|
||||
"url");
|
||||
assertThat(methodNames).containsExactly("contextId", "decode404", "fallback",
|
||||
"fallbackFactory", "name", "path", "url");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forType_preinitializedBuilder() {
|
||||
// when:
|
||||
final FeignClientBuilder.Builder builder = this.feignClientBuilder.forType(TestFeignClient.class, "TestClient");
|
||||
final FeignClientBuilder.Builder builder = this.feignClientBuilder
|
||||
.forType(TestFeignClient.class, "TestClient");
|
||||
|
||||
// then:
|
||||
assertFactoryBeanField(builder, "applicationContext", this.applicationContext);
|
||||
assertFactoryBeanField(builder, "type", TestFeignClient.class);
|
||||
assertFactoryBeanField(builder, "name", "TestClient");
|
||||
assertFactoryBeanField(builder, "contextId", "TestClient");
|
||||
assertFactoryBeanField(builder, "inheritParentContext", true);
|
||||
|
||||
// and:
|
||||
assertFactoryBeanField(builder, "url", getDefaultValueFromFeignClientAnnotation("url"));
|
||||
assertFactoryBeanField(builder, "path", getDefaultValueFromFeignClientAnnotation("path"));
|
||||
assertFactoryBeanField(builder, "decode404", getDefaultValueFromFeignClientAnnotation("decode404"));
|
||||
assertFactoryBeanField(builder, "fallback", getDefaultValueFromFeignClientAnnotation("fallback"));
|
||||
assertFactoryBeanField(builder, "fallbackFactory", getDefaultValueFromFeignClientAnnotation("fallbackFactory"));
|
||||
assertFactoryBeanField(builder, "url",
|
||||
getDefaultValueFromFeignClientAnnotation("url"));
|
||||
assertFactoryBeanField(builder, "path",
|
||||
getDefaultValueFromFeignClientAnnotation("path"));
|
||||
assertFactoryBeanField(builder, "decode404",
|
||||
getDefaultValueFromFeignClientAnnotation("decode404"));
|
||||
assertFactoryBeanField(builder, "fallback",
|
||||
getDefaultValueFromFeignClientAnnotation("fallback"));
|
||||
assertFactoryBeanField(builder, "fallbackFactory",
|
||||
getDefaultValueFromFeignClientAnnotation("fallbackFactory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forType_allFieldsSetOnBuilder() {
|
||||
// when:
|
||||
final FeignClientBuilder.Builder builder = this.feignClientBuilder.forType(TestFeignClient.class, "TestClient")
|
||||
.decode404(true).url("Url/").path("/Path").contextId("TestContext");
|
||||
|
||||
// then:
|
||||
assertFactoryBeanField(builder, "applicationContext", this.applicationContext);
|
||||
assertFactoryBeanField(builder, "type", TestFeignClient.class);
|
||||
assertFactoryBeanField(builder, "name", "TestClient");
|
||||
assertFactoryBeanField(builder, "contextId", "TestContext");
|
||||
|
||||
// and:
|
||||
assertFactoryBeanField(builder, "url", "http://Url/");
|
||||
assertFactoryBeanField(builder, "path", "/Path");
|
||||
assertFactoryBeanField(builder, "decode404", true);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forType_clientFactoryBeanProvided() {
|
||||
// when:
|
||||
final FeignClientBuilder.Builder builder = this.feignClientBuilder
|
||||
.forType(TestFeignClient.class, new FeignClientFactoryBean(), "TestClient").decode404(true)
|
||||
.path("Path/").url("Url/").contextId("TestContext");
|
||||
.forType(TestFeignClient.class, "TestClient").inheritParentContext(false)
|
||||
.fallback(TestFeignClientFallback.class)
|
||||
.fallbackFactory(TestFeignClientFallbackFactory.class).decode404(true)
|
||||
.url("Url/").path("/Path").contextId("TestContext");
|
||||
|
||||
// then:
|
||||
assertFactoryBeanField(builder, "applicationContext", this.applicationContext);
|
||||
@@ -148,19 +143,21 @@ public class FeignClientBuilderTests {
|
||||
assertFactoryBeanField(builder, "url", "http://Url/");
|
||||
assertFactoryBeanField(builder, "path", "/Path");
|
||||
assertFactoryBeanField(builder, "decode404", true);
|
||||
assertFactoryBeanField(builder, "inheritParentContext", false);
|
||||
assertFactoryBeanField(builder, "fallback", TestFeignClientFallback.class);
|
||||
assertFactoryBeanField(builder, "fallbackFactory",
|
||||
TestFeignClientFallbackFactory.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forType_build() {
|
||||
// given:
|
||||
Mockito.when(this.applicationContext.getBean(FeignContext.class)).thenThrow(new ClosedFileSystemException()); // throw
|
||||
// an
|
||||
// unusual
|
||||
// exception
|
||||
// in
|
||||
// the
|
||||
// FeignClientFactoryBean
|
||||
final FeignClientBuilder.Builder builder = this.feignClientBuilder.forType(TestClient.class, "TestClient");
|
||||
Mockito.when(this.applicationContext.getBean(FeignContext.class))
|
||||
.thenThrow(new ClosedFileSystemException()); // throw an unusual exception
|
||||
// in the
|
||||
// FeignClientFactoryBean
|
||||
final FeignClientBuilder.Builder builder = this.feignClientBuilder
|
||||
.forType(TestClient.class, "TestClient");
|
||||
|
||||
// expect: 'the build will fail right after calling build() with the mocked
|
||||
// unusual exception'
|
||||
@@ -172,4 +169,18 @@ public class FeignClientBuilderTests {
|
||||
|
||||
}
|
||||
|
||||
private class TestFeignClientFallback implements TestFeignClient {
|
||||
|
||||
}
|
||||
|
||||
private class TestFeignClientFallbackFactory
|
||||
implements FallbackFactory<TestFeignClient> {
|
||||
|
||||
@Override
|
||||
public TestFeignClientFallback create(Throwable throwable) {
|
||||
return new TestFeignClientFallback();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-2
@@ -67,7 +67,8 @@ public class FeignClientErrorDecoderTests {
|
||||
|
||||
@Test
|
||||
public void errorDecoderInConfiguration() {
|
||||
assertThat(this.context.getInstance("foo", ErrorDecoder.class)).isInstanceOf(ErrorDecoder.Default.class);
|
||||
assertThat(this.context.getInstance("foo", ErrorDecoder.class))
|
||||
.isInstanceOf(ErrorDecoder.Default.class);
|
||||
assertThat(this.context.getInstance("bar", ErrorDecoder.class)).isNull();
|
||||
}
|
||||
|
||||
@@ -89,7 +90,8 @@ public class FeignClientErrorDecoderTests {
|
||||
Map<Method, InvocationHandlerFactory.MethodHandler> dispatch = (Map<Method, InvocationHandlerFactory.MethodHandler>) ReflectionTestUtils
|
||||
.getField(invocationHandler, "dispatch");
|
||||
Method key = new ArrayList<>(dispatch.keySet()).get(0);
|
||||
return ReflectionTestUtils.getField(ReflectionTestUtils.getField(dispatch.get(key), "asyncResponseHandler"),
|
||||
return ReflectionTestUtils.getField(
|
||||
ReflectionTestUtils.getField(dispatch.get(key), "asyncResponseHandler"),
|
||||
"errorDecoder");
|
||||
}
|
||||
|
||||
|
||||
+8
-6
@@ -29,7 +29,6 @@ import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
|
||||
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
|
||||
import org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
@@ -53,7 +52,8 @@ public class FeignClientFactoryTests {
|
||||
parent.refresh();
|
||||
FeignContext context = new FeignContext();
|
||||
context.setApplicationContext(parent);
|
||||
context.setConfigurations(Arrays.asList(getSpec("foo", FooConfig.class), getSpec("bar", BarConfig.class)));
|
||||
context.setConfigurations(Arrays.asList(getSpec("foo", FooConfig.class),
|
||||
getSpec("bar", BarConfig.class)));
|
||||
|
||||
Foo foo = context.getInstance("foo", Foo.class);
|
||||
assertThat(foo).as("foo was null").isNotNull();
|
||||
@@ -67,7 +67,8 @@ public class FeignClientFactoryTests {
|
||||
|
||||
@Test
|
||||
public void shouldRedirectToDelegateWhenUrlSet() {
|
||||
new ApplicationContextRunner().withUserConfiguration(TestConfig.class).run(this::defaultClientUsed);
|
||||
new ApplicationContextRunner().withUserConfiguration(TestConfig.class)
|
||||
.run(this::defaultClientUsed);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "ConstantConditions" })
|
||||
@@ -97,14 +98,15 @@ public class FeignClientFactoryTests {
|
||||
|
||||
@Bean
|
||||
BlockingLoadBalancerClient loadBalancerClient() {
|
||||
return new BlockingLoadBalancerClient(new LoadBalancerClientFactory(), new LoadBalancerProperties());
|
||||
return new BlockingLoadBalancerClient(new LoadBalancerClientFactory());
|
||||
}
|
||||
|
||||
@Bean
|
||||
FeignContext feignContext() {
|
||||
FeignContext feignContext = new FeignContext();
|
||||
feignContext.setConfigurations(Collections.singletonList(
|
||||
new FeignClientSpecification("test", new Class[] { LoadBalancerAutoConfiguration.class })));
|
||||
feignContext.setConfigurations(
|
||||
Collections.singletonList(new FeignClientSpecification("test",
|
||||
new Class[] { LoadBalancerAutoConfiguration.class })));
|
||||
return feignContext;
|
||||
}
|
||||
|
||||
|
||||
+34
-10
@@ -18,6 +18,7 @@ package org.springframework.cloud.openfeign;
|
||||
|
||||
import feign.Contract;
|
||||
import feign.ExceptionPropagationPolicy;
|
||||
import feign.Feign;
|
||||
import feign.Logger;
|
||||
import feign.QueryMapEncoder;
|
||||
import feign.Request;
|
||||
@@ -28,18 +29,21 @@ import feign.auth.BasicAuthRequestInterceptor;
|
||||
import feign.codec.Decoder;
|
||||
import feign.codec.Encoder;
|
||||
import feign.codec.ErrorDecoder;
|
||||
import feign.hystrix.HystrixFeign;
|
||||
import feign.optionals.OptionalDecoder;
|
||||
import feign.querymap.BeanQueryMapEncoder;
|
||||
import feign.slf4j.Slf4jLogger;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.support.PageableSpringEncoder;
|
||||
import org.springframework.cloud.openfeign.support.SpringMvcContract;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
@@ -95,12 +99,14 @@ class FeignClientOverrideDefaultsTests {
|
||||
@Test
|
||||
void overrideLoggerLevel() {
|
||||
assertThat(context.getInstance("foo", Logger.Level.class)).isNull();
|
||||
assertThat(context.getInstance("bar", Logger.Level.class)).isEqualTo(Logger.Level.HEADERS);
|
||||
assertThat(context.getInstance("bar", Logger.Level.class))
|
||||
.isEqualTo(Logger.Level.HEADERS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void overrideRetryer() {
|
||||
assertThat(context.getInstance("foo", Retryer.class)).isEqualTo(Retryer.NEVER_RETRY);
|
||||
assertThat(context.getInstance("foo", Retryer.class))
|
||||
.isEqualTo(Retryer.NEVER_RETRY);
|
||||
Retryer.Default.class.cast(context.getInstance("bar", Retryer.class));
|
||||
}
|
||||
|
||||
@@ -110,6 +116,12 @@ class FeignClientOverrideDefaultsTests {
|
||||
ErrorDecoder.Default.class.cast(context.getInstance("bar", ErrorDecoder.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void overrideBuilder() {
|
||||
HystrixFeign.Builder.class.cast(context.getInstance("foo", Feign.Builder.class));
|
||||
Feign.Builder.class.cast(context.getInstance("bar", Feign.Builder.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void overrideRequestOptions() {
|
||||
assertThat(context.getInstance("foo", Request.Options.class)).isNull();
|
||||
@@ -120,24 +132,29 @@ class FeignClientOverrideDefaultsTests {
|
||||
|
||||
@Test
|
||||
void overrideQueryMapEncoder() {
|
||||
QueryMapEncoder.Default.class.cast(context.getInstance("foo", QueryMapEncoder.class));
|
||||
QueryMapEncoder.Default.class
|
||||
.cast(context.getInstance("foo", QueryMapEncoder.class));
|
||||
BeanQueryMapEncoder.class.cast(context.getInstance("bar", QueryMapEncoder.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void addRequestInterceptor() {
|
||||
assertThat(context.getInstances("foo", RequestInterceptor.class).size()).isEqualTo(1);
|
||||
assertThat(context.getInstances("bar", RequestInterceptor.class).size()).isEqualTo(2);
|
||||
assertThat(context.getInstances("foo", RequestInterceptor.class).size())
|
||||
.isEqualTo(1);
|
||||
assertThat(context.getInstances("bar", RequestInterceptor.class).size())
|
||||
.isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void exceptionPropagationPolicy() {
|
||||
assertThat(context.getInstances("foo", ExceptionPropagationPolicy.class)).isEmpty();
|
||||
assertThat(context.getInstances("foo", ExceptionPropagationPolicy.class))
|
||||
.isEmpty();
|
||||
assertThat(context.getInstances("bar", ExceptionPropagationPolicy.class))
|
||||
.containsValues(ExceptionPropagationPolicy.UNWRAP);
|
||||
}
|
||||
|
||||
@FeignClient(name = "foo", url = "https://foo", configuration = FooConfiguration.class)
|
||||
@FeignClient(name = "foo", url = "https://foo",
|
||||
configuration = FooConfiguration.class)
|
||||
interface FooClient {
|
||||
|
||||
@RequestLine("GET /")
|
||||
@@ -145,7 +162,8 @@ class FeignClientOverrideDefaultsTests {
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "bar", url = "https://bar", configuration = BarConfiguration.class)
|
||||
@FeignClient(name = "bar", url = "https://bar",
|
||||
configuration = BarConfiguration.class)
|
||||
interface BarClient {
|
||||
|
||||
@RequestMapping(value = "/", method = RequestMethod.GET)
|
||||
@@ -155,7 +173,8 @@ class FeignClientOverrideDefaultsTests {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableFeignClients(clients = { FooClient.class, BarClient.class })
|
||||
@EnableAutoConfiguration
|
||||
@Import({ PropertyPlaceholderAutoConfiguration.class, ArchaiusAutoConfiguration.class,
|
||||
FeignAutoConfiguration.class })
|
||||
protected static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
@@ -188,6 +207,11 @@ class FeignClientOverrideDefaultsTests {
|
||||
return new Contract.Default();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Feign.Builder feignBuilder() {
|
||||
return HystrixFeign.builder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public QueryMapEncoder queryMapEncoder() {
|
||||
return new feign.QueryMapEncoder.Default();
|
||||
|
||||
+12
-6
@@ -64,9 +64,11 @@ public class FeignClientUsingConfigurerTest {
|
||||
.getAttribute("feignClientsRegistrarFactoryBean");
|
||||
Feign.Builder builder = factoryBean.feign(context);
|
||||
|
||||
List<RequestInterceptor> interceptors = (List) getBuilderValue(builder, "requestInterceptors");
|
||||
List<RequestInterceptor> interceptors = (List) getBuilderValue(builder,
|
||||
"requestInterceptors");
|
||||
assertThat(interceptors.size()).as("interceptors not set").isEqualTo(3);
|
||||
assertThat(getBuilderValue(builder, "logLevel")).as("log level not set").isEqualTo(Logger.Level.FULL);
|
||||
assertThat(getBuilderValue(builder, "logLevel")).as("log level not set")
|
||||
.isEqualTo(Logger.Level.FULL);
|
||||
}
|
||||
|
||||
private Object getBuilderValue(Feign.Builder builder, String member) {
|
||||
@@ -83,10 +85,12 @@ public class FeignClientUsingConfigurerTest {
|
||||
.getAttribute("feignClientsRegistrarFactoryBean");
|
||||
Feign.Builder builder = factoryBean.feign(context);
|
||||
|
||||
List<RequestInterceptor> interceptors = (List) getBuilderValue(builder, "requestInterceptors");
|
||||
List<RequestInterceptor> interceptors = (List) getBuilderValue(builder,
|
||||
"requestInterceptors");
|
||||
|
||||
assertThat(interceptors).as("interceptors not set").isEmpty();
|
||||
assertThat(factoryBean.isInheritParentContext()).as("is inheriting from parent configuration").isFalse();
|
||||
assertThat(factoryBean.isInheritParentContext())
|
||||
.as("is inheriting from parent configuration").isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -96,7 +100,8 @@ public class FeignClientUsingConfigurerTest {
|
||||
.getAttribute("feignClientsRegistrarFactoryBean");
|
||||
Feign.Builder builder = factoryBean.feign(context);
|
||||
|
||||
assertThat(getBuilderValue(builder, "logLevel")).as("log level not set").isEqualTo(Logger.Level.HEADERS);
|
||||
assertThat(getBuilderValue(builder, "logLevel")).as("log level not set")
|
||||
.isEqualTo(Logger.Level.HEADERS);
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@@ -138,7 +143,8 @@ public class FeignClientUsingConfigurerTest {
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "noInheritFeignClient", configuration = NoInheritConfiguration.class)
|
||||
@FeignClient(name = "noInheritFeignClient",
|
||||
configuration = NoInheritConfiguration.class)
|
||||
interface NoInheritFeignClient {
|
||||
|
||||
}
|
||||
|
||||
+48
-28
@@ -79,7 +79,8 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
|
||||
*/
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = FeignClientUsingPropertiesTests.Application.class, webEnvironment = RANDOM_PORT)
|
||||
@SpringBootTest(classes = FeignClientUsingPropertiesTests.Application.class,
|
||||
webEnvironment = RANDOM_PORT)
|
||||
@TestPropertySource("classpath:feign-properties.properties")
|
||||
@DirtiesContext
|
||||
public class FeignClientUsingPropertiesTests {
|
||||
@@ -123,32 +124,40 @@ public class FeignClientUsingPropertiesTests {
|
||||
formFactoryBean.setType(FeignClientFactoryBean.class);
|
||||
|
||||
this.defaultHeadersAndQuerySingleParamsFeignClientFactoryBean = new FeignClientFactoryBean();
|
||||
this.defaultHeadersAndQuerySingleParamsFeignClientFactoryBean.setContextId("singleValue");
|
||||
this.defaultHeadersAndQuerySingleParamsFeignClientFactoryBean.setType(FeignClientFactoryBean.class);
|
||||
this.defaultHeadersAndQuerySingleParamsFeignClientFactoryBean
|
||||
.setContextId("singleValue");
|
||||
this.defaultHeadersAndQuerySingleParamsFeignClientFactoryBean
|
||||
.setType(FeignClientFactoryBean.class);
|
||||
|
||||
this.defaultHeadersAndQueryMultipleParamsFeignClientFactoryBean = new FeignClientFactoryBean();
|
||||
this.defaultHeadersAndQueryMultipleParamsFeignClientFactoryBean.setContextId("multipleValue");
|
||||
this.defaultHeadersAndQueryMultipleParamsFeignClientFactoryBean.setType(FeignClientFactoryBean.class);
|
||||
this.defaultHeadersAndQueryMultipleParamsFeignClientFactoryBean
|
||||
.setContextId("multipleValue");
|
||||
this.defaultHeadersAndQueryMultipleParamsFeignClientFactoryBean
|
||||
.setType(FeignClientFactoryBean.class);
|
||||
}
|
||||
|
||||
public FooClient fooClient() {
|
||||
fooFactoryBean.setApplicationContext(applicationContext);
|
||||
return fooFactoryBean.feign(context).target(FooClient.class, "http://localhost:" + port);
|
||||
return fooFactoryBean.feign(context).target(FooClient.class,
|
||||
"http://localhost:" + port);
|
||||
}
|
||||
|
||||
public BarClient barClient() {
|
||||
barFactoryBean.setApplicationContext(applicationContext);
|
||||
return barFactoryBean.feign(context).target(BarClient.class, "http://localhost:" + port);
|
||||
return barFactoryBean.feign(context).target(BarClient.class,
|
||||
"http://localhost:" + port);
|
||||
}
|
||||
|
||||
public UnwrapClient unwrapClient() {
|
||||
unwrapFactoryBean.setApplicationContext(applicationContext);
|
||||
return unwrapFactoryBean.feign(context).target(UnwrapClient.class, "http://localhost:" + port);
|
||||
return unwrapFactoryBean.feign(context).target(UnwrapClient.class,
|
||||
"http://localhost:" + port);
|
||||
}
|
||||
|
||||
public FormClient formClient() {
|
||||
formFactoryBean.setApplicationContext(applicationContext);
|
||||
return formFactoryBean.feign(context).target(FormClient.class, "http://localhost:" + port);
|
||||
return formFactoryBean.feign(context).target(FormClient.class,
|
||||
"http://localhost:" + port);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -185,18 +194,23 @@ public class FeignClientUsingPropertiesTests {
|
||||
@Test
|
||||
public void testMultipleValue() {
|
||||
List<String> response = multipleValueClient().multipleValue();
|
||||
assertThat(response).isEqualTo(Arrays.asList("header1", "header2", "parameter1", "parameter2"));
|
||||
assertThat(response).isEqualTo(
|
||||
Arrays.asList("header1", "header2", "parameter1", "parameter2"));
|
||||
}
|
||||
|
||||
public SingleValueClient singleValueClient() {
|
||||
this.defaultHeadersAndQuerySingleParamsFeignClientFactoryBean.setApplicationContext(this.applicationContext);
|
||||
return this.defaultHeadersAndQuerySingleParamsFeignClientFactoryBean.feign(this.context)
|
||||
this.defaultHeadersAndQuerySingleParamsFeignClientFactoryBean
|
||||
.setApplicationContext(this.applicationContext);
|
||||
return this.defaultHeadersAndQuerySingleParamsFeignClientFactoryBean
|
||||
.feign(this.context)
|
||||
.target(SingleValueClient.class, "http://localhost:" + this.port);
|
||||
}
|
||||
|
||||
public MultipleValueClient multipleValueClient() {
|
||||
this.defaultHeadersAndQueryMultipleParamsFeignClientFactoryBean.setApplicationContext(this.applicationContext);
|
||||
return this.defaultHeadersAndQueryMultipleParamsFeignClientFactoryBean.feign(this.context)
|
||||
this.defaultHeadersAndQueryMultipleParamsFeignClientFactoryBean
|
||||
.setApplicationContext(this.applicationContext);
|
||||
return this.defaultHeadersAndQueryMultipleParamsFeignClientFactoryBean
|
||||
.feign(this.context)
|
||||
.target(MultipleValueClient.class, "http://localhost:" + this.port);
|
||||
}
|
||||
|
||||
@@ -207,8 +221,8 @@ public class FeignClientUsingPropertiesTests {
|
||||
readTimeoutFactoryBean.setType(FeignClientFactoryBean.class);
|
||||
readTimeoutFactoryBean.setApplicationContext(applicationContext);
|
||||
|
||||
TimeoutClient client = readTimeoutFactoryBean.feign(context).target(TimeoutClient.class,
|
||||
"http://localhost:" + port);
|
||||
TimeoutClient client = readTimeoutFactoryBean.feign(context)
|
||||
.target(TimeoutClient.class, "http://localhost:" + port);
|
||||
|
||||
Request.Options options = getRequestOptions((Proxy) client);
|
||||
|
||||
@@ -223,8 +237,8 @@ public class FeignClientUsingPropertiesTests {
|
||||
readTimeoutFactoryBean.setType(FeignClientFactoryBean.class);
|
||||
readTimeoutFactoryBean.setApplicationContext(applicationContext);
|
||||
|
||||
TimeoutClient client = readTimeoutFactoryBean.feign(context).target(TimeoutClient.class,
|
||||
"http://localhost:" + port);
|
||||
TimeoutClient client = readTimeoutFactoryBean.feign(context)
|
||||
.target(TimeoutClient.class, "http://localhost:" + port);
|
||||
|
||||
Request.Options options = getRequestOptions((Proxy) client);
|
||||
|
||||
@@ -237,7 +251,8 @@ public class FeignClientUsingPropertiesTests {
|
||||
Map<Method, InvocationHandlerFactory.MethodHandler> dispatch = (Map<Method, InvocationHandlerFactory.MethodHandler>) ReflectionTestUtils
|
||||
.getField(Objects.requireNonNull(invocationHandler), "dispatch");
|
||||
Method key = new ArrayList<>(dispatch.keySet()).get(0);
|
||||
return (Request.Options) ReflectionTestUtils.getField(dispatch.get(key), "options");
|
||||
return (Request.Options) ReflectionTestUtils.getField(dispatch.get(key),
|
||||
"options");
|
||||
}
|
||||
|
||||
protected interface FooClient {
|
||||
@@ -298,7 +313,8 @@ public class FeignClientUsingPropertiesTests {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/foo")
|
||||
public String foo(HttpServletRequest request) throws IllegalAccessException {
|
||||
if ("Foo".equals(request.getHeader("Foo")) && "Bar".equals(request.getHeader("Bar"))) {
|
||||
if ("Foo".equals(request.getHeader("Foo"))
|
||||
&& "Bar".equals(request.getHeader("Bar"))) {
|
||||
return "OK";
|
||||
}
|
||||
else {
|
||||
@@ -312,7 +328,8 @@ public class FeignClientUsingPropertiesTests {
|
||||
return "OK";
|
||||
}
|
||||
|
||||
@PostMapping(path = "/form", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
@PostMapping(path = "/form",
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
|
||||
public String form(HttpServletRequest request) {
|
||||
return request.getParameter("form");
|
||||
}
|
||||
@@ -320,15 +337,16 @@ public class FeignClientUsingPropertiesTests {
|
||||
@GetMapping(path = "/singleValue")
|
||||
public List<String> singleValue(@RequestHeader List<String> singleValueHeaders,
|
||||
@RequestParam List<String> singleValueParameters) {
|
||||
return Stream.of(singleValueHeaders, singleValueParameters).flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
return Stream.of(singleValueHeaders, singleValueParameters)
|
||||
.flatMap(Collection::stream).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@GetMapping(path = "/multipleValue")
|
||||
public List<String> multipleValue(@RequestHeader List<String> multipleValueHeaders,
|
||||
public List<String> multipleValue(
|
||||
@RequestHeader List<String> multipleValueHeaders,
|
||||
@RequestParam List<String> multipleValueParameters) {
|
||||
return Stream.of(multipleValueHeaders, multipleValueParameters).flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
return Stream.of(multipleValueHeaders, multipleValueParameters)
|
||||
.flatMap(Collection::stream).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -372,14 +390,16 @@ public class FeignClientUsingPropertiesTests {
|
||||
public static class FormEncoder implements Encoder {
|
||||
|
||||
@Override
|
||||
public void encode(Object o, Type type, RequestTemplate requestTemplate) throws EncodeException {
|
||||
public void encode(Object o, Type type, RequestTemplate requestTemplate)
|
||||
throws EncodeException {
|
||||
Map<String, String> form = (Map<String, String>) o;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
form.forEach((key, value) -> {
|
||||
builder.append(key + "=" + value + "&");
|
||||
});
|
||||
|
||||
requestTemplate.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||
requestTemplate.header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||
requestTemplate.body(builder.toString());
|
||||
}
|
||||
|
||||
|
||||
+12
-6
@@ -22,6 +22,7 @@ import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.test.TestAutoConfiguration;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
@@ -95,15 +96,17 @@ public class FeignClientsRegistrarTests {
|
||||
@Test
|
||||
public void shouldPassSubLevelFeignClient() {
|
||||
AnnotationConfigApplicationContext config = new AnnotationConfigApplicationContext();
|
||||
((DefaultListableBeanFactory) config.getBeanFactory()).setAllowBeanDefinitionOverriding(false);
|
||||
((DefaultListableBeanFactory) config.getBeanFactory())
|
||||
.setAllowBeanDefinitionOverriding(false);
|
||||
config.register(TopLevelSubLevelTestConfig.class);
|
||||
assertThatCode(() -> config.refresh())
|
||||
.as("Case https://github.com/spring-cloud/spring-cloud-openfeign/issues/331 should be solved")
|
||||
assertThatCode(() -> config.refresh()).as(
|
||||
"Case https://github.com/spring-cloud/spring-cloud-openfeign/issues/331 should be solved")
|
||||
.doesNotThrowAnyException();
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "fallbackTestClient", url = "http://localhost:8080/", fallback = FallbackClient.class)
|
||||
@FeignClient(name = "fallbackTestClient", url = "http://localhost:8080/",
|
||||
fallback = FallbackClient.class)
|
||||
protected interface FallbackClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
@@ -129,13 +132,16 @@ public class FeignClientsRegistrarTests {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableAutoConfiguration
|
||||
@EnableFeignClients(clients = { FeignClientsRegistrarTests.FallbackFactoryClient.class })
|
||||
@EnableFeignClients(
|
||||
clients = { FeignClientsRegistrarTests.FallbackFactoryClient.class })
|
||||
protected static class FallbackFactoryTestConfig {
|
||||
|
||||
}
|
||||
|
||||
@EnableFeignClients(clients = { org.springframework.cloud.openfeign.feignclientsregistrar.TopLevelClient.class,
|
||||
@EnableFeignClients(clients = {
|
||||
org.springframework.cloud.openfeign.feignclientsregistrar.TopLevelClient.class,
|
||||
org.springframework.cloud.openfeign.feignclientsregistrar.sub.SubLevelClient.class })
|
||||
@EnableAutoConfiguration(exclude = TestAutoConfiguration.class)
|
||||
protected static class TopLevelSubLevelTestConfig {
|
||||
|
||||
}
|
||||
|
||||
+9
-5
@@ -28,6 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.commons.httpclient.HttpClientConfiguration;
|
||||
import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingInterceptor;
|
||||
import org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration;
|
||||
@@ -50,15 +51,18 @@ public class FeignCompressionTests {
|
||||
@Test
|
||||
public void testInterceptors() {
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("feign.compression.response.enabled=true", "feign.compression.request.enabled=true",
|
||||
.withPropertyValues("feign.compression.response.enabled=true",
|
||||
"feign.compression.request.enabled=true",
|
||||
"feign.okhttp.enabled=false")
|
||||
.withConfiguration(AutoConfigurations.of(FeignAutoConfiguration.class,
|
||||
FeignContentGzipEncodingAutoConfiguration.class, FeignAcceptGzipEncodingAutoConfiguration.class,
|
||||
.withConfiguration(AutoConfigurations.of(ArchaiusAutoConfiguration.class,
|
||||
FeignAutoConfiguration.class,
|
||||
FeignContentGzipEncodingAutoConfiguration.class,
|
||||
FeignAcceptGzipEncodingAutoConfiguration.class,
|
||||
HttpClientConfiguration.class, PlainConfig.class))
|
||||
.run(context -> {
|
||||
FeignContext feignContext = context.getBean(FeignContext.class);
|
||||
Map<String, RequestInterceptor> interceptors = feignContext.getInstances("foo",
|
||||
RequestInterceptor.class);
|
||||
Map<String, RequestInterceptor> interceptors = feignContext
|
||||
.getInstances("foo", RequestInterceptor.class);
|
||||
assertThat(interceptors.size()).isEqualTo(2);
|
||||
assertThat(interceptors.get("feignAcceptGzipEncodingInterceptor"))
|
||||
.isInstanceOf(FeignAcceptGzipEncodingInterceptor.class);
|
||||
|
||||
+12
-6
@@ -39,9 +39,11 @@ public class FeignContextTest {
|
||||
|
||||
FeignContext feignContext = new FeignContext();
|
||||
feignContext.setApplicationContext(parent);
|
||||
feignContext.setConfigurations(Lists.newArrayList(getSpec("empty", EmptyConfiguration.class)));
|
||||
feignContext.setConfigurations(
|
||||
Lists.newArrayList(getSpec("empty", EmptyConfiguration.class)));
|
||||
|
||||
Logger.Level level = feignContext.getInstanceWithoutAncestors("empty", Logger.Level.class);
|
||||
Logger.Level level = feignContext.getInstanceWithoutAncestors("empty",
|
||||
Logger.Level.class);
|
||||
|
||||
assertThat(level).as("Logger was not null").isNull();
|
||||
}
|
||||
@@ -57,7 +59,8 @@ public class FeignContextTest {
|
||||
|
||||
FeignContext feignContext = new FeignContext();
|
||||
feignContext.setApplicationContext(parent);
|
||||
feignContext.setConfigurations(Lists.newArrayList(getSpec("empty", EmptyConfiguration.class)));
|
||||
feignContext.setConfigurations(
|
||||
Lists.newArrayList(getSpec("empty", EmptyConfiguration.class)));
|
||||
|
||||
Collection<RequestInterceptor> interceptors = feignContext
|
||||
.getInstancesWithoutAncestors("empty", RequestInterceptor.class).values();
|
||||
@@ -72,9 +75,11 @@ public class FeignContextTest {
|
||||
|
||||
FeignContext feignContext = new FeignContext();
|
||||
feignContext.setApplicationContext(parent);
|
||||
feignContext.setConfigurations(Lists.newArrayList(getSpec("demo", DemoConfiguration.class)));
|
||||
feignContext.setConfigurations(
|
||||
Lists.newArrayList(getSpec("demo", DemoConfiguration.class)));
|
||||
|
||||
Logger.Level level = feignContext.getInstanceWithoutAncestors("demo", Logger.Level.class);
|
||||
Logger.Level level = feignContext.getInstanceWithoutAncestors("demo",
|
||||
Logger.Level.class);
|
||||
|
||||
assertThat(level).isEqualTo(Logger.Level.FULL);
|
||||
}
|
||||
@@ -86,7 +91,8 @@ public class FeignContextTest {
|
||||
|
||||
FeignContext feignContext = new FeignContext();
|
||||
feignContext.setApplicationContext(parent);
|
||||
feignContext.setConfigurations(Lists.newArrayList(getSpec("demo", DemoConfiguration.class)));
|
||||
feignContext.setConfigurations(
|
||||
Lists.newArrayList(getSpec("demo", DemoConfiguration.class)));
|
||||
|
||||
Collection<RequestInterceptor> interceptors = feignContext
|
||||
.getInstancesWithoutAncestors("demo", RequestInterceptor.class).values();
|
||||
|
||||
+12
-6
@@ -34,16 +34,20 @@ public class FeignErrorDecoderFactoryTests {
|
||||
|
||||
@Test
|
||||
public void testNoDefaultFactory() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SampleConfiguration1.class);
|
||||
String[] beanNamesForType = context.getBeanNamesForType(FeignErrorDecoderFactory.class);
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
SampleConfiguration1.class);
|
||||
String[] beanNamesForType = context
|
||||
.getBeanNamesForType(FeignErrorDecoderFactory.class);
|
||||
assertThat(beanNamesForType).isEmpty();
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomErrorDecoderFactory() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SampleConfiguration2.class);
|
||||
FeignErrorDecoderFactory errorDecoderFactory = context.getBean(FeignErrorDecoderFactory.class);
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
SampleConfiguration2.class);
|
||||
FeignErrorDecoderFactory errorDecoderFactory = context
|
||||
.getBean(FeignErrorDecoderFactory.class);
|
||||
assertThat(errorDecoderFactory).isNotNull();
|
||||
ErrorDecoder errorDecoder = errorDecoderFactory.create(Object.class);
|
||||
assertThat(errorDecoder).isNotNull();
|
||||
@@ -53,8 +57,10 @@ public class FeignErrorDecoderFactoryTests {
|
||||
|
||||
@Test
|
||||
public void testCustomErrorDecoderFactoryNotOverwritingErrorDecoder() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SampleConfiguration3.class);
|
||||
FeignErrorDecoderFactory errorDecoderFactory = context.getBean(FeignErrorDecoderFactory.class);
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
SampleConfiguration3.class);
|
||||
FeignErrorDecoderFactory errorDecoderFactory = context
|
||||
.getBean(FeignErrorDecoderFactory.class);
|
||||
assertThat(errorDecoderFactory).isNotNull();
|
||||
ErrorDecoder errorDecoderFromFactory = errorDecoderFactory.create(Object.class);
|
||||
assertThat(errorDecoderFromFactory).isNotNull();
|
||||
|
||||
+16
-8
@@ -53,8 +53,10 @@ public class FeignHttpClientConfigurationTests {
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.context = new SpringApplicationBuilder()
|
||||
.properties("debug=true", "feign.httpclient.disableSslValidation=true").web(WebApplicationType.NONE)
|
||||
.sources(HttpClientConfiguration.class, FeignAutoConfiguration.class).run();
|
||||
.properties("debug=true", "feign.httpclient.disableSslValidation=true")
|
||||
.web(WebApplicationType.NONE)
|
||||
.sources(HttpClientConfiguration.class, FeignAutoConfiguration.class)
|
||||
.run();
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -66,10 +68,13 @@ public class FeignHttpClientConfigurationTests {
|
||||
|
||||
@Test
|
||||
public void disableSslTest() throws Exception {
|
||||
HttpClientConnectionManager connectionManager = this.context.getBean(HttpClientConnectionManager.class);
|
||||
Lookup<ConnectionSocketFactory> socketFactoryRegistry = getConnectionSocketFactoryLookup(connectionManager);
|
||||
HttpClientConnectionManager connectionManager = this.context
|
||||
.getBean(HttpClientConnectionManager.class);
|
||||
Lookup<ConnectionSocketFactory> socketFactoryRegistry = getConnectionSocketFactoryLookup(
|
||||
connectionManager);
|
||||
assertThat(socketFactoryRegistry.lookup("https")).isNotNull();
|
||||
assertThat(this.getX509TrustManager(socketFactoryRegistry).getAcceptedIssuers()).isNull();
|
||||
assertThat(this.getX509TrustManager(socketFactoryRegistry).getAcceptedIssuers())
|
||||
.isNull();
|
||||
}
|
||||
|
||||
private Lookup<ConnectionSocketFactory> getConnectionSocketFactoryLookup(
|
||||
@@ -79,11 +84,14 @@ public class FeignHttpClientConfigurationTests {
|
||||
return (Lookup) this.getField(connectionOperator, "socketFactoryRegistry");
|
||||
}
|
||||
|
||||
private X509TrustManager getX509TrustManager(Lookup<ConnectionSocketFactory> socketFactoryRegistry) {
|
||||
private X509TrustManager getX509TrustManager(
|
||||
Lookup<ConnectionSocketFactory> socketFactoryRegistry) {
|
||||
ConnectionSocketFactory connectionSocketFactory = (ConnectionSocketFactory) socketFactoryRegistry
|
||||
.lookup("https");
|
||||
SSLSocketFactory sslSocketFactory = (SSLSocketFactory) this.getField(connectionSocketFactory, "socketfactory");
|
||||
SSLContextSpi sslContext = (SSLContextSpi) this.getField(sslSocketFactory, "context");
|
||||
SSLSocketFactory sslSocketFactory = (SSLSocketFactory) this
|
||||
.getField(connectionSocketFactory, "socketfactory");
|
||||
SSLContextSpi sslContext = (SSLContextSpi) this.getField(sslSocketFactory,
|
||||
"context");
|
||||
return (X509TrustManager) this.getField(sslContext, "trustManager");
|
||||
}
|
||||
|
||||
|
||||
+38
-28
@@ -23,9 +23,10 @@ import feign.Client;
|
||||
import feign.Feign;
|
||||
import feign.Target;
|
||||
import feign.httpclient.ApacheHttpClient;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
@@ -35,6 +36,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.SocketUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -47,11 +49,13 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@SpringBootTest(classes = FeignHttpClientUrlTests.TestConfig.class, webEnvironment = DEFINED_PORT,
|
||||
value = { "spring.application.name=feignclienturltest", "feign.circuitbreaker.enabled=false",
|
||||
"feign.okhttp.enabled=false", "spring.cloud.loadbalancer.retry.enabled=false" })
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(classes = FeignHttpClientUrlTests.TestConfig.class,
|
||||
webEnvironment = DEFINED_PORT,
|
||||
value = { "spring.application.name=feignclienturltest",
|
||||
"feign.hystrix.enabled=false", "feign.okhttp.enabled=false" })
|
||||
@DirtiesContext
|
||||
class FeignHttpClientUrlTests {
|
||||
public class FeignHttpClientUrlTests {
|
||||
|
||||
static int port;
|
||||
|
||||
@@ -64,37 +68,40 @@ class FeignHttpClientUrlTests {
|
||||
@Autowired
|
||||
private BeanUrlClient beanClient;
|
||||
|
||||
@BeforeAll
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
port = SocketUtils.findAvailableTcpPort();
|
||||
System.setProperty("server.port", String.valueOf(port));
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
@AfterClass
|
||||
public static void afterClass() {
|
||||
System.clearProperty("server.port");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUrlHttpClient() {
|
||||
assertThat(urlClient).as("UrlClient was null").isNotNull();
|
||||
Hello hello = urlClient.getHello();
|
||||
public void testUrlHttpClient() {
|
||||
assertThat(this.urlClient).as("UrlClient was null").isNotNull();
|
||||
Hello hello = this.urlClient.getHello();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello("hello world 1"));
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world 1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBeanUrl() {
|
||||
Hello hello = beanClient.getHello();
|
||||
public void testBeanUrl() {
|
||||
Hello hello = this.beanClient.getHello();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello("hello world 1"));
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world 1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanUrlNoProtocol() {
|
||||
Hello hello = beanClientNoProtocol.getHello();
|
||||
Hello hello = this.beanClientNoProtocol.getHello();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello("hello world 1"));
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world 1"));
|
||||
}
|
||||
|
||||
// this tests that
|
||||
@@ -125,7 +132,8 @@ class FeignHttpClientUrlTests {
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@EnableFeignClients(clients = { UrlClient.class, BeanUrlClient.class, BeanUrlClientNoProtocol.class })
|
||||
@EnableFeignClients(clients = { UrlClient.class, BeanUrlClient.class,
|
||||
BeanUrlClientNoProtocol.class })
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
protected static class TestConfig {
|
||||
|
||||
@@ -153,13 +161,15 @@ class FeignHttpClientUrlTests {
|
||||
public Targeter feignTargeter() {
|
||||
return new Targeter() {
|
||||
@Override
|
||||
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
|
||||
Target.HardCodedTarget<T> target) {
|
||||
Field field = ReflectionUtils.findField(Feign.Builder.class, "client");
|
||||
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign,
|
||||
FeignContext context, Target.HardCodedTarget<T> target) {
|
||||
Field field = ReflectionUtils.findField(Feign.Builder.class,
|
||||
"client");
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
Client client = (Client) ReflectionUtils.getField(field, feign);
|
||||
if (target.name().equals("localappurl")) {
|
||||
assertThat(client).isInstanceOf(ApacheHttpClient.class).as("client was wrong type");
|
||||
assertThat(client).isInstanceOf(ApacheHttpClient.class)
|
||||
.as("client was wrong type");
|
||||
}
|
||||
return feign.target(target);
|
||||
}
|
||||
@@ -172,15 +182,15 @@ class FeignHttpClientUrlTests {
|
||||
|
||||
private String message;
|
||||
|
||||
Hello() {
|
||||
public Hello() {
|
||||
}
|
||||
|
||||
Hello(String message) {
|
||||
public Hello(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
return this.message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
@@ -196,12 +206,12 @@ class FeignHttpClientUrlTests {
|
||||
return false;
|
||||
}
|
||||
Hello that = (Hello) o;
|
||||
return Objects.equals(message, that.message);
|
||||
return Objects.equals(this.message, that.message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(message);
|
||||
return Objects.hash(this.message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-3
@@ -34,7 +34,8 @@ public class FeignLoggerFactoryTests {
|
||||
|
||||
@Test
|
||||
public void testDefaultLogger() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SampleConfiguration1.class);
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
SampleConfiguration1.class);
|
||||
FeignLoggerFactory loggerFactory = context.getBean(FeignLoggerFactory.class);
|
||||
assertThat(loggerFactory).isNotNull();
|
||||
Logger logger = loggerFactory.create(Object.class);
|
||||
@@ -45,7 +46,8 @@ public class FeignLoggerFactoryTests {
|
||||
|
||||
@Test
|
||||
public void testCustomLogger() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SampleConfiguration2.class);
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
SampleConfiguration2.class);
|
||||
FeignLoggerFactory loggerFactory = context.getBean(FeignLoggerFactory.class);
|
||||
assertThat(loggerFactory).isNotNull();
|
||||
Logger logger = loggerFactory.create(Object.class);
|
||||
@@ -56,7 +58,8 @@ public class FeignLoggerFactoryTests {
|
||||
|
||||
@Test
|
||||
public void testCustomLoggerFactory() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SampleConfiguration3.class);
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
SampleConfiguration3.class);
|
||||
FeignLoggerFactory loggerFactory = context.getBean(FeignLoggerFactory.class);
|
||||
assertThat(loggerFactory).isNotNull();
|
||||
assertThat(loggerFactory instanceof LoggerFactoryImpl).isTrue();
|
||||
|
||||
+9
-5
@@ -49,9 +49,10 @@ public class FeignOkHttpConfigurationTests {
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.context = new SpringApplicationBuilder()
|
||||
.properties("debug=true", "feign.httpclient.disableSslValidation=true", "feign.okhttp.enabled=true",
|
||||
"feign.httpclient.enabled=false")
|
||||
.web(WebApplicationType.NONE).sources(HttpClientConfiguration.class, FeignAutoConfiguration.class)
|
||||
.properties("debug=true", "feign.httpclient.disableSslValidation=true",
|
||||
"feign.okhttp.enabled=true", "feign.httpclient.enabled=false")
|
||||
.web(WebApplicationType.NONE)
|
||||
.sources(HttpClientConfiguration.class, FeignAutoConfiguration.class)
|
||||
.run();
|
||||
}
|
||||
|
||||
@@ -65,8 +66,11 @@ public class FeignOkHttpConfigurationTests {
|
||||
@Test
|
||||
public void disableSslTest() throws Exception {
|
||||
OkHttpClient httpClient = this.context.getBean(OkHttpClient.class);
|
||||
HostnameVerifier hostnameVerifier = (HostnameVerifier) this.getField(httpClient, "hostnameVerifier");
|
||||
assertThat(OkHttpClientFactory.TrustAllHostnames.class.isInstance(hostnameVerifier)).isTrue();
|
||||
HostnameVerifier hostnameVerifier = (HostnameVerifier) this.getField(httpClient,
|
||||
"hostnameVerifier");
|
||||
assertThat(
|
||||
OkHttpClientFactory.TrustAllHostnames.class.isInstance(hostnameVerifier))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
protected <T> Object getField(Object target, String name) {
|
||||
|
||||
+29
-16
@@ -25,10 +25,10 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.cloud.openfeign.test.NoSecurityConfiguration;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
@@ -50,15 +50,16 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = SpringDecoderTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT,
|
||||
value = { "spring.application.name=springdecodertest", "spring.jmx.enabled=false" })
|
||||
@SpringBootTest(classes = SpringDecoderTests.Application.class,
|
||||
webEnvironment = WebEnvironment.RANDOM_PORT, value = {
|
||||
"spring.application.name=springdecodertest", "spring.jmx.enabled=false" })
|
||||
@DirtiesContext
|
||||
public class SpringDecoderTests extends FeignClientFactoryBean {
|
||||
|
||||
@Autowired
|
||||
FeignContext context;
|
||||
|
||||
@LocalServerPort
|
||||
@Value("${local.server.port}")
|
||||
private int port = 0;
|
||||
|
||||
public SpringDecoderTests() {
|
||||
@@ -73,24 +74,28 @@ public class SpringDecoderTests extends FeignClientFactoryBean {
|
||||
public TestClient testClient(boolean decode404) {
|
||||
setType(this.getClass());
|
||||
setDecode404(decode404);
|
||||
return feign(this.context).target(TestClient.class, "http://localhost:" + this.port);
|
||||
return feign(this.context).target(TestClient.class,
|
||||
"http://localhost:" + this.port);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseEntity() {
|
||||
ResponseEntity<Hello> response = testClient().getHelloResponse();
|
||||
assertThat(response).as("response was null").isNotNull();
|
||||
assertThat(response.getStatusCode()).as("wrong status code").isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getStatusCode()).as("wrong status code")
|
||||
.isEqualTo(HttpStatus.OK);
|
||||
Hello hello = response.getBody();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello("hello world via response"));
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world via response"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleType() {
|
||||
Hello hello = testClient().getHello();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello("hello world 1"));
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world 1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -98,7 +103,8 @@ public class SpringDecoderTests extends FeignClientFactoryBean {
|
||||
List<Hello> hellos = testClient().getHellos();
|
||||
assertThat(hellos).as("hellos was null").isNotNull();
|
||||
assertThat(hellos.size()).as("hellos was not the right size").isEqualTo(2);
|
||||
assertThat(hellos.get(0)).as("first hello didn't match").isEqualTo(new Hello("hello world 1"));
|
||||
assertThat(hellos.get(0)).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world 1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -106,7 +112,8 @@ public class SpringDecoderTests extends FeignClientFactoryBean {
|
||||
List<String> hellos = testClient().getHelloStrings();
|
||||
assertThat(hellos).as("hellos was null").isNotNull();
|
||||
assertThat(hellos.size()).as("hellos was not the right size").isEqualTo(2);
|
||||
assertThat(hellos.get(0)).as("first hello didn't match").isEqualTo("hello world 1");
|
||||
assertThat(hellos.get(0)).as("first hello didn't match")
|
||||
.isEqualTo("hello world 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -114,19 +121,22 @@ public class SpringDecoderTests extends FeignClientFactoryBean {
|
||||
public void testWildcardTypeDecode() {
|
||||
ResponseEntity<?> wildcard = testClient().getWildcard();
|
||||
assertThat(wildcard).as("wildcard was null").isNotNull();
|
||||
assertThat(wildcard.getStatusCode()).as("wrong status code").isEqualTo(HttpStatus.OK);
|
||||
assertThat(wildcard.getStatusCode()).as("wrong status code")
|
||||
.isEqualTo(HttpStatus.OK);
|
||||
Object wildcardBody = wildcard.getBody();
|
||||
assertThat(wildcardBody).as("wildcardBody was null").isNotNull();
|
||||
assertThat(wildcardBody instanceof Map).as("wildcard not an instance of Map").isTrue();
|
||||
assertThat(wildcardBody instanceof Map).as("wildcard not an instance of Map")
|
||||
.isTrue();
|
||||
Map<String, String> hello = (Map<String, String>) wildcardBody;
|
||||
assertThat(hello.get("message")).as("first hello didn't match").isEqualTo("wildcard");
|
||||
assertThat(hello.get("message")).as("first hello didn't match")
|
||||
.isEqualTo("wildcard");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseEntityVoid() {
|
||||
ResponseEntity<Void> response = testClient().getHelloVoid();
|
||||
assertThat(response).as("response was null").isNotNull();
|
||||
List<String> headerVals = response.getHeaders().get("x-test-header");
|
||||
List<String> headerVals = response.getHeaders().get("X-test-header");
|
||||
assertThat(headerVals).as("headerVals was null").isNotNull();
|
||||
assertThat(headerVals.size()).as("headerVals size was wrong").isEqualTo(1);
|
||||
String header = headerVals.get(0);
|
||||
@@ -149,7 +159,8 @@ public class SpringDecoderTests extends FeignClientFactoryBean {
|
||||
// Issue: https://github.com/spring-cloud/spring-cloud-openfeign/issues/456
|
||||
public void testResponseEntityHeaders() {
|
||||
ResponseEntity<String> response = testClient().getContentType();
|
||||
assertThat(response.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
|
||||
assertThat(response.getHeaders().getContentType())
|
||||
.isEqualTo(MediaType.APPLICATION_JSON);
|
||||
}
|
||||
|
||||
protected interface TestClient {
|
||||
@@ -267,7 +278,9 @@ public class SpringDecoderTests extends FeignClientFactoryBean {
|
||||
|
||||
@Override
|
||||
public ResponseEntity<String> getContentType() {
|
||||
return ResponseEntity.ok().header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).body("test");
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
.body("test");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration;
|
||||
import org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory;
|
||||
import org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer;
|
||||
import org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.ribbon.RetryableFeignLoadBalancer;
|
||||
import org.springframework.cloud.test.ClassPathExclusions;
|
||||
import org.springframework.cloud.test.ModifiedClassPathRunner;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@RunWith(ModifiedClassPathRunner.class)
|
||||
@ClassPathExclusions({ "spring-retry-*.jar", "spring-boot-starter-aop-*.jar" })
|
||||
public class SpringRetryDisabledTests {
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE)
|
||||
.sources(RibbonAutoConfiguration.class,
|
||||
LoadBalancerAutoConfiguration.class,
|
||||
RibbonClientConfiguration.class,
|
||||
FeignRibbonClientAutoConfiguration.class)
|
||||
.run();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadBalancedRetryFactoryBean() throws Exception {
|
||||
Map<String, CachingSpringLoadBalancerFactory> lbFactorys = this.context
|
||||
.getBeansOfType(CachingSpringLoadBalancerFactory.class);
|
||||
assertThat(lbFactorys.values()).hasSize(1);
|
||||
FeignLoadBalancer lb = lbFactorys.values().iterator().next().create("foo");
|
||||
assertThat(lb).isInstanceOf(FeignLoadBalancer.class);
|
||||
assertThat(lb).isNotInstanceOf(RetryableFeignLoadBalancer.class);
|
||||
}
|
||||
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
|
||||
import org.springframework.cloud.commons.httpclient.HttpClientConfiguration;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration;
|
||||
import org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory;
|
||||
import org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer;
|
||||
import org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration;
|
||||
import org.springframework.cloud.openfeign.ribbon.RetryableFeignLoadBalancer;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = { RibbonAutoConfiguration.class,
|
||||
RibbonClientConfiguration.class, LoadBalancerAutoConfiguration.class,
|
||||
FeignRibbonClientAutoConfiguration.class, HttpClientConfiguration.class })
|
||||
public class SpringRetryEnabledTests implements ApplicationContextAware {
|
||||
|
||||
private ApplicationContext context;
|
||||
|
||||
@Test
|
||||
public void testLoadBalancedRetryFactoryBean() throws Exception {
|
||||
Map<String, CachingSpringLoadBalancerFactory> lbFactorys = this.context
|
||||
.getBeansOfType(CachingSpringLoadBalancerFactory.class);
|
||||
assertThat(lbFactorys.values()).hasSize(1);
|
||||
FeignLoadBalancer lb = lbFactorys.values().iterator().next().create("foo");
|
||||
assertThat(lb).isInstanceOf(RetryableFeignLoadBalancer.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext context) throws BeansException {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
}
|
||||
+19
-17
@@ -47,19 +47,15 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @author Spencer Gibb
|
||||
* @author Jakub Narloch
|
||||
* @author Erik Kringen
|
||||
* @author Halvdan Hoem Grelland
|
||||
* @author Aaron Whiteside
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = BeansFeignClientTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT,
|
||||
@SpringBootTest(classes = FeignClientTests.Application.class,
|
||||
webEnvironment = WebEnvironment.RANDOM_PORT,
|
||||
value = { "spring.application.name=feignclienttest",
|
||||
"logging.level.org.springframework.cloud.openfeign.valid=DEBUG", "feign.httpclient.enabled=false",
|
||||
"feign.okhttp.enabled=false" })
|
||||
"logging.level.org.springframework.cloud.openfeign.valid=DEBUG",
|
||||
"feign.httpclient.enabled=false", "feign.okhttp.enabled=false" })
|
||||
@DirtiesContext
|
||||
public class BeansFeignClientTests {
|
||||
public class FeignClientTests {
|
||||
|
||||
@Value("${local.server.port}")
|
||||
private int port = 0;
|
||||
@@ -80,15 +76,18 @@ public class BeansFeignClientTests {
|
||||
|
||||
@Test
|
||||
public void testAnnotations() {
|
||||
Map<String, Object> beans = this.context.getBeansWithAnnotation(FeignClient.class);
|
||||
assertThat(beans.containsKey(TestClient.class.getName())).as("Wrong clients: " + beans).isTrue();
|
||||
Map<String, Object> beans = this.context
|
||||
.getBeansWithAnnotation(FeignClient.class);
|
||||
assertThat(beans.containsKey(TestClient.class.getName()))
|
||||
.as("Wrong clients: " + beans).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClient() {
|
||||
assertThat(this.testClient).as("testClient was null").isNotNull();
|
||||
assertThat(this.extraClient).as("extraClient was null").isNotNull();
|
||||
assertThat(Proxy.isProxyClass(this.testClient.getClass())).as("testClient is not a java Proxy").isTrue();
|
||||
assertThat(Proxy.isProxyClass(this.testClient.getClass()))
|
||||
.as("testClient is not a java Proxy").isTrue();
|
||||
InvocationHandler invocationHandler = Proxy.getInvocationHandler(this.testClient);
|
||||
assertThat(invocationHandler).as("invocationHandler was null").isNotNull();
|
||||
}
|
||||
@@ -96,17 +95,20 @@ public class BeansFeignClientTests {
|
||||
@Test
|
||||
public void extraClient() {
|
||||
assertThat(this.extraClient).as("extraClient was null").isNotNull();
|
||||
assertThat(Proxy.isProxyClass(this.extraClient.getClass())).as("extraClient is not a java Proxy").isTrue();
|
||||
InvocationHandler invocationHandler = Proxy.getInvocationHandler(this.extraClient);
|
||||
assertThat(Proxy.isProxyClass(this.extraClient.getClass()))
|
||||
.as("extraClient is not a java Proxy").isTrue();
|
||||
InvocationHandler invocationHandler = Proxy
|
||||
.getInvocationHandler(this.extraClient);
|
||||
assertThat(invocationHandler).as("invocationHandler was null").isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildByBuilder() {
|
||||
assertThat(this.buildByBuilder).as("buildByBuilder was null").isNotNull();
|
||||
assertThat(Proxy.isProxyClass(this.buildByBuilder.getClass())).as("buildByBuilder is not a java Proxy")
|
||||
.isTrue();
|
||||
InvocationHandler invocationHandler = Proxy.getInvocationHandler(this.buildByBuilder);
|
||||
assertThat(Proxy.isProxyClass(this.buildByBuilder.getClass()))
|
||||
.as("buildByBuilder is not a java Proxy").isTrue();
|
||||
InvocationHandler invocationHandler = Proxy
|
||||
.getInvocationHandler(this.buildByBuilder);
|
||||
assertThat(invocationHandler).as("invocationHandler was null").isNotNull();
|
||||
}
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
package org.springframework.cloud.openfeign.beans;
|
||||
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.cloud.openfeign.beans.BeansFeignClientTests.Hello;
|
||||
import org.springframework.cloud.openfeign.beans.FeignClientTests.Hello;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
package org.springframework.cloud.openfeign.beans.extra;
|
||||
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.cloud.openfeign.beans.BeansFeignClientTests.Hello;
|
||||
import org.springframework.cloud.openfeign.beans.FeignClientTests.Hello;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
|
||||
+16
-8
@@ -56,8 +56,10 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = CircuitBreakerTests.Application.class, webEnvironment = WebEnvironment.DEFINED_PORT, value = {
|
||||
"spring.application.name=springcircuittest", "spring.jmx.enabled=false", "feign.circuitbreaker.enabled=true" })
|
||||
@SpringBootTest(classes = CircuitBreakerTests.Application.class,
|
||||
webEnvironment = WebEnvironment.DEFINED_PORT,
|
||||
value = { "spring.application.name=springcircuittest", "spring.jmx.enabled=false",
|
||||
"feign.circuitbreaker.enabled=true" })
|
||||
@DirtiesContext
|
||||
public class CircuitBreakerTests {
|
||||
|
||||
@@ -75,7 +77,8 @@ public class CircuitBreakerTests {
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeClass() {
|
||||
System.setProperty("server.port", String.valueOf(SocketUtils.findAvailableTcpPort()));
|
||||
System.setProperty("server.port",
|
||||
String.valueOf(SocketUtils.findAvailableTcpPort()));
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
@@ -93,8 +96,10 @@ public class CircuitBreakerTests {
|
||||
Hello hello = testClient.getHello();
|
||||
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello("hello world 1"));
|
||||
assertThat(myCircuitBreaker.runWasCalled).as("Circuit Breaker was called").isTrue();
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world 1"));
|
||||
assertThat(myCircuitBreaker.runWasCalled).as("Circuit Breaker was called")
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -107,8 +112,10 @@ public class CircuitBreakerTests {
|
||||
Hello hello = testClientWithFactory.getHello();
|
||||
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello("hello world 1"));
|
||||
assertThat(myCircuitBreaker.runWasCalled).as("Circuit Breaker was called").isTrue();
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world 1"));
|
||||
assertThat(myCircuitBreaker.runWasCalled).as("Circuit Breaker was called")
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -117,7 +124,8 @@ public class CircuitBreakerTests {
|
||||
}
|
||||
|
||||
// tag::client_with_fallback[]
|
||||
@FeignClient(name = "test", url = "http://localhost:${server.port}/", fallback = Fallback.class)
|
||||
@FeignClient(name = "test", url = "http://localhost:${server.port}/",
|
||||
fallback = Fallback.class)
|
||||
protected interface TestClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
|
||||
+8
-4
@@ -67,7 +67,8 @@ public class CircuitBreakerWithNoFallbackTests {
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeClass() {
|
||||
System.setProperty("server.port", String.valueOf(SocketUtils.findAvailableTcpPort()));
|
||||
System.setProperty("server.port",
|
||||
String.valueOf(SocketUtils.findAvailableTcpPort()));
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
@@ -85,13 +86,16 @@ public class CircuitBreakerWithNoFallbackTests {
|
||||
Hello hello = testClient.getHello();
|
||||
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello("hello world 1"));
|
||||
assertThat(myCircuitBreaker.runWasCalled).as("Circuit Breaker was called").isTrue();
|
||||
assertThat(hello).as("first hello didn't match")
|
||||
.isEqualTo(new Hello("hello world 1"));
|
||||
assertThat(myCircuitBreaker.runWasCalled).as("Circuit Breaker was called")
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test404WithoutFallback() {
|
||||
assertThatThrownBy(() -> testClient.getException()).isInstanceOf(NoFallbackAvailableException.class);
|
||||
assertThatThrownBy(() -> testClient.getException())
|
||||
.isInstanceOf(NoFallbackAvailableException.class);
|
||||
}
|
||||
|
||||
@FeignClient(name = "test", url = "http://localhost:${server.port}/")
|
||||
|
||||
+18
-13
@@ -16,19 +16,20 @@
|
||||
|
||||
package org.springframework.cloud.openfeign.encoding;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.netflix.loadbalancer.BaseLoadBalancer;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
|
||||
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.support.ServiceInstanceListSuppliers;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonClient;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.cloud.openfeign.encoding.app.client.InvoiceClient;
|
||||
import org.springframework.cloud.openfeign.encoding.app.domain.Invoice;
|
||||
@@ -49,7 +50,8 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
|
||||
*
|
||||
* @author Jakub Narloch
|
||||
*/
|
||||
@SpringBootTest(classes = FeignAcceptEncodingTests.Application.class, webEnvironment = RANDOM_PORT,
|
||||
@SpringBootTest(classes = FeignAcceptEncodingTests.Application.class,
|
||||
webEnvironment = RANDOM_PORT,
|
||||
value = { "feign.compression.response.enabled=true" })
|
||||
@RunWith(SpringRunner.class)
|
||||
@DirtiesContext
|
||||
@@ -73,23 +75,26 @@ public class FeignAcceptEncodingTests {
|
||||
}
|
||||
|
||||
@EnableFeignClients(clients = InvoiceClient.class)
|
||||
@LoadBalancerClient(name = "local", configuration = LocalClientConfiguration.class)
|
||||
@SpringBootApplication(scanBasePackages = "org.springframework.cloud.openfeign.encoding.app")
|
||||
@RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class)
|
||||
@SpringBootApplication(
|
||||
scanBasePackages = "org.springframework.cloud.openfeign.encoding.app")
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
public static class Application {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class LocalClientConfiguration {
|
||||
static class LocalRibbonClientConfiguration {
|
||||
|
||||
@LocalServerPort
|
||||
@Value("${local.server.port}")
|
||||
private int port = 0;
|
||||
|
||||
@Bean
|
||||
public ServiceInstanceListSupplier staticServiceInstanceListSupplier() {
|
||||
return ServiceInstanceListSuppliers.from("local",
|
||||
new DefaultServiceInstance("local-1", "local", "localhost", port, false));
|
||||
public ILoadBalancer ribbonLoadBalancer() {
|
||||
BaseLoadBalancer balancer = new BaseLoadBalancer();
|
||||
balancer.setServersList(
|
||||
Collections.singletonList(new Server("localhost", this.port)));
|
||||
return balancer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+23
-15
@@ -16,19 +16,20 @@
|
||||
|
||||
package org.springframework.cloud.openfeign.encoding;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.netflix.loadbalancer.BaseLoadBalancer;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
|
||||
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.support.ServiceInstanceListSuppliers;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonClient;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.cloud.openfeign.encoding.app.client.InvoiceClient;
|
||||
import org.springframework.cloud.openfeign.encoding.app.domain.Invoice;
|
||||
@@ -48,8 +49,11 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
|
||||
*
|
||||
* @author Jakub Narloch
|
||||
*/
|
||||
@SpringBootTest(classes = FeignContentEncodingTests.Application.class, webEnvironment = RANDOM_PORT,
|
||||
value = { "feign.compression.request.enabled=true", "ribbon.OkToRetryOnAllOperations=false" })
|
||||
@SpringBootTest(classes = FeignContentEncodingTests.Application.class,
|
||||
webEnvironment = RANDOM_PORT,
|
||||
value = { "feign.compression.request.enabled=true",
|
||||
"hystrix.command.default.execution.isolation.strategy=SEMAPHORE",
|
||||
"ribbon.OkToRetryOnAllOperations=false" })
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class FeignContentEncodingTests {
|
||||
|
||||
@@ -63,7 +67,8 @@ public class FeignContentEncodingTests {
|
||||
final List<Invoice> invoices = Invoices.createInvoiceList(50);
|
||||
|
||||
// when
|
||||
final ResponseEntity<List<Invoice>> response = this.invoiceClient.saveInvoices(invoices);
|
||||
final ResponseEntity<List<Invoice>> response = this.invoiceClient
|
||||
.saveInvoices(invoices);
|
||||
|
||||
// then
|
||||
assertThat(response).isNotNull();
|
||||
@@ -74,23 +79,26 @@ public class FeignContentEncodingTests {
|
||||
}
|
||||
|
||||
@EnableFeignClients(clients = InvoiceClient.class)
|
||||
@LoadBalancerClient(name = "local", configuration = LocalClientConfiguration.class)
|
||||
@SpringBootApplication(scanBasePackages = "org.springframework.cloud.openfeign.encoding.app")
|
||||
@RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class)
|
||||
@SpringBootApplication(
|
||||
scanBasePackages = "org.springframework.cloud.openfeign.encoding.app")
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
public static class Application {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class LocalClientConfiguration {
|
||||
static class LocalRibbonClientConfiguration {
|
||||
|
||||
@LocalServerPort
|
||||
@Value("${local.server.port}")
|
||||
private int port = 0;
|
||||
|
||||
@Bean
|
||||
public ServiceInstanceListSupplier staticServiceInstanceListSupplier() {
|
||||
return ServiceInstanceListSuppliers.from("local",
|
||||
new DefaultServiceInstance("local-1", "local", "localhost", port, false));
|
||||
public ILoadBalancer ribbonLoadBalancer() {
|
||||
BaseLoadBalancer balancer = new BaseLoadBalancer();
|
||||
balancer.setServersList(
|
||||
Collections.singletonList(new Server("localhost", this.port)));
|
||||
return balancer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+88
-16
@@ -16,20 +16,22 @@
|
||||
|
||||
package org.springframework.cloud.openfeign.encoding;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.netflix.loadbalancer.BaseLoadBalancer;
|
||||
import com.netflix.loadbalancer.ILoadBalancer;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.web.server.LocalServerPort;
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
|
||||
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.support.ServiceInstanceListSuppliers;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonClient;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.cloud.openfeign.encoding.app.client.InvoiceClient;
|
||||
import org.springframework.cloud.openfeign.encoding.app.domain.Invoice;
|
||||
@@ -53,9 +55,14 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
|
||||
* Tests the pagination encoding.
|
||||
*
|
||||
* @author Charlie Mordant.
|
||||
* @author Hyeonmin Park
|
||||
*/
|
||||
@SpringBootTest(classes = FeignPageableEncodingTests.Application.class, webEnvironment = RANDOM_PORT,
|
||||
value = { "feign.compression.request.enabled=true", "feign.autoconfiguration.jackson.enabled=true" })
|
||||
@SpringBootTest(classes = FeignPageableEncodingTests.Application.class,
|
||||
webEnvironment = RANDOM_PORT,
|
||||
value = { "feign.compression.request.enabled=true",
|
||||
"feign.autoconfiguration.jackson.enabled=true",
|
||||
"hystrix.command.default.execution.isolation.strategy=SEMAPHORE",
|
||||
"ribbon.OkToRetryOnAllOperations=false" })
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class FeignPageableEncodingTests {
|
||||
|
||||
@@ -69,7 +76,8 @@ public class FeignPageableEncodingTests {
|
||||
Pageable pageable = PageRequest.of(0, 10, Sort.Direction.ASC, "sortProperty");
|
||||
|
||||
// when
|
||||
final ResponseEntity<Page<Invoice>> response = this.invoiceClient.getInvoicesPaged(pageable);
|
||||
final ResponseEntity<Page<Invoice>> response = this.invoiceClient
|
||||
.getInvoicesPaged(pageable);
|
||||
|
||||
// then
|
||||
assertThat(response).isNotNull();
|
||||
@@ -77,7 +85,8 @@ public class FeignPageableEncodingTests {
|
||||
assertThat(response.getBody()).isNotNull();
|
||||
assertThat(pageable.getPageSize()).isEqualTo(response.getBody().getSize());
|
||||
assertThat(response.getBody().getPageable().getSort()).hasSize(1);
|
||||
Optional<Sort.Order> optionalOrder = response.getBody().getPageable().getSort().get().findFirst();
|
||||
Optional<Sort.Order> optionalOrder = response.getBody().getPageable().getSort()
|
||||
.get().findFirst();
|
||||
if (optionalOrder.isPresent()) {
|
||||
Sort.Order order = optionalOrder.get();
|
||||
assertThat(order.getDirection()).isEqualTo(Sort.Direction.ASC);
|
||||
@@ -86,9 +95,70 @@ public class FeignPageableEncodingTests {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPageableWithDescDirection() {
|
||||
// given
|
||||
Pageable pageable = PageRequest.of(0, 10, Sort.Direction.DESC, "sortProperty");
|
||||
|
||||
// when
|
||||
final ResponseEntity<Page<Invoice>> response = this.invoiceClient
|
||||
.getInvoicesPaged(pageable);
|
||||
|
||||
// then
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isNotNull();
|
||||
assertThat(pageable.getPageSize()).isEqualTo(response.getBody().getSize());
|
||||
|
||||
Sort sort = response.getBody().getPageable().getSort();
|
||||
assertThat(sort).hasSize(1);
|
||||
assertThat(sort.get()).hasSize(1);
|
||||
|
||||
Optional<Sort.Order> optionalOrder = sort.get().findFirst();
|
||||
assertThat(optionalOrder.isPresent()).isTrue();
|
||||
|
||||
Sort.Order order = optionalOrder.get();
|
||||
assertThat(order.getDirection()).isEqualTo(Sort.Direction.DESC);
|
||||
assertThat(order.getProperty()).isEqualTo("sortProperty");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPageableWithMultipleSort() {
|
||||
// given
|
||||
Pageable pageable = PageRequest.of(0, 10, Sort
|
||||
.by(Sort.Order.desc("sortProperty1"), Sort.Order.asc("sortProperty2")));
|
||||
|
||||
// when
|
||||
final ResponseEntity<Page<Invoice>> response = this.invoiceClient
|
||||
.getInvoicesPaged(pageable);
|
||||
|
||||
// then
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isNotNull();
|
||||
assertThat(pageable.getPageSize()).isEqualTo(response.getBody().getSize());
|
||||
|
||||
Sort sort = response.getBody().getPageable().getSort();
|
||||
assertThat(sort).hasSize(2);
|
||||
|
||||
List<Sort.Order> orderList = sort.toList();
|
||||
assertThat(orderList).hasSize(2);
|
||||
|
||||
Sort.Order firstOrder = orderList.get(0);
|
||||
assertThat(firstOrder.getDirection()).isEqualTo(Sort.Direction.DESC);
|
||||
assertThat(firstOrder.getProperty()).isEqualTo("sortProperty1");
|
||||
|
||||
Sort.Order secondOrder = orderList.get(1);
|
||||
assertThat(secondOrder.getDirection()).isEqualTo(Sort.Direction.ASC);
|
||||
assertThat(secondOrder.getProperty()).isEqualTo("sortProperty2");
|
||||
|
||||
}
|
||||
|
||||
@EnableFeignClients(clients = InvoiceClient.class)
|
||||
@LoadBalancerClient(name = "local", configuration = LocalClientConfiguration.class)
|
||||
@SpringBootApplication(scanBasePackages = "org.springframework.cloud.openfeign.encoding.app",
|
||||
@RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class)
|
||||
@SpringBootApplication(
|
||||
scanBasePackages = "org.springframework.cloud.openfeign.encoding.app",
|
||||
exclude = { RepositoryRestMvcAutoConfiguration.class })
|
||||
@EnableSpringDataWebSupport
|
||||
@Import({ NoSecurityConfiguration.class })
|
||||
@@ -97,15 +167,17 @@ public class FeignPageableEncodingTests {
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class LocalClientConfiguration {
|
||||
static class LocalRibbonClientConfiguration {
|
||||
|
||||
@LocalServerPort
|
||||
@Value("${local.server.port}")
|
||||
private int port = 0;
|
||||
|
||||
@Bean
|
||||
public ServiceInstanceListSupplier staticServiceInstanceListSupplier() {
|
||||
return ServiceInstanceListSuppliers.from("local",
|
||||
new DefaultServiceInstance("local-1", "local", "localhost", port, false));
|
||||
public ILoadBalancer ribbonLoadBalancer() {
|
||||
BaseLoadBalancer balancer = new BaseLoadBalancer();
|
||||
balancer.setServersList(
|
||||
Collections.singletonList(new Server("localhost", this.port)));
|
||||
return balancer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -35,7 +35,8 @@ final class Invoices {
|
||||
for (int ind = 0; ind < count; ind++) {
|
||||
final Invoice invoice = new Invoice();
|
||||
invoice.setTitle("Invoice " + (ind + 1));
|
||||
invoice.setAmount(new BigDecimal(String.format(Locale.US, "%.2f", Math.random() * 1000)));
|
||||
invoice.setAmount(new BigDecimal(
|
||||
String.format(Locale.US, "%.2f", Math.random() * 1000)));
|
||||
invoices.add(invoice);
|
||||
}
|
||||
return invoices;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user