Compare commits

...
Author SHA1 Message Date
buildmaster 3e7e74c5bf Update SNAPSHOT to 3.0.0-RC1 2020-12-11 22:06:22 +00:00
Olga Maciaszek-Sharma 86551041d9 Adjust to changes in commons. 2020-12-10 17:15:26 +01:00
Olga Maciaszek-Sharma 35a5b8d614 Adjust to changes in commons. 2020-12-10 14:17:15 +01:00
Olga Maciaszek-Sharma 7047439624 Remove deprecations. 2020-12-09 19:38:04 +01:00
Olga Maciaszek-Sharma 5e61666f7c Remove deprecations. 2020-12-09 18:49:54 +01:00
Olga Maciaszek-Sharma e3810f1586 Adjust to changes in commons. 2020-12-09 15:22:10 +01:00
buildmaster 9bdda59fbb Going back to snapshots 2020-12-01 22:47:14 +00:00
buildmaster e257ec31ff Update SNAPSHOT to 3.0.0-M6 2020-12-01 22:46:17 +00:00
Olga Maciaszek-Sharma 246805815f Merge remote-tracking branch 'origin/2.2.x' 2020-12-01 17:30:47 +01:00
Olga Maciaszek-Sharma 89fb98daf7 Upgrade protobuf-java. 2020-12-01 17:30:13 +01:00
Olga Maciaszek-Sharma 15054fa37e Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	docs/src/main/asciidoc/_configprops.adoc
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractIntegrationTests.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractTests.java
2020-12-01 17:22:59 +01:00
Olga Maciaszek-Sharma 1a7917e2bb Add information about decodeSlash property to docs. 2020-12-01 17:08:43 +01:00
Olga Maciaszek-Sharma 49d3830276 Remove unnecessary keyword from recently modified class. 2020-12-01 17:02:09 +01:00
Ram Anaswara d419826ca0 Fix for Path variables with / are not url encoded
# Conflicts:
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractIntegrationTests.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractTests.java
2020-12-01 16:53:39 +01:00
Olga Maciaszek-Sharma b231783e84 Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/FeignBlockingLoadBalancerClient.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClient.java
2020-11-30 19:17:55 +01:00
Olga Maciaszek-Sharma 64d3f05e70 Allow overriding creating requests. Fixes gh-423. 2020-11-30 19:12:14 +01:00
Olga Maciaszek-Sharma a7c804db63 Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	docs/pom.xml
#	pom.xml
#	spring-cloud-openfeign-core/pom.xml
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignBuilderCustomizer.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/clientconfig/HttpClientFeignConfiguration.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignBuilderCustomizerTests.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientsRegistrarTests.java
#	spring-cloud-openfeign-dependencies/pom.xml
#	spring-cloud-starter-openfeign/pom.xml
2020-11-30 18:58:29 +01:00
Olga Maciaszek-Sharma 4d021fd3f8 Do not throw exceptions from @PreDestroy methods. Fixes gh-435. 2020-11-30 18:21:44 +01:00
matt62king a8bcda0b5b add customizer for feign builder (#289).
Fixes gh-436.

* add customizer for feign builder

* support @Order for customizers

* switch to annotation aware comparator for @Order support

* remove intermediate instances

* removed default feign builder customizer
# Conflicts:
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java
2020-11-30 17:56:48 +01:00
buildmaster 4120ca2bc6 Bumping versions 2020-11-19 11:37:07 +00:00
buildmaster 29295425f3 Going back to snapshots 2020-11-17 20:42:04 +00:00
buildmaster ef9ded650f Update SNAPSHOT to 3.0.0-M5 2020-11-17 20:41:05 +00:00
buildmaster 3c25b010c3 Bumping versions 2020-11-17 11:37:02 +00:00
buildmaster 1b42820c3a Bumping versions to 2.2.7.BUILD-SNAPSHOT after release 2020-11-09 10:40:38 +00:00
buildmaster e31defb2dd Going back to snapshots 2020-11-09 10:40:37 +00:00
Olga Maciaszek-Sharma f9a8dd13f2 Adjust to changes in security config. 2020-11-03 11:49:21 +01:00
Olga Maciaszek-Sharma f6fdd63cef Merge remote-tracking branch 'origin/master' 2020-11-03 11:47:36 +01:00
Olga Maciaszek-Sharma 410d0f29a3 Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsRegistrar.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientsRegistrarTests.java
2020-11-03 11:47:30 +01:00
Olga Maciaszek-Sharma 82de713d06 Remove unnecessary test configuration. 2020-11-03 11:46:15 +01:00
Olga Maciaszek-Sharma 5e455ed14b Adjust to changes in master. 2020-11-02 14:33:58 +01:00
Olga Maciaszek-Sharma a3584a9cc8 Merge remote-tracking branch 'origin/2.2.x' 2020-11-02 14:30:54 +01:00
Olga Maciaszek-Sharma ffb17a4552 Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java
2020-10-29 16:54:47 +01:00
Olga Maciaszek-Sharma 20e2548a5d Automatic reformat. 2020-10-28 18:39:20 +01:00
Olga Maciaszek-Sharma d93defe1c1 Merge remote-tracking branch 'origin/2.2.x' 2020-10-28 18:37:15 +01:00
Olga Maciaszek-Sharma db8d10a511 Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	pom.xml
2020-10-28 17:53:01 +01:00
Olga Maciaszek-Sharma 6649c86be1 Support loadbalancer lifecycle (#418)
* Add LoadBalancerLifecycle support.

* Add info on SC LoadBalancer documentation.
2020-10-27 11:12:34 -05:00
Olga Maciaszek-Sharma 3f0c2850ee Extract duplicate method to utility class. 2020-10-27 17:02:04 +01:00
Olga Maciaszek-Sharma 479e1ab743 Add info on SC LoadBalancer documentation. 2020-10-23 16:12:39 +02:00
Olga Maciaszek-Sharma 77a1367796 Add LoadBalancerLifecycle support. 2020-10-23 16:04:03 +02:00
buildmaster 89f8942dff Going back to snapshots 2020-10-03 13:57:43 +00:00
buildmaster d31ce6d428 Update SNAPSHOT to 3.0.0-M4 2020-10-03 13:56:37 +00:00
Olga Maciaszek-Sharma 3c3b9a5a14 Add support for passing hints to LoadBalancer. (#412) 2020-10-02 17:15:20 +02:00
Olga Maciaszek-Sharma 582e4c0175 Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/DefaultFeignLoadBalancerConfiguration.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/HttpClientFeignLoadBalancerConfiguration.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/OkHttpFeignLoadBalancerConfiguration.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClient.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/loadbalancer/FeignLoadBalancerAutoConfigurationTests.java
2020-10-02 15:32:53 +02:00
Olga Maciaszek-Sharma db3513b97b Merge remote-tracking branch 'origin/2.2.x'
Fixes gh-286.

# Conflicts:
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/DefaultFeignLoadBalancerConfiguration.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/HttpClientFeignLoadBalancerConfiguration.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/OkHttpFeignLoadBalancerConfiguration.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/FeignLoadBalancer.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/RetryableFeignLoadBalancer.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/loadbalancer/FeignLoadBalancerAutoConfigurationTests.java
2020-10-02 15:15:43 +02:00
Olga Maciaszek-Sharma 1178739437 Merge remote-tracking branch 'origin/2.2.x' 2020-09-17 14:51:13 +02:00
Olga Maciaszek-Sharma 2d9f51a17f Merge 2.2.x. 2020-09-17 14:09:15 +02:00
Olga Maciaszek-Sharma faff6aef8a Reformat with updated JavaFormat plugin version. 2020-09-17 13:54:45 +02:00
Olga Maciaszek-Sharma 80710a9f20 Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	docs/pom.xml
#	pom.xml
#	spring-cloud-openfeign-core/pom.xml
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientOverrideDefaultsTests.java
#	spring-cloud-openfeign-dependencies/pom.xml
#	spring-cloud-starter-openfeign/pom.xml
2020-09-16 13:01:34 +02:00
Olga Maciaszek-Sharma d41f041efc Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	pom.xml
2020-08-13 14:41:12 +02:00
Olga Maciaszek-Sharma 2f13eb4816 Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	docs/pom.xml
#	pom.xml
#	spring-cloud-openfeign-core/pom.xml
#	spring-cloud-openfeign-dependencies/pom.xml
#	spring-cloud-starter-openfeign/pom.xml
2020-08-03 10:05:52 +02:00
Olga Maciaszek-Sharma 62aa76fb97 Merge remote-tracking branch 'origin/2.2.x' 2020-07-30 16:24:25 +02:00
buildmaster 5ce900828f Going back to snapshots 2020-07-23 18:26:47 +00:00
buildmaster 48bb0cdba5 Update SNAPSHOT to 3.0.0-M3 2020-07-23 18:25:41 +00:00
spencergibb dc5eef0125 Updates to new BlockingLoadBalancerClient constructor 2020-07-23 13:36:14 -04:00
Olga Maciaszek-Sharma 343f436b71 Merge remote-tracking branch 'origin/2.2.x' 2020-07-17 15:07:20 +02:00
Olga Maciaszek-Sharma f184c3f293 Merge remote-tracking branch 'origin/2.2.x' 2020-07-14 17:38:50 +02:00
Olga Maciaszek-Sharma f600140004 Merge remote-tracking branch 'origin/2.2.x' 2020-07-14 16:03:00 +02:00
Olga Maciaszek-Sharma d07c229b0e Merge remote-tracking branch 'origin/2.2.x' 2020-07-14 15:20:23 +02:00
Olga Maciaszek-Sharma 84cb74a898 Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java
2020-07-13 17:23:49 +02:00
Ryan Baxter 9677353c27 Fixing broken tests 2020-07-09 11:13:05 -04:00
spencergibb 4766d95def Adds junit-vintage-engine 2020-06-15 16:40:29 -04:00
Marcin Grzejszczak bba3f2e92c Updated props 2020-06-08 17:54:58 +02:00
Marcin Grzejszczak c4fd4eb42e Changed packaging to jar 2020-06-08 17:12:15 +02:00
Marcin Grzejszczak b56a4a1259 Uploading sources for docs 2020-06-08 16:48:07 +02:00
Olga Maciaszek-Sharma 4a4da0e2d1 Merge remote-tracking branch 'origin/2.2.x' 2020-06-05 18:27:15 +02:00
Olga Maciaszek-Sharma efca091fda Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	pom.xml
2020-06-05 16:30:27 +02:00
Olga Maciaszek-Sharma 2e2987e787 Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	docs/src/main/asciidoc/spring-cloud-openfeign.adoc
2020-06-04 14:59:17 +02:00
Olga Maciaszek-Sharma 7cd7ed5c70 Merge remote-tracking branch 'origin/master' 2020-06-04 13:40:57 +02:00
Olga Maciaszek-Sharma 5a6a75d22b Merge 2.2.x. 2020-06-04 13:40:24 +02:00
buildmaster a790386732 Bumping versions 2020-06-04 11:37:17 +00:00
buildmaster fa5162c2fc Going back to snapshots 2020-05-29 18:40:30 +00:00
buildmaster 6d3181e718 Update SNAPSHOT to 3.0.0-M2 2020-05-29 18:39:55 +00:00
buildmaster d0278f63b1 Bumping versions 2020-05-28 11:37:14 +00:00
Olga Maciaszek-Sharma 39e915e568 Merge remote-tracking branch 'origin/2.2.x' 2020-05-27 20:14:30 +02:00
buildmaster e03d7da36d Bumping versions 2020-05-27 11:37:14 +00:00
Marcin Grzejszczak dcd5a56c86 Setting up repository for docs.spring.io migration 2020-05-25 14:39:49 +02:00
Marcin Grzejszczak d492fe0742 Setting up repository for docs.spring.io migration 2020-05-25 13:42:33 +02:00
buildmaster c9fe3af3ef Bumping versions 2020-05-22 11:37:15 +00:00
Olga Maciaszek-Sharma afb0b436c0 Merge remote-tracking branch 'origin/2.2.x' 2020-05-14 12:16:01 +02:00
spencergibb f857cca5b3 Merge branch '2.2.x' 2020-05-13 15:29:43 -04:00
Olga Maciaszek-Sharma 94ad7066a6 Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java
2020-05-11 13:39:15 +02:00
Olga Maciaszek-Sharma 1b66594ed4 Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/HystrixTargeter.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/CachingSpringLoadBalancerFactory.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/DefaultFeignLoadBalancedConfiguration.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/FeignLoadBalancer.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/FeignRetryPolicy.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonClientAutoConfiguration.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/HttpClientFeignLoadBalancedConfiguration.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/LoadBalancerFeignClient.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/OkHttpFeignLoadBalancedConfiguration.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/RetryableFeignLoadBalancer.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/RibbonResponseStatusCodeException.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/FallbackCommand.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientBuilderTests.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/SpringRetryDisabledTests.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/SpringRetryEnabledTests.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/HystrixSecurityApplication.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/HystrixSecurityTests.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/CustomConcurrenyStrategy.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/ProxyUsernameController.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/TestInterceptor.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/UsernameController.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/CachingSpringLoadBalancerFactoryTests.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignLoadBalancerTests.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonClientPathTests.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonClientRetryTests.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonClientTests.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonHttpClientConfigurationTests.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonOkHttpClientConfigurationTests.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/LoadBalancerFeignClientOverrideTests.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/RetryableFeignLoadBalancerTests.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/RibbonResponseStatusCodeExceptionTest.java
2020-05-11 13:27:20 +02:00
Olga Maciaszek-Sharma 9a335536bc Update copyright comments dates. 2020-04-21 16:59:54 +02:00
buildmaster 6ac428b21c Bumping versions 2020-04-17 02:55:34 +00:00
Spencer Gibb 30eafcd02d Removes dep mgmt for netflix 2020-04-16 22:51:51 -04:00
Olga Maciaszek-Sharma 147136b85b Remove elements that are no longer valid from tests. 2020-04-16 14:10:09 +02:00
matt62king 45589b7977 exposed client factory bean and targeter (#282) 2020-04-16 13:50:36 +02:00
matt62king f4b49d5542 add customizer for feign builder (#289)
* add customizer for feign builder

* support @Order for customizers

* switch to annotation aware comparator for @Order support

* remove intermediate instances

* removed default feign builder customizer
2020-04-15 13:55:27 +02:00
Olga Maciaszek-Sharma a6b5663c69 Merge branch '2.2.x'
# Conflicts:
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientsConfiguration.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/ValidFeignClientTests.java
2020-04-14 16:47:36 +02:00
Ryan Baxter 8f421ced21 Merge remote-tracking branch 'origin/2.2.x' 2020-04-13 16:59:00 -04:00
buildmaster 78653c3e8e Going back to snapshots 2020-04-09 22:28:42 +00:00
buildmaster 1f3ee5cfec Update SNAPSHOT to 3.0.0.M1 2020-04-09 22:28:08 +00:00
Marcin Grzejszczak e4a8b0a6a6 Updated docs 2020-04-01 13:43:38 +02:00
Olga Maciaszek-Sharma c87e5f2dbf Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	docs/src/main/asciidoc/spring-cloud-openfeign.adoc
2020-03-27 13:04:51 +01:00
nekkiy 0afb60a72a Fix double @RequestMapping with empty value (#245)
* Fix double @RequestMapping with empty value

* Fix code style

* Fix for review

* add author to SpringMvcContract

* split SpringMvcContractTests#testDoubleRequestMapping() test
2020-03-27 11:58:26 +01:00
Olga Maciaszek-Sharma a77bd2bdf1 Remove unnecessary exception clause. 2020-03-24 17:17:25 +01:00
Olga Maciaszek-Sharma 56bee946d8 Merge remote-tracking branch 'origin/2.2.x' 2020-03-24 17:13:36 +01:00
Olga Maciaszek-Sharma 90181f34c0 Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	docs/pom.xml
#	pom.xml
#	spring-cloud-openfeign-core/pom.xml
#	spring-cloud-openfeign-dependencies/pom.xml
#	spring-cloud-starter-openfeign/pom.xml
2020-03-20 20:05:09 +01:00
Olga Maciaszek-Sharma 6b9be3f513 Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	docs/pom.xml
#	pom.xml
#	spring-cloud-openfeign-core/pom.xml
#	spring-cloud-openfeign-dependencies/pom.xml
#	spring-cloud-starter-openfeign/pom.xml
2020-03-20 20:02:12 +01:00
Olga Maciaszek-Sharma efe264d2bb Adjust to ribbon changes in commons (#300)
* Adjust to Ribbon changes in commons.

* Switch to using LoadBalancerClient bean instead of BlockingLoadBalancerClient bean following changes in commons. Remove references of Ribbon from docs and javadocs.
2020-02-22 14:11:30 +01:00
Olga Maciaszek-Sharma ffa18c4346 Fix after merge. 2020-02-05 14:59:35 +01:00
Olga Maciaszek-Sharma d6e1a9f6a1 Merge branch '2.2.x'
# Conflicts:
#	spring-cloud-openfeign-core/.flattened-pom.xml
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/FeignClientTests.java
#	spring-cloud-openfeign-dependencies/pom.xml
#	spring-cloud-starter-openfeign/.flattened-pom.xml
2020-02-05 14:41:11 +01:00
Ryan Baxter a2e968465a Merge remote-tracking branch 'origin/2.2.x' 2020-01-29 11:00:53 -05:00
Ryan Baxter 6b656195ce Merge remote-tracking branch 'origin/2.2.x' 2020-01-29 10:39:48 -05:00
buildmaster 186711914a Bumping versions 2020-01-24 11:37:05 +00:00
Spencer Gibb 569dd54ccc Moves multipart tests and un-ignores them.
fixes gh-287
2020-01-23 16:12:06 -05:00
Spencer Gibb 91895278bf Renames test classes to be unique 2020-01-23 16:08:58 -05:00
Spencer Gibb 8683a04048 Moves multipart tests and un-ignores them.
fixes gh-287
2020-01-23 16:07:30 -05:00
Spencer Gibb 7c531d9dae Restores ignored tests.
fixes gh-280
2020-01-23 15:46:12 -05:00
Olga Maciaszek-Sharma 487390fc20 Merge branch '2.2.x'
# Conflicts:
#	spring-cloud-openfeign-core/.flattened-pom.xml
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/valid/FeignClientTests.java
#	spring-cloud-openfeign-dependencies/pom.xml
#	spring-cloud-starter-openfeign/.flattened-pom.xml
2020-01-23 15:33:04 +01:00
Olga Maciaszek-Sharma c187ff8792 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/FeignBlockingLoadBalancerClient.java
2020-01-23 15:09:14 +01:00
Spencer Gibb 826c9e9699 Makes constuctor public 2020-01-22 16:55:35 -05:00
Spencer Gibb 87ebb3cbdf Removes ribbon from auto config 2020-01-22 12:51:07 -05:00
Spencer Gibb 14a43f976d Merge branch 'master' of github.com:spring-cloud/spring-cloud-openfeign 2020-01-21 12:41:37 -05:00
Spencer Gibb 08f0fe4ae1 Ignores broken tests.
See gh-280
2020-01-21 12:40:22 -05:00
Spencer Gibb 8a08e1ec4b Removes ribbon and hystrix support 2020-01-17 19:28:33 -05:00
Spencer Gibb bf1a5e6d33 Merge branch '2.2.x' 2020-01-17 18:59:33 -05:00
Spencer Gibb 5560bc19be ignores .flattened-pom.xml 2020-01-17 18:59:24 -05:00
Spencer Gibb 36fd5d7755 removes .flattened-pom.xml 2020-01-17 18:58:51 -05:00
Spencer Gibb 3b191bcb9e Updates version to 3.0.0.BUILD-SNAPSHOT 2020-01-17 18:58:05 -05:00
147 changed files with 2884 additions and 7210 deletions
+8 -2
View File
@@ -6,10 +6,10 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId>
<version>2.2.6.RELEASE</version>
<version>3.0.0-RC1</version>
</parent>
<artifactId>spring-cloud-openfeign-docs</artifactId>
<packaging>pom</packaging>
<packaging>jar</packaging>
<name>Spring Cloud OpenFeign Docs</name>
<description>Spring Cloud Docs</description>
<properties>
@@ -24,6 +24,9 @@
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/asciidoc</sourceDirectory>
</build>
<profiles>
<profile>
<id>docs</id>
@@ -34,9 +37,11 @@
<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>
@@ -48,6 +53,7 @@
<artifactId>asciidoctor-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
</plugin>
<plugin>
+18 -17
View File
@@ -2,23 +2,24 @@
|Name | Default | Description
|feign.client.config | |
|feign.client.default-config | default |
|feign.client.default-to-properties | true |
|feign.compression.request.enabled | false | Enables the request sent by Feign to be compressed.
|feign.compression.request.mime-types | [text/xml, application/xml, application/json] | The list of supported mime types.
|feign.compression.request.min-request-size | 2048 | The minimum threshold content size.
|feign.compression.response.enabled | false | Enables the response from Feign to be compressed.
|feign.compression.response.useGzipDecoder | false | Enables the default gzip decoder to be used.
|feign.httpclient.connection-timeout | 2000 |
|feign.httpclient.connection-timer-repeat | 3000 |
|feign.httpclient.disable-ssl-validation | false |
|feign.httpclient.enabled | true | Enables the use of the Apache HTTP Client by Feign.
|feign.httpclient.follow-redirects | true |
|feign.httpclient.max-connections | 200 |
|feign.httpclient.max-connections-per-route | 50 |
|feign.httpclient.time-to-live | 900 |
|feign.client.decode-slash | `true` | Feign clients do not encode slash `/` characters by default. To change this behavior, set the `decodeSlash` to `false`.
|feign.client.default-config | `default` |
|feign.client.default-to-properties | `true` |
|feign.compression.request.enabled | `false` | Enables the request sent by Feign to be compressed.
|feign.compression.request.mime-types | `[text/xml, application/xml, application/json]` | The list of supported mime types.
|feign.compression.request.min-request-size | `2048` | The minimum threshold content size.
|feign.compression.response.enabled | `false` | Enables the response from Feign to be compressed.
|feign.compression.response.useGzipDecoder | `false` | Enables the default gzip decoder to be used.
|feign.httpclient.connection-timeout | `2000` |
|feign.httpclient.connection-timer-repeat | `3000` |
|feign.httpclient.disable-ssl-validation | `false` |
|feign.httpclient.enabled | `true` | Enables the use of the Apache HTTP Client by Feign.
|feign.httpclient.follow-redirects | `true` |
|feign.httpclient.max-connections | `200` |
|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.
|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 Ribbon and Eureka, as well as Spring Cloud LoadBalancer to provide a load-balanced http client when using Feign.
Spring Cloud integrates Eureka, 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 either a https://github.com/Netflix/ribbon[Ribbon] load-balancer (see <<spring-cloud-ribbon,below for details of Ribbon support>>) or https://github.com/spring-cloud/spring-cloud-commons/blob/master/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/ReactiveLoadBalancer.java[Spring Cloud LoadBalancer].
In the `@FeignClient` annotation the String value ("stores" above) is an arbitrary client name, which is used to create 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].
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,9 +68,7 @@ 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`].
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 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].
[[spring-cloud-feign-overriding-defaults]]
=== Overriding Feign Defaults
@@ -94,8 +92,6 @@ 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.
@@ -119,10 +115,10 @@ Spring Cloud OpenFeign provides the following beans by default for feign (`BeanT
* `Logger` feignLogger: `Slf4jLogger`
* `Contract` feignContract: `SpringMvcContract`
* `Feign.Builder` feignBuilder: `HystrixFeign.Builder`
* `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.
* `Client` feignClient: 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 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.
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.
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.
@@ -274,6 +270,8 @@ public FeignClientConfigurer feignClientConfigurer() {
}
----
TIP: By default, Feign clients do not encode slash `/` characters. You can change this behaviour, by setting the value of `feign.client.decodeSlash` to `false`.
[[timeout-handling]]
=== Timeout Handling
@@ -284,32 +282,6 @@ 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
+3 -11
View File
@@ -4,14 +4,14 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-openfeign</artifactId>
<version>2.2.6.RELEASE</version>
<version>3.0.0-RC1</version>
<packaging>pom</packaging>
<name>Spring Cloud OpenFeign</name>
<description>Spring Cloud OpenFeign</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
<version>2.3.1.RELEASE</version>
<version>3.0.0-RC1</version>
<relativePath/>
</parent>
<scm>
@@ -26,8 +26,7 @@
<properties>
<main.basedir>${basedir}</main.basedir>
<jackson.version>2.11.3</jackson.version>
<spring-cloud-commons.version>2.2.6.RELEASE</spring-cloud-commons.version>
<spring-cloud-netflix.version>2.2.6.RELEASE</spring-cloud-netflix.version>
<spring-cloud-commons.version>3.0.0-RC1</spring-cloud-commons.version>
<!-- Plugin versions -->
<maven-eclipse-plugin.version>2.10</maven-eclipse-plugin.version>
@@ -94,13 +93,6 @@
<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>
+7 -56
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId>
<version>2.2.6.RELEASE</version>
<version>3.0.0-RC1</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-openfeign-core</artifactId>
@@ -21,10 +21,6 @@
<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>
@@ -89,11 +85,6 @@
<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>
@@ -113,46 +104,11 @@
<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>
@@ -178,6 +134,11 @@
<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>
@@ -193,16 +154,6 @@
<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>
@@ -217,7 +168,7 @@
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.13.0</version>
<version>3.14.0</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -43,8 +43,7 @@ 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.
@@ -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);
}
@@ -16,6 +16,7 @@
package org.springframework.cloud.openfeign;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
@@ -29,6 +30,8 @@ import feign.Feign;
import feign.httpclient.ApacheHttpClient;
import feign.okhttp.OkHttpClient;
import okhttp3.ConnectionPool;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.RegistryBuilder;
@@ -38,7 +41,6 @@ import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.beans.factory.annotation.Autowired;
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;
@@ -58,11 +60,12 @@ import org.springframework.context.annotation.Import;
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class,
FeignHttpClientProperties.class })
@EnableConfigurationProperties({ FeignClientProperties.class, FeignHttpClientProperties.class })
@Import(DefaultGzipDecoderConfiguration.class)
public class FeignAutoConfiguration {
private static final Log LOG = LogFactory.getLog(FeignAutoConfiguration.class);
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@@ -79,19 +82,6 @@ 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)
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@@ -103,12 +93,11 @@ public class FeignAutoConfiguration {
}
// the following configuration is for alternate feign clients if
// ribbon is not on the class path.
// SC loadbalancer is not on the class path.
// see corresponding configurations in FeignRibbonClientAutoConfiguration
// for load balanced ribbon clients.
// for load-balanced 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 {
@@ -126,13 +115,10 @@ 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() {
@@ -148,10 +134,8 @@ 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;
}
@@ -163,10 +147,17 @@ public class FeignAutoConfiguration {
}
@PreDestroy
public void destroy() throws Exception {
public void destroy() {
this.connectionManagerTimer.cancel();
if (this.httpClient != null) {
this.httpClient.close();
try {
this.httpClient.close();
}
catch (IOException e) {
if (LOG.isErrorEnabled()) {
LOG.error("Could not correctly close httpClient.");
}
}
}
}
@@ -174,7 +165,6 @@ 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 {
@@ -183,8 +173,7 @@ 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();
@@ -193,16 +182,14 @@ 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;
}
@@ -14,18 +14,18 @@
* limitations under the License.
*/
package org.springframework.cloud.openfeign.hystrix.security.app;
package org.springframework.cloud.openfeign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import feign.Feign;
/**
* @author Daniel Lavoie
* Allows application to customize the Feign builder.
*
* @author Matt King
*/
@FeignClient("username")
public interface UsernameClient {
@FunctionalInterface
public interface FeignBuilderCustomizer {
@RequestMapping("/username")
String getUsername();
void customize(Feign.Builder builder);
}
@@ -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 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.
* 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.
*
* @author Spencer Gibb
* @author Venil Noronha
@@ -49,14 +49,6 @@ 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.
@@ -114,8 +106,7 @@ public @interface FeignClient {
Class<?> fallbackFactory() default void.class;
/**
* @return path prefix to be used by all method-level mappings. Can be used with or
* without <code>@RibbonClient</code>.
* @return path prefix to be used by all method-level mappings.
*/
String path() default "";
@@ -16,8 +16,6 @@
package org.springframework.cloud.openfeign;
import feign.hystrix.FallbackFactory;
import org.springframework.context.ApplicationContext;
/**
@@ -41,6 +39,11 @@ 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.
*
@@ -50,9 +53,13 @@ public class FeignClientBuilder {
private FeignClientFactoryBean feignClientFactoryBean;
private Builder(final ApplicationContext applicationContext, final Class<T> type,
final String name) {
this.feignClientFactoryBean = new 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;
this.feignClientFactoryBean.setApplicationContext(applicationContext);
this.feignClientFactoryBean.setType(type);
@@ -95,13 +102,6 @@ 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
*/
@@ -43,7 +43,6 @@ 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;
@@ -59,8 +58,7 @@ import org.springframework.util.StringUtils;
* @author Olga Maciaszek-Sharma
* @author Ilia Ilinykh
*/
class FeignClientFactoryBean
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
public class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {
/***********************************
* WARNING! Nothing in this class should be @Autowired. It causes NPEs because of some
@@ -111,30 +109,35 @@ class FeignClientFactoryBean
// @formatter:on
configureFeign(context, builder);
applyBuildCustomizers(context, builder);
return builder;
}
protected void configureFeign(FeignContext context, Feign.Builder builder) {
FeignClientProperties properties = applicationContext
.getBean(FeignClientProperties.class);
private void applyBuildCustomizers(FeignContext context, Feign.Builder builder) {
Map<String, FeignBuilderCustomizer> customizerMap = context.getInstances(contextId,
FeignBuilderCustomizer.class);
FeignClientConfigurer feignClientConfigurer = getOptional(context,
FeignClientConfigurer.class);
if (customizerMap != null) {
customizerMap.values().stream().sorted(AnnotationAwareOrderComparator.INSTANCE)
.forEach(feignBuilderCustomizer -> feignBuilderCustomizer.customize(builder));
}
}
protected void configureFeign(FeignContext context, Feign.Builder builder) {
FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.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);
}
@@ -144,8 +147,7 @@ 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);
@@ -154,51 +156,45 @@ 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;
@@ -208,13 +204,11 @@ 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());
@@ -226,8 +220,7 @@ 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,13 +239,11 @@ 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())) {
@@ -280,8 +271,7 @@ 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;
}
@@ -299,8 +289,7 @@ 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);
}
@@ -309,8 +298,7 @@ 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);
@@ -319,11 +307,11 @@ class FeignClientFactoryBean
}
throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?");
}
@Override
public Object getObject() throws Exception {
public Object getObject() {
return getTarget();
}
@@ -344,8 +332,7 @@ 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;
@@ -353,11 +340,6 @@ 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
@@ -366,8 +348,7 @@ 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() {
@@ -483,32 +464,26 @@ class FeignClientFactoryBean
return false;
}
FeignClientFactoryBean that = (FeignClientFactoryBean) o;
return Objects.equals(applicationContext, that.applicationContext)
&& 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);
return Objects.equals(applicationContext, that.applicationContext) && 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, decode404, inheritParentContext, fallback,
fallbackFactory, name, path, type, url);
return Objects.hash(applicationContext, 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("fallback=").append(fallback).append(", ")
.append("fallbackFactory=").append(fallbackFactory).append("}")
.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("fallback=").append(fallback).append(", ").append("fallbackFactory=")
.append(fallbackFactory).append("}").toString();
}
}
@@ -36,6 +36,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Eko Kurniawan Khannedy
* @author Ilia Ilinykh
* @author Ram Anaswara
*/
@ConfigurationProperties("feign.client")
public class FeignClientProperties {
@@ -46,8 +47,14 @@ public class FeignClientProperties {
private Map<String, FeignClientConfiguration> config = new HashMap<>();
/**
* Feign clients do not encode slash `/` characters by default. To change this
* behavior, set the `decodeSlash` to `false`.
*/
private boolean decodeSlash = true;
public boolean isDefaultToProperties() {
return this.defaultToProperties;
return defaultToProperties;
}
public void setDefaultToProperties(boolean defaultToProperties) {
@@ -55,7 +62,7 @@ public class FeignClientProperties {
}
public String getDefaultConfig() {
return this.defaultConfig;
return defaultConfig;
}
public void setDefaultConfig(String defaultConfig) {
@@ -63,13 +70,21 @@ public class FeignClientProperties {
}
public Map<String, FeignClientConfiguration> getConfig() {
return this.config;
return config;
}
public void setConfig(Map<String, FeignClientConfiguration> config) {
this.config = config;
}
public boolean isDecodeSlash() {
return decodeSlash;
}
public void setDecodeSlash(boolean decodeSlash) {
this.decodeSlash = decodeSlash;
}
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -79,14 +94,13 @@ public class FeignClientProperties {
return false;
}
FeignClientProperties that = (FeignClientProperties) o;
return this.defaultToProperties == that.defaultToProperties
&& Objects.equals(this.defaultConfig, that.defaultConfig)
&& Objects.equals(this.config, that.config);
return defaultToProperties == that.defaultToProperties && Objects.equals(defaultConfig, that.defaultConfig)
&& Objects.equals(config, that.config) && Objects.equals(decodeSlash, that.decodeSlash);
}
@Override
public int hashCode() {
return Objects.hash(this.defaultToProperties, this.defaultConfig, this.config);
return Objects.hash(defaultToProperties, defaultConfig, config, decodeSlash);
}
/**
@@ -121,7 +135,7 @@ public class FeignClientProperties {
private ExceptionPropagationPolicy exceptionPropagationPolicy;
public Logger.Level getLoggerLevel() {
return this.loggerLevel;
return loggerLevel;
}
public void setLoggerLevel(Logger.Level loggerLevel) {
@@ -129,7 +143,7 @@ public class FeignClientProperties {
}
public Integer getConnectTimeout() {
return this.connectTimeout;
return connectTimeout;
}
public void setConnectTimeout(Integer connectTimeout) {
@@ -137,7 +151,7 @@ public class FeignClientProperties {
}
public Integer getReadTimeout() {
return this.readTimeout;
return readTimeout;
}
public void setReadTimeout(Integer readTimeout) {
@@ -145,7 +159,7 @@ public class FeignClientProperties {
}
public Class<Retryer> getRetryer() {
return this.retryer;
return retryer;
}
public void setRetryer(Class<Retryer> retryer) {
@@ -153,7 +167,7 @@ public class FeignClientProperties {
}
public Class<ErrorDecoder> getErrorDecoder() {
return this.errorDecoder;
return errorDecoder;
}
public void setErrorDecoder(Class<ErrorDecoder> errorDecoder) {
@@ -161,11 +175,10 @@ public class FeignClientProperties {
}
public List<Class<RequestInterceptor>> getRequestInterceptors() {
return this.requestInterceptors;
return requestInterceptors;
}
public void setRequestInterceptors(
List<Class<RequestInterceptor>> requestInterceptors) {
public void setRequestInterceptors(List<Class<RequestInterceptor>> requestInterceptors) {
this.requestInterceptors = requestInterceptors;
}
@@ -173,8 +186,7 @@ public class FeignClientProperties {
return defaultRequestHeaders;
}
public void setDefaultRequestHeaders(
Map<String, Collection<String>> defaultRequestHeaders) {
public void setDefaultRequestHeaders(Map<String, Collection<String>> defaultRequestHeaders) {
this.defaultRequestHeaders = defaultRequestHeaders;
}
@@ -182,13 +194,12 @@ public class FeignClientProperties {
return defaultQueryParameters;
}
public void setDefaultQueryParameters(
Map<String, Collection<String>> defaultQueryParameters) {
public void setDefaultQueryParameters(Map<String, Collection<String>> defaultQueryParameters) {
this.defaultQueryParameters = defaultQueryParameters;
}
public Boolean getDecode404() {
return this.decode404;
return decode404;
}
public void setDecode404(Boolean decode404) {
@@ -196,7 +207,7 @@ public class FeignClientProperties {
}
public Class<Decoder> getDecoder() {
return this.decoder;
return decoder;
}
public void setDecoder(Class<Decoder> decoder) {
@@ -204,7 +215,7 @@ public class FeignClientProperties {
}
public Class<Encoder> getEncoder() {
return this.encoder;
return encoder;
}
public void setEncoder(Class<Encoder> encoder) {
@@ -212,7 +223,7 @@ public class FeignClientProperties {
}
public Class<Contract> getContract() {
return this.contract;
return contract;
}
public void setContract(Class<Contract> contract) {
@@ -223,8 +234,7 @@ public class FeignClientProperties {
return exceptionPropagationPolicy;
}
public void setExceptionPropagationPolicy(
ExceptionPropagationPolicy exceptionPropagationPolicy) {
public void setExceptionPropagationPolicy(ExceptionPropagationPolicy exceptionPropagationPolicy) {
this.exceptionPropagationPolicy = exceptionPropagationPolicy;
}
@@ -237,31 +247,22 @@ public class FeignClientProperties {
return false;
}
FeignClientConfiguration that = (FeignClientConfiguration) o;
return this.loggerLevel == that.loggerLevel
&& Objects.equals(this.connectTimeout, that.connectTimeout)
&& Objects.equals(this.readTimeout, that.readTimeout)
&& Objects.equals(this.retryer, that.retryer)
&& Objects.equals(this.errorDecoder, that.errorDecoder)
&& Objects.equals(this.requestInterceptors, that.requestInterceptors)
&& Objects.equals(this.decode404, that.decode404)
&& Objects.equals(this.encoder, that.encoder)
&& Objects.equals(this.decoder, that.decoder)
&& Objects.equals(this.contract, that.contract)
&& Objects.equals(this.exceptionPropagationPolicy,
that.exceptionPropagationPolicy)
&& Objects.equals(this.defaultRequestHeaders,
that.defaultRequestHeaders)
&& Objects.equals(this.defaultQueryParameters,
that.defaultQueryParameters);
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(defaultRequestHeaders, that.defaultRequestHeaders)
&& Objects.equals(defaultQueryParameters, that.defaultQueryParameters);
}
@Override
public int hashCode() {
return Objects.hash(this.loggerLevel, this.connectTimeout, this.readTimeout,
this.retryer, this.errorDecoder, this.requestInterceptors,
this.decode404, this.encoder, this.decoder, this.contract,
this.exceptionPropagationPolicy, this.defaultQueryParameters,
this.defaultRequestHeaders);
return Objects.hash(loggerLevel, connectTimeout, readTimeout, retryer, errorDecoder, requestInterceptors,
decode404, encoder, decoder, contract, exceptionPropagationPolicy, defaultQueryParameters,
defaultRequestHeaders);
}
}
@@ -64,8 +64,7 @@ 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
@@ -75,9 +74,8 @@ 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();
}
}
@@ -20,7 +20,6 @@ import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.databind.Module;
import com.netflix.hystrix.HystrixCommand;
import feign.Contract;
import feign.Feign;
import feign.Logger;
@@ -29,7 +28,6 @@ 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;
@@ -38,7 +36,6 @@ import org.springframework.beans.factory.annotation.Autowired;
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.autoconfigure.data.web.SpringDataWebProperties;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.clientconfig.FeignClientConfigurer;
@@ -82,11 +79,13 @@ public class FeignClientsConfiguration {
@Autowired(required = false)
private SpringDataWebProperties springDataWebProperties;
@Autowired(required = false)
private FeignClientProperties feignClientProperties;
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new OptionalDecoder(
new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
}
@Bean
@@ -99,18 +98,13 @@ 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;
}
@@ -118,7 +112,8 @@ public class FeignClientsConfiguration {
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
boolean decodeSlash = feignClientProperties == null || feignClientProperties.isDecodeSlash();
return new SpringMvcContract(this.parameterProcessors, feignConversionService, decodeSlash);
}
@Bean
@@ -172,35 +167,19 @@ 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);
}
@@ -54,8 +54,7 @@ import org.springframework.util.StringUtils;
* @author Gang Li
* @author Michal Domagala
*/
class FeignClientsRegistrar
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
// patterned after Spring Integration IntegrationComponentScanRegistrar
// and RibbonClientsConfigurationRegistgrar
@@ -68,8 +67,7 @@ class FeignClientsRegistrar
}
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) {
@@ -134,16 +132,13 @@ class FeignClientsRegistrar
}
@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;
@@ -153,21 +148,16 @@ class FeignClientsRegistrar
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());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
FeignClient.class);
final Class<?>[] clients = attrs == null ? null
: (Class<?>[]) attrs.get("clients");
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
final Class<?>[] clients = attrs == null ? null : (Class<?>[]) attrs.get("clients");
if (clients == null || clients.length == 0) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
@@ -188,26 +178,23 @@ class FeignClientsRegistrar
// 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();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
@@ -235,8 +222,7 @@ class FeignClientsRegistrar
alias = qualifier;
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
new String[] { alias });
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[] { alias });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
@@ -290,8 +276,7 @@ class FeignClientsRegistrar
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()) {
@@ -323,8 +308,7 @@ class FeignClientsRegistrar
}
if (basePackages.isEmpty()) {
basePackages.add(
ClassUtils.getPackageName(importingClassMetadata.getClassName()));
basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));
}
return basePackages;
}
@@ -358,18 +342,15 @@ class FeignClientsRegistrar
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());
}
@@ -1,101 +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;
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);
}
}
@@ -22,9 +22,9 @@ import feign.Target;
/**
* @author Spencer Gibb
*/
interface Targeter {
public 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);
}
@@ -48,15 +48,13 @@ 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);
@@ -65,8 +63,7 @@ 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;
@@ -75,8 +72,7 @@ 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());
}
@@ -46,18 +46,15 @@ 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);
}
@@ -40,8 +40,7 @@ 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) {
@@ -46,27 +46,23 @@ 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;
}
@@ -46,15 +46,13 @@ 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;
@@ -62,12 +60,10 @@ 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;
}
@@ -44,19 +44,16 @@ 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;
}
@@ -16,11 +16,14 @@
package org.springframework.cloud.openfeign.clientconfig;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import javax.annotation.PreDestroy;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
@@ -48,8 +51,10 @@ import org.springframework.context.annotation.Configuration;
@ConditionalOnMissingBean(CloseableHttpClient.class)
public class HttpClientFeignConfiguration {
private final Timer connectionManagerTimer = new Timer(
"FeignApacheHttpClientConfiguration.connectionManagerTimer", true);
private static final Log LOG = LogFactory.getLog(HttpClientFeignConfiguration.class);
private final Timer connectionManagerTimer = new Timer("FeignApacheHttpClientConfiguration.connectionManagerTimer",
true);
private CloseableHttpClient httpClient;
@@ -61,12 +66,10 @@ 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() {
@@ -77,46 +80,45 @@ 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);
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)
@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);
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;
}
@PreDestroy
public void destroy() throws Exception {
public void destroy() {
this.connectionManagerTimer.cancel();
if (this.httpClient != null) {
this.httpClient.close();
try {
this.httpClient.close();
}
catch (IOException e) {
if (LOG.isErrorEnabled()) {
LOG.error("Could not correctly close httpClient.");
}
}
}
}
@@ -46,8 +46,7 @@ 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();
@@ -56,15 +55,13 @@ 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;
}
@@ -48,8 +48,7 @@ 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);
@@ -39,8 +39,7 @@ 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")
@@ -33,8 +33,7 @@ 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);
}
@@ -44,8 +43,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);
}
}
@@ -32,8 +32,7 @@ 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.
@@ -77,9 +76,8 @@ 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();
}
}
@@ -33,8 +33,7 @@ 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);
}
@@ -45,8 +44,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);
}
}
@@ -93,8 +92,7 @@ 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;
}
@@ -51,8 +51,7 @@ 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 {
@@ -60,24 +59,21 @@ 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);
@@ -16,51 +16,52 @@
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.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
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 BlockingLoadBalancerClient}-based {@link Client}
* object that uses {@link Client.Default} under the hood.
* Configuration instantiating a {@link LoadBalancerClient}-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(BlockingLoadBalancerClient loadBalancerClient) {
return new FeignBlockingLoadBalancerClient(new Client.Default(null, null),
loadBalancerClient);
public Client feignClient(LoadBalancerClient loadBalancerClient, LoadBalancerProperties properties,
LoadBalancerClientFactory loadBalancerClientFactory) {
return new FeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient, properties,
loadBalancerClientFactory);
}
@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(BlockingLoadBalancerClient loadBalancerClient,
List<LoadBalancedRetryFactory> loadBalancedRetryFactories) {
AnnotationAwareOrderComparator.sort(loadBalancedRetryFactories);
return new RetryableFeignBlockingLoadBalancerClient(
new Client.Default(null, null), loadBalancerClient,
loadBalancedRetryFactories.get(0));
@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);
}
}
@@ -19,6 +19,7 @@ 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;
@@ -27,55 +28,86 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
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.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 BlockingLoadBalancerClient} to select
* a {@link ServiceInstance} to use while resolving the request host.
* A {@link Client} implementation that uses {@link LoadBalancerClient} 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 BlockingLoadBalancerClient loadBalancerClient;
private final LoadBalancerClient loadBalancerClient;
public FeignBlockingLoadBalancerClient(Client delegate,
BlockingLoadBalancerClient loadBalancerClient) {
private final LoadBalancerProperties properties;
private final LoadBalancerClientFactory loadBalancerClientFactory;
public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
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);
ServiceInstance instance = loadBalancerClient.choose(serviceId);
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);
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);
}
return Response.builder().request(request)
.status(HttpStatus.SERVICE_UNAVAILABLE.value())
supportedLifecycleProcessors
.forEach(lifecycle -> lifecycle.onComplete(new CompletionContext<ResponseData, ServiceInstance>(
CompletionContext.Status.DISCARD, lbResponse)));
return Response.builder().request(request).status(HttpStatus.SERVICE_UNAVAILABLE.value())
.body(message, StandardCharsets.UTF_8).build();
}
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri)
.toString();
Request newRequest = Request.create(request.httpMethod(), reconstructedUrl,
request.headers(), request.body(), request.charset(),
request.requestTemplate());
return delegate.execute(newRequest, options);
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
Request newRequest = buildRequest(request, reconstructedUrl);
return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbResponse,
supportedLifecycleProcessors);
}
protected Request buildRequest(Request request, String reconstructedUrl) {
return Request.create(request.httpMethod(), reconstructedUrl, request.headers(), request.body(),
request.charset(), request.requestTemplate());
}
// Visible for Sleuth instrumentation
@@ -83,4 +115,10 @@ 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;
}
}
@@ -19,38 +19,33 @@ package org.springframework.cloud.openfeign.loadbalancer;
import feign.Client;
import feign.Feign;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
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 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>.
* An autoconfiguration that instantiates {@link LoadBalancerClient}-based implementations
* of {@link Client}.
*
* @author Olga Maciaszek-Sharma
* @since 2.2.0
*/
@ConditionalOnClass(Feign.class)
@ConditionalOnBean(BlockingLoadBalancerClient.class)
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
@AutoConfigureBefore(FeignAutoConfiguration.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 {
@@ -16,8 +16,6 @@
package org.springframework.cloud.openfeign.loadbalancer;
import java.util.List;
import feign.Client;
import feign.httpclient.ApacheHttpClient;
import org.apache.http.client.HttpClient;
@@ -26,51 +24,53 @@ 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.loadbalancer.blocking.client.BlockingLoadBalancerClient;
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.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 BlockingLoadBalancerClient}-based {@link Client}
* object that uses {@link ApacheHttpClient} under the hood.
* Configuration instantiating a {@link LoadBalancerClient}-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(BlockingLoadBalancerClient.class)
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
@Import(HttpClientFeignConfiguration.class)
@EnableConfigurationProperties(LoadBalancerProperties.class)
class HttpClientFeignLoadBalancerConfiguration {
@Bean
@ConditionalOnMissingBean
@Conditional(OnRetryNotEnabledCondition.class)
public Client feignClient(BlockingLoadBalancerClient loadBalancerClient,
HttpClient httpClient) {
public Client feignClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, properties, loadBalancerClientFactory);
}
@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(BlockingLoadBalancerClient loadBalancerClient,
HttpClient httpClient,
List<LoadBalancedRetryFactory> loadBalancedRetryFactories) {
AnnotationAwareOrderComparator.sort(loadBalancedRetryFactories);
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
matchIfMissing = true)
public Client feignRetryClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,
LoadBalancerClientFactory loadBalancerClientFactory) {
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient,
loadBalancedRetryFactories.get(0));
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
properties, loadBalancerClientFactory);
}
}
@@ -0,0 +1,92 @@
/*
* 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.Response<ServiceInstance> lbResponse,
Set<LoadBalancerLifecycle> supportedLifecycleProcessors, boolean loadBalanced) throws IOException {
try {
Response response = feignClient.execute(feignRequest, options);
if (loadBalanced) {
supportedLifecycleProcessors.forEach(
lifecycle -> lifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS,
lbResponse, buildResponseData(response))));
}
return response;
}
catch (Exception exception) {
if (loadBalanced) {
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
.onComplete(new CompletionContext<>(CompletionContext.Status.FAILED, exception, 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.Response<ServiceInstance> lbResponse,
Set<LoadBalancerLifecycle> supportedLifecycleProcessors) throws IOException {
return executeWithLoadBalancerLifecycleProcessing(feignClient, options, feignRequest, lbResponse,
supportedLifecycleProcessors, true);
}
}
@@ -16,8 +16,6 @@
package org.springframework.cloud.openfeign.loadbalancer;
import java.util.List;
import feign.Client;
import feign.okhttp.OkHttpClient;
@@ -25,18 +23,20 @@ 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.loadbalancer.blocking.client.BlockingLoadBalancerClient;
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.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 BlockingLoadBalancerClient}-based {@link Client}
* object that uses {@link OkHttpClient} under the hood.
* Configuration instantiating a {@link LoadBalancerClient}-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.core.annotation.AnnotationAwareOrderComparator;
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
@ConditionalOnBean(BlockingLoadBalancerClient.class)
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
@Import(OkHttpFeignConfiguration.class)
@EnableConfigurationProperties(LoadBalancerProperties.class)
class OkHttpFeignLoadBalancerConfiguration {
@Bean
@ConditionalOnMissingBean
@Conditional(OnRetryNotEnabledCondition.class)
public Client feignClient(okhttp3.OkHttpClient okHttpClient,
BlockingLoadBalancerClient loadBalancerClient) {
public Client feignClient(okhttp3.OkHttpClient okHttpClient, LoadBalancerClient loadBalancerClient,
LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
OkHttpClient delegate = new OkHttpClient(okHttpClient);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, properties, loadBalancerClientFactory);
}
@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(BlockingLoadBalancerClient loadBalancerClient,
okhttp3.OkHttpClient okHttpClient,
List<LoadBalancedRetryFactory> loadBalancedRetryFactories) {
AnnotationAwareOrderComparator.sort(loadBalancedRetryFactories);
@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) {
OkHttpClient delegate = new OkHttpClient(okHttpClient);
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient,
loadBalancedRetryFactories.get(0));
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
properties, loadBalancerClientFactory);
}
}
@@ -47,8 +47,7 @@ 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 {
}
@@ -23,6 +23,7 @@ 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;
@@ -31,13 +32,23 @@ 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.RequestData;
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.blocking.client.BlockingLoadBalancerClient;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
@@ -46,6 +57,9 @@ 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.executeWithLoadBalancerLifecycleProcessing;
/**
* A {@link Client} implementation that provides Spring Retry support for requests
@@ -54,69 +68,100 @@ import org.springframework.retry.support.RetryTemplate;
* @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 BlockingLoadBalancerClient loadBalancerClient;
private final LoadBalancerClient loadBalancerClient;
private final LoadBalancedRetryFactory loadBalancedRetryFactory;
public RetryableFeignBlockingLoadBalancerClient(Client delegate,
BlockingLoadBalancerClient loadBalancerClient,
LoadBalancedRetryFactory loadBalancedRetryFactory) {
private final LoadBalancerProperties properties;
private final LoadBalancerClientFactory loadBalancerClientFactory;
public RetryableFeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerProperties properties,
LoadBalancerClientFactory loadBalancerClientFactory) {
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();
final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryFactory
.createRetryPolicy(serviceId, loadBalancerClient);
Assert.state(serviceId != null, "Request URI does not contain a valid hostname: " + originalUri);
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, RequestData.class, ServiceInstance.class);
// 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) {
ServiceInstance serviceInstance = ((LoadBalancedRetryContext) context)
.getServiceInstance();
if (serviceInstance != null) {
LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
ServiceInstance serviceInstance = lbContext.getServiceInstance();
if (serviceInstance == null) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format(
"Using service instance from LoadBalancedRetryContext: %s",
serviceInstance));
LOG.debug("Service instance retrieved from LoadBalancedRetryContext: was null. "
+ "Reattempting service instance selection");
}
String reconstructedUrl = loadBalancerClient
.reconstructURI(serviceInstance, originalUri).toString();
feignRequest = Request.create(request.httpMethod(), reconstructedUrl,
request.headers(), request.body(), request.charset(),
request.requestTemplate());
ServiceInstance previousServiceInstance = lbContext.getPreviousServiceInstance();
String hint = getHint(serviceId);
DefaultRequest<RetryableRequestContext> lbRequest = new DefaultRequest<>(
new RetryableRequestContext(previousServiceInstance, request, hint));
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>(
CompletionContext.Status.DISCARD, 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();
feignRequest = buildRequest(request, reconstructedUrl);
}
}
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);
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
retrievedServiceInstance);
Response response = executeWithLoadBalancerLifecycleProcessing(delegate, options, feignRequest, lbResponse,
supportedLifecycleProcessors, retrievedServiceInstance != null);
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>() {
@@ -127,22 +172,22 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
});
}
private RetryTemplate buildRetryTemplate(String serviceId, Request request,
LoadBalancedRetryPolicy retryPolicy) {
protected Request buildRequest(Request request, String reconstructedUrl) {
return Request.create(request.httpMethod(), reconstructedUrl, request.headers(), request.body(),
request.charset(), request.requestTemplate());
}
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;
}
@@ -182,4 +227,10 @@ 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;
}
}
@@ -1,72 +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.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;
}
}
@@ -1,40 +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.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);
}
}
@@ -1,245 +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.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();
}
}
}
}
@@ -1,129 +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.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;
}
}
}
@@ -1,83 +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.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;
}
}
@@ -1,50 +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.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);
}
}
@@ -1,143 +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.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() {
}
}
}
@@ -1,49 +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.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);
}
}
@@ -1,141 +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.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));
}
}
@@ -1,49 +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.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;
}
}
@@ -29,7 +29,6 @@ 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
@@ -45,12 +44,10 @@ 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);
}
@@ -45,19 +45,15 @@ 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);
}
}
@@ -69,8 +65,7 @@ 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 = "";
@@ -43,8 +43,7 @@ public class DefaultGzipDecoderConfiguration {
private ObjectFactory<HttpMessageConverters> messageConverters;
public DefaultGzipDecoderConfiguration(
ObjectFactory<HttpMessageConverters> messageConverters) {
public DefaultGzipDecoderConfiguration(ObjectFactory<HttpMessageConverters> messageConverters) {
this.messageConverters = messageConverters;
}
@@ -52,8 +51,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))));
}
}
@@ -1,79 +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.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;
}
}
@@ -54,10 +54,8 @@ 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;
}
@@ -63,9 +63,8 @@ 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") long totalElements,
@JsonProperty("sort") Sort sort) {
PageRequest pageRequest;
if (sort != null) {
@@ -75,8 +75,7 @@ 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) {
@@ -101,18 +100,15 @@ 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) {
@@ -46,8 +46,7 @@ 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];
@@ -86,8 +85,7 @@ 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()));
}
}
@@ -48,8 +48,7 @@ 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);
}
@@ -44,8 +44,7 @@ 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 {
@@ -68,15 +67,14 @@ 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);
}
@@ -48,18 +48,15 @@ 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());
}
@@ -69,19 +69,16 @@ 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()) {
@@ -95,24 +92,20 @@ 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) {
@@ -131,20 +124,19 @@ 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 + "]";
}
@@ -153,8 +145,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);
@@ -167,9 +159,8 @@ 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);
@@ -185,12 +176,11 @@ 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 + "]");
}
}
}
@@ -78,19 +78,17 @@ import static org.springframework.core.annotation.AnnotatedElementUtils.findMerg
* @author Aaron Whiteside
* @author Artyom Romanenko
* @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();
@@ -104,20 +102,24 @@ public class SpringMvcContract extends Contract.BaseContract
private ResourceLoader resourceLoader = new DefaultResourceLoader();
private boolean decodeSlash;
public SpringMvcContract() {
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) {
Assert.notNull(annotatedParameterProcessors,
"Parameter processors can not be null.");
this(annotatedParameterProcessors, conversionService, true);
}
public SpringMvcContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors,
ConversionService conversionService, boolean decodeSlash) {
Assert.notNull(annotatedParameterProcessors, "Parameter processors can not be null.");
Assert.notNull(conversionService, "ConversionService can not be null.");
List<AnnotatedParameterProcessor> processors = getDefaultAnnotatedArgumentsProcessors();
@@ -126,6 +128,7 @@ public class SpringMvcContract extends Contract.BaseContract
annotatedArgumentProcessors = toAnnotatedArgumentProcessorMap(processors);
this.conversionService = conversionService;
convertingExpanderFactory = new ConvertingExpanderFactory(conversionService);
this.decodeSlash = decodeSlash;
}
private static TypeDescriptor createTypeDescriptor(Method method, int paramIndex) {
@@ -136,26 +139,21 @@ public class SpringMvcContract extends Contract.BaseContract
// 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;
}
@@ -172,8 +170,7 @@ public class SpringMvcContract extends Contract.BaseContract
@Override
protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
if (clz.getInterfaces().length == 0) {
RequestMapping classAnnotation = findMergedAnnotation(clz,
RequestMapping.class);
RequestMapping classAnnotation = findMergedAnnotation(clz, RequestMapping.class);
if (classAnnotation != null) {
// Prepend path from class annotation if specified
if (classAnnotation.value().length > 0) {
@@ -183,6 +180,9 @@ public class SpringMvcContract extends Contract.BaseContract
pathValue = "/" + pathValue;
}
data.template().uri(pathValue);
if (data.template().decodeSlash() != decodeSlash) {
data.template().decodeSlash(decodeSlash);
}
}
}
}
@@ -193,8 +193,7 @@ public class SpringMvcContract extends Contract.BaseContract
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)) {
@@ -214,16 +213,14 @@ public class SpringMvcContract extends Contract.BaseContract
}
@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;
}
@@ -247,6 +244,9 @@ public class SpringMvcContract extends Contract.BaseContract
pathValue = "/" + pathValue;
}
data.template().uri(pathValue, true);
if (data.template().decodeSlash() != decodeSlash) {
data.template().decodeSlash(decodeSlash);
}
}
}
@@ -263,34 +263,29 @@ public class SpringMvcContract extends Contract.BaseContract
}
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
@@ -299,19 +294,16 @@ public class SpringMvcContract extends Contract.BaseContract
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);
}
@@ -320,28 +312,23 @@ public class SpringMvcContract extends Contract.BaseContract
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()) {
@@ -377,26 +364,21 @@ public class SpringMvcContract extends Contract.BaseContract
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
@@ -404,14 +386,12 @@ public class SpringMvcContract extends Contract.BaseContract
}
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;
@@ -421,25 +401,6 @@ public class SpringMvcContract extends Contract.BaseContract
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;
@@ -450,23 +411,20 @@ public class SpringMvcContract extends Contract.BaseContract
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;
}
@@ -487,8 +445,7 @@ public class SpringMvcContract extends Contract.BaseContract
}
@Override
public Collection<String> setTemplateParameter(String name,
Collection<String> rest) {
public Collection<String> setTemplateParameter(String name, Collection<String> rest) {
return addTemplateParameter(rest, name);
}
@@ -1,5 +1,4 @@
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,\
@@ -43,10 +43,8 @@ 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 {
@@ -71,20 +69,17 @@ 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);
@@ -94,12 +89,10 @@ 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 {
@@ -21,12 +21,10 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
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;
@@ -44,13 +42,11 @@ 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)
@Import({ PropertyPlaceholderAutoConfiguration.class, ArchaiusAutoConfiguration.class,
FeignAutoConfiguration.class })
@EnableAutoConfiguration
protected static class PlainConfiguration {
}
@@ -30,7 +30,6 @@ 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;
@@ -88,7 +87,7 @@ public class EnableFeignClientsTests {
}
@Configuration(proxyBeanMethods = false)
@Import({ ArchaiusAutoConfiguration.class, FeignAutoConfiguration.class })
@Import({ FeignAutoConfiguration.class })
protected static class PlainConfiguration {
}
@@ -0,0 +1,156 @@
/*
* 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.lang.reflect.Field;
import feign.Feign;
import feign.Logger;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Matt King
*/
public class FeignBuilderCustomizerTests {
@Test
public void testBuilderCustomizer() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
FeignBuilderCustomizerTests.SampleConfiguration2.class);
FeignClientFactoryBean clientFactoryBean = context.getBean(FeignClientFactoryBean.class);
FeignContext feignContext = context.getBean(FeignContext.class);
Feign.Builder builder = clientFactoryBean.feign(feignContext);
assertFeignBuilderField(builder, "logLevel", Logger.Level.HEADERS);
assertFeignBuilderField(builder, "decode404", true);
context.close();
}
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);
}
@Test
public void testBuildCustomizerOrdered() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
FeignBuilderCustomizerTests.SampleConfiguration3.class);
FeignClientFactoryBean clientFactoryBean = context.getBean(FeignClientFactoryBean.class);
FeignContext feignContext = context.getBean(FeignContext.class);
Feign.Builder builder = clientFactoryBean.feign(feignContext);
assertFeignBuilderField(builder, "logLevel", Logger.Level.FULL);
assertFeignBuilderField(builder, "decode404", true);
context.close();
}
private static FeignClientFactoryBean defaultFeignClientFactoryBean() {
FeignClientFactoryBean feignClientFactoryBean = new FeignClientFactoryBean();
feignClientFactoryBean.setContextId("test");
feignClientFactoryBean.setName("test");
feignClientFactoryBean.setType(FeignClientFactoryTests.TestType.class);
feignClientFactoryBean.setPath("");
feignClientFactoryBean.setUrl("http://some.absolute.url");
return feignClientFactoryBean;
}
@Configuration(proxyBeanMethods = false)
@Import(FeignClientsConfiguration.class)
protected static class SampleConfiguration2 {
@Bean
FeignContext feignContext() {
return new FeignContext();
}
@Bean
FeignClientProperties feignClientProperties() {
return new FeignClientProperties();
}
@Bean
FeignBuilderCustomizer feignBuilderCustomizer() {
return builder -> builder.logLevel(Logger.Level.HEADERS);
}
@Bean
FeignBuilderCustomizer feignBuilderCustomizer2() {
return Feign.Builder::decode404;
}
@Bean
FeignClientFactoryBean feignClientFactoryBean() {
return defaultFeignClientFactoryBean();
}
}
@Configuration(proxyBeanMethods = false)
@Import(FeignClientsConfiguration.class)
protected static class SampleConfiguration3 {
@Bean
FeignContext feignContext() {
return new FeignContext();
}
@Bean
FeignClientProperties feignClientProperties() {
return new FeignClientProperties();
}
@Bean
@Order(1)
FeignBuilderCustomizer feignBuilderCustomizer() {
return builder -> builder.logLevel(Logger.Level.HEADERS);
}
@Bean
@Order(2)
FeignBuilderCustomizer feignBuilderCustomizer1() {
return builder -> builder.logLevel(Logger.Level.FULL);
}
@Bean
FeignBuilderCustomizer feignBuilderCustomizer2() {
return Feign.Builder::decode404;
}
@Bean
FeignClientFactoryBean feignClientFactoryBean() {
return defaultFeignClientFactoryBean();
}
}
}
@@ -24,7 +24,6 @@ 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;
@@ -50,26 +49,23 @@ 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
@@ -84,9 +80,8 @@ 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
@@ -94,44 +89,54 @@ 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, "TestClient").inheritParentContext(false)
.fallback(TestFeignClientFallback.class)
.fallbackFactory(TestFeignClientFallbackFactory.class).decode404(true)
.url("Url/").path("/Path").contextId("TestContext");
.forType(TestFeignClient.class, new FeignClientFactoryBean(), "TestClient").decode404(true)
.path("Path/").url("Url/").contextId("TestContext");
// then:
assertFactoryBeanField(builder, "applicationContext", this.applicationContext);
@@ -143,21 +148,19 @@ 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'
@@ -169,18 +172,4 @@ public class FeignClientBuilderTests {
}
private class TestFeignClientFallback implements TestFeignClient {
}
private class TestFeignClientFallbackFactory
implements FallbackFactory<TestFeignClient> {
@Override
public TestFeignClientFallback create(Throwable throwable) {
return new TestFeignClientFallback();
}
}
}
@@ -67,8 +67,7 @@ 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();
}
@@ -90,8 +89,7 @@ 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");
}
@@ -29,6 +29,7 @@ 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;
@@ -52,8 +53,7 @@ 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,8 +67,7 @@ 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" })
@@ -98,15 +97,14 @@ public class FeignClientFactoryTests {
@Bean
BlockingLoadBalancerClient loadBalancerClient() {
return new BlockingLoadBalancerClient(new LoadBalancerClientFactory());
return new BlockingLoadBalancerClient(new LoadBalancerClientFactory(), new LoadBalancerProperties());
}
@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;
}
@@ -18,7 +18,6 @@ package org.springframework.cloud.openfeign;
import feign.Contract;
import feign.ExceptionPropagationPolicy;
import feign.Feign;
import feign.Logger;
import feign.QueryMapEncoder;
import feign.Request;
@@ -29,21 +28,18 @@ 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.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
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;
@@ -99,14 +95,12 @@ 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));
}
@@ -116,12 +110,6 @@ 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();
@@ -132,29 +120,24 @@ 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 /")
@@ -162,8 +145,7 @@ 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)
@@ -173,8 +155,7 @@ class FeignClientOverrideDefaultsTests {
@Configuration(proxyBeanMethods = false)
@EnableFeignClients(clients = { FooClient.class, BarClient.class })
@Import({ PropertyPlaceholderAutoConfiguration.class, ArchaiusAutoConfiguration.class,
FeignAutoConfiguration.class })
@EnableAutoConfiguration
protected static class TestConfiguration {
@Bean
@@ -207,11 +188,6 @@ class FeignClientOverrideDefaultsTests {
return new Contract.Default();
}
@Bean
public Feign.Builder feignBuilder() {
return HystrixFeign.builder();
}
@Bean
public QueryMapEncoder queryMapEncoder() {
return new feign.QueryMapEncoder.Default();
@@ -63,11 +63,9 @@ public class FeignClientUsingConfigurerTest {
.getBean(BEAN_NAME_PREFIX + "TestFeignClient");
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,12 +81,10 @@ public class FeignClientUsingConfigurerTest {
.getBean(BEAN_NAME_PREFIX + "NoInheritFeignClient");
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
@@ -97,8 +93,7 @@ public class FeignClientUsingConfigurerTest {
.getBean(BEAN_NAME_PREFIX + "NoInheritFeignClient");
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
@@ -140,8 +135,7 @@ public class FeignClientUsingConfigurerTest {
}
@FeignClient(name = "noInheritFeignClient",
configuration = NoInheritConfiguration.class)
@FeignClient(name = "noInheritFeignClient", configuration = NoInheritConfiguration.class)
interface NoInheritFeignClient {
}
@@ -79,8 +79,7 @@ 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 {
@@ -124,40 +123,32 @@ 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
@@ -194,23 +185,18 @@ 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);
}
@@ -221,8 +207,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,8 +223,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);
@@ -251,8 +237,7 @@ 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 {
@@ -313,8 +298,7 @@ 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 {
@@ -328,8 +312,7 @@ 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");
}
@@ -337,16 +320,15 @@ 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());
}
}
@@ -390,16 +372,14 @@ 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());
}
@@ -22,7 +22,6 @@ 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;
@@ -96,17 +95,15 @@ 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")
@@ -132,16 +129,13 @@ 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 {
}
@@ -28,7 +28,6 @@ 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;
@@ -51,18 +50,15 @@ 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(ArchaiusAutoConfiguration.class,
FeignAutoConfiguration.class,
FeignContentGzipEncodingAutoConfiguration.class,
FeignAcceptGzipEncodingAutoConfiguration.class,
.withConfiguration(AutoConfigurations.of(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);
@@ -39,11 +39,9 @@ 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();
}
@@ -59,8 +57,7 @@ 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();
@@ -75,11 +72,9 @@ 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);
}
@@ -91,8 +86,7 @@ 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();
@@ -34,20 +34,16 @@ 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();
@@ -57,10 +53,8 @@ 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();
@@ -53,10 +53,8 @@ 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
@@ -68,13 +66,10 @@ 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(
@@ -84,14 +79,11 @@ 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");
}
@@ -23,10 +23,9 @@ import feign.Client;
import feign.Feign;
import feign.Target;
import feign.httpclient.ApacheHttpClient;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -36,7 +35,6 @@ 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;
@@ -49,13 +47,11 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
/**
* @author Spencer Gibb
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = FeignHttpClientUrlTests.TestConfig.class,
webEnvironment = DEFINED_PORT,
value = { "spring.application.name=feignclienturltest",
"feign.hystrix.enabled=false", "feign.okhttp.enabled=false" })
@SpringBootTest(classes = FeignHttpClientUrlTests.TestConfig.class, webEnvironment = DEFINED_PORT,
value = { "spring.application.name=feignclienturltest", "feign.hystrix.enabled=false",
"feign.okhttp.enabled=false", "spring.cloud.loadbalancer.retry.enabled=false" })
@DirtiesContext
public class FeignHttpClientUrlTests {
class FeignHttpClientUrlTests {
static int port;
@@ -68,40 +64,37 @@ public class FeignHttpClientUrlTests {
@Autowired
private BeanUrlClient beanClient;
@BeforeClass
@BeforeAll
public static void beforeClass() {
port = SocketUtils.findAvailableTcpPort();
System.setProperty("server.port", String.valueOf(port));
}
@AfterClass
@AfterAll
public static void afterClass() {
System.clearProperty("server.port");
}
@Test
public void testUrlHttpClient() {
assertThat(this.urlClient).as("UrlClient was null").isNotNull();
Hello hello = this.urlClient.getHello();
void testUrlHttpClient() {
assertThat(urlClient).as("UrlClient was null").isNotNull();
Hello hello = 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
public void testBeanUrl() {
Hello hello = this.beanClient.getHello();
void testBeanUrl() {
Hello hello = 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 = this.beanClientNoProtocol.getHello();
Hello hello = 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
@@ -132,8 +125,7 @@ public 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 {
@@ -161,15 +153,13 @@ public 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);
}
@@ -182,15 +172,15 @@ public class FeignHttpClientUrlTests {
private String message;
public Hello() {
Hello() {
}
public Hello(String message) {
Hello(String message) {
this.message = message;
}
public String getMessage() {
return this.message;
return message;
}
public void setMessage(String message) {
@@ -206,12 +196,12 @@ public class FeignHttpClientUrlTests {
return false;
}
Hello that = (Hello) o;
return Objects.equals(this.message, that.message);
return Objects.equals(message, that.message);
}
@Override
public int hashCode() {
return Objects.hash(this.message);
return Objects.hash(message);
}
}
@@ -34,8 +34,7 @@ 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);
@@ -46,8 +45,7 @@ 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);
@@ -58,8 +56,7 @@ 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();
@@ -49,10 +49,9 @@ 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();
}
@@ -66,11 +65,8 @@ 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) {
@@ -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;
@@ -47,16 +47,15 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Spencer Gibb
*/
@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;
@Value("${local.server.port}")
@LocalServerPort
private int port = 0;
public SpringDecoderTests() {
@@ -71,28 +70,24 @@ 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
@@ -100,8 +95,7 @@ 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
@@ -109,8 +103,7 @@ 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
@@ -118,22 +111,19 @@ 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);
@@ -1,77 +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;
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);
}
}
@@ -1,65 +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;
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;
}
}
@@ -47,15 +47,19 @@ 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 = FeignClientTests.Application.class,
webEnvironment = WebEnvironment.RANDOM_PORT,
@SpringBootTest(classes = BeansFeignClientTests.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 FeignClientTests {
public class BeansFeignClientTests {
@Value("${local.server.port}")
private int port = 0;
@@ -76,18 +80,15 @@ public class FeignClientTests {
@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();
}
@@ -95,20 +96,17 @@ public class FeignClientTests {
@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();
}
@@ -17,7 +17,7 @@
package org.springframework.cloud.openfeign.beans;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.beans.FeignClientTests.Hello;
import org.springframework.cloud.openfeign.beans.BeansFeignClientTests.Hello;
import org.springframework.context.annotation.Primary;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@@ -17,7 +17,7 @@
package org.springframework.cloud.openfeign.beans.extra;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.beans.FeignClientTests.Hello;
import org.springframework.cloud.openfeign.beans.BeansFeignClientTests.Hello;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@@ -16,20 +16,19 @@
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.cloud.netflix.ribbon.RibbonClient;
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.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.encoding.app.client.InvoiceClient;
import org.springframework.cloud.openfeign.encoding.app.domain.Invoice;
@@ -50,8 +49,7 @@ 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
@@ -75,26 +73,23 @@ public class FeignAcceptEncodingTests {
}
@EnableFeignClients(clients = InvoiceClient.class)
@RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class)
@SpringBootApplication(
scanBasePackages = "org.springframework.cloud.openfeign.encoding.app")
@LoadBalancerClient(name = "local", configuration = LocalClientConfiguration.class)
@SpringBootApplication(scanBasePackages = "org.springframework.cloud.openfeign.encoding.app")
@Import(NoSecurityConfiguration.class)
public static class Application {
}
@Configuration(proxyBeanMethods = false)
static class LocalRibbonClientConfiguration {
static class LocalClientConfiguration {
@Value("${local.server.port}")
@LocalServerPort
private int port = 0;
@Bean
public ILoadBalancer ribbonLoadBalancer() {
BaseLoadBalancer balancer = new BaseLoadBalancer();
balancer.setServersList(
Collections.singletonList(new Server("localhost", this.port)));
return balancer;
public ServiceInstanceListSupplier staticServiceInstanceListSupplier() {
return ServiceInstanceListSuppliers.from("local",
new DefaultServiceInstance("local-1", "local", "localhost", port, false));
}
}
@@ -16,20 +16,19 @@
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.cloud.netflix.ribbon.RibbonClient;
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.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.encoding.app.client.InvoiceClient;
import org.springframework.cloud.openfeign.encoding.app.domain.Invoice;
@@ -49,8 +48,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
*
* @author Jakub Narloch
*/
@SpringBootTest(classes = FeignContentEncodingTests.Application.class,
webEnvironment = RANDOM_PORT,
@SpringBootTest(classes = FeignContentEncodingTests.Application.class, webEnvironment = RANDOM_PORT,
value = { "feign.compression.request.enabled=true",
"hystrix.command.default.execution.isolation.strategy=SEMAPHORE",
"ribbon.OkToRetryOnAllOperations=false" })
@@ -67,8 +65,7 @@ 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();
@@ -79,26 +76,23 @@ public class FeignContentEncodingTests {
}
@EnableFeignClients(clients = InvoiceClient.class)
@RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class)
@SpringBootApplication(
scanBasePackages = "org.springframework.cloud.openfeign.encoding.app")
@LoadBalancerClient(name = "local", configuration = LocalClientConfiguration.class)
@SpringBootApplication(scanBasePackages = "org.springframework.cloud.openfeign.encoding.app")
@Import(NoSecurityConfiguration.class)
public static class Application {
}
@Configuration(proxyBeanMethods = false)
static class LocalRibbonClientConfiguration {
static class LocalClientConfiguration {
@Value("${local.server.port}")
@LocalServerPort
private int port = 0;
@Bean
public ILoadBalancer ribbonLoadBalancer() {
BaseLoadBalancer balancer = new BaseLoadBalancer();
balancer.setServersList(
Collections.singletonList(new Server("localhost", this.port)));
return balancer;
public ServiceInstanceListSupplier staticServiceInstanceListSupplier() {
return ServiceInstanceListSuppliers.from("local",
new DefaultServiceInstance("local-1", "local", "localhost", port, false));
}
}
@@ -16,21 +16,20 @@
package org.springframework.cloud.openfeign.encoding;
import java.util.Collections;
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.cloud.netflix.ribbon.RibbonClient;
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.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClientsConfiguration;
import org.springframework.cloud.openfeign.encoding.app.client.InvoiceClient;
@@ -56,8 +55,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
*
* @author Charlie Mordant.
*/
@SpringBootTest(classes = FeignPageableEncodingTests.Application.class,
webEnvironment = RANDOM_PORT,
@SpringBootTest(classes = FeignPageableEncodingTests.Application.class, webEnvironment = RANDOM_PORT,
value = { "feign.compression.request.enabled=true",
"hystrix.command.default.execution.isolation.strategy=SEMAPHORE",
"ribbon.OkToRetryOnAllOperations=false" })
@@ -74,8 +72,7 @@ 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();
@@ -83,8 +80,7 @@ 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);
@@ -94,9 +90,8 @@ public class FeignPageableEncodingTests {
}
@EnableFeignClients(clients = InvoiceClient.class)
@RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class)
@SpringBootApplication(
scanBasePackages = "org.springframework.cloud.openfeign.encoding.app",
@LoadBalancerClient(name = "local", configuration = LocalClientConfiguration.class)
@SpringBootApplication(scanBasePackages = "org.springframework.cloud.openfeign.encoding.app",
exclude = { RepositoryRestMvcAutoConfiguration.class })
@EnableSpringDataWebSupport
@Import({ NoSecurityConfiguration.class, FeignClientsConfiguration.class })
@@ -105,17 +100,15 @@ public class FeignPageableEncodingTests {
}
@Configuration(proxyBeanMethods = false)
static class LocalRibbonClientConfiguration {
static class LocalClientConfiguration {
@Value("${local.server.port}")
@LocalServerPort
private int port = 0;
@Bean
public ILoadBalancer ribbonLoadBalancer() {
BaseLoadBalancer balancer = new BaseLoadBalancer();
balancer.setServersList(
Collections.singletonList(new Server("localhost", this.port)));
return balancer;
public ServiceInstanceListSupplier staticServiceInstanceListSupplier() {
return ServiceInstanceListSuppliers.from("local",
new DefaultServiceInstance("local-1", "local", "localhost", port, false));
}
}
@@ -35,8 +35,7 @@ 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;
@@ -34,17 +34,13 @@ import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient("local")
public interface InvoiceClient {
@RequestMapping(value = "invoicesPaged", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Page<Invoice>> getInvoicesPaged(
org.springframework.data.domain.Pageable pageable);
@RequestMapping(value = "invoicesPaged", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Page<Invoice>> getInvoicesPaged(org.springframework.data.domain.Pageable pageable);
@RequestMapping(value = "invoices", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "invoices", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<List<Invoice>> getInvoices();
@RequestMapping(value = "invoices", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
@RequestMapping(value = "invoices", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<List<Invoice>> saveInvoices(List<Invoice> invoices);
@@ -39,27 +39,22 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
public class InvoiceResource {
@RequestMapping(value = "invoices", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "invoices", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Invoice>> getInvoices() {
return ResponseEntity.ok(createInvoiceList(100));
}
@RequestMapping(value = "invoices", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_JSON_VALUE,
@RequestMapping(value = "invoices", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<List<Invoice>> saveInvoices(@RequestBody List<Invoice> invoices) {
return ResponseEntity.ok(invoices);
}
@RequestMapping(value = "invoicesPaged", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Page<Invoice>> getInvoicesPaged(
org.springframework.data.domain.Pageable pageable) {
Page<Invoice> page = new PageImpl<>(createInvoiceList(pageable.getPageSize()),
pageable, 100);
@RequestMapping(value = "invoicesPaged", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Page<Invoice>> getInvoicesPaged(org.springframework.data.domain.Pageable pageable) {
Page<Invoice> page = new PageImpl<>(createInvoiceList(pageable.getPageSize()), pageable, 100);
return ResponseEntity.ok(page);
}
@@ -68,8 +63,7 @@ public class InvoiceResource {
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;
@@ -68,9 +68,8 @@ public class ProtobufSpringEncoderTest {
// a protobuf object with some content
private org.springframework.cloud.openfeign.encoding.proto.Request request = org.springframework.cloud.openfeign.encoding.proto.Request
.newBuilder().setId(1000000).setMsg("Erlang/OTP 最初是爱立信为开发电信设备系统设计的编程语言平台,"
+ "电信设备(路由器、接入网关、…)典型设计是通过背板连接主控板卡与多块业务板卡的分布式系统。")
.build();
.newBuilder().setId(1000000)
.setMsg("Erlang/OTP 最初是爱立信为开发电信设备系统设计的编程语言平台," + "电信设备(路由器、接入网关、…)典型设计是通过背板连接主控板卡与多块业务板卡的分布式系统。").build();
@Test
public void testProtobuf() throws IOException, URISyntaxException {
@@ -92,8 +91,7 @@ public class ProtobufSpringEncoderTest {
RequestTemplate requestTemplate = newRequestTemplate();
newEncoder().encode(this.request, Request.class, requestTemplate);
// set a charset
requestTemplate.body(
encoded(requestTemplate.requestBody().asBytes(), StandardCharsets.UTF_8));
requestTemplate.body(encoded(requestTemplate.requestBody().asBytes(), StandardCharsets.UTF_8));
HttpEntity entity = toApacheHttpEntity(requestTemplate);
byte[] bytes = read(entity.getContent(), (int) entity.getContentLength());
@@ -125,21 +123,17 @@ public class ProtobufSpringEncoderTest {
return requestTemplate;
}
private HttpEntity toApacheHttpEntity(RequestTemplate requestTemplate)
throws IOException, URISyntaxException {
private HttpEntity toApacheHttpEntity(RequestTemplate requestTemplate) throws IOException, URISyntaxException {
final List<HttpUriRequest> request = new ArrayList<>(1);
BDDMockito.given(this.httpClient.execute(ArgumentMatchers.<HttpUriRequest>any()))
.will(new Answer<HttpResponse>() {
@Override
public HttpResponse answer(InvocationOnMock invocationOnMock)
throws Throwable {
public HttpResponse answer(InvocationOnMock invocationOnMock) throws Throwable {
request.add((HttpUriRequest) invocationOnMock.getArguments()[0]);
return new BasicHttpResponse(new BasicStatusLine(
new ProtocolVersion("http", 1, 1), 200, null));
return new BasicHttpResponse(new BasicStatusLine(new ProtocolVersion("http", 1, 1), 200, null));
}
});
new ApacheHttpClient(this.httpClient).execute(
requestTemplate.resolve(new HashMap<>()).request(),
new ApacheHttpClient(this.httpClient).execute(requestTemplate.resolve(new HashMap<>()).request(),
new feign.Request.Options());
HttpUriRequest httpUriRequest = request.get(0);
return ((HttpEntityEnclosingRequestBase) httpUriRequest).getEntity();
@@ -27,10 +27,8 @@ public final class ProtobufTest {
private static com.google.protobuf.Descriptors.FileDescriptor descriptor;
static {
String[] descriptorData = {
"\n\023protobuf_test.proto\"\"\n\007Request\022\n\n\002id\030\001"
+ " \001(\005\022\013\n\003msg\030\002 \001(\tB\024\n\020feign.httpclientP\001b"
+ "\006proto3" };
String[] descriptorData = { "\n\023protobuf_test.proto\"\"\n\007Request\022\n\n\002id\030\001"
+ " \001(\005\022\013\n\003msg\030\002 \001(\tB\024\n\020feign.httpclientP\001b" + "\006proto3" };
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
public com.google.protobuf.ExtensionRegistry assignDescriptors(
com.google.protobuf.Descriptors.FileDescriptor root) {
@@ -38,9 +36,8 @@ public final class ProtobufTest {
return null;
}
};
com.google.protobuf.Descriptors.FileDescriptor.internalBuildGeneratedFileFrom(
descriptorData, new com.google.protobuf.Descriptors.FileDescriptor[] {},
assigner);
com.google.protobuf.Descriptors.FileDescriptor.internalBuildGeneratedFileFrom(descriptorData,
new com.google.protobuf.Descriptors.FileDescriptor[] {}, assigner);
internal_static_Request_descriptor = getDescriptor().getMessageTypes().get(0);
internal_static_Request_fieldAccessorTable = new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
internal_static_Request_descriptor, new String[] { "Id", "Msg", });
@@ -49,12 +46,10 @@ public final class ProtobufTest {
private ProtobufTest() {
}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistryLite registry) {
public static void registerAllExtensions(com.google.protobuf.ExtensionRegistryLite registry) {
}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistry registry) {
public static void registerAllExtensions(com.google.protobuf.ExtensionRegistry registry) {
registerAllExtensions((com.google.protobuf.ExtensionRegistryLite) registry);
}
@@ -68,8 +68,7 @@ public final class Request extends com.google.protobuf.GeneratedMessageV3 implem
throws com.google.protobuf.InvalidProtocolBufferException {
this();
int mutable_bitField0_ = 0;
com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet
.newBuilder();
com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder();
try {
boolean done = false;
while (!done) {
@@ -79,8 +78,7 @@ public final class Request extends com.google.protobuf.GeneratedMessageV3 implem
done = true;
break;
default:
if (!parseUnknownFieldProto3(input, unknownFields, extensionRegistry,
tag)) {
if (!parseUnknownFieldProto3(input, unknownFields, extensionRegistry, tag)) {
done = true;
}
break;
@@ -98,8 +96,7 @@ public final class Request extends com.google.protobuf.GeneratedMessageV3 implem
throw e.setUnfinishedMessage(this);
}
catch (java.io.IOException e) {
throw new com.google.protobuf.InvalidProtocolBufferException(e)
.setUnfinishedMessage(this);
throw new com.google.protobuf.InvalidProtocolBufferException(e).setUnfinishedMessage(this);
}
finally {
this.unknownFields = unknownFields.build();
@@ -133,52 +130,40 @@ public final class Request extends com.google.protobuf.GeneratedMessageV3 implem
return PARSER.parseFrom(data, extensionRegistry);
}
public static Request parseFrom(byte[] data)
throws com.google.protobuf.InvalidProtocolBufferException {
public static Request parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data);
}
public static Request parseFrom(byte[] data,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
public static Request parseFrom(byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data, extensionRegistry);
}
public static Request parseFrom(java.io.InputStream input)
throws java.io.IOException {
public static Request parseFrom(java.io.InputStream input) throws java.io.IOException {
return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input);
}
public static Request parseFrom(java.io.InputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input,
extensionRegistry);
com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException {
return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input, extensionRegistry);
}
public static Request parseDelimitedFrom(java.io.InputStream input)
throws java.io.IOException {
return com.google.protobuf.GeneratedMessageV3
.parseDelimitedWithIOException(PARSER, input);
public static Request parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException {
return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException(PARSER, input);
}
public static Request parseDelimitedFrom(java.io.InputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
return com.google.protobuf.GeneratedMessageV3
.parseDelimitedWithIOException(PARSER, input, extensionRegistry);
com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException {
return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException(PARSER, input, extensionRegistry);
}
public static Request parseFrom(com.google.protobuf.CodedInputStream input)
throws java.io.IOException {
public static Request parseFrom(com.google.protobuf.CodedInputStream input) throws java.io.IOException {
return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input);
}
public static Request parseFrom(com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input,
extensionRegistry);
com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException {
return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input, extensionRegistry);
}
public static Builder newBuilder() {
@@ -236,8 +221,7 @@ public final class Request extends com.google.protobuf.GeneratedMessageV3 implem
public com.google.protobuf.ByteString getMsgBytes() {
Object ref = this.msg_;
if (ref instanceof String) {
com.google.protobuf.ByteString b = com.google.protobuf.ByteString
.copyFromUtf8((String) ref);
com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((String) ref);
this.msg_ = b;
return b;
}
@@ -258,8 +242,7 @@ public final class Request extends com.google.protobuf.GeneratedMessageV3 implem
return true;
}
public void writeTo(com.google.protobuf.CodedOutputStream output)
throws java.io.IOException {
public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException {
if (this.id_ != 0) {
output.writeInt32(1, this.id_);
}
@@ -280,8 +263,7 @@ public final class Request extends com.google.protobuf.GeneratedMessageV3 implem
size += com.google.protobuf.CodedOutputStream.computeInt32Size(1, this.id_);
}
if (!getMsgBytes().isEmpty()) {
size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2,
this.msg_);
size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, this.msg_);
}
size += this.unknownFields.getSerializedSize();
this.memoizedSize = size;
@@ -347,8 +329,7 @@ public final class Request extends com.google.protobuf.GeneratedMessageV3 implem
/**
* Protobuf type {@code Request}
*/
public static final class Builder
extends com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
public static final class Builder extends com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
// @@protoc_insertion_point(builder_implements:Request)
org.springframework.cloud.openfeign.encoding.proto.RequestOrBuilder {
@@ -373,8 +354,7 @@ public final class Request extends com.google.protobuf.GeneratedMessageV3 implem
protected FieldAccessorTable internalGetFieldAccessorTable() {
return org.springframework.cloud.openfeign.encoding.proto.ProtobufTest.internal_static_Request_fieldAccessorTable
.ensureFieldAccessorsInitialized(Request.class,
Request.Builder.class);
.ensureFieldAccessorsInitialized(Request.class, Request.Builder.class);
}
private void maybeForceBuilderInitialization() {
@@ -418,8 +398,7 @@ public final class Request extends com.google.protobuf.GeneratedMessageV3 implem
return (Builder) super.clone();
}
public Builder setField(com.google.protobuf.Descriptors.FieldDescriptor field,
Object value) {
public Builder setField(com.google.protobuf.Descriptors.FieldDescriptor field, Object value) {
return (Builder) super.setField(field, value);
}
@@ -431,14 +410,12 @@ public final class Request extends com.google.protobuf.GeneratedMessageV3 implem
return (Builder) super.clearOneof(oneof);
}
public Builder setRepeatedField(
com.google.protobuf.Descriptors.FieldDescriptor field, int index,
public Builder setRepeatedField(com.google.protobuf.Descriptors.FieldDescriptor field, int index,
Object value) {
return (Builder) super.setRepeatedField(field, index, value);
}
public Builder addRepeatedField(
com.google.protobuf.Descriptors.FieldDescriptor field, Object value) {
public Builder addRepeatedField(com.google.protobuf.Descriptors.FieldDescriptor field, Object value) {
return (Builder) super.addRepeatedField(field, value);
}
@@ -473,8 +450,7 @@ public final class Request extends com.google.protobuf.GeneratedMessageV3 implem
}
public Builder mergeFrom(com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException {
Request parsedMessage = null;
try {
parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
@@ -553,8 +529,7 @@ public final class Request extends com.google.protobuf.GeneratedMessageV3 implem
public com.google.protobuf.ByteString getMsgBytes() {
Object ref = this.msg_;
if (ref instanceof String) {
com.google.protobuf.ByteString b = com.google.protobuf.ByteString
.copyFromUtf8((String) ref);
com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((String) ref);
this.msg_ = b;
return b;
}
@@ -587,13 +562,11 @@ public final class Request extends com.google.protobuf.GeneratedMessageV3 implem
return this;
}
public Builder setUnknownFields(
final com.google.protobuf.UnknownFieldSet unknownFields) {
public Builder setUnknownFields(final com.google.protobuf.UnknownFieldSet unknownFields) {
return super.setUnknownFieldsProto3(unknownFields);
}
public Builder mergeUnknownFields(
final com.google.protobuf.UnknownFieldSet unknownFields) {
public Builder mergeUnknownFields(final com.google.protobuf.UnknownFieldSet unknownFields) {
return super.mergeUnknownFields(unknownFields);
}
@@ -42,34 +42,29 @@ public class FeignHalAutoConfigurationContextTests {
public void setUp() {
contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
HypermediaAutoConfiguration.class,
RepositoryRestMvcAutoConfiguration.class,
FeignHalAutoConfiguration.class))
HttpMessageConvertersAutoConfiguration.class, HypermediaAutoConfiguration.class,
RepositoryRestMvcAutoConfiguration.class, FeignHalAutoConfiguration.class))
.withPropertyValues("debug=true");
}
@Test
public void testHalJacksonHttpMessageConverterIsNotLoaded() {
FilteredClassLoader filteredClassLoader = new FilteredClassLoader(
RepositoryRestMvcConfiguration.class, RepresentationModel.class);
FilteredClassLoader filteredClassLoader = new FilteredClassLoader(RepositoryRestMvcConfiguration.class,
RepresentationModel.class);
contextRunner.withClassLoader(filteredClassLoader)
.run(context -> assertThat(context)
.doesNotHaveBean("halJacksonHttpMessageConverter"));
.run(context -> assertThat(context).doesNotHaveBean("halJacksonHttpMessageConverter"));
}
@Test
public void testHalJacksonHttpMessageConverterIsLoaded() {
FilteredClassLoader filteredClassLoader = new FilteredClassLoader(
RepositoryRestMvcConfiguration.class);
contextRunner.withClassLoader(filteredClassLoader).run(
context -> assertThat(context).hasBean("halJacksonHttpMessageConverter"));
FilteredClassLoader filteredClassLoader = new FilteredClassLoader(RepositoryRestMvcConfiguration.class);
contextRunner.withClassLoader(filteredClassLoader)
.run(context -> assertThat(context).hasBean("halJacksonHttpMessageConverter"));
}
@Test
public void testHalJacksonHttpMessageConverterIsNotLoadedUseRestDataMessageConverterInstead() {
contextRunner.run(
context -> assertThat(context).hasBean("halJacksonHttpMessageConverter"));
contextRunner.run(context -> assertThat(context).hasBean("halJacksonHttpMessageConverter"));
}
}
@@ -68,22 +68,20 @@ public class FeignHalAutoConfigurationTests {
ObjectMapper mapper = new ObjectMapper();
when(objectMapper.getIfAvailable(any())).thenReturn(mapper);
when(halConfiguration.getIfAvailable(any()))
.thenReturn(mock(HalConfiguration.class));
when(halConfiguration.getIfAvailable(any())).thenReturn(mock(HalConfiguration.class));
when(relProvider.getIfAvailable()).thenReturn(mock(LinkRelationProvider.class));
when(curieProvider.getIfAvailable(any())).thenReturn(mock(CurieProvider.class));
when(messageResolver.getIfAvailable()).thenReturn(mock(MessageResolver.class));
TypeConstrainedMappingJackson2HttpMessageConverter converter = feignHalAutoConfiguration
.halJacksonHttpMessageConverter(objectMapper, halConfiguration,
messageResolver, curieProvider, relProvider);
.halJacksonHttpMessageConverter(objectMapper, halConfiguration, messageResolver, curieProvider,
relProvider);
assertThat(converter).isNotNull();
assertThat(converter.getObjectMapper()).isNotNull();
assertThat(converter.getSupportedMediaTypes()).isEqualTo(Arrays.asList(HAL_JSON));
assertThat(Jackson2HalModule.isAlreadyRegisteredIn(converter.getObjectMapper()))
.isTrue();
assertThat(Jackson2HalModule.isAlreadyRegisteredIn(converter.getObjectMapper())).isTrue();
}
}
@@ -41,8 +41,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
*
* @author Hector Espert
*/
@SpringBootTest(classes = FeignHalApplication.class, webEnvironment = RANDOM_PORT,
value = "debug=true")
@SpringBootTest(classes = FeignHalApplication.class, webEnvironment = RANDOM_PORT, value = "debug=true")
@RunWith(SpringRunner.class)
@DirtiesContext
public class FeignHalTests {
@@ -74,8 +73,7 @@ public class FeignHalTests {
assertThat(collectionModel.hasLinks()).isTrue();
assertThat(collectionModel.hasLink("self")).isTrue();
assertThat(collectionModel.getLink("self")).map(Link::getHref)
.contains("/collection");
assertThat(collectionModel.getLink("self")).map(Link::getHref).contains("/collection");
Collection<MarsRover> collection = collectionModel.getContent();
assertThat(collection).isNotEmpty();

Some files were not shown because too many files have changed in this diff Show More