Compare commits

..
15 Commits
Author SHA1 Message Date
Marcin Grzejszczak a5113db4e3 Added symbolic link of index.adoc 2019-07-29 14:43:05 +02:00
buildmaster a98f379e68 Bumping versions to 2.0.5.BUILD-SNAPSHOT after release 2019-06-08 00:24:42 +00:00
buildmaster 7b5d469839 Going back to snapshots 2019-06-08 00:24:42 +00:00
buildmaster 816eaa6abf Update SNAPSHOT to 2.0.4.RELEASE 2019-06-08 00:16:51 +00:00
Ryan Baxter 4d9a7c4ddd Merge branch 'spring-operator-polish-urls-remaining-2.0.x' into 2.0.x 2019-03-28 16:06:58 -04:00
Ryan Baxter ab1f1f43a8 Fixing tests 2019-03-28 16:06:47 -04:00
Ryan Baxter c51117d556 Merge branch 'polish-urls-remaining-2.0.x' of https://github.com/spring-operator/spring-cloud-openfeign into spring-operator-polish-urls-remaining-2.0.x 2019-03-28 15:59:38 -04:00
Spring Operator d6ad74a933 URL Cleanup (#162)
This commit updates URLs to prefer the https protocol. Redirects are not followed to avoid accidentally expanding intentionally shortened URLs (i.e. if using a URL shortener).

# Fixed URLs

## Fixed Success
These URLs were switched to an https URL with a 2xx status. While the status was successful, your review is still recommended.

* [ ] http://repo.spring.io/libs-milestone-local with 2 occurrences migrated to:
  https://repo.spring.io/libs-milestone-local ([https](https://repo.spring.io/libs-milestone-local) result 302).
* [ ] http://repo.spring.io/libs-snapshot-local with 2 occurrences migrated to:
  https://repo.spring.io/libs-snapshot-local ([https](https://repo.spring.io/libs-snapshot-local) result 302).
* [ ] http://repo.spring.io/release with 1 occurrences migrated to:
  https://repo.spring.io/release ([https](https://repo.spring.io/release) result 302).

# Ignored
These URLs were intentionally ignored.

* http://maven.apache.org/POM/4.0.0 with 10 occurrences
* http://www.w3.org/2001/XMLSchema-instance with 5 occurrences
2019-03-28 12:52:53 -04:00
Spring Operator 6aa6684d13 URL Cleanup
This commit updates URLs to prefer the https protocol. Redirects are not followed to avoid accidentally expanding intentionally shortened URLs (i.e. if using a URL shortener).

# Fixed URLs

## Fixed But Review Recommended
These URLs were fixed, but the https status was not OK. However, the https status was the same as the http request or http redirected to an https URL, so they were migrated. Your review is recommended.

* [ ] http://foo.com:8000/ (ConnectTimeoutException) with 2 occurrences migrated to:
  https://foo.com:8000/ ([https](https://foo.com:8000/) result ConnectTimeoutException).
* [ ] http://foo.com:8000/abc/bcd.json (ConnectTimeoutException) with 1 occurrences migrated to:
  https://foo.com:8000/abc/bcd.json ([https](https://foo.com:8000/abc/bcd.json) result ConnectTimeoutException).
* [ ] http://compose.docker.io/ (UnknownHostException) with 1 occurrences migrated to:
  https://compose.docker.io/ ([https](https://compose.docker.io/) result UnknownHostException).
* [ ] http://tp/abc/bcd.json (UnknownHostException) with 1 occurrences migrated to:
  https://tp/abc/bcd.json ([https](https://tp/abc/bcd.json) result UnknownHostException).
* [ ] http://www.puppycrawl.com/dtds/suppressions_1_1.dtd (404) with 1 occurrences migrated to:
  https://www.puppycrawl.com/dtds/suppressions_1_1.dtd ([https](https://www.puppycrawl.com/dtds/suppressions_1_1.dtd) result 404).

## Fixed Success
These URLs were switched to an https URL with a 2xx status. While the status was successful, your review is still recommended.

* [ ] http://asciidoctor.org with 1 occurrences migrated to:
  https://asciidoctor.org ([https](https://asciidoctor.org) result 200).
* [ ] http://example.com with 2 occurrences migrated to:
  https://example.com ([https](https://example.com) result 200).
* [ ] http://github.com/ with 1 occurrences migrated to:
  https://github.com/ ([https](https://github.com/) result 200).
* [ ] http://projects.spring.io/spring-cloud/ with 1 occurrences migrated to:
  https://projects.spring.io/spring-cloud/ ([https](https://projects.spring.io/spring-cloud/) result 200).
* [ ] http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html with 2 occurrences migrated to:
  https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html ([https](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) result 200).
* [ ] http://plugins.jetbrains.com/plugin/6546 with 2 occurrences migrated to:
  https://plugins.jetbrains.com/plugin/6546 ([https](https://plugins.jetbrains.com/plugin/6546) result 301).
* [ ] http://raw.github.com/ with 1 occurrences migrated to:
  https://raw.github.com/ ([https](https://raw.github.com/) result 301).
* [ ] http://service.com with 1 occurrences migrated to:
  https://service.com ([https](https://service.com) result 301).
* [ ] http://eclipse.org with 1 occurrences migrated to:
  https://eclipse.org ([https](https://eclipse.org) result 302).
* [ ] http://eclipse.org/m2e/ with 2 occurrences migrated to:
  https://eclipse.org/m2e/ ([https](https://eclipse.org/m2e/) result 302).
* [ ] http://www.springsource.com/developer/sts with 1 occurrences migrated to:
  https://www.springsource.com/developer/sts ([https](https://www.springsource.com/developer/sts) result 302).

# Ignored
These URLs were intentionally ignored.

* http://PROD-SVC with 2 occurrences
* http://Url/ with 1 occurrences
* http://bad_hostname with 1 occurrences
* http://bar with 1 occurrences
* http://bar/ with 1 occurrences
* http://foo with 18 occurrences
* http://foo/ with 5 occurrences
* http://foo/?name=%7bcookie with 1 occurrences
* http://foo:7777/ with 1 occurrences
* http://good-name with 2 occurrences
* http://listener with 6 occurrences
* http://localhost with 11 occurrences
* http://localhost:8080/ with 1 occurrences
* http://localhost:8081/ with 1 occurrences
2019-03-26 03:45:11 -05:00
Spring Operator 11098e9a99 URL Cleanup (#155)
This commit updates URLs to prefer the https protocol. Redirects are not followed to avoid accidentally expanding intentionally shortened URLs (i.e. if using a URL shortener).

# Fixed URLs

## Fixed Success
These URLs were switched to an https URL with a 2xx status. While the status was successful, your review is still recommended.

* [ ] http://www.apache.org/licenses/ with 1 occurrences migrated to:
  https://www.apache.org/licenses/ ([https](https://www.apache.org/licenses/) result 200).
* [ ] http://www.apache.org/licenses/LICENSE-2.0 with 102 occurrences migrated to:
  https://www.apache.org/licenses/LICENSE-2.0 ([https](https://www.apache.org/licenses/LICENSE-2.0) result 200).
2019-03-21 16:35:33 -04:00
Spring Operator 36cc521a81 URL Cleanup (#148)
This commit updates URLs to prefer the https protocol. Redirects are not followed to avoid accidentally expanding intentionally shortened URLs (i.e. if using a URL shortener).

# Fixed URLs

## Fixed Success 
These URLs were switched to an https URL with a 2xx status. While the status was successful, your review is still recommended.

* http://maven.apache.org/xsd/maven-4.0.0.xsd with 5 occurrences migrated to:  
  https://maven.apache.org/xsd/maven-4.0.0.xsd ([https](https://maven.apache.org/xsd/maven-4.0.0.xsd) result 200).
* http://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch with 1 occurrences migrated to:  
  https://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch ([https](https://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch) result 200).
* http://stackoverflow.com/questions/29300806/a-bash-script-to-check-if-a-string-is-present-in-a-comma-separated-list-of-strin with 1 occurrences migrated to:  
  https://stackoverflow.com/questions/29300806/a-bash-script-to-check-if-a-string-is-present-in-a-comma-separated-list-of-strin ([https](https://stackoverflow.com/questions/29300806/a-bash-script-to-check-if-a-string-is-present-in-a-comma-separated-list-of-strin) result 200).
* http://www.apache.org/licenses/LICENSE-2.0 with 2 occurrences migrated to:  
  https://www.apache.org/licenses/LICENSE-2.0 ([https](https://www.apache.org/licenses/LICENSE-2.0) result 200).

# Ignored
These URLs were intentionally ignored.

* http://maven.apache.org/POM/4.0.0 with 10 occurrences
* http://www.w3.org/2001/XMLSchema-instance with 5 occurrences
2019-03-16 11:52:56 -04:00
buildmaster 3a79850467 Bumping versions to 2.0.4.BUILD-SNAPSHOT after release 2019-02-22 15:48:44 +00:00
buildmaster 117e2c2b01 Going back to snapshots 2019-02-22 15:48:43 +00:00
buildmaster 80de4e6b66 Update SNAPSHOT to 2.0.3.RELEASE 2019-02-22 15:47:07 +00:00
buildmaster 5f4acbe80e Bumping versions 2019-02-14 17:49:07 +00:00
131 changed files with 4284 additions and 8198 deletions
-14
View File
@@ -1,14 +0,0 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = tab
indent_size = 4
end_of_line = lf
insert_final_newline = true
[*.yml]
indent_style = space
indent_size = 2
+63 -65
View File
@@ -1,68 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<settings> <settings>
<servers> <servers>
<server> <server>
<id>repo.spring.io</id> <id>repo.spring.io</id>
<username>${env.CI_DEPLOY_USERNAME}</username> <username>${env.CI_DEPLOY_USERNAME}</username>
<password>${env.CI_DEPLOY_PASSWORD}</password> <password>${env.CI_DEPLOY_PASSWORD}</password>
</server> </server>
</servers> </servers>
<profiles> <profiles>
<profile> <profile>
<!-- <!--
N.B. this profile is only here to support users and IDEs that do not use Maven 3.3. N.B. this profile is only here to support users and IDEs that do not use Maven 3.3.
It isn't needed on the command line if you use the wrapper script (mvnw) or if you use It isn't needed on the command line if you use the wrapper script (mvnw) or if you use
a native Maven with the right version. Eclipse users should points their Maven tooling to a native Maven with the right version. Eclipse users should points their Maven tooling to
this settings file, or copy the profile into their ~/.m2/settings.xml. this settings file, or copy the profile into their ~/.m2/settings.xml.
--> -->
<id>spring</id> <id>spring</id>
<activation> <activation><activeByDefault>true</activeByDefault></activation>
<activeByDefault>true</activeByDefault> <repositories>
</activation> <repository>
<repositories> <id>spring-snapshots</id>
<repository> <name>Spring Snapshots</name>
<id>spring-snapshots</id> <url>https://repo.spring.io/libs-snapshot-local</url>
<name>Spring Snapshots</name> <snapshots>
<url>https://repo.spring.io/libs-snapshot-local</url> <enabled>true</enabled>
<snapshots> </snapshots>
<enabled>true</enabled> </repository>
</snapshots> <repository>
</repository> <id>spring-milestones</id>
<repository> <name>Spring Milestones</name>
<id>spring-milestones</id> <url>https://repo.spring.io/libs-milestone-local</url>
<name>Spring Milestones</name> <snapshots>
<url>https://repo.spring.io/libs-milestone-local</url> <enabled>false</enabled>
<snapshots> </snapshots>
<enabled>false</enabled> </repository>
</snapshots> <repository>
</repository> <id>spring-releases</id>
<repository> <name>Spring Releases</name>
<id>spring-releases</id> <url>https://repo.spring.io/release</url>
<name>Spring Releases</name> <snapshots>
<url>https://repo.spring.io/release</url> <enabled>false</enabled>
<snapshots> </snapshots>
<enabled>false</enabled> </repository>
</snapshots> </repositories>
</repository> <pluginRepositories>
</repositories> <pluginRepository>
<pluginRepositories> <id>spring-snapshots</id>
<pluginRepository> <name>Spring Snapshots</name>
<id>spring-snapshots</id> <url>https://repo.spring.io/libs-snapshot-local</url>
<name>Spring Snapshots</name> <snapshots>
<url>https://repo.spring.io/libs-snapshot-local</url> <enabled>true</enabled>
<snapshots> </snapshots>
<enabled>true</enabled> </pluginRepository>
</snapshots> <pluginRepository>
</pluginRepository> <id>spring-milestones</id>
<pluginRepository> <name>Spring Milestones</name>
<id>spring-milestones</id> <url>https://repo.spring.io/libs-milestone-local</url>
<name>Spring Milestones</name> <snapshots>
<url>https://repo.spring.io/libs-milestone-local</url> <enabled>false</enabled>
<snapshots> </snapshots>
<enabled>false</enabled> </pluginRepository>
</snapshots> </pluginRepositories>
</pluginRepository> </profile>
</pluginRepositories> </profiles>
</profile>
</profiles>
</settings> </settings>
View File
+2 -7
View File
@@ -1,8 +1,4 @@
//// // Do not edit this file (e.g. go instead to src/main/asciidoc)
DO NOT EDIT THIS FILE. IT WAS GENERATED.
Manual changes to this file will be lost when it is generated again.
Edit the files in the src/main/asciidoc/ directory instead.
////
image:https://circleci.com/gh/spring-cloud/spring-cloud-openfeign.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-openfeign"] image:https://circleci.com/gh/spring-cloud/spring-cloud-openfeign.svg?style=svg["CircleCI", link="https://circleci.com/gh/spring-cloud/spring-cloud-openfeign"]
@@ -55,7 +51,7 @@ credentials and you already have those.
The projects that require middleware generally include a The projects that require middleware generally include a
`docker-compose.yml`, so consider using `docker-compose.yml`, so consider using
https://docs.docker.com/compose/[Docker Compose] to run the middeware servers https://compose.docker.io/[Docker Compose] to run the middeware servers
in Docker containers. See the README in the in Docker containers. See the README in the
https://github.com/spring-cloud-samples/scripts[scripts demo https://github.com/spring-cloud-samples/scripts[scripts demo
repository] for specific instructions about the common cases of mongo, repository] for specific instructions about the common cases of mongo,
@@ -245,7 +241,6 @@ $ touch .springformat
==== Intellij IDEA ==== Intellij IDEA
In order to setup Intellij you should import our coding conventions, inspection profiles and set up the checkstyle plugin. In order to setup Intellij you should import our coding conventions, inspection profiles and set up the checkstyle plugin.
The following files can be found in the https://github.com/spring-cloud/spring-cloud-build/tree/master/spring-cloud-build-tools[Spring Cloud Build] project.
.spring-cloud-build-tools/ .spring-cloud-build-tools/
---- ----
+396 -2008
View File
File diff suppressed because it is too large Load Diff
+4 -5
View File
@@ -1,12 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
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> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId> <artifactId>spring-cloud-openfeign</artifactId>
<version>2.1.3.RELEASE</version> <version>2.0.5.BUILD-SNAPSHOT</version>
</parent> </parent>
<artifactId>spring-cloud-openfeign-docs</artifactId> <artifactId>spring-cloud-openfeign-docs</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
@@ -15,7 +14,7 @@
<properties> <properties>
<docs.main>spring-cloud-openfeign</docs.main> <docs.main>spring-cloud-openfeign</docs.main>
<main.basedir>${basedir}/..</main.basedir> <main.basedir>${basedir}/..</main.basedir>
<docs.whitelisted.branches>2.0.x,2.1.x</docs.whitelisted.branches> <docs.whitelisted.branches>2.0.x</docs.whitelisted.branches>
</properties> </properties>
<build> <build>
<plugins> <plugins>
+2 -2
View File
@@ -40,7 +40,7 @@ function check_if_anything_to_sync() {
function retrieve_current_branch() { function retrieve_current_branch() {
# Code getting the name of the current branch. For master we want to publish as we did until now # Code getting the name of the current branch. For master we want to publish as we did until now
# http://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch # https://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch
# If there is a branch already passed will reuse it - otherwise will try to find it # If there is a branch already passed will reuse it - otherwise will try to find it
CURRENT_BRANCH=${BRANCH} CURRENT_BRANCH=${BRANCH}
if [[ -z "${CURRENT_BRANCH}" ]] ; then if [[ -z "${CURRENT_BRANCH}" ]] ; then
@@ -147,7 +147,7 @@ function copy_docs_for_current_version() {
COMMIT_CHANGES="yes" COMMIT_CHANGES="yes"
else else
echo -e "Current branch is [${CURRENT_BRANCH}]" echo -e "Current branch is [${CURRENT_BRANCH}]"
# http://stackoverflow.com/questions/29300806/a-bash-script-to-check-if-a-string-is-present-in-a-comma-separated-list-of-strin # https://stackoverflow.com/questions/29300806/a-bash-script-to-check-if-a-string-is-present-in-a-comma-separated-list-of-strin
if [[ ",${WHITELISTED_BRANCHES_VALUE}," = *",${CURRENT_BRANCH},"* ]] ; then if [[ ",${WHITELISTED_BRANCHES_VALUE}," = *",${CURRENT_BRANCH},"* ]] ; then
mkdir -p ${ROOT_FOLDER}/${CURRENT_BRANCH} mkdir -p ${ROOT_FOLDER}/${CURRENT_BRANCH}
echo -e "Branch [${CURRENT_BRANCH}] is whitelisted! Will copy the current docs to the [${CURRENT_BRANCH}] folder" echo -e "Branch [${CURRENT_BRANCH}] is whitelisted! Will copy the current docs to the [${CURRENT_BRANCH}] folder"
@@ -73,8 +73,6 @@ in your external configuration (see
A central concept in Spring Cloud's Feign support is that of the named client. Each feign client is part of an ensemble of components that work together to contact a remote server on demand, and the ensemble has a name that you give it as an application developer using the `@FeignClient` annotation. Spring Cloud creates a new ensemble as an A central concept in Spring Cloud's Feign support is that of the named client. Each feign client is part of an ensemble of components that work together to contact a remote server on demand, and the ensemble has a name that you give it as an application developer using the `@FeignClient` annotation. Spring Cloud creates a new ensemble as an
`ApplicationContext` on demand for each named client using `FeignClientsConfiguration`. This contains (amongst other things) an `feign.Decoder`, a `feign.Encoder`, and a `feign.Contract`. `ApplicationContext` on demand for each named client using `FeignClientsConfiguration`. This contains (amongst other things) an `feign.Decoder`, a `feign.Encoder`, and a `feign.Contract`.
It is possible to override the name of that ensemble by using the `contextId`
attribute of the `@FeignClient` annotation.
Spring Cloud lets you take full control of the feign client by declaring additional configuration (on top of the `FeignClientsConfiguration`) using `@FeignClient`. Example: Spring Cloud lets you take full control of the feign client by declaring additional configuration (on top of the `FeignClientsConfiguration`) using `@FeignClient`. Example:
@@ -92,10 +90,6 @@ NOTE: `FooConfiguration` does not need to be annotated with `@Configuration`. Ho
NOTE: The `serviceId` attribute is now deprecated in favor of the `name` attribute. 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.
WARNING: Previously, using the `url` attribute, did not require the `name` attribute. Using `name` is now required. WARNING: Previously, using the `url` attribute, did not require the `name` attribute. Using `name` is now required.
Placeholders are supported in the `name` and `url` attributes. Placeholders are supported in the `name` and `url` attributes.
@@ -212,27 +206,6 @@ hystrix:
strategy: SEMAPHORE strategy: SEMAPHORE
---- ----
If we want to create multiple feign clients with the same name or url
so that they would point to the same server but each with a different custom configuration then
we have to use `contextId` attribute of the `@FeignClient` in order to avoid name
collision of these configuration beans.
[source,java,indent=0]
----
@FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class)
public interface FooClient {
//..
}
----
[source,java,indent=0]
----
@FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class)
public interface BarClient {
//..
}
----
=== Creating Feign Clients Manually === Creating Feign Clients Manually
In some cases it might be necessary to customize your Feign Clients in a way that is not In some cases it might be necessary to customize your Feign Clients in a way that is not
@@ -452,38 +425,4 @@ public class FooConfiguration {
return Logger.Level.FULL; return Logger.Level.FULL;
} }
} }
---- ----
=== Feign @QueryMap support
The OpenFeign `@QueryMap` annotation provides support for POJOs to be used as
GET parameter maps. Unfortunately, the default OpenFeign QueryMap annotation is
incompatible with Spring because it lacks a `value` property.
Spring Cloud OpenFeign provides an equivalent `@SpringQueryMap` annotation, which
is used to annotate a POJO or Map parameter as a query parameter map.
For example, the `Params` class defines parameters `param1` and `param2`:
[source,java,indent=0]
----
// Params.java
public class Params {
private String param1;
private String param2;
// [Getters and setters omitted for brevity]
}
----
The following feign client uses the `Params` class by using the `@SpringQueryMap` annotation:
[source,java,indent=0]
----
@FeignClient("demo")
public class DemoTemplate {
@GetMapping(path = "/demo")
String demoEndpoint(@SpringQueryMap Params params);
}
----
Vendored
+1 -1
View File
@@ -8,7 +8,7 @@
# "License"); you may not use this file except in compliance # "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at # with the License. You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, # Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an # software distributed under the License is distributed on an
Vendored
+1 -1
View File
@@ -7,7 +7,7 @@
@REM "License"); you may not use this file except in compliance @REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at @REM with the License. You may obtain a copy of the License at
@REM @REM
@REM http://www.apache.org/licenses/LICENSE-2.0 @REM https://www.apache.org/licenses/LICENSE-2.0
@REM @REM
@REM Unless required by applicable law or agreed to in writing, @REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an @REM software distributed under the License is distributed on an
+15 -48
View File
@@ -1,33 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
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> <modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-openfeign</artifactId> <artifactId>spring-cloud-openfeign</artifactId>
<version>2.1.3.RELEASE</version> <version>2.0.5.BUILD-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>Spring Cloud OpenFeign</name> <name>Spring Cloud OpenFeign</name>
<description>Spring Cloud OpenFeign</description> <description>Spring Cloud OpenFeign</description>
<parent> <parent>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId> <artifactId>spring-cloud-build</artifactId>
<version>2.1.7.RELEASE</version> <version>2.0.5.BUILD-SNAPSHOT</version>
<relativePath/> <relativePath />
</parent> </parent>
<scm> <scm>
<url>https://github.com/spring-cloud/spring-cloud-openfeign</url> <url>https://github.com/spring-cloud/spring-cloud-openfeign</url>
<connection>scm:git:git://github.com/spring-cloud/spring-cloud-openfeign.git <connection>scm:git:git://github.com/spring-cloud/spring-cloud-openfeign.git</connection>
</connection> <developerConnection>scm:git:ssh://git@github.com/spring-cloud/spring-cloud-openfeign.git</developerConnection>
<developerConnection>
scm:git:ssh://git@github.com/spring-cloud/spring-cloud-openfeign.git
</developerConnection>
<tag>HEAD</tag> <tag>HEAD</tag>
</scm> </scm>
<properties> <properties>
<main.basedir>${basedir}</main.basedir> <main.basedir>${basedir}</main.basedir>
<jackson.version>2.7.3</jackson.version> <jackson.version>2.7.3</jackson.version>
<spring-cloud-commons.version>2.1.3.RELEASE</spring-cloud-commons.version> <spring-cloud-commons.version>2.0.3.BUILD-SNAPSHOT</spring-cloud-commons.version>
<spring-cloud-netflix.version>2.1.3.RELEASE</spring-cloud-netflix.version> <spring-cloud-netflix.version>2.0.3.BUILD-SNAPSHOT</spring-cloud-netflix.version>
<!-- Plugin versions --> <!-- Plugin versions -->
<maven-compiler-plugin.version>3.6.1</maven-compiler-plugin.version> <maven-compiler-plugin.version>3.6.1</maven-compiler-plugin.version>
@@ -36,15 +32,8 @@
<surefire.plugin.version>2.19.1</surefire.plugin.version> <surefire.plugin.version>2.19.1</surefire.plugin.version>
<sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin> <sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin>
<sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis> <sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis>
<sonar.jacoco.reportPath>${project.basedir}/../target/jacoco.exec <sonar.jacoco.reportPath>${project.basedir}/../target/jacoco.exec</sonar.jacoco.reportPath>
</sonar.jacoco.reportPath>
<sonar.language>java</sonar.language> <sonar.language>java</sonar.language>
<maven-checkstyle-plugin.failsOnError>true</maven-checkstyle-plugin.failsOnError>
<maven-checkstyle-plugin.failsOnViolation>true
</maven-checkstyle-plugin.failsOnViolation>
<maven-checkstyle-plugin.includeTestSourceDirectory>true
</maven-checkstyle-plugin.includeTestSourceDirectory>
</properties> </properties>
<build> <build>
<plugins> <plugins>
@@ -57,13 +46,11 @@
<additionalConfig> <additionalConfig>
<file> <file>
<name>.settings/org.eclipse.jdt.ui.prefs</name> <name>.settings/org.eclipse.jdt.ui.prefs</name>
<location>${main.basedir}/eclipse/org.eclipse.jdt.ui.prefs <location>${main.basedir}/eclipse/org.eclipse.jdt.ui.prefs</location>
</location>
</file> </file>
<file> <file>
<name>.settings/org.eclipse.jdt.core.prefs</name> <name>.settings/org.eclipse.jdt.core.prefs</name>
<location>${main.basedir}/eclipse/org.eclipse.jdt.core.prefs <location>${main.basedir}/eclipse/org.eclipse.jdt.core.prefs</location>
</location>
</file> </file>
</additionalConfig> </additionalConfig>
</configuration> </configuration>
@@ -76,26 +63,8 @@
<target>1.8</target> <target>1.8</target>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
</plugin>
<plugin>
<groupId>io.spring.javaformat</groupId>
<artifactId>spring-javaformat-maven-plugin</artifactId>
</plugin>
</plugins> </plugins>
</build> </build>
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
</plugin>
</plugins>
</reporting>
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
<dependency> <dependency>
@@ -137,7 +106,7 @@
<module>spring-cloud-openfeign-core</module> <module>spring-cloud-openfeign-core</module>
<module>spring-cloud-starter-openfeign</module> <module>spring-cloud-starter-openfeign</module>
<module>docs</module> <module>docs</module>
</modules> </modules>
<profiles> <profiles>
<profile> <profile>
<id>spring</id> <id>spring</id>
@@ -215,8 +184,7 @@
</goals> </goals>
<configuration> <configuration>
<propertyName>surefireArgLine</propertyName> <propertyName>surefireArgLine</propertyName>
<destFile>${project.build.directory}/jacoco.exec <destFile>${project.build.directory}/jacoco.exec</destFile>
</destFile>
</configuration> </configuration>
</execution> </execution>
<execution> <execution>
@@ -227,8 +195,7 @@
</goals> </goals>
<configuration> <configuration>
<!-- Sets the path to the file which contains the execution data. --> <!-- Sets the path to the file which contains the execution data. -->
<dataFile>${project.build.directory}/jacoco.exec <dataFile>${project.build.directory}/jacoco.exec</dataFile>
</dataFile>
</configuration> </configuration>
</execution> </execution>
</executions> </executions>
+19 -27
View File
@@ -1,12 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 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> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId> <artifactId>spring-cloud-openfeign</artifactId>
<version>2.1.3.RELEASE</version> <version>2.0.5.BUILD-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository --> <relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent> </parent>
<artifactId>spring-cloud-openfeign-core</artifactId> <artifactId>spring-cloud-openfeign-core</artifactId>
@@ -158,10 +157,14 @@
<artifactId>rxjava</artifactId> <artifactId>rxjava</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-apache-client4</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>com.squareup.okhttp3</groupId> <groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId> <artifactId>okhttp</artifactId>
<version>4.1.0</version>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
@@ -184,11 +187,6 @@
<artifactId>spring-cloud-test-support</artifactId> <artifactId>spring-cloud-test-support</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>io.projectreactor</groupId> <groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId> <artifactId>reactor-test</artifactId>
@@ -216,28 +214,22 @@
<version>3.4.0</version> <version>3.4.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>0.10.0</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<profiles> <profiles>
<profile> <profile>
<id>java8plus</id> <id>java8plus</id>
<activation> <activation>
<jdk>[1.8,2.0)</jdk> <jdk>[1.8,2.0)</jdk>
</activation> </activation>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<configuration> <configuration>
<compilerArgs> <compilerArgs>
<arg>-parameters</arg> <arg>-parameters</arg>
</compilerArgs> </compilerArgs>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -32,19 +32,20 @@ public interface AnnotatedParameterProcessor {
/** /**
* Retrieves the processor supported annotation type. * Retrieves the processor supported annotation type.
*
* @return the annotation type * @return the annotation type
*/ */
Class<? extends Annotation> getAnnotationType(); Class<? extends Annotation> getAnnotationType();
/** /**
* Process the annotated parameter. * Process the annotated parameter.
* @param context the parameter context *
* @param context the parameter context
* @param annotation the annotation instance * @param annotation the annotation instance
* @param method the method that contains the annotation * @param method the method that contains the annotation
* @return whether the parameter is http * @return whether the parameter is http
*/ */
boolean processArgument(AnnotatedParameterContext context, Annotation annotation, boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method);
Method method);
/** /**
* Specifies the parameter context. * Specifies the parameter context.
@@ -55,30 +56,32 @@ public interface AnnotatedParameterProcessor {
/** /**
* Retrieves the method metadata. * Retrieves the method metadata.
*
* @return the method metadata * @return the method metadata
*/ */
MethodMetadata getMethodMetadata(); MethodMetadata getMethodMetadata();
/** /**
* Retrieves the index of the parameter. * Retrieves the index of the parameter.
*
* @return the parameter index * @return the parameter index
*/ */
int getParameterIndex(); int getParameterIndex();
/** /**
* Sets the parameter name. * Sets the parameter name.
*
* @param name the name of the parameter * @param name the name of the parameter
*/ */
void setParameterName(String name); void setParameterName(String name);
/** /**
* Sets the template parameter. * Sets the template parameter.
*
* @param name the template parameter * @param name the template parameter
* @param rest the existing parameter values * @param rest the existing parameter values
* @return parameters * @return parameters
*/ */
Collection<String> setTemplateParameter(String name, Collection<String> rest); Collection<String> setTemplateParameter(String name, Collection<String> rest);
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2016-2019 the original author or authors. * Copyright 2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -12,6 +12,7 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
@@ -25,9 +26,8 @@ import feign.Target;
class DefaultTargeter implements Targeter { class DefaultTargeter implements Targeter {
@Override @Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
FeignContext context, Target.HardCodedTarget<T> target) { Target.HardCodedTarget<T> target) {
return feign.target(target); return feign.target(target);
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -25,11 +25,10 @@ import java.lang.annotation.Target;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
/** /**
* Scans for interfaces that declare they are feign clients (via * Scans for interfaces that declare they are feign clients (via {@link FeignClient
* {@link org.springframework.cloud.openfeign.FeignClient} <code>@FeignClient</code>). * <code>@FeignClient</code>}). Configures component scanning directives for use with
* Configures component scanning directives for use with * {@link org.springframework.context.annotation.Configuration
* {@link org.springframework.context.annotation.Configuration} * <code>@Configuration</code>} classes.
* <code>@Configuration</code> classes.
* *
* @author Spencer Gibb * @author Spencer Gibb
* @author Dave Syer * @author Dave Syer
@@ -56,6 +55,7 @@ public @interface EnableFeignClients {
* <p> * <p>
* Use {@link #basePackageClasses()} for a type-safe alternative to String-based * Use {@link #basePackageClasses()} for a type-safe alternative to String-based
* package names. * package names.
*
* @return the array of 'basePackages'. * @return the array of 'basePackages'.
*/ */
String[] basePackages() default {}; String[] basePackages() default {};
@@ -66,6 +66,7 @@ public @interface EnableFeignClients {
* <p> * <p>
* Consider creating a special no-op marker class or interface in each package that * Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute. * serves no purpose other than being referenced by this attribute.
*
* @return the array of 'basePackageClasses'. * @return the array of 'basePackageClasses'.
*/ */
Class<?>[] basePackageClasses() default {}; Class<?>[] basePackageClasses() default {};
@@ -76,15 +77,12 @@ public @interface EnableFeignClients {
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}. * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
* *
* @see FeignClientsConfiguration for the defaults * @see FeignClientsConfiguration for the defaults
* @return list of default configurations
*/ */
Class<?>[] defaultConfiguration() default {}; Class<?>[] defaultConfiguration() default {};
/** /**
* List of classes annotated with @FeignClient. If not empty, disables classpath * List of classes annotated with @FeignClient. If not empty, disables classpath scanning.
* scanning. * @return
* @return list of FeignClient classes
*/ */
Class<?>[] clients() default {}; Class<?>[] clients() default {};
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -22,19 +22,11 @@ import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.annotation.PreDestroy;
import feign.Client;
import feign.Feign;
import feign.httpclient.ApacheHttpClient;
import feign.okhttp.OkHttpClient;
import okhttp3.ConnectionPool;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig; import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.RegistryBuilder; import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -50,14 +42,21 @@ import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import feign.Client;
import feign.Feign;
import feign.httpclient.ApacheHttpClient;
import feign.okhttp.OkHttpClient;
import okhttp3.ConnectionPool;
import javax.annotation.PreDestroy;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
* @author Julien Roy * @author Julien Roy
*/ */
@Configuration @Configuration
@ConditionalOnClass(Feign.class) @ConditionalOnClass(Feign.class)
@EnableConfigurationProperties({ FeignClientProperties.class, @EnableConfigurationProperties({FeignClientProperties.class, FeignHttpClientProperties.class})
FeignHttpClientProperties.class })
public class FeignAutoConfiguration { public class FeignAutoConfiguration {
@Autowired(required = false) @Autowired(required = false)
@@ -78,25 +77,21 @@ public class FeignAutoConfiguration {
@Configuration @Configuration
@ConditionalOnClass(name = "feign.hystrix.HystrixFeign") @ConditionalOnClass(name = "feign.hystrix.HystrixFeign")
protected static class HystrixFeignTargeterConfiguration { protected static class HystrixFeignTargeterConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public Targeter feignTargeter() { public Targeter feignTargeter() {
return new HystrixTargeter(); return new HystrixTargeter();
} }
} }
@Configuration @Configuration
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign") @ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration { protected static class DefaultFeignTargeterConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public Targeter feignTargeter() { public Targeter feignTargeter() {
return new DefaultTargeter(); return new DefaultTargeter();
} }
} }
// the following configuration is for alternate feign clients if // the following configuration is for alternate feign clients if
@@ -109,7 +104,6 @@ public class FeignAutoConfiguration {
@ConditionalOnMissingBean(CloseableHttpClient.class) @ConditionalOnMissingBean(CloseableHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true) @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
protected static class HttpClientFeignConfiguration { protected static class HttpClientFeignConfiguration {
private final Timer connectionManagerTimer = new Timer( private final Timer connectionManagerTimer = new Timer(
"FeignApacheHttpClientConfiguration.connectionManagerTimer", true); "FeignApacheHttpClientConfiguration.connectionManagerTimer", true);
@@ -124,12 +118,10 @@ public class FeignAutoConfiguration {
ApacheHttpClientConnectionManagerFactory connectionManagerFactory, ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
FeignHttpClientProperties httpClientProperties) { FeignHttpClientProperties httpClientProperties) {
final HttpClientConnectionManager connectionManager = connectionManagerFactory final HttpClientConnectionManager connectionManager = connectionManagerFactory
.newConnectionManager(httpClientProperties.isDisableSslValidation(), .newConnectionManager(httpClientProperties.isDisableSslValidation(), httpClientProperties.getMaxConnections(),
httpClientProperties.getMaxConnections(),
httpClientProperties.getMaxConnectionsPerRoute(), httpClientProperties.getMaxConnectionsPerRoute(),
httpClientProperties.getTimeToLive(), httpClientProperties.getTimeToLive(),
httpClientProperties.getTimeToLiveUnit(), httpClientProperties.getTimeToLiveUnit(), registryBuilder);
this.registryBuilder);
this.connectionManagerTimer.schedule(new TimerTask() { this.connectionManagerTimer.schedule(new TimerTask() {
@Override @Override
public void run() { public void run() {
@@ -147,9 +139,9 @@ public class FeignAutoConfiguration {
.setConnectTimeout(httpClientProperties.getConnectionTimeout()) .setConnectTimeout(httpClientProperties.getConnectionTimeout())
.setRedirectsEnabled(httpClientProperties.isFollowRedirects()) .setRedirectsEnabled(httpClientProperties.isFollowRedirects())
.build(); .build();
this.httpClient = httpClientFactory.createBuilder() this.httpClient = httpClientFactory.createBuilder().
.setConnectionManager(httpClientConnectionManager) setConnectionManager(httpClientConnectionManager).
.setDefaultRequestConfig(defaultRequestConfig).build(); setDefaultRequestConfig(defaultRequestConfig).build();
return this.httpClient; return this.httpClient;
} }
@@ -161,28 +153,26 @@ public class FeignAutoConfiguration {
@PreDestroy @PreDestroy
public void destroy() throws Exception { public void destroy() throws Exception {
this.connectionManagerTimer.cancel(); connectionManagerTimer.cancel();
if (this.httpClient != null) { if(httpClient != null) {
this.httpClient.close(); httpClient.close();
} }
} }
} }
@Configuration @Configuration
@ConditionalOnClass(OkHttpClient.class) @ConditionalOnClass(OkHttpClient.class)
@ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer") @ConditionalOnMissingClass("com.netflix.loadbalancer.ILoadBalancer")
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class) @ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled") @ConditionalOnProperty(value = "feign.okhttp.enabled")
protected static class OkHttpFeignConfiguration { protected static class OkHttpFeignConfiguration {
private okhttp3.OkHttpClient okHttpClient; private okhttp3.OkHttpClient okHttpClient;
@Bean @Bean
@ConditionalOnMissingBean(ConnectionPool.class) @ConditionalOnMissingBean(ConnectionPool.class)
public ConnectionPool httpClientConnectionPool( public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,
FeignHttpClientProperties httpClientProperties, OkHttpClientConnectionPoolFactory connectionPoolFactory) {
OkHttpClientConnectionPoolFactory connectionPoolFactory) {
Integer maxTotalConnections = httpClientProperties.getMaxConnections(); Integer maxTotalConnections = httpClientProperties.getMaxConnections();
Long timeToLive = httpClientProperties.getTimeToLive(); Long timeToLive = httpClientProperties.getTimeToLive();
TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit(); TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
@@ -191,23 +181,22 @@ public class FeignAutoConfiguration {
@Bean @Bean
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
ConnectionPool connectionPool, ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) {
FeignHttpClientProperties httpClientProperties) {
Boolean followRedirects = httpClientProperties.isFollowRedirects(); Boolean followRedirects = httpClientProperties.isFollowRedirects();
Integer connectTimeout = httpClientProperties.getConnectionTimeout(); Integer connectTimeout = httpClientProperties.getConnectionTimeout();
Boolean disableSslValidation = httpClientProperties.isDisableSslValidation(); Boolean disableSslValidation = httpClientProperties.isDisableSslValidation();
this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation) this.okHttpClient = httpClientFactory.createBuilder(disableSslValidation).
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS) connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).
.followRedirects(followRedirects).connectionPool(connectionPool) followRedirects(followRedirects).
.build(); connectionPool(connectionPool).build();
return this.okHttpClient; return this.okHttpClient;
} }
@PreDestroy @PreDestroy
public void destroy() { public void destroy() {
if (this.okHttpClient != null) { if(okHttpClient != null) {
this.okHttpClient.dispatcher().executorService().shutdown(); okHttpClient.dispatcher().executorService().shutdown();
this.okHttpClient.connectionPool().evictAll(); okHttpClient.connectionPool().evictAll();
} }
} }
@@ -216,7 +205,6 @@ public class FeignAutoConfiguration {
public Client feignClient(okhttp3.OkHttpClient client) { public Client feignClient(okhttp3.OkHttpClient client) {
return new OkHttpClient(client); return new OkHttpClient(client);
} }
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -42,45 +42,36 @@ public @interface FeignClient {
* The name of the service with optional protocol prefix. Synonym for {@link #name() * The name of the service with optional protocol prefix. Synonym for {@link #name()
* name}. A name must be specified for all clients, whether or not a url is provided. * name}. A name must be specified for all clients, whether or not a url is provided.
* Can be specified as property key, eg: ${propertyKey}. * Can be specified as property key, eg: ${propertyKey}.
* @return the name of the service with optional protocol prefix
*/ */
@AliasFor("name") @AliasFor("name")
String value() default ""; String value() default "";
/** /**
* The service id with optional protocol prefix. Synonym for {@link #value() value}. * The service id with optional protocol prefix. Synonym for {@link #value() value}.
*
* @deprecated use {@link #name() name} instead * @deprecated use {@link #name() name} instead
* @return the service id with optional protocol prefix
*/ */
@Deprecated @Deprecated
String serviceId() default ""; String serviceId() default "";
/** /**
* This will be used as the bean name instead of name if present, but will not be used * The service id with optional protocol prefix. Synonym for {@link #value() value}.
* as a service id.
* @return bean name instead of name if present
*/
String contextId() default "";
/**
* @return The service id with optional protocol prefix. Synonym for {@link #value()
* value}.
*/ */
@AliasFor("value") @AliasFor("value")
String name() default ""; String name() default "";
/** /**
* @return the <code>@Qualifier</code> value for the feign client. * Sets the <code>@Qualifier</code> value for the feign client.
*/ */
String qualifier() default ""; String qualifier() default "";
/** /**
* @return an absolute URL or resolvable hostname (the protocol is optional). * An absolute URL or resolvable hostname (the protocol is optional).
*/ */
String url() default ""; String url() default "";
/** /**
* @return whether 404s should be decoded instead of throwing FeignExceptions * Whether 404s should be decoded instead of throwing FeignExceptions
*/ */
boolean decode404() default false; boolean decode404() default false;
@@ -90,35 +81,33 @@ public @interface FeignClient {
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}. * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
* *
* @see FeignClientsConfiguration for the defaults * @see FeignClientsConfiguration for the defaults
* @return list of configurations for feign client
*/ */
Class<?>[] configuration() default {}; Class<?>[] configuration() default {};
/** /**
* Fallback class for the specified Feign client interface. The fallback class must * Fallback class for the specified Feign client interface. The fallback class must
* implement the interface annotated by this annotation and be a valid spring bean. * implement the interface annotated by this annotation and be a valid spring bean.
* @return fallback class for the specified Feign client interface
*/ */
Class<?> fallback() default void.class; Class<?> fallback() default void.class;
/** /**
* Define a fallback factory for the specified Feign client interface. The fallback * Define a fallback factory for the specified Feign client interface. The fallback
* factory must produce instances of fallback classes that implement the interface * factory must produce instances of fallback classes that implement the interface
* annotated by {@link FeignClient}. The fallback factory must be a valid spring bean. * annotated by {@link FeignClient}. The fallback factory must be a valid spring
* bean.
* *
* @see feign.hystrix.FallbackFactory for details. * @see feign.hystrix.FallbackFactory for details.
* @return fallback factory for the specified Feign client interface
*/ */
Class<?> fallbackFactory() default void.class; Class<?> fallbackFactory() default void.class;
/** /**
* @return path prefix to be used by all method-level mappings. Can be used with or * Path prefix to be used by all method-level mappings. Can be used with or without
* without <code>@RibbonClient</code>. * <code>@RibbonClient</code>.
*/ */
String path() default ""; String path() default "";
/** /**
* @return whether to mark the feign proxy as a primary bean. Defaults to true. * Whether to mark the feign proxy as a primary bean. Defaults to true.
*/ */
boolean primary() default true; boolean primary() default true;
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -35,15 +35,10 @@ public class FeignClientBuilder {
} }
public <T> Builder<T> forType(final Class<T> type, final String name) { public <T> Builder<T> forType(final Class<T> type, final String name) {
return new Builder<>(this.applicationContext, type, name); return new Builder<>(applicationContext, type, name);
} }
/** public static class Builder<T> {
* Builder of feign targets.
*
* @param <T> type of target
*/
public static final class Builder<T> {
private FeignClientFactoryBean feignClientFactoryBean; private FeignClientFactoryBean feignClientFactoryBean;
@@ -54,7 +49,6 @@ public class FeignClientBuilder {
this.feignClientFactoryBean.setApplicationContext(applicationContext); this.feignClientFactoryBean.setApplicationContext(applicationContext);
this.feignClientFactoryBean.setType(type); this.feignClientFactoryBean.setType(type);
this.feignClientFactoryBean.setName(FeignClientsRegistrar.getName(name)); this.feignClientFactoryBean.setName(FeignClientsRegistrar.getName(name));
this.feignClientFactoryBean.setContextId(FeignClientsRegistrar.getName(name));
// preset default values - these values resemble the default values on the // preset default values - these values resemble the default values on the
// FeignClient annotation // FeignClient annotation
this.url("").path("").decode404(false).fallback(void.class) this.url("").path("").decode404(false).fallback(void.class)
@@ -66,11 +60,6 @@ public class FeignClientBuilder {
return this; return this;
} }
public Builder contextId(final String contextId) {
this.feignClientFactoryBean.setContextId(contextId);
return this;
}
public Builder path(final String path) { public Builder path(final String path) {
this.feignClientFactoryBean.setPath(FeignClientsRegistrar.getPath(path)); this.feignClientFactoryBean.setPath(FeignClientsRegistrar.getPath(path));
return this; return this;
@@ -100,7 +89,5 @@ public class FeignClientBuilder {
public <T> T build() { public <T> T build() {
return this.feignClientFactoryBean.getTarget(); return this.feignClientFactoryBean.getTarget();
} }
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,6 +19,17 @@ package org.springframework.cloud.openfeign;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import feign.Client; import feign.Client;
import feign.Contract; import feign.Contract;
import feign.Feign; import feign.Feign;
@@ -31,29 +42,16 @@ import feign.codec.Decoder;
import feign.codec.Encoder; import feign.codec.Encoder;
import feign.codec.ErrorDecoder; import feign.codec.ErrorDecoder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
* @author Venil Noronha * @author Venil Noronha
* @author Eko Kurniawan Khannedy * @author Eko Kurniawan Khannedy
* @author Gregor Zurowski * @author Gregor Zurowski
*/ */
class FeignClientFactoryBean class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
implements FactoryBean<Object>, InitializingBean, ApplicationContextAware { ApplicationContextAware {
/*********************************** /***********************************
* WARNING! Nothing in this class should be @Autowired. It causes NPEs because of some * WARNING! Nothing in this class should be @Autowired. It causes NPEs because of some lifecycle race condition.
* lifecycle race condition.
***********************************/ ***********************************/
private Class<?> type; private Class<?> type;
@@ -62,8 +60,6 @@ class FeignClientFactoryBean
private String url; private String url;
private String contextId;
private String path; private String path;
private boolean decode404; private boolean decode404;
@@ -76,10 +72,14 @@ class FeignClientFactoryBean
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
Assert.hasText(this.contextId, "Context id must be set");
Assert.hasText(this.name, "Name must be set"); Assert.hasText(this.name, "Name must be set");
} }
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.applicationContext = context;
}
protected Feign.Builder feign(FeignContext context) { protected Feign.Builder feign(FeignContext context) {
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class); FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type); Logger logger = loggerFactory.create(this.type);
@@ -99,33 +99,23 @@ class FeignClientFactoryBean
} }
protected void configureFeign(FeignContext context, Feign.Builder builder) { protected void configureFeign(FeignContext context, Feign.Builder builder) {
FeignClientProperties properties = this.applicationContext FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
.getBean(FeignClientProperties.class);
if (properties != null) { if (properties != null) {
if (properties.isDefaultToProperties()) { if (properties.isDefaultToProperties()) {
configureUsingConfiguration(context, builder); configureUsingConfiguration(context, builder);
configureUsingProperties( configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
properties.getConfig().get(properties.getDefaultConfig()), configureUsingProperties(properties.getConfig().get(this.name), builder);
builder); } else {
configureUsingProperties(properties.getConfig().get(this.contextId), configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
builder); configureUsingProperties(properties.getConfig().get(this.name), builder);
}
else {
configureUsingProperties(
properties.getConfig().get(properties.getDefaultConfig()),
builder);
configureUsingProperties(properties.getConfig().get(this.contextId),
builder);
configureUsingConfiguration(context, builder); configureUsingConfiguration(context, builder);
} }
} } else {
else {
configureUsingConfiguration(context, builder); configureUsingConfiguration(context, builder);
} }
} }
protected void configureUsingConfiguration(FeignContext context, protected void configureUsingConfiguration(FeignContext context, Feign.Builder builder) {
Feign.Builder builder) {
Logger.Level level = getOptional(context, Logger.Level.class); Logger.Level level = getOptional(context, Logger.Level.class);
if (level != null) { if (level != null) {
builder.logLevel(level); builder.logLevel(level);
@@ -142,20 +132,18 @@ class FeignClientFactoryBean
if (options != null) { if (options != null) {
builder.options(options); builder.options(options);
} }
Map<String, RequestInterceptor> requestInterceptors = context Map<String, RequestInterceptor> requestInterceptors = context.getInstances(
.getInstances(this.contextId, RequestInterceptor.class); this.name, RequestInterceptor.class);
if (requestInterceptors != null) { if (requestInterceptors != null) {
builder.requestInterceptors(requestInterceptors.values()); builder.requestInterceptors(requestInterceptors.values());
} }
if (this.decode404) { if (decode404) {
builder.decode404(); builder.decode404();
} }
} }
protected void configureUsingProperties( protected void configureUsingProperties(FeignClientProperties.FeignClientConfiguration config, Feign.Builder builder) {
FeignClientProperties.FeignClientConfiguration config,
Feign.Builder builder) {
if (config == null) { if (config == null) {
return; return;
} }
@@ -165,8 +153,7 @@ class FeignClientFactoryBean
} }
if (config.getConnectTimeout() != null && config.getReadTimeout() != null) { if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {
builder.options(new Request.Options(config.getConnectTimeout(), builder.options(new Request.Options(config.getConnectTimeout(), config.getReadTimeout()));
config.getReadTimeout()));
} }
if (config.getRetryer() != null) { if (config.getRetryer() != null) {
@@ -179,8 +166,7 @@ class FeignClientFactoryBean
builder.errorDecoder(errorDecoder); builder.errorDecoder(errorDecoder);
} }
if (config.getRequestInterceptors() != null if (config.getRequestInterceptors() != null && !config.getRequestInterceptors().isEmpty()) {
&& !config.getRequestInterceptors().isEmpty()) {
// this will add request interceptor to builder, not replace existing // this will add request interceptor to builder, not replace existing
for (Class<RequestInterceptor> bean : config.getRequestInterceptors()) { for (Class<RequestInterceptor> bean : config.getRequestInterceptors()) {
RequestInterceptor interceptor = getOrInstantiate(bean); RequestInterceptor interceptor = getOrInstantiate(bean);
@@ -209,24 +195,23 @@ class FeignClientFactoryBean
private <T> T getOrInstantiate(Class<T> tClass) { private <T> T getOrInstantiate(Class<T> tClass) {
try { try {
return this.applicationContext.getBean(tClass); return applicationContext.getBean(tClass);
} } catch (NoSuchBeanDefinitionException e) {
catch (NoSuchBeanDefinitionException e) {
return BeanUtils.instantiateClass(tClass); return BeanUtils.instantiateClass(tClass);
} }
} }
protected <T> T get(FeignContext context, Class<T> type) { protected <T> T get(FeignContext context, Class<T> type) {
T instance = context.getInstance(this.contextId, type); T instance = context.getInstance(this.name, type);
if (instance == null) { if (instance == null) {
throw new IllegalStateException( throw new IllegalStateException("No bean found of type " + type + " for "
"No bean found of type " + type + " for " + this.contextId); + this.name);
} }
return instance; return instance;
} }
protected <T> T getOptional(FeignContext context, Class<T> type) { protected <T> T getOptional(FeignContext context, Class<T> type) {
return context.getInstance(this.contextId, type); return context.getInstance(this.name, type);
} }
protected <T> T loadBalance(Feign.Builder builder, FeignContext context, protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
@@ -249,23 +234,23 @@ class FeignClientFactoryBean
/** /**
* @param <T> the target type of the Feign client * @param <T> the target type of the Feign client
* @return a {@link Feign} client created with the specified data and the context * @return a {@link Feign} client created with the specified data and the context information
* information
*/ */
<T> T getTarget() { <T> T getTarget() {
FeignContext context = this.applicationContext.getBean(FeignContext.class); FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context); Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) { if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) { if (!this.name.startsWith("http")) {
this.url = "http://" + this.name; url = "http://" + this.name;
} }
else { else {
this.url = this.name; url = this.name;
} }
this.url += cleanPath(); url += cleanPath();
return (T) loadBalance(builder, context, return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
new HardCodedTarget<>(this.type, this.name, this.url)); this.name, url));
} }
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) { if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url; this.url = "http://" + this.url;
@@ -276,13 +261,13 @@ class FeignClientFactoryBean
if (client instanceof LoadBalancerFeignClient) { if (client instanceof LoadBalancerFeignClient) {
// not load balancing because we have a url, // not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap // but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient) client).getDelegate(); client = ((LoadBalancerFeignClient)client).getDelegate();
} }
builder.client(client); builder.client(client);
} }
Targeter targeter = get(context, Targeter.class); Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
new HardCodedTarget<>(this.type, this.name, url)); this.type, this.name, url));
} }
private String cleanPath() { private String cleanPath() {
@@ -309,7 +294,7 @@ class FeignClientFactoryBean
} }
public Class<?> getType() { public Class<?> getType() {
return this.type; return type;
} }
public void setType(Class<?> type) { public void setType(Class<?> type) {
@@ -317,23 +302,15 @@ class FeignClientFactoryBean
} }
public String getName() { public String getName() {
return this.name; return name;
} }
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
public String getContextId() {
return this.contextId;
}
public void setContextId(String contextId) {
this.contextId = contextId;
}
public String getUrl() { public String getUrl() {
return this.url; return url;
} }
public void setUrl(String url) { public void setUrl(String url) {
@@ -341,7 +318,7 @@ class FeignClientFactoryBean
} }
public String getPath() { public String getPath() {
return this.path; return path;
} }
public void setPath(String path) { public void setPath(String path) {
@@ -349,7 +326,7 @@ class FeignClientFactoryBean
} }
public boolean isDecode404() { public boolean isDecode404() {
return this.decode404; return decode404;
} }
public void setDecode404(boolean decode404) { public void setDecode404(boolean decode404) {
@@ -357,16 +334,11 @@ class FeignClientFactoryBean
} }
public ApplicationContext getApplicationContext() { public ApplicationContext getApplicationContext() {
return this.applicationContext; return applicationContext;
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.applicationContext = context;
} }
public Class<?> getFallback() { public Class<?> getFallback() {
return this.fallback; return fallback;
} }
public void setFallback(Class<?> fallback) { public void setFallback(Class<?> fallback) {
@@ -374,7 +346,7 @@ class FeignClientFactoryBean
} }
public Class<?> getFallbackFactory() { public Class<?> getFallbackFactory() {
return this.fallbackFactory; return fallbackFactory;
} }
public void setFallbackFactory(Class<?> fallbackFactory) { public void setFallbackFactory(Class<?> fallbackFactory) {
@@ -383,39 +355,37 @@ class FeignClientFactoryBean
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) { if (this == o) return true;
return true; if (o == null || getClass() != o.getClass()) return false;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
FeignClientFactoryBean that = (FeignClientFactoryBean) o; FeignClientFactoryBean that = (FeignClientFactoryBean) o;
return Objects.equals(this.applicationContext, that.applicationContext) return Objects.equals(applicationContext, that.applicationContext) &&
&& this.decode404 == that.decode404 decode404 == that.decode404 &&
&& Objects.equals(this.fallback, that.fallback) Objects.equals(fallback, that.fallback) &&
&& Objects.equals(this.fallbackFactory, that.fallbackFactory) Objects.equals(fallbackFactory, that.fallbackFactory) &&
&& Objects.equals(this.name, that.name) Objects.equals(name, that.name) &&
&& Objects.equals(this.path, that.path) Objects.equals(path, that.path) &&
&& Objects.equals(this.type, that.type) Objects.equals(type, that.type) &&
&& Objects.equals(this.url, that.url); Objects.equals(url, that.url);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(this.applicationContext, this.decode404, this.fallback, return Objects.hash(applicationContext, decode404, fallback, fallbackFactory,
this.fallbackFactory, this.name, this.path, this.type, this.url); name, path, type, url);
} }
@Override @Override
public String toString() { public String toString() {
return new StringBuilder("FeignClientFactoryBean{").append("type=") return new StringBuilder("FeignClientFactoryBean{")
.append(this.type).append(", ").append("name='").append(this.name) .append("type=").append(type).append(", ")
.append("', ").append("url='").append(this.url).append("', ") .append("name='").append(name).append("', ")
.append("path='").append(this.path).append("', ").append("decode404=") .append("url='").append(url).append("', ")
.append(this.decode404).append(", ").append("applicationContext=") .append("path='").append(path).append("', ")
.append(this.applicationContext).append(", ").append("fallback=") .append("decode404=").append(decode404).append(", ")
.append(this.fallback).append(", ").append("fallbackFactory=") .append("applicationContext=").append(applicationContext).append(", ")
.append(this.fallbackFactory).append("}").toString(); .append("fallback=").append(fallback).append(", ")
.append("fallbackFactory=").append(fallbackFactory)
.append("}").toString();
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,14 +13,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import feign.Contract; import feign.Contract;
import feign.Logger; import feign.Logger;
import feign.RequestInterceptor; import feign.RequestInterceptor;
@@ -28,9 +22,13 @@ import feign.Retryer;
import feign.codec.Decoder; import feign.codec.Decoder;
import feign.codec.Encoder; import feign.codec.Encoder;
import feign.codec.ErrorDecoder; import feign.codec.ErrorDecoder;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/** /**
* @author Eko Kurniawan Khannedy * @author Eko Kurniawan Khannedy
*/ */
@@ -44,7 +42,7 @@ public class FeignClientProperties {
private Map<String, FeignClientConfiguration> config = new HashMap<>(); private Map<String, FeignClientConfiguration> config = new HashMap<>();
public boolean isDefaultToProperties() { public boolean isDefaultToProperties() {
return this.defaultToProperties; return defaultToProperties;
} }
public void setDefaultToProperties(boolean defaultToProperties) { public void setDefaultToProperties(boolean defaultToProperties) {
@@ -52,7 +50,7 @@ public class FeignClientProperties {
} }
public String getDefaultConfig() { public String getDefaultConfig() {
return this.defaultConfig; return defaultConfig;
} }
public void setDefaultConfig(String defaultConfig) { public void setDefaultConfig(String defaultConfig) {
@@ -60,7 +58,7 @@ public class FeignClientProperties {
} }
public Map<String, FeignClientConfiguration> getConfig() { public Map<String, FeignClientConfiguration> getConfig() {
return this.config; return config;
} }
public void setConfig(Map<String, FeignClientConfiguration> config) { public void setConfig(Map<String, FeignClientConfiguration> config) {
@@ -69,26 +67,19 @@ public class FeignClientProperties {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) { if (this == o) return true;
return true; if (o == null || getClass() != o.getClass()) return false;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
FeignClientProperties that = (FeignClientProperties) o; FeignClientProperties that = (FeignClientProperties) o;
return this.defaultToProperties == that.defaultToProperties return defaultToProperties == that.defaultToProperties &&
&& Objects.equals(this.defaultConfig, that.defaultConfig) Objects.equals(defaultConfig, that.defaultConfig) &&
&& Objects.equals(this.config, that.config); Objects.equals(config, that.config);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(this.defaultToProperties, this.defaultConfig, this.config); return Objects.hash(defaultToProperties, defaultConfig, config);
} }
/**
* Feign client configuration.
*/
public static class FeignClientConfiguration { public static class FeignClientConfiguration {
private Logger.Level loggerLevel; private Logger.Level loggerLevel;
@@ -112,7 +103,7 @@ public class FeignClientProperties {
private Class<Contract> contract; private Class<Contract> contract;
public Logger.Level getLoggerLevel() { public Logger.Level getLoggerLevel() {
return this.loggerLevel; return loggerLevel;
} }
public void setLoggerLevel(Logger.Level loggerLevel) { public void setLoggerLevel(Logger.Level loggerLevel) {
@@ -120,7 +111,7 @@ public class FeignClientProperties {
} }
public Integer getConnectTimeout() { public Integer getConnectTimeout() {
return this.connectTimeout; return connectTimeout;
} }
public void setConnectTimeout(Integer connectTimeout) { public void setConnectTimeout(Integer connectTimeout) {
@@ -128,7 +119,7 @@ public class FeignClientProperties {
} }
public Integer getReadTimeout() { public Integer getReadTimeout() {
return this.readTimeout; return readTimeout;
} }
public void setReadTimeout(Integer readTimeout) { public void setReadTimeout(Integer readTimeout) {
@@ -136,7 +127,7 @@ public class FeignClientProperties {
} }
public Class<Retryer> getRetryer() { public Class<Retryer> getRetryer() {
return this.retryer; return retryer;
} }
public void setRetryer(Class<Retryer> retryer) { public void setRetryer(Class<Retryer> retryer) {
@@ -144,7 +135,7 @@ public class FeignClientProperties {
} }
public Class<ErrorDecoder> getErrorDecoder() { public Class<ErrorDecoder> getErrorDecoder() {
return this.errorDecoder; return errorDecoder;
} }
public void setErrorDecoder(Class<ErrorDecoder> errorDecoder) { public void setErrorDecoder(Class<ErrorDecoder> errorDecoder) {
@@ -152,16 +143,15 @@ public class FeignClientProperties {
} }
public List<Class<RequestInterceptor>> getRequestInterceptors() { public List<Class<RequestInterceptor>> getRequestInterceptors() {
return this.requestInterceptors; return requestInterceptors;
} }
public void setRequestInterceptors( public void setRequestInterceptors(List<Class<RequestInterceptor>> requestInterceptors) {
List<Class<RequestInterceptor>> requestInterceptors) {
this.requestInterceptors = requestInterceptors; this.requestInterceptors = requestInterceptors;
} }
public Boolean getDecode404() { public Boolean getDecode404() {
return this.decode404; return decode404;
} }
public void setDecode404(Boolean decode404) { public void setDecode404(Boolean decode404) {
@@ -169,7 +159,7 @@ public class FeignClientProperties {
} }
public Class<Decoder> getDecoder() { public Class<Decoder> getDecoder() {
return this.decoder; return decoder;
} }
public void setDecoder(Class<Decoder> decoder) { public void setDecoder(Class<Decoder> decoder) {
@@ -177,7 +167,7 @@ public class FeignClientProperties {
} }
public Class<Encoder> getEncoder() { public Class<Encoder> getEncoder() {
return this.encoder; return encoder;
} }
public void setEncoder(Class<Encoder> encoder) { public void setEncoder(Class<Encoder> encoder) {
@@ -185,7 +175,7 @@ public class FeignClientProperties {
} }
public Class<Contract> getContract() { public Class<Contract> getContract() {
return this.contract; return contract;
} }
public void setContract(Class<Contract> contract) { public void setContract(Class<Contract> contract) {
@@ -194,32 +184,26 @@ public class FeignClientProperties {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) { if (this == o) return true;
return true; if (o == null || getClass() != o.getClass()) return false;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
FeignClientConfiguration that = (FeignClientConfiguration) o; FeignClientConfiguration that = (FeignClientConfiguration) o;
return this.loggerLevel == that.loggerLevel return loggerLevel == that.loggerLevel &&
&& Objects.equals(this.connectTimeout, that.connectTimeout) Objects.equals(connectTimeout, that.connectTimeout) &&
&& Objects.equals(this.readTimeout, that.readTimeout) Objects.equals(readTimeout, that.readTimeout) &&
&& Objects.equals(this.retryer, that.retryer) Objects.equals(retryer, that.retryer) &&
&& Objects.equals(this.errorDecoder, that.errorDecoder) Objects.equals(errorDecoder, that.errorDecoder) &&
&& Objects.equals(this.requestInterceptors, that.requestInterceptors) Objects.equals(requestInterceptors, that.requestInterceptors) &&
&& Objects.equals(this.decode404, that.decode404) Objects.equals(decode404, that.decode404) &&
&& Objects.equals(this.encoder, that.encoder) Objects.equals(encoder, that.encoder) &&
&& Objects.equals(this.decoder, that.decoder) Objects.equals(decoder, that.decoder) &&
&& Objects.equals(this.contract, that.contract); Objects.equals(contract, that.contract);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(this.loggerLevel, this.connectTimeout, this.readTimeout, return Objects.hash(loggerLevel, connectTimeout, readTimeout, retryer,
this.retryer, this.errorDecoder, this.requestInterceptors, errorDecoder, requestInterceptors, decode404, encoder, decoder, contract);
this.decode404, this.encoder, this.decoder, this.contract);
} }
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,11 +16,11 @@
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
import org.springframework.cloud.context.named.NamedContextFactory;
import java.util.Arrays; import java.util.Arrays;
import java.util.Objects; import java.util.Objects;
import org.springframework.cloud.context.named.NamedContextFactory;
/** /**
* @author Dave Syer * @author Dave Syer
* @author Gregor Zurowski * @author Gregor Zurowski
@@ -31,16 +31,15 @@ class FeignClientSpecification implements NamedContextFactory.Specification {
private Class<?>[] configuration; private Class<?>[] configuration;
FeignClientSpecification() { public FeignClientSpecification() {}
}
FeignClientSpecification(String name, Class<?>[] configuration) { public FeignClientSpecification(String name, Class<?>[] configuration) {
this.name = name; this.name = name;
this.configuration = configuration; this.configuration = configuration;
} }
public String getName() { public String getName() {
return this.name; return name;
} }
public void setName(String name) { public void setName(String name) {
@@ -48,7 +47,7 @@ class FeignClientSpecification implements NamedContextFactory.Specification {
} }
public Class<?>[] getConfiguration() { public Class<?>[] getConfiguration() {
return this.configuration; return configuration;
} }
public void setConfiguration(Class<?>[] configuration) { public void setConfiguration(Class<?>[] configuration) {
@@ -57,27 +56,24 @@ class FeignClientSpecification implements NamedContextFactory.Specification {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) { if (this == o) return true;
return true; if (o == null || getClass() != o.getClass()) return false;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
FeignClientSpecification that = (FeignClientSpecification) o; FeignClientSpecification that = (FeignClientSpecification) o;
return Objects.equals(this.name, that.name) return Objects.equals(name, that.name) &&
&& Arrays.equals(this.configuration, that.configuration); Arrays.equals(configuration, that.configuration);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(this.name, this.configuration); return Objects.hash(name, configuration);
} }
@Override @Override
public String toString() { public String toString() {
return new StringBuilder("FeignClientSpecification{").append("name='") return new StringBuilder("FeignClientSpecification{")
.append(this.name).append("', ").append("configuration=") .append("name='").append(name).append("', ")
.append(Arrays.toString(this.configuration)).append("}").toString(); .append("configuration=").append(Arrays.toString(configuration))
.append("}").toString();
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -12,6 +12,7 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
@@ -19,26 +20,12 @@ package org.springframework.cloud.openfeign;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.fasterxml.jackson.databind.Module;
import com.netflix.hystrix.HystrixCommand;
import feign.Contract;
import feign.Feign;
import feign.Logger;
import feign.Retryer;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.hystrix.HystrixFeign;
import feign.optionals.OptionalDecoder;
import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 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.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.PageJacksonModule;
import org.springframework.cloud.openfeign.support.PageableSpringEncoder;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder; import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.cloud.openfeign.support.SpringDecoder; import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.cloud.openfeign.support.SpringEncoder; import org.springframework.cloud.openfeign.support.SpringEncoder;
@@ -50,6 +37,17 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.format.support.FormattingConversionService; import org.springframework.format.support.FormattingConversionService;
import com.netflix.hystrix.HystrixCommand;
import feign.Contract;
import feign.Feign;
import feign.Logger;
import feign.Retryer;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.hystrix.HystrixFeign;
import feign.optionals.OptionalDecoder;
/** /**
* @author Dave Syer * @author Dave Syer
* @author Venil Noronha * @author Venil Noronha
@@ -72,24 +70,15 @@ public class FeignClientsConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public Decoder feignDecoder() { public Decoder feignDecoder() {
return new OptionalDecoder( return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
} }
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnMissingClass("org.springframework.data.domain.Pageable")
public Encoder feignEncoder() { public Encoder feignEncoder() {
return new SpringEncoder(this.messageConverters); return new SpringEncoder(this.messageConverters);
} }
@Bean
@ConditionalOnClass(name = "org.springframework.data.domain.Pageable")
@ConditionalOnMissingBean
public Encoder feignEncoderPageable() {
return new PageableSpringEncoder(new SpringEncoder(this.messageConverters));
}
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) { public Contract feignContract(ConversionService feignConversionService) {
@@ -99,12 +88,24 @@ public class FeignClientsConfiguration {
@Bean @Bean
public FormattingConversionService feignConversionService() { public FormattingConversionService feignConversionService() {
FormattingConversionService conversionService = new DefaultFormattingConversionService(); FormattingConversionService conversionService = new DefaultFormattingConversionService();
for (FeignFormatterRegistrar feignFormatterRegistrar : this.feignFormatterRegistrars) { for (FeignFormatterRegistrar feignFormatterRegistrar : feignFormatterRegistrars) {
feignFormatterRegistrar.registerFormatters(conversionService); feignFormatterRegistrar.registerFormatters(conversionService);
} }
return conversionService; return conversionService;
} }
@Configuration
@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();
}
}
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public Retryer feignRetryer() { public Retryer feignRetryer() {
@@ -121,27 +122,7 @@ public class FeignClientsConfiguration {
@Bean @Bean
@ConditionalOnMissingBean(FeignLoggerFactory.class) @ConditionalOnMissingBean(FeignLoggerFactory.class)
public FeignLoggerFactory feignLoggerFactory() { public FeignLoggerFactory feignLoggerFactory() {
return new DefaultFeignLoggerFactory(this.logger); return new DefaultFeignLoggerFactory(logger);
}
@Bean
@ConditionalOnClass(name = "org.springframework.data.domain.Page")
public Module pageJacksonModule() {
return new PageJacksonModule();
}
@Configuration
@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();
}
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -58,8 +58,8 @@ import org.springframework.util.StringUtils;
* @author Venil Noronha * @author Venil Noronha
* @author Gang Li * @author Gang Li
*/ */
class FeignClientsRegistrar class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { ResourceLoaderAware, EnvironmentAware {
// patterned after Spring Integration IntegrationComponentScanRegistrar // patterned after Spring Integration IntegrationComponentScanRegistrar
// and RibbonClientsConfigurationRegistgrar // and RibbonClientsConfigurationRegistgrar
@@ -68,68 +68,7 @@ class FeignClientsRegistrar
private Environment environment; private Environment environment;
FeignClientsRegistrar() { public FeignClientsRegistrar() {
}
static void validateFallback(final Class clazz) {
Assert.isTrue(!clazz.isInterface(),
"Fallback class must implement the interface annotated by @FeignClient");
}
static void validateFallbackFactory(final Class clazz) {
Assert.isTrue(!clazz.isInterface(), "Fallback factory must produce instances "
+ "of fallback classes that implement the interface annotated by @FeignClient");
}
static String getName(String name) {
if (!StringUtils.hasText(name)) {
return "";
}
String host = null;
try {
String url;
if (!name.startsWith("http://") && !name.startsWith("https://")) {
url = "http://" + name;
}
else {
url = name;
}
host = new URI(url).getHost();
}
catch (URISyntaxException e) {
}
Assert.state(host != null, "Service id not legal hostname (" + name + ")");
return name;
}
static String getUrl(String url) {
if (StringUtils.hasText(url) && !(url.startsWith("#{") && url.contains("}"))) {
if (!url.contains("://")) {
url = "http://" + url;
}
try {
new URL(url);
}
catch (MalformedURLException e) {
throw new IllegalArgumentException(url + " is malformed", e);
}
}
return url;
}
static String getPath(String path) {
if (StringUtils.hasText(path)) {
path = path.trim();
if (!path.startsWith("/")) {
path = "/" + path;
}
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
}
return path;
} }
@Override @Override
@@ -232,19 +171,16 @@ class FeignClientsRegistrar
definition.addPropertyValue("path", getPath(attributes)); definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes); String name = getName(attributes);
definition.addPropertyValue("name", name); definition.addPropertyValue("name", name);
String contextId = getContextId(attributes);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className); definition.addPropertyValue("type", className);
definition.addPropertyValue("decode404", attributes.get("decode404")); definition.addPropertyValue("decode404", attributes.get("decode404"));
definition.addPropertyValue("fallback", attributes.get("fallback")); definition.addPropertyValue("fallback", attributes.get("fallback"));
definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory")); definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
String alias = contextId + "FeignClient"; String alias = name + "FeignClient";
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
boolean primary = (Boolean) attributes.get("primary"); // has a default, won't be boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null
// null
beanDefinition.setPrimary(primary); beanDefinition.setPrimary(primary);
@@ -266,6 +202,19 @@ class FeignClientsRegistrar
validateFallbackFactory(annotation.getClass("fallbackFactory")); validateFallbackFactory(annotation.getClass("fallbackFactory"));
} }
static void validateFallback(final Class clazz) {
Assert.isTrue(
!clazz.isInterface(),
"Fallback class must implement the interface annotated by @FeignClient"
);
}
static void validateFallbackFactory(final Class clazz) {
Assert.isTrue(!clazz.isInterface(),
"Fallback factory must produce instances of fallback classes that implement the interface annotated by @FeignClient"
);
}
/* for testing */ String getName(Map<String, Object> attributes) { /* for testing */ String getName(Map<String, Object> attributes) {
String name = (String) attributes.get("serviceId"); String name = (String) attributes.get("serviceId");
if (!StringUtils.hasText(name)) { if (!StringUtils.hasText(name)) {
@@ -278,14 +227,26 @@ class FeignClientsRegistrar
return getName(name); return getName(name);
} }
private String getContextId(Map<String, Object> attributes) { static String getName(String name) {
String contextId = (String) attributes.get("contextId"); if (!StringUtils.hasText(name)) {
if (!StringUtils.hasText(contextId)) { return "";
return getName(attributes);
} }
contextId = resolve(contextId); String host = null;
return getName(contextId); try {
String url;
if (!name.startsWith("http://") && !name.startsWith("https://")) {
url = "http://" + name;
} else {
url = name;
}
host = new URI(url).getHost();
}
catch (URISyntaxException e) {
}
Assert.state(host != null, "Service id not legal hostname (" + name + ")");
return name;
} }
private String resolve(String value) { private String resolve(String value) {
@@ -300,16 +261,43 @@ class FeignClientsRegistrar
return getUrl(url); return getUrl(url);
} }
static String getUrl(String url) {
if (StringUtils.hasText(url) && !(url.startsWith("#{") && url.contains("}"))) {
if (!url.contains("://")) {
url = "http://" + url;
}
try {
new URL(url);
}
catch (MalformedURLException e) {
throw new IllegalArgumentException(url + " is malformed", e);
}
}
return url;
}
private String getPath(Map<String, Object> attributes) { private String getPath(Map<String, Object> attributes) {
String path = resolve((String) attributes.get("path")); String path = resolve((String) attributes.get("path"));
return getPath(path); return getPath(path);
} }
static String getPath(String path) {
if (StringUtils.hasText(path)) {
path = path.trim();
if (!path.startsWith("/")) {
path = "/" + path;
}
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
}
return path;
}
protected ClassPathScanningCandidateComponentProvider getScanner() { protected ClassPathScanningCandidateComponentProvider getScanner() {
return new ClassPathScanningCandidateComponentProvider(false, this.environment) { return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
@Override @Override
protected boolean isCandidateComponent( protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotatedBeanDefinition beanDefinition) {
boolean isCandidate = false; boolean isCandidate = false;
if (beanDefinition.getMetadata().isIndependent()) { if (beanDefinition.getMetadata().isIndependent()) {
if (!beanDefinition.getMetadata().isAnnotation()) { if (!beanDefinition.getMetadata().isAnnotation()) {
@@ -346,7 +334,7 @@ class FeignClientsRegistrar
} }
return basePackages; return basePackages;
} }
private String getQualifier(Map<String, Object> client) { private String getQualifier(Map<String, Object> client) {
if (client == null) { if (client == null) {
return null; return null;
@@ -362,10 +350,7 @@ class FeignClientsRegistrar
if (client == null) { if (client == null) {
return null; return null;
} }
String value = (String) client.get("contextId"); String value = (String) client.get("value");
if (!StringUtils.hasText(value)) {
value = (String) client.get("value");
}
if (!StringUtils.hasText(value)) { if (!StringUtils.hasText(value)) {
value = (String) client.get("name"); value = (String) client.get("name");
} }
@@ -408,9 +393,10 @@ class FeignClientsRegistrar
/** /**
* Creates a new {@link AllTypeFilter} to match if all the given delegates match. * Creates a new {@link AllTypeFilter} to match if all the given delegates match.
*
* @param delegates must not be {@literal null}. * @param delegates must not be {@literal null}.
*/ */
AllTypeFilter(List<TypeFilter> delegates) { public AllTypeFilter(List<TypeFilter> delegates) {
Assert.notNull(delegates, "This argument is required, it must not be null"); Assert.notNull(delegates, "This argument is required, it must not be null");
this.delegates = delegates; this.delegates = delegates;
} }
@@ -427,7 +413,5 @@ class FeignClientsRegistrar
return true; return true;
} }
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/* /*
* Copyright 2016-2019 the original author or authors. * Copyright 2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -27,9 +27,10 @@ public interface FeignLoggerFactory {
/** /**
* Factory method to provide a {@link Logger} for a given {@link Class}. * Factory method to provide a {@link Logger} for a given {@link Class}.
*
* @param type the {@link Class} for which a {@link Logger} instance is to be created * @param type the {@link Class} for which a {@link Logger} instance is to be created
* @return a {@link Logger} instance * @return a {@link Logger} instance
*/ */
Logger create(Class<?> type); public Logger create(Class<?> type);
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -12,10 +12,13 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
import org.springframework.util.Assert;
import feign.Feign; import feign.Feign;
import feign.Target; import feign.Target;
import feign.hystrix.FallbackFactory; import feign.hystrix.FallbackFactory;
@@ -30,70 +33,66 @@ import feign.hystrix.SetterFactory;
class HystrixTargeter implements Targeter { class HystrixTargeter implements Targeter {
@Override @Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
FeignContext context, Target.HardCodedTarget<T> target) { Target.HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) { if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
return feign.target(target); return feign.target(target);
} }
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign; feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
SetterFactory setterFactory = getOptional(factory.getName(), context, SetterFactory setterFactory = getOptional(factory.getName(), context,
SetterFactory.class); SetterFactory.class);
if (setterFactory != null) { if (setterFactory != null) {
builder.setterFactory(setterFactory); builder.setterFactory(setterFactory);
} }
Class<?> fallback = factory.getFallback(); Class<?> fallback = factory.getFallback();
if (fallback != void.class) { if (fallback != void.class) {
return targetWithFallback(factory.getName(), context, target, builder, return targetWithFallback(factory.getName(), context, target, builder, fallback);
fallback);
} }
Class<?> fallbackFactory = factory.getFallbackFactory(); Class<?> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) { if (fallbackFactory != void.class) {
return targetWithFallbackFactory(factory.getName(), context, target, builder, return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
fallbackFactory);
} }
return feign.target(target); return feign.target(target);
} }
private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context, private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context,
Target.HardCodedTarget<T> target, HystrixFeign.Builder builder, Target.HardCodedTarget<T> target,
Class<?> fallbackFactoryClass) { HystrixFeign.Builder builder,
FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>) getFromContext( Class<?> fallbackFactoryClass) {
"fallbackFactory", feignClientName, context, fallbackFactoryClass, FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>)
FallbackFactory.class); getFromContext("fallbackFactory", feignClientName, context, fallbackFactoryClass, FallbackFactory.class);
return builder.target(target, fallbackFactory); return builder.target(target, fallbackFactory);
} }
private <T> T targetWithFallback(String feignClientName, FeignContext context, private <T> T targetWithFallback(String feignClientName, FeignContext context,
Target.HardCodedTarget<T> target, HystrixFeign.Builder builder, Target.HardCodedTarget<T> target,
Class<?> fallback) { HystrixFeign.Builder builder, Class<?> fallback) {
T fallbackInstance = getFromContext("fallback", feignClientName, context, T fallbackInstance = getFromContext("fallback", feignClientName, context, fallback, target.type());
fallback, target.type());
return builder.target(target, fallbackInstance); return builder.target(target, fallbackInstance);
} }
private <T> T getFromContext(String fallbackMechanism, String feignClientName, private <T> T getFromContext(String fallbackMechanism, String feignClientName, FeignContext context,
FeignContext context, Class<?> beanType, Class<T> targetType) { Class<?> beanType, Class<T> targetType) {
Object fallbackInstance = context.getInstance(feignClientName, beanType); Object fallbackInstance = context.getInstance(feignClientName, beanType);
if (fallbackInstance == null) { if (fallbackInstance == null) {
throw new IllegalStateException(String.format( throw new IllegalStateException(String.format(
"No " + fallbackMechanism "No " + fallbackMechanism + " instance of type %s found for feign client %s",
+ " instance of type %s found for feign client %s", beanType, feignClientName));
beanType, feignClientName));
} }
if (!targetType.isAssignableFrom(beanType)) { if (!targetType.isAssignableFrom(beanType)) {
throw new IllegalStateException(String.format("Incompatible " throw new IllegalStateException(
+ fallbackMechanism String.format(
+ " instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s", "Incompatible " + fallbackMechanism + " instance. Fallback/fallbackFactory of type %s is not assignable to %s for feign client %s",
beanType, targetType, feignClientName)); beanType, targetType, feignClientName));
} }
return (T) fallbackInstance; return (T) fallbackInstance;
} }
private <T> T getOptional(String feignClientName, FeignContext context, private <T> T getOptional(String feignClientName, FeignContext context,
Class<T> beanType) { Class<T> beanType) {
return context.getInstance(feignClientName, beanType); return context.getInstance(feignClientName, beanType);
} }
} }
@@ -1,53 +0,0 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import feign.QueryMap;
import org.springframework.core.annotation.AliasFor;
/**
* Spring MVC equivalent of OpenFeign's {@link feign.QueryMap} parameter annotation.
*
* @author Aram Peres
* @see feign.QueryMap
* @see org.springframework.cloud.openfeign.annotation.QueryMapParameterProcessor
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.PARAMETER })
public @interface SpringQueryMap {
/**
* @see QueryMap#encoded()
* @return alias for {@link #encoded()}.
*/
@AliasFor("encoded")
boolean value() default false;
/**
* @see QueryMap#encoded()
* @return Specifies whether parameter names and values are already encoded.
*/
@AliasFor("value")
boolean encoded() default false;
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -12,6 +12,7 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
@@ -23,8 +24,6 @@ import feign.Target;
* @author Spencer Gibb * @author Spencer Gibb
*/ */
interface Targeter { interface Targeter {
<T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
<T> T target(FeignClientFactoryBean factory, Feign.Builder feign, Target.HardCodedTarget<T> target);
FeignContext context, Target.HardCodedTarget<T> target);
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -21,11 +21,11 @@ import java.lang.reflect.Method;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import feign.MethodMetadata;
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor; import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import feign.MethodMetadata;
import static feign.Util.checkState; import static feign.Util.checkState;
import static feign.Util.emptyToNull; import static feign.Util.emptyToNull;
@@ -46,12 +46,10 @@ public class PathVariableParameterProcessor implements AnnotatedParameterProcess
} }
@Override @Override
public boolean processArgument(AnnotatedParameterContext context, public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
Annotation annotation, Method method) {
String name = ANNOTATION.cast(annotation).value(); String name = ANNOTATION.cast(annotation).value();
checkState(emptyToNull(name) != null, checkState(emptyToNull(name) != null,
"PathVariable annotation was empty on param %s.", "PathVariable annotation was empty on param %s.", context.getParameterIndex());
context.getParameterIndex());
context.setParameterName(name); context.setParameterName(name);
MethodMetadata data = context.getMethodMetadata(); MethodMetadata data = context.getMethodMetadata();
@@ -76,5 +74,4 @@ public class PathVariableParameterProcessor implements AnnotatedParameterProcess
} }
return false; return false;
} }
} }
@@ -1,54 +0,0 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import feign.MethodMetadata;
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
import org.springframework.cloud.openfeign.SpringQueryMap;
/**
* {@link SpringQueryMap} parameter processor.
*
* @author Aram Peres
* @see AnnotatedParameterProcessor
*/
public class QueryMapParameterProcessor implements AnnotatedParameterProcessor {
private static final Class<SpringQueryMap> ANNOTATION = SpringQueryMap.class;
@Override
public Class<? extends Annotation> getAnnotationType() {
return ANNOTATION;
}
@Override
public boolean processArgument(AnnotatedParameterContext context,
Annotation annotation, Method method) {
int paramIndex = context.getParameterIndex();
MethodMetadata metadata = context.getMethodMetadata();
if (metadata.queryMapIndex() == null) {
metadata.queryMapIndex(paramIndex);
metadata.queryMapEncoded(SpringQueryMap.class.cast(annotation).encoded());
}
return true;
}
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -21,11 +21,11 @@ import java.lang.reflect.Method;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import feign.MethodMetadata;
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor; import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestHeader;
import feign.MethodMetadata;
import static feign.Util.checkState; import static feign.Util.checkState;
import static feign.Util.emptyToNull; import static feign.Util.emptyToNull;
@@ -46,15 +46,13 @@ public class RequestHeaderParameterProcessor implements AnnotatedParameterProces
} }
@Override @Override
public boolean processArgument(AnnotatedParameterContext context, public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
Annotation annotation, Method method) {
int parameterIndex = context.getParameterIndex(); int parameterIndex = context.getParameterIndex();
Class<?> parameterType = method.getParameterTypes()[parameterIndex]; Class<?> parameterType = method.getParameterTypes()[parameterIndex];
MethodMetadata data = context.getMethodMetadata(); MethodMetadata data = context.getMethodMetadata();
if (Map.class.isAssignableFrom(parameterType)) { if (Map.class.isAssignableFrom(parameterType)) {
checkState(data.headerMapIndex() == null, checkState(data.headerMapIndex() == null, "Header map can only be present once.");
"Header map can only be present once.");
data.headerMapIndex(parameterIndex); data.headerMapIndex(parameterIndex);
return true; return true;
@@ -65,10 +63,8 @@ public class RequestHeaderParameterProcessor implements AnnotatedParameterProces
"RequestHeader.value() was empty on parameter %s", parameterIndex); "RequestHeader.value() was empty on parameter %s", parameterIndex);
context.setParameterName(name); context.setParameterName(name);
Collection<String> header = context.setTemplateParameter(name, Collection<String> header = context.setTemplateParameter(name, data.template().headers().get(name));
data.template().headers().get(name));
data.template().header(name, header); data.template().header(name, header);
return true; return true;
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -21,14 +21,14 @@ import java.lang.reflect.Method;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import feign.MethodMetadata;
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor; import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import static feign.Util.checkState; import static feign.Util.checkState;
import static feign.Util.emptyToNull; import static feign.Util.emptyToNull;
import feign.MethodMetadata;
/** /**
* {@link RequestParam} parameter processor. * {@link RequestParam} parameter processor.
* *
@@ -46,15 +46,13 @@ public class RequestParamParameterProcessor implements AnnotatedParameterProcess
} }
@Override @Override
public boolean processArgument(AnnotatedParameterContext context, public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
Annotation annotation, Method method) {
int parameterIndex = context.getParameterIndex(); int parameterIndex = context.getParameterIndex();
Class<?> parameterType = method.getParameterTypes()[parameterIndex]; Class<?> parameterType = method.getParameterTypes()[parameterIndex];
MethodMetadata data = context.getMethodMetadata(); MethodMetadata data = context.getMethodMetadata();
if (Map.class.isAssignableFrom(parameterType)) { if (Map.class.isAssignableFrom(parameterType)) {
checkState(data.queryMapIndex() == null, checkState(data.queryMapIndex() == null, "Query map can only be present once.");
"Query map can only be present once.");
data.queryMapIndex(parameterIndex); data.queryMapIndex(parameterIndex);
return true; return true;
@@ -63,7 +61,8 @@ public class RequestParamParameterProcessor implements AnnotatedParameterProcess
RequestParam requestParam = ANNOTATION.cast(annotation); RequestParam requestParam = ANNOTATION.cast(annotation);
String name = requestParam.value(); String name = requestParam.value();
checkState(emptyToNull(name) != null, checkState(emptyToNull(name) != null,
"RequestParam.value() was empty on parameter %s", parameterIndex); "RequestParam.value() was empty on parameter %s",
parameterIndex);
context.setParameterName(name); context.setParameterName(name);
Collection<String> query = context.setTemplateParameter(name, Collection<String> query = context.setTemplateParameter(name,
@@ -71,5 +70,4 @@ public class RequestParamParameterProcessor implements AnnotatedParameterProcess
data.template().query(name, query); data.template().query(name, query);
return true; return true;
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ package org.springframework.cloud.openfeign.encoding;
import feign.RequestInterceptor; import feign.RequestInterceptor;
import feign.RequestTemplate; import feign.RequestTemplate;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@@ -35,6 +34,7 @@ public abstract class BaseRequestInterceptor implements RequestInterceptor {
/** /**
* Creates new instance of {@link BaseRequestInterceptor}. * Creates new instance of {@link BaseRequestInterceptor}.
*
* @param properties the encoding properties * @param properties the encoding properties
*/ */
protected BaseRequestInterceptor(FeignClientEncodingProperties properties) { protected BaseRequestInterceptor(FeignClientEncodingProperties properties) {
@@ -44,12 +44,12 @@ public abstract class BaseRequestInterceptor implements RequestInterceptor {
/** /**
* Adds the header if it wasn't yet specified. * Adds the header if it wasn't yet specified.
*
* @param requestTemplate the request * @param requestTemplate the request
* @param name the header name * @param name the header name
* @param values the header values * @param values the header values
*/ */
protected void addHeader(RequestTemplate requestTemplate, String name, protected void addHeader(RequestTemplate requestTemplate, String name, String... values) {
String... values) {
if (!requestTemplate.headers().containsKey(name)) { if (!requestTemplate.headers().containsKey(name)) {
requestTemplate.header(name, values); requestTemplate.header(name, values);
@@ -57,7 +57,7 @@ public abstract class BaseRequestInterceptor implements RequestInterceptor {
} }
protected FeignClientEncodingProperties getProperties() { protected FeignClientEncodingProperties getProperties() {
return this.properties; return properties;
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -40,16 +40,14 @@ import org.springframework.context.annotation.Configuration;
@ConditionalOnClass(Feign.class) @ConditionalOnClass(Feign.class)
@ConditionalOnBean(Client.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. //The OK HTTP client uses "transparent" compression.
// If the accept-encoding header is present it disable transparent compression //If the accept-encoding header is present it disable transparent compression
@ConditionalOnMissingBean(type = "okhttp3.OkHttpClient") @ConditionalOnMissingBean(type = "okhttp3.OkHttpClient")
@AutoConfigureAfter(FeignAutoConfiguration.class) @AutoConfigureAfter(FeignAutoConfiguration.class)
public class FeignAcceptGzipEncodingAutoConfiguration { public class FeignAcceptGzipEncodingAutoConfiguration {
@Bean @Bean
public FeignAcceptGzipEncodingInterceptor feignAcceptGzipEncodingInterceptor( public FeignAcceptGzipEncodingInterceptor feignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
FeignClientEncodingProperties properties) {
return new FeignAcceptGzipEncodingInterceptor(properties); return new FeignAcceptGzipEncodingInterceptor(properties);
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,11 +19,10 @@ package org.springframework.cloud.openfeign.encoding;
import feign.RequestTemplate; import feign.RequestTemplate;
/** /**
* Enables the HTTP response payload compression by specifying the {@code Accept-Encoding} * Enables the HTTP response payload compression by specifying the {@code Accept-Encoding} headers.
* headers. Although this does not yet mean that the requests will be compressed, it * Although this does not yet mean that the requests will be compressed, it requires the remote server
* requires the remote server to understand the header and be configured to compress * to understand the header and be configured to compress responses. Still no all responses might be compressed
* responses. Still no all responses might be compressed based on the media type matching * based on the media type matching and other factors like the response content length.
* and other factors like the response content length.
* *
* @author Jakub Narloch * @author Jakub Narloch
*/ */
@@ -31,10 +30,10 @@ public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor {
/** /**
* Creates new instance of {@link FeignAcceptGzipEncodingInterceptor}. * Creates new instance of {@link FeignAcceptGzipEncodingInterceptor}.
*
* @param properties the encoding properties * @param properties the encoding properties
*/ */
protected FeignAcceptGzipEncodingInterceptor( protected FeignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
FeignClientEncodingProperties properties) {
super(properties); super(properties);
} }
@@ -44,8 +43,7 @@ public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor {
@Override @Override
public void apply(RequestTemplate template) { public void apply(RequestTemplate template) {
addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER, addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING,
HttpEncoding.GZIP_ENCODING, HttpEncoding.DEFLATE_ENCODING); HttpEncoding.DEFLATE_ENCODING);
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,11 +16,11 @@
package org.springframework.cloud.openfeign.encoding; package org.springframework.cloud.openfeign.encoding;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Arrays; import java.util.Arrays;
import java.util.Objects; import java.util.Objects;
import org.springframework.boot.context.properties.ConfigurationProperties;
/** /**
* The Feign encoding properties. * The Feign encoding properties.
* *
@@ -32,8 +32,7 @@ public class FeignClientEncodingProperties {
/** /**
* The list of supported mime types. * The list of supported mime types.
*/ */
private String[] mimeTypes = new String[] { "text/xml", "application/xml", private String[] mimeTypes = new String[]{"text/xml", "application/xml", "application/json"};
"application/json" };
/** /**
* The minimum threshold content size. * The minimum threshold content size.
@@ -41,7 +40,7 @@ public class FeignClientEncodingProperties {
private int minRequestSize = 2048; private int minRequestSize = 2048;
public String[] getMimeTypes() { public String[] getMimeTypes() {
return this.mimeTypes; return mimeTypes;
} }
public void setMimeTypes(String[] mimeTypes) { public void setMimeTypes(String[] mimeTypes) {
@@ -49,7 +48,7 @@ public class FeignClientEncodingProperties {
} }
public int getMinRequestSize() { public int getMinRequestSize() {
return this.minRequestSize; return minRequestSize;
} }
public void setMinRequestSize(int minRequestSize) { public void setMinRequestSize(int minRequestSize) {
@@ -58,28 +57,24 @@ public class FeignClientEncodingProperties {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) { if (this == o) return true;
return true; if (o == null || getClass() != o.getClass()) return false;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
FeignClientEncodingProperties that = (FeignClientEncodingProperties) o; FeignClientEncodingProperties that = (FeignClientEncodingProperties) o;
return Arrays.equals(this.mimeTypes, that.mimeTypes) return Arrays.equals(mimeTypes, that.mimeTypes) &&
&& Objects.equals(this.minRequestSize, that.minRequestSize); Objects.equals(minRequestSize, that.minRequestSize);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(this.mimeTypes, this.minRequestSize); return Objects.hash(mimeTypes, minRequestSize);
} }
@Override @Override
public String toString() { public String toString() {
return new StringBuilder("FeignClientEncodingProperties{").append("mimeTypes=") return new StringBuilder("FeignClientEncodingProperties{")
.append(Arrays.toString(this.mimeTypes)).append(", ") .append("mimeTypes=").append(Arrays.toString(mimeTypes)).append(", ")
.append("minRequestSize=").append(this.minRequestSize).append("}") .append("minRequestSize=").append(minRequestSize)
.toString(); .append("}").toString();
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -39,17 +39,15 @@ import org.springframework.context.annotation.Configuration;
@EnableConfigurationProperties(FeignClientEncodingProperties.class) @EnableConfigurationProperties(FeignClientEncodingProperties.class)
@ConditionalOnClass(Feign.class) @ConditionalOnClass(Feign.class)
@ConditionalOnBean(Client.class) @ConditionalOnBean(Client.class)
// The OK HTTP client uses "transparent" compression. //The OK HTTP client uses "transparent" compression.
// If the content-encoding header is present it disable transparent compression //If the content-encoding header is present it disable transparent compression
@ConditionalOnMissingBean(type = "okhttp3.OkHttpClient") @ConditionalOnMissingBean(type = "okhttp3.OkHttpClient")
@ConditionalOnProperty(value = "feign.compression.request.enabled", matchIfMissing = false) @ConditionalOnProperty(value = "feign.compression.request.enabled", matchIfMissing = false)
@AutoConfigureAfter(FeignAutoConfiguration.class) @AutoConfigureAfter(FeignAutoConfiguration.class)
public class FeignContentGzipEncodingAutoConfiguration { public class FeignContentGzipEncodingAutoConfiguration {
@Bean @Bean
public FeignContentGzipEncodingInterceptor feignContentGzipEncodingInterceptor( public FeignContentGzipEncodingInterceptor feignContentGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
FeignClientEncodingProperties properties) {
return new FeignContentGzipEncodingInterceptor(properties); return new FeignContentGzipEncodingInterceptor(properties);
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,14 +16,13 @@
package org.springframework.cloud.openfeign.encoding; package org.springframework.cloud.openfeign.encoding;
import feign.RequestTemplate;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import feign.RequestTemplate;
/** /**
* Enables the HTTP request payload compression by specifying the {@code Content-Encoding} * Enables the HTTP request payload compression by specifying the {@code Content-Encoding} headers.
* headers.
* *
* @author Jakub Narloch * @author Jakub Narloch
*/ */
@@ -31,10 +30,10 @@ public class FeignContentGzipEncodingInterceptor extends BaseRequestInterceptor
/** /**
* Creates new instance of {@link FeignContentGzipEncodingInterceptor}. * Creates new instance of {@link FeignContentGzipEncodingInterceptor}.
*
* @param properties the encoding properties * @param properties the encoding properties
*/ */
protected FeignContentGzipEncodingInterceptor( protected FeignContentGzipEncodingInterceptor(FeignClientEncodingProperties properties) {
FeignClientEncodingProperties properties) {
super(properties); super(properties);
} }
@@ -45,13 +44,14 @@ public class FeignContentGzipEncodingInterceptor extends BaseRequestInterceptor
public void apply(RequestTemplate template) { public void apply(RequestTemplate template) {
if (requiresCompression(template)) { if (requiresCompression(template)) {
addHeader(template, HttpEncoding.CONTENT_ENCODING_HEADER, addHeader(template, HttpEncoding.CONTENT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING,
HttpEncoding.GZIP_ENCODING, HttpEncoding.DEFLATE_ENCODING); HttpEncoding.DEFLATE_ENCODING);
} }
} }
/** /**
* Returns whether the request requires GZIP compression. * Returns whether the request requires GZIP compression.
*
* @param template the request template * @param template the request template
* @return true if request requires compression, false otherwise * @return true if request requires compression, false otherwise
*/ */
@@ -64,6 +64,7 @@ public class FeignContentGzipEncodingInterceptor extends BaseRequestInterceptor
/** /**
* Returns whether the request content length exceed configured minimum size. * Returns whether the request content length exceed configured minimum size.
*
* @param contentLength the content length header value * @param contentLength the content length header value
* @return true if length is grater than minimum size, false otherwise * @return true if length is grater than minimum size, false otherwise
*/ */
@@ -77,14 +78,14 @@ public class FeignContentGzipEncodingInterceptor extends BaseRequestInterceptor
final String strLen = contentLength.iterator().next(); final String strLen = contentLength.iterator().next();
final long length = Long.parseLong(strLen); final long length = Long.parseLong(strLen);
return length > getProperties().getMinRequestSize(); return length > getProperties().getMinRequestSize();
} } catch (NumberFormatException ex) {
catch (NumberFormatException ex) {
return false; return false;
} }
} }
/** /**
* Returns whether the content mime types matches the configures mime types. * Returns whether the content mime types matches the configures mime types.
*
* @param contentTypes the content types * @param contentTypes the content types
* @return true if any specified content type matches the request content types * @return true if any specified content type matches the request content types
*/ */
@@ -93,8 +94,7 @@ public class FeignContentGzipEncodingInterceptor extends BaseRequestInterceptor
return false; return false;
} }
if (getProperties().getMimeTypes() == null if (getProperties().getMimeTypes() == null || getProperties().getMimeTypes().length == 0) {
|| getProperties().getMimeTypes().length == 0) {
// no specific mime types has been set - matching everything // no specific mime types has been set - matching everything
return true; return true;
} }
@@ -107,5 +107,4 @@ public class FeignContentGzipEncodingInterceptor extends BaseRequestInterceptor
return false; return false;
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -52,5 +52,4 @@ public interface HttpEncoding {
* The Deflate encoding. * The Deflate encoding.
*/ */
String DEFLATE_ENCODING = "deflate"; String DEFLATE_ENCODING = "deflate";
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -18,14 +18,14 @@ package org.springframework.cloud.openfeign.ribbon;
import java.util.Map; 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.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.cloud.netflix.ribbon.ServerIntrospector; import org.springframework.cloud.netflix.ribbon.ServerIntrospector;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ConcurrentReferenceHashMap;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ILoadBalancer;
/** /**
* Factory for SpringLoadBalancer instances that caches the entries created. * Factory for SpringLoadBalancer instances that caches the entries created.
* *
@@ -36,9 +36,8 @@ import org.springframework.util.ConcurrentReferenceHashMap;
*/ */
public class CachingSpringLoadBalancerFactory { public class CachingSpringLoadBalancerFactory {
protected final SpringClientFactory factory; private final SpringClientFactory factory;
private LoadBalancedRetryFactory loadBalancedRetryFactory = null;
protected LoadBalancedRetryFactory loadBalancedRetryFactory = null;
private volatile Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap<>(); private volatile Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap<>();
@@ -46,27 +45,23 @@ public class CachingSpringLoadBalancerFactory {
this.factory = factory; this.factory = factory;
} }
public CachingSpringLoadBalancerFactory(SpringClientFactory factory, public CachingSpringLoadBalancerFactory(SpringClientFactory factory, LoadBalancedRetryFactory loadBalancedRetryPolicyFactory) {
LoadBalancedRetryFactory loadBalancedRetryPolicyFactory) {
this.factory = factory; this.factory = factory;
this.loadBalancedRetryFactory = loadBalancedRetryPolicyFactory; this.loadBalancedRetryFactory = loadBalancedRetryPolicyFactory;
} }
public FeignLoadBalancer create(String clientName) { public FeignLoadBalancer create(String clientName) {
FeignLoadBalancer client = this.cache.get(clientName); FeignLoadBalancer client = this.cache.get(clientName);
if (client != null) { if(client != null) {
return client; return client;
} }
IClientConfig config = this.factory.getClientConfig(clientName); IClientConfig config = this.factory.getClientConfig(clientName);
ILoadBalancer lb = this.factory.getLoadBalancer(clientName); ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
ServerIntrospector.class); client = loadBalancedRetryFactory != null ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
client = this.loadBalancedRetryFactory != null loadBalancedRetryFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
this.loadBalancedRetryFactory)
: new FeignLoadBalancer(lb, config, serverIntrospector);
this.cache.put(clientName, client); this.cache.put(clientName, client);
return client; return client;
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -12,12 +12,12 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package org.springframework.cloud.openfeign.ribbon; package org.springframework.cloud.openfeign.ribbon;
import feign.Client; import feign.Client;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@@ -28,13 +28,11 @@ import org.springframework.context.annotation.Configuration;
*/ */
@Configuration @Configuration
class DefaultFeignLoadBalancedConfiguration { class DefaultFeignLoadBalancedConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) { SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory, return new LoadBalancerFeignClient(new Client.Default(null, null),
clientFactory); cachingFactory, clientFactory);
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,6 +16,10 @@
package org.springframework.cloud.openfeign.ribbon; package org.springframework.cloud.openfeign.ribbon;
import feign.Client;
import feign.Request;
import feign.Response;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
@@ -25,6 +29,11 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
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 com.netflix.client.AbstractLoadBalancerAwareClient; import com.netflix.client.AbstractLoadBalancerAwareClient;
import com.netflix.client.ClientException; import com.netflix.client.ClientException;
import com.netflix.client.ClientRequest; import com.netflix.client.ClientRequest;
@@ -34,15 +43,6 @@ import com.netflix.client.RetryHandler;
import com.netflix.client.config.IClientConfig; import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server; 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; import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToSecureConnectionIfNeeded;
@@ -51,23 +51,18 @@ import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToSecur
* @author Spencer Gibb * @author Spencer Gibb
* @author Ryan Baxter * @author Ryan Baxter
* @author Tim Ysewyn * @author Tim Ysewyn
* @author Olga Maciaszek-Sharma
*/ */
public class FeignLoadBalancer extends public class FeignLoadBalancer extends
AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> { AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse> {
private final RibbonProperties ribbon; private final RibbonProperties ribbon;
protected int connectTimeout; protected int connectTimeout;
protected int readTimeout; protected int readTimeout;
protected IClientConfig clientConfig; protected IClientConfig clientConfig;
protected ServerIntrospector serverIntrospector; protected ServerIntrospector serverIntrospector;
public FeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig, public FeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig,
ServerIntrospector serverIntrospector) { ServerIntrospector serverIntrospector) {
super(lb, clientConfig); super(lb, clientConfig);
this.setRetryHandler(RetryHandler.DEFAULT); this.setRetryHandler(RetryHandler.DEFAULT);
this.clientConfig = clientConfig; this.clientConfig = clientConfig;
@@ -84,7 +79,8 @@ public class FeignLoadBalancer extends
Request.Options options; Request.Options options;
if (configOverride != null) { if (configOverride != null) {
RibbonProperties override = RibbonProperties.from(configOverride); RibbonProperties override = RibbonProperties.from(configOverride);
options = new Request.Options(override.connectTimeout(this.connectTimeout), options = new Request.Options(
override.connectTimeout(this.connectTimeout),
override.readTimeout(this.readTimeout)); override.readTimeout(this.readTimeout));
} }
else { else {
@@ -101,7 +97,7 @@ public class FeignLoadBalancer extends
return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(), return new RequestSpecificRetryHandler(true, true, this.getRetryHandler(),
requestConfig); requestConfig);
} }
if (!request.toRequest().httpMethod().name().equals("GET")) { if (!request.toRequest().method().equals("GET")) {
return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(), return new RequestSpecificRetryHandler(true, false, this.getRetryHandler(),
requestConfig); requestConfig);
} }
@@ -113,18 +109,16 @@ public class FeignLoadBalancer extends
@Override @Override
public URI reconstructURIWithServer(Server server, URI original) { public URI reconstructURIWithServer(Server server, URI original) {
URI uri = updateToSecureConnectionIfNeeded(original, this.clientConfig, URI uri = updateToSecureConnectionIfNeeded(original, this.clientConfig, this.serverIntrospector, server);
this.serverIntrospector, server);
return super.reconstructURIWithServer(server, uri); return super.reconstructURIWithServer(server, uri);
} }
protected static class RibbonRequest extends ClientRequest implements Cloneable { protected static class RibbonRequest extends ClientRequest implements Cloneable {
private final Request request; private final Request request;
private final Client client; private final Client client;
protected RibbonRequest(Client client, Request request, URI uri) { RibbonRequest(Client client, Request request, URI uri) {
this.client = client; this.client = client;
setUri(uri); setUri(uri);
this.request = toRequest(request); this.request = toRequest(request);
@@ -133,8 +127,7 @@ public class FeignLoadBalancer extends
private Request toRequest(Request request) { private Request toRequest(Request request) {
Map<String, Collection<String>> headers = new LinkedHashMap<>( Map<String, Collection<String>> headers = new LinkedHashMap<>(
request.headers()); request.headers());
return Request.create(request.httpMethod(), getUri().toASCIIString(), headers, return Request.create(request.method(),getUri().toASCIIString(),headers,request.body(),request.charset());
request.requestBody());
} }
Request toRequest() { Request toRequest() {
@@ -149,8 +142,7 @@ public class FeignLoadBalancer extends
return new HttpRequest() { return new HttpRequest() {
@Override @Override
public HttpMethod getMethod() { public HttpMethod getMethod() {
return HttpMethod return HttpMethod.resolve(RibbonRequest.this.toRequest().method());
.resolve(RibbonRequest.this.toRequest().httpMethod().name());
} }
@Override @Override
@@ -166,9 +158,8 @@ public class FeignLoadBalancer extends
@Override @Override
public HttpHeaders getHeaders() { public HttpHeaders getHeaders() {
Map<String, List<String>> headers = new HashMap<>(); Map<String, List<String>> headers = new HashMap<>();
Map<String, Collection<String>> feignHeaders = RibbonRequest.this Map<String, Collection<String>> feignHeaders = RibbonRequest.this.toRequest().headers();
.toRequest().headers(); for(String key : feignHeaders.keySet()) {
for (String key : feignHeaders.keySet()) {
headers.put(key, new ArrayList<String>(feignHeaders.get(key))); headers.put(key, new ArrayList<String>(feignHeaders.get(key)));
} }
HttpHeaders httpHeaders = new HttpHeaders(); HttpHeaders httpHeaders = new HttpHeaders();
@@ -179,28 +170,19 @@ public class FeignLoadBalancer extends
}; };
} }
public Request getRequest() {
return this.request;
}
public Client getClient() {
return this.client;
}
@Override @Override
public Object clone() { public Object clone() {
return new RibbonRequest(this.client, this.request, getUri()); return new RibbonRequest(this.client, this.request, getUri());
} }
} }
protected static class RibbonResponse implements IResponse { protected static class RibbonResponse implements IResponse {
private final URI uri; private final URI uri;
private final Response response; private final Response response;
protected RibbonResponse(URI uri, Response response) { RibbonResponse(URI uri, Response response) {
this.uri = uri; this.uri = uri;
this.response = response; this.response = response;
} }
@@ -243,4 +225,4 @@ public class FeignLoadBalancer extends
} }
} }
@@ -1,17 +1,19 @@
/* /*
* Copyright 2013-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * * Copyright 2013-2016 the original author or authors.
* you may not use this file except in compliance with the License. * *
* You may obtain a copy of the License at * * 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.
* *
* 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; package org.springframework.cloud.openfeign.ribbon;
@@ -32,13 +34,9 @@ import org.springframework.retry.RetryContext;
* @author Ryan Baxter * @author Ryan Baxter
*/ */
public class FeignRetryPolicy extends InterceptorRetryPolicy { public class FeignRetryPolicy extends InterceptorRetryPolicy {
private HttpRequest request; private HttpRequest request;
private String serviceId; private String serviceId;
public FeignRetryPolicy(HttpRequest request, LoadBalancedRetryPolicy policy, ServiceInstanceChooser serviceInstanceChooser, String serviceName) {
public FeignRetryPolicy(HttpRequest request, LoadBalancedRetryPolicy policy,
ServiceInstanceChooser serviceInstanceChooser, String serviceName) {
super(request, policy, serviceInstanceChooser, serviceName); super(request, policy, serviceInstanceChooser, serviceName);
this.request = request; this.request = request;
this.serviceId = serviceName; this.serviceId = serviceName;
@@ -47,18 +45,17 @@ public class FeignRetryPolicy extends InterceptorRetryPolicy {
@Override @Override
public boolean canRetry(RetryContext context) { public boolean canRetry(RetryContext context) {
/* /*
* In InterceptorRetryPolicy.canRetry we ask the LoadBalancer to choose a server * In InterceptorRetryPolicy.canRetry we ask the LoadBalancer to choose a server if one is not
* if one is not set in the retry context and then return true. RetryTemplat calls * set in the retry context and then return true. RetryTemplat calls the canRetry method of
* the canRetry method of the policy even on its first execution. So the fact that * the policy even on its first execution. So the fact that we didnt have a service instance set
* we didnt have a service instance set in the RetryContext signaled that it was * in the RetryContext signaled that it was the first execution and we should return true.
* the first execution and we should return true.
* *
* In the Feign scenario, Feign as actually already queried the load balancer for * In the Feign scenario, Feign as actually already queried the load balancer for a service instance
* a service instance and we set that service instance in the context when we call * and we set that service instance in the context when we call the open method of the policy. So in
* the open method of the policy. So in the Feign case we just return true if the * the Feign case we just return true if the retry count is 0 indicating we haven't yet made a failed
* retry count is 0 indicating we haven't yet made a failed request. * request.
*/ */
if (context.getRetryCount() == 0) { if(context.getRetryCount() == 0) {
return true; return true;
} }
return super.canRetry(context); return super.canRetry(context);
@@ -67,25 +64,20 @@ public class FeignRetryPolicy extends InterceptorRetryPolicy {
@Override @Override
public RetryContext open(RetryContext parent) { public RetryContext open(RetryContext parent) {
/* /*
* With Feign (unlike Ribbon) the request already has the URI for the service * With Feign (unlike Ribbon) the request already has the URI for the service instance
* instance we are going to make the request to, so extract that information and * we are going to make the request to, so extract that information and set the service
* set the service instance in the context. In the Ribbon scenario the URI in the * instance in the context. In the Ribbon scenario the URI in the request object still has
* request object still has the service id so we choose and set the service * the service id so we choose and set the service instance later on.
* instance later on.
*/ */
LoadBalancedRetryContext context = new LoadBalancedRetryContext(parent, LoadBalancedRetryContext context = new LoadBalancedRetryContext(parent, this.request);
this.request); context.setServiceInstance(new FeignRetryPolicyServiceInstance(serviceId, request));
context.setServiceInstance(
new FeignRetryPolicyServiceInstance(this.serviceId, this.request));
return context; return context;
} }
class FeignRetryPolicyServiceInstance implements ServiceInstance { class FeignRetryPolicyServiceInstance implements ServiceInstance {
private String serviceId; private String serviceId;
private HttpRequest request; private HttpRequest request;
private Map<String, String> metadata; private Map<String, String> metadata;
FeignRetryPolicyServiceInstance(String serviceId, HttpRequest request) { FeignRetryPolicyServiceInstance(String serviceId, HttpRequest request) {
@@ -96,34 +88,32 @@ public class FeignRetryPolicy extends InterceptorRetryPolicy {
@Override @Override
public String getServiceId() { public String getServiceId() {
return this.serviceId; return serviceId;
} }
@Override @Override
public String getHost() { public String getHost() {
return this.request.getURI().getHost(); return request.getURI().getHost();
} }
@Override @Override
public int getPort() { public int getPort() {
return this.request.getURI().getPort(); return request.getURI().getPort();
} }
@Override @Override
public boolean isSecure() { public boolean isSecure() {
return "https".equals(this.request.getURI().getScheme()); return "https".equals(request.getURI().getScheme());
} }
@Override @Override
public URI getUri() { public URI getUri() {
return this.request.getURI(); return request.getURI();
} }
@Override @Override
public Map<String, String> getMetadata() { public Map<String, String> getMetadata() {
return this.metadata; return metadata;
} }
} }
}
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,24 +16,25 @@
package org.springframework.cloud.openfeign.ribbon; 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.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory; 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.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties; import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import com.netflix.loadbalancer.ILoadBalancer;
import feign.Feign;
import feign.Request;
/** /**
* Autoconfiguration to be activated if Feign is in use and needs to be use Ribbon as a * Autoconfiguration to be activated if Feign is in use and needs to be use Ribbon as a
* load balancer. * load balancer.
@@ -44,9 +45,8 @@ import org.springframework.context.annotation.Primary;
@Configuration @Configuration
@AutoConfigureBefore(FeignAutoConfiguration.class) @AutoConfigureBefore(FeignAutoConfiguration.class)
@EnableConfigurationProperties({ FeignHttpClientProperties.class }) @EnableConfigurationProperties({ FeignHttpClientProperties.class })
// Order is important here, last should be the default, first should be optional //Order is important here, last should be the default, first should be optional
// see // see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ HttpClientFeignLoadBalancedConfiguration.class, @Import({ HttpClientFeignLoadBalancedConfiguration.class,
OkHttpFeignLoadBalancedConfiguration.class, OkHttpFeignLoadBalancedConfiguration.class,
DefaultFeignLoadBalancedConfiguration.class }) DefaultFeignLoadBalancedConfiguration.class })
@@ -66,7 +66,8 @@ public class FeignRibbonClientAutoConfiguration {
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate") @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory( public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
SpringClientFactory factory, LoadBalancedRetryFactory retryFactory) { SpringClientFactory factory,
LoadBalancedRetryFactory retryFactory) {
return new CachingSpringLoadBalancerFactory(factory, retryFactory); return new CachingSpringLoadBalancerFactory(factory, retryFactory);
} }
@@ -75,5 +76,4 @@ public class FeignRibbonClientAutoConfiguration {
public Request.Options feignRequestOptions() { public Request.Options feignRequestOptions() {
return LoadBalancerFeignClient.DEFAULT_OPTIONS; return LoadBalancerFeignClient.DEFAULT_OPTIONS;
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -12,32 +12,31 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package org.springframework.cloud.openfeign.ribbon; package org.springframework.cloud.openfeign.ribbon;
import java.util.Timer;
import java.util.TimerTask;
import javax.annotation.PreDestroy;
import feign.Client; import feign.Client;
import feign.httpclient.ApacheHttpClient; import feign.httpclient.ApacheHttpClient;
import java.util.Timer;
import java.util.TimerTask;
import javax.annotation.PreDestroy;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig; import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.RegistryBuilder; import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory; import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory; import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties; import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@@ -49,18 +48,9 @@ import org.springframework.context.annotation.Configuration;
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true) @ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
class HttpClientFeignLoadBalancedConfiguration { 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);
}
@Configuration @Configuration
@ConditionalOnMissingBean(CloseableHttpClient.class) @ConditionalOnMissingBean(CloseableHttpClient.class)
protected static class HttpClientFeignConfiguration { protected static class HttpClientFeignConfiguration {
private final Timer connectionManagerTimer = new Timer( private final Timer connectionManagerTimer = new Timer(
"FeignApacheHttpClientConfiguration.connectionManagerTimer", true); "FeignApacheHttpClientConfiguration.connectionManagerTimer", true);
@@ -75,12 +65,10 @@ class HttpClientFeignLoadBalancedConfiguration {
ApacheHttpClientConnectionManagerFactory connectionManagerFactory, ApacheHttpClientConnectionManagerFactory connectionManagerFactory,
FeignHttpClientProperties httpClientProperties) { FeignHttpClientProperties httpClientProperties) {
final HttpClientConnectionManager connectionManager = connectionManagerFactory final HttpClientConnectionManager connectionManager = connectionManagerFactory
.newConnectionManager(httpClientProperties.isDisableSslValidation(), .newConnectionManager(httpClientProperties.isDisableSslValidation(), httpClientProperties.getMaxConnections(),
httpClientProperties.getMaxConnections(),
httpClientProperties.getMaxConnectionsPerRoute(), httpClientProperties.getMaxConnectionsPerRoute(),
httpClientProperties.getTimeToLive(), httpClientProperties.getTimeToLive(),
httpClientProperties.getTimeToLiveUnit(), httpClientProperties.getTimeToLiveUnit(), registryBuilder);
this.registryBuilder);
this.connectionManagerTimer.schedule(new TimerTask() { this.connectionManagerTimer.schedule(new TimerTask() {
@Override @Override
public void run() { public void run() {
@@ -92,47 +80,49 @@ class HttpClientFeignLoadBalancedConfiguration {
@Bean @Bean
@ConditionalOnProperty(value = "feign.compression.response.enabled", havingValue = "true") @ConditionalOnProperty(value = "feign.compression.response.enabled", havingValue = "true")
public CloseableHttpClient customHttpClient( public CloseableHttpClient customHttpClient(HttpClientConnectionManager httpClientConnectionManager,
HttpClientConnectionManager httpClientConnectionManager, FeignHttpClientProperties httpClientProperties) {
FeignHttpClientProperties httpClientProperties) { HttpClientBuilder builder = HttpClientBuilder.create().disableCookieManagement().useSystemProperties();
HttpClientBuilder builder = HttpClientBuilder.create() this.httpClient = createClient(builder, httpClientConnectionManager, httpClientProperties);
.disableCookieManagement().useSystemProperties();
this.httpClient = createClient(builder, httpClientConnectionManager,
httpClientProperties);
return this.httpClient; return this.httpClient;
} }
@Bean @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, public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory, HttpClientConnectionManager httpClientConnectionManager,
HttpClientConnectionManager httpClientConnectionManager, FeignHttpClientProperties httpClientProperties) {
FeignHttpClientProperties httpClientProperties) { this.httpClient = createClient(httpClientFactory.createBuilder(), httpClientConnectionManager, httpClientProperties);
this.httpClient = createClient(httpClientFactory.createBuilder(),
httpClientConnectionManager, httpClientProperties);
return this.httpClient; return this.httpClient;
} }
private CloseableHttpClient createClient(HttpClientBuilder builder, private CloseableHttpClient createClient(HttpClientBuilder builder, HttpClientConnectionManager httpClientConnectionManager,
HttpClientConnectionManager httpClientConnectionManager, FeignHttpClientProperties httpClientProperties) {
FeignHttpClientProperties httpClientProperties) {
RequestConfig defaultRequestConfig = RequestConfig.custom() RequestConfig defaultRequestConfig = RequestConfig.custom()
.setConnectTimeout(httpClientProperties.getConnectionTimeout()) .setConnectTimeout(httpClientProperties.getConnectionTimeout())
.setRedirectsEnabled(httpClientProperties.isFollowRedirects()) .setRedirectsEnabled(httpClientProperties.isFollowRedirects())
.build(); .build();
CloseableHttpClient httpClient = builder CloseableHttpClient httpClient = builder.setDefaultRequestConfig(defaultRequestConfig).
.setDefaultRequestConfig(defaultRequestConfig) setConnectionManager(httpClientConnectionManager).build();
.setConnectionManager(httpClientConnectionManager).build();
return httpClient; return httpClient;
} }
@PreDestroy @PreDestroy
public void destroy() throws Exception { public void destroy() throws Exception {
this.connectionManagerTimer.cancel(); connectionManagerTimer.cancel();
if (this.httpClient != null) { if(httpClient != null) {
this.httpClient.close(); httpClient.close();
} }
} }
} }
@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,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,16 +19,17 @@ package org.springframework.cloud.openfeign.ribbon;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import com.netflix.client.ClientException; import com.netflix.client.ClientException;
import com.netflix.client.config.CommonClientConfigKey; import com.netflix.client.config.CommonClientConfigKey;
import com.netflix.client.config.DefaultClientConfigImpl; import com.netflix.client.config.DefaultClientConfigImpl;
import com.netflix.client.config.IClientConfig; import com.netflix.client.config.IClientConfig;
import feign.Client; import feign.Client;
import feign.Request; import feign.Request;
import feign.Response; import feign.Response;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
/** /**
* @author Dave Syer * @author Dave Syer
* *
@@ -38,37 +39,17 @@ public class LoadBalancerFeignClient implements Client {
static final Request.Options DEFAULT_OPTIONS = new Request.Options(); static final Request.Options DEFAULT_OPTIONS = new Request.Options();
private final Client delegate; private final Client delegate;
private CachingSpringLoadBalancerFactory lbClientFactory; private CachingSpringLoadBalancerFactory lbClientFactory;
private SpringClientFactory clientFactory; private SpringClientFactory clientFactory;
public LoadBalancerFeignClient(Client delegate, public LoadBalancerFeignClient(Client delegate,
CachingSpringLoadBalancerFactory lbClientFactory, CachingSpringLoadBalancerFactory lbClientFactory,
SpringClientFactory clientFactory) { SpringClientFactory clientFactory) {
this.delegate = delegate; this.delegate = delegate;
this.lbClientFactory = lbClientFactory; this.lbClientFactory = lbClientFactory;
this.clientFactory = clientFactory; 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 @Override
public Response execute(Request request, Request.Options options) throws IOException { public Response execute(Request request, Request.Options options) throws IOException {
try { try {
@@ -79,8 +60,8 @@ public class LoadBalancerFeignClient implements Client {
this.delegate, request, uriWithoutHost); this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName); IClientConfig requestConfig = getClientConfig(options, clientName);
return lbClient(clientName) return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse(); requestConfig).toResponse();
} }
catch (ClientException e) { catch (ClientException e) {
IOException io = findIOException(e); IOException io = findIOException(e);
@@ -95,8 +76,7 @@ public class LoadBalancerFeignClient implements Client {
IClientConfig requestConfig; IClientConfig requestConfig;
if (options == DEFAULT_OPTIONS) { if (options == DEFAULT_OPTIONS) {
requestConfig = this.clientFactory.getClientConfig(clientName); requestConfig = this.clientFactory.getClientConfig(clientName);
} } else {
else {
requestConfig = new FeignOptionsClientConfig(options); requestConfig = new FeignOptionsClientConfig(options);
} }
return requestConfig; return requestConfig;
@@ -116,13 +96,28 @@ public class LoadBalancerFeignClient implements Client {
return this.delegate; return this.delegate;
} }
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());
}
private FeignLoadBalancer lbClient(String clientName) { private FeignLoadBalancer lbClient(String clientName) {
return this.lbClientFactory.create(clientName); return this.lbClientFactory.create(clientName);
} }
static class FeignOptionsClientConfig extends DefaultClientConfigImpl { static class FeignOptionsClientConfig extends DefaultClientConfigImpl {
FeignOptionsClientConfig(Request.Options options) { public FeignOptionsClientConfig(Request.Options options) {
setProperty(CommonClientConfigKey.ConnectTimeout, setProperty(CommonClientConfigKey.ConnectTimeout,
options.connectTimeoutMillis()); options.connectTimeoutMillis());
setProperty(CommonClientConfigKey.ReadTimeout, options.readTimeoutMillis()); setProperty(CommonClientConfigKey.ReadTimeout, options.readTimeoutMillis());
@@ -139,5 +134,4 @@ public class LoadBalancerFeignClient implements Client {
} }
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -12,25 +12,24 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package org.springframework.cloud.openfeign.ribbon; package org.springframework.cloud.openfeign.ribbon;
import java.util.concurrent.TimeUnit;
import javax.annotation.PreDestroy;
import feign.Client; import feign.Client;
import feign.okhttp.OkHttpClient; import feign.okhttp.OkHttpClient;
import okhttp3.ConnectionPool; import okhttp3.ConnectionPool;
import java.util.concurrent.TimeUnit;
import javax.annotation.PreDestroy;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory; import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory; import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties; import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@@ -39,28 +38,18 @@ import org.springframework.context.annotation.Configuration;
*/ */
@Configuration @Configuration
@ConditionalOnClass(OkHttpClient.class) @ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled") @ConditionalOnProperty(value = "feign.okhttp.enabled")
class OkHttpFeignLoadBalancedConfiguration { 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);
}
@Configuration @Configuration
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class) @ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
protected static class OkHttpFeignConfiguration { protected static class OkHttpFeignConfiguration {
private okhttp3.OkHttpClient okHttpClient; private okhttp3.OkHttpClient okHttpClient;
@Bean @Bean
@ConditionalOnMissingBean(ConnectionPool.class) @ConditionalOnMissingBean(ConnectionPool.class)
public ConnectionPool httpClientConnectionPool( public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,
FeignHttpClientProperties httpClientProperties, OkHttpClientConnectionPoolFactory connectionPoolFactory) {
OkHttpClientConnectionPoolFactory connectionPoolFactory) {
Integer maxTotalConnections = httpClientProperties.getMaxConnections(); Integer maxTotalConnections = httpClientProperties.getMaxConnections();
Long timeToLive = httpClientProperties.getTimeToLive(); Long timeToLive = httpClientProperties.getTimeToLive();
TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit(); TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();
@@ -69,26 +58,30 @@ class OkHttpFeignLoadBalancedConfiguration {
@Bean @Bean
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory, public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,
ConnectionPool connectionPool, ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) {
FeignHttpClientProperties httpClientProperties) {
Boolean followRedirects = httpClientProperties.isFollowRedirects(); Boolean followRedirects = httpClientProperties.isFollowRedirects();
Integer connectTimeout = httpClientProperties.getConnectionTimeout(); Integer connectTimeout = httpClientProperties.getConnectionTimeout();
this.okHttpClient = httpClientFactory this.okHttpClient = httpClientFactory.createBuilder(httpClientProperties.isDisableSslValidation()).
.createBuilder(httpClientProperties.isDisableSslValidation()) connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS) followRedirects(followRedirects).
.followRedirects(followRedirects).connectionPool(connectionPool) connectionPool(connectionPool).build();
.build();
return this.okHttpClient; return this.okHttpClient;
} }
@PreDestroy @PreDestroy
public void destroy() { public void destroy() {
if (this.okHttpClient != null) { if(okHttpClient != null) {
this.okHttpClient.dispatcher().executorService().shutdown(); okHttpClient.dispatcher().executorService().shutdown();
this.okHttpClient.connectionPool().evictAll(); okHttpClient.connectionPool().evictAll();
} }
} }
} }
@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,32 +1,28 @@
/* /*
* Copyright 2013-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * * Copyright 2013-2016 the original author or authors.
* you may not use this file except in compliance with the License. * *
* You may obtain a copy of the License at * * 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.
* *
* 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; 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.Request;
import feign.Response; import feign.Response;
import java.io.IOException;
import java.net.URI;
import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRecoveryCallback; import org.springframework.cloud.client.loadbalancer.LoadBalancedRecoveryCallback;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryContext;
@@ -44,82 +40,71 @@ import org.springframework.retry.backoff.NoBackOffPolicy;
import org.springframework.retry.policy.NeverRetryPolicy; import org.springframework.retry.policy.NeverRetryPolicy;
import org.springframework.retry.support.RetryTemplate; import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.StreamUtils; import org.springframework.util.StreamUtils;
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;
/** /**
* A {@link FeignLoadBalancer} that leverages Spring Retry to retry failed requests. * A {@link FeignLoadBalancer} that leverages Spring Retry to retry failed requests.
*
* @author Ryan Baxter * @author Ryan Baxter
* @author Gang Li * @author Gang Li
*/ */
public class RetryableFeignLoadBalancer extends FeignLoadBalancer public class RetryableFeignLoadBalancer extends FeignLoadBalancer implements ServiceInstanceChooser {
implements ServiceInstanceChooser {
private final LoadBalancedRetryFactory loadBalancedRetryFactory; private final LoadBalancedRetryFactory loadBalancedRetryFactory;
public RetryableFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig, public RetryableFeignLoadBalancer(ILoadBalancer lb, IClientConfig clientConfig, ServerIntrospector serverIntrospector,
ServerIntrospector serverIntrospector, LoadBalancedRetryFactory loadBalancedRetryFactory) {
LoadBalancedRetryFactory loadBalancedRetryFactory) {
super(lb, clientConfig, serverIntrospector); super(lb, clientConfig, serverIntrospector);
this.loadBalancedRetryFactory = loadBalancedRetryFactory; this.loadBalancedRetryFactory = loadBalancedRetryFactory;
this.setRetryHandler(new DefaultLoadBalancerRetryHandler(clientConfig)); this.setRetryHandler(new DefaultLoadBalancerRetryHandler(clientConfig));
} }
@Override @Override
public RibbonResponse execute(final RibbonRequest request, public RibbonResponse execute(final RibbonRequest request, IClientConfig configOverride)
IClientConfig configOverride) throws IOException { throws IOException {
final Request.Options options; final Request.Options options;
if (configOverride != null) { if (configOverride != null) {
RibbonProperties ribbon = RibbonProperties.from(configOverride); RibbonProperties ribbon = RibbonProperties.from(configOverride);
options = new Request.Options(ribbon.connectTimeout(this.connectTimeout), options = new Request.Options(
ribbon.connectTimeout(this.connectTimeout),
ribbon.readTimeout(this.readTimeout)); ribbon.readTimeout(this.readTimeout));
} }
else { else {
options = new Request.Options(this.connectTimeout, this.readTimeout); options = new Request.Options(this.connectTimeout, this.readTimeout);
} }
final LoadBalancedRetryPolicy retryPolicy = this.loadBalancedRetryFactory final LoadBalancedRetryPolicy retryPolicy = loadBalancedRetryFactory.createRetryPolicy(this.getClientName(), this);
.createRetryPolicy(this.getClientName(), this);
RetryTemplate retryTemplate = new RetryTemplate(); RetryTemplate retryTemplate = new RetryTemplate();
BackOffPolicy backOffPolicy = this.loadBalancedRetryFactory BackOffPolicy backOffPolicy = loadBalancedRetryFactory.createBackOffPolicy(this.getClientName());
.createBackOffPolicy(this.getClientName()); retryTemplate.setBackOffPolicy(backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
retryTemplate.setBackOffPolicy( RetryListener[] retryListeners = this.loadBalancedRetryFactory.createRetryListeners(this.getClientName());
backOffPolicy == null ? new NoBackOffPolicy() : backOffPolicy);
RetryListener[] retryListeners = this.loadBalancedRetryFactory
.createRetryListeners(this.getClientName());
if (retryListeners != null && retryListeners.length != 0) { if (retryListeners != null && retryListeners.length != 0) {
retryTemplate.setListeners(retryListeners); retryTemplate.setListeners(retryListeners);
} }
retryTemplate.setRetryPolicy(retryPolicy == null ? new NeverRetryPolicy() retryTemplate.setRetryPolicy(retryPolicy == null ? new NeverRetryPolicy()
: new FeignRetryPolicy(request.toHttpRequest(), retryPolicy, this, : new FeignRetryPolicy(request.toHttpRequest(), retryPolicy, this, this.getClientName()));
this.getClientName()));
return retryTemplate.execute(new RetryCallback<RibbonResponse, IOException>() { return retryTemplate.execute(new RetryCallback<RibbonResponse, IOException>() {
@Override @Override
public RibbonResponse doWithRetry(RetryContext retryContext) public RibbonResponse doWithRetry(RetryContext retryContext) throws IOException {
throws IOException {
Request feignRequest = null; Request feignRequest = null;
// on retries the policy will choose the server and set it in the context //on retries the policy will choose the server and set it in the context
// extract the server and update the request being made //extract the server and update the request being made
if (retryContext instanceof LoadBalancedRetryContext) { if (retryContext instanceof LoadBalancedRetryContext) {
ServiceInstance service = ((LoadBalancedRetryContext) retryContext) ServiceInstance service = ((LoadBalancedRetryContext) retryContext).getServiceInstance();
.getServiceInstance();
if (service != null) { if (service != null) {
feignRequest = ((RibbonRequest) request feignRequest = ((RibbonRequest) request.replaceUri(reconstructURIWithServer(new Server(service.getHost(), service.getPort()), request.getUri()))).toRequest();
.replaceUri(reconstructURIWithServer(
new Server(service.getHost(), service.getPort()),
request.getUri()))).toRequest();
} }
} }
if (feignRequest == null) { if (feignRequest == null) {
feignRequest = request.toRequest(); feignRequest = request.toRequest();
} }
Response response = request.client().execute(feignRequest, options); Response response = request.client().execute(feignRequest, options);
if (retryPolicy != null if (retryPolicy != null && retryPolicy.retryableStatusCode(response.status())) {
&& retryPolicy.retryableStatusCode(response.status())) { byte[] byteArray = response.body() == null ? new byte[]{} : StreamUtils.copyToByteArray(response.body().asInputStream());
byte[] byteArray = response.body() == null ? new byte[] {}
: StreamUtils
.copyToByteArray(response.body().asInputStream());
response.close(); response.close();
throw new RibbonResponseStatusCodeException( throw new RibbonResponseStatusCodeException(RetryableFeignLoadBalancer.this.clientName, response,
RetryableFeignLoadBalancer.this.clientName, response,
byteArray, request.getUri()); byteArray, request.getUri());
} }
return new RibbonResponse(request.getUri(), response); return new RibbonResponse(request.getUri(), response);
@@ -135,8 +120,7 @@ public class RetryableFeignLoadBalancer extends FeignLoadBalancer
@Override @Override
public RequestSpecificRetryHandler getRequestSpecificRetryHandler( public RequestSpecificRetryHandler getRequestSpecificRetryHandler(
FeignLoadBalancer.RibbonRequest request, IClientConfig requestConfig) { FeignLoadBalancer.RibbonRequest request, IClientConfig requestConfig) {
return new RequestSpecificRetryHandler(false, false, this.getRetryHandler(), return new RequestSpecificRetryHandler(false, false, this.getRetryHandler(), requestConfig);
requestConfig);
} }
@Override @Override
@@ -144,5 +128,4 @@ public class RetryableFeignLoadBalancer extends FeignLoadBalancer
return new RibbonLoadBalancerClient.RibbonServer(serviceId, return new RibbonLoadBalancerClient.RibbonServer(serviceId,
this.getLoadBalancer().chooseServer(serviceId)); this.getLoadBalancer().chooseServer(serviceId));
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,37 +13,33 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.cloud.openfeign.ribbon; package org.springframework.cloud.openfeign.ribbon;
import java.io.ByteArrayInputStream;
import java.net.URI;
import feign.Response; import feign.Response;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException; import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
import org.springframework.util.StreamUtils;
/** /**
* A {@link RetryableStatusCodeException} for {@link Response}s. * A {@link RetryableStatusCodeException} for {@link Response}s
*
* @author Ryan Baxter * @author Ryan Baxter
*/ */
public class RibbonResponseStatusCodeException extends RetryableStatusCodeException { public class RibbonResponseStatusCodeException extends RetryableStatusCodeException {
private Response response;
private final Response response; public RibbonResponseStatusCodeException(String serviceId, Response response, byte[] body, URI uri) {
public RibbonResponseStatusCodeException(String serviceId, Response response,
byte[] body, URI uri) {
super(serviceId, response.status(), response, uri); super(serviceId, response.status(), response, uri);
this.response = Response.builder() this.response = Response.builder().body(new ByteArrayInputStream(body), body.length)
.body(new ByteArrayInputStream(body), body.length)
.headers(response.headers()).reason(response.reason()) .headers(response.headers()).reason(response.reason())
.status(response.status()).request(response.request()).build(); .status(response.status()).request(response.request()).build();
} }
@Override @Override
public Response getResponse() { public Response getResponse() {
return this.response; return response;
} }
} }
@@ -1,19 +1,3 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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; package org.springframework.cloud.openfeign.support;
import com.netflix.hystrix.HystrixCommand; import com.netflix.hystrix.HystrixCommand;
@@ -22,11 +6,8 @@ import com.netflix.hystrix.HystrixThreadPoolKey;
/** /**
* Convenience class for implementing feign fallbacks that return {@link HystrixCommand}. * Convenience class for implementing feign fallbacks that return {@link HystrixCommand}.
* Also useful for return types of {@link rx.Observable} and * Also useful for return types of {@link rx.Observable} and {@link java.util.concurrent.Future}.
* {@link java.util.concurrent.Future}. For those return types, just call * For those return types, just call {@link FallbackCommand#observe()} or {@link FallbackCommand#queue()} respectively.
* {@link FallbackCommand#observe()} or {@link FallbackCommand#queue()} respectively.
*
* @param <T> result type
* @author Spencer Gibb * @author Spencer Gibb
*/ */
public class FallbackCommand<T> extends HystrixCommand<T> { public class FallbackCommand<T> extends HystrixCommand<T> {
@@ -47,21 +28,17 @@ public class FallbackCommand<T> extends HystrixCommand<T> {
this.result = result; this.result = result;
} }
public FallbackCommand(T result, HystrixCommandGroupKey group, public FallbackCommand(T result, HystrixCommandGroupKey group, int executionIsolationThreadTimeoutInMilliseconds) {
int executionIsolationThreadTimeoutInMilliseconds) {
super(group, executionIsolationThreadTimeoutInMilliseconds); super(group, executionIsolationThreadTimeoutInMilliseconds);
this.result = result; this.result = result;
} }
public FallbackCommand(T result, HystrixCommandGroupKey group, public FallbackCommand(T result, HystrixCommandGroupKey group, HystrixThreadPoolKey threadPool) {
HystrixThreadPoolKey threadPool) {
super(group, threadPool); super(group, threadPool);
this.result = result; this.result = result;
} }
public FallbackCommand(T result, HystrixCommandGroupKey group, public FallbackCommand(T result, HystrixCommandGroupKey group, HystrixThreadPoolKey threadPool, int executionIsolationThreadTimeoutInMilliseconds) {
HystrixThreadPoolKey threadPool,
int executionIsolationThreadTimeoutInMilliseconds) {
super(group, threadPool, executionIsolationThreadTimeoutInMilliseconds); super(group, threadPool, executionIsolationThreadTimeoutInMilliseconds);
this.result = result; this.result = result;
} }
@@ -75,5 +52,4 @@ public class FallbackCommand<T> extends HystrixCommand<T> {
protected T run() throws Exception { protected T run() throws Exception {
return this.result; return this.result;
} }
} }
@@ -1,23 +1,24 @@
/* /*
* Copyright 2013-2019 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * * Copyright 2013-2016 the original author or authors.
* you may not use this file except in compliance with the License. * *
* You may obtain a copy of the License at * * 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.
* *
* 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; package org.springframework.cloud.openfeign.support;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
/** /**
@@ -25,65 +26,26 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
*/ */
@ConfigurationProperties(prefix = "feign.httpclient") @ConfigurationProperties(prefix = "feign.httpclient")
public class FeignHttpClientProperties { public class FeignHttpClientProperties {
/**
* Default value for disabling SSL validation.
*/
public static final boolean DEFAULT_DISABLE_SSL_VALIDATION = false; public static final boolean DEFAULT_DISABLE_SSL_VALIDATION = false;
/**
* Default value for max number od connections.
*/
public static final int DEFAULT_MAX_CONNECTIONS = 200; public static final int DEFAULT_MAX_CONNECTIONS = 200;
/**
* Default value for max number od connections per route.
*/
public static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 50; public static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 50;
/**
* Default value for time to live.
*/
public static final long DEFAULT_TIME_TO_LIVE = 900L; public static final long DEFAULT_TIME_TO_LIVE = 900L;
/**
* Default time to live unit.
*/
public static final TimeUnit DEFAULT_TIME_TO_LIVE_UNIT = TimeUnit.SECONDS; public static final TimeUnit DEFAULT_TIME_TO_LIVE_UNIT = TimeUnit.SECONDS;
/**
* Default value for following redirects.
*/
public static final boolean DEFAULT_FOLLOW_REDIRECTS = true; public static final boolean DEFAULT_FOLLOW_REDIRECTS = true;
/**
* Default value for connection timeout.
*/
public static final int DEFAULT_CONNECTION_TIMEOUT = 2000; public static final int DEFAULT_CONNECTION_TIMEOUT = 2000;
/**
* Default value for connection timer repeat.
*/
public static final int DEFAULT_CONNECTION_TIMER_REPEAT = 3000; public static final int DEFAULT_CONNECTION_TIMER_REPEAT = 3000;
private boolean disableSslValidation = DEFAULT_DISABLE_SSL_VALIDATION; private boolean disableSslValidation = DEFAULT_DISABLE_SSL_VALIDATION;
private int maxConnections = DEFAULT_MAX_CONNECTIONS; private int maxConnections = DEFAULT_MAX_CONNECTIONS;
private int maxConnectionsPerRoute = DEFAULT_MAX_CONNECTIONS_PER_ROUTE; private int maxConnectionsPerRoute = DEFAULT_MAX_CONNECTIONS_PER_ROUTE;
private long timeToLive = DEFAULT_TIME_TO_LIVE; private long timeToLive = DEFAULT_TIME_TO_LIVE;
private TimeUnit timeToLiveUnit = DEFAULT_TIME_TO_LIVE_UNIT; private TimeUnit timeToLiveUnit = DEFAULT_TIME_TO_LIVE_UNIT;
private boolean followRedirects = DEFAULT_FOLLOW_REDIRECTS; private boolean followRedirects = DEFAULT_FOLLOW_REDIRECTS;
private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT; private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
private int connectionTimerRepeat = DEFAULT_CONNECTION_TIMER_REPEAT; private int connectionTimerRepeat = DEFAULT_CONNECTION_TIMER_REPEAT;
public int getConnectionTimerRepeat() { public int getConnectionTimerRepeat() {
return this.connectionTimerRepeat; return connectionTimerRepeat;
} }
public void setConnectionTimerRepeat(int connectionTimerRepeat) { public void setConnectionTimerRepeat(int connectionTimerRepeat) {
@@ -91,7 +53,7 @@ public class FeignHttpClientProperties {
} }
public boolean isDisableSslValidation() { public boolean isDisableSslValidation() {
return this.disableSslValidation; return disableSslValidation;
} }
public void setDisableSslValidation(boolean disableSslValidation) { public void setDisableSslValidation(boolean disableSslValidation) {
@@ -99,7 +61,7 @@ public class FeignHttpClientProperties {
} }
public int getMaxConnections() { public int getMaxConnections() {
return this.maxConnections; return maxConnections;
} }
public void setMaxConnections(int maxConnections) { public void setMaxConnections(int maxConnections) {
@@ -107,7 +69,7 @@ public class FeignHttpClientProperties {
} }
public int getMaxConnectionsPerRoute() { public int getMaxConnectionsPerRoute() {
return this.maxConnectionsPerRoute; return maxConnectionsPerRoute;
} }
public void setMaxConnectionsPerRoute(int maxConnectionsPerRoute) { public void setMaxConnectionsPerRoute(int maxConnectionsPerRoute) {
@@ -115,7 +77,7 @@ public class FeignHttpClientProperties {
} }
public long getTimeToLive() { public long getTimeToLive() {
return this.timeToLive; return timeToLive;
} }
public void setTimeToLive(long timeToLive) { public void setTimeToLive(long timeToLive) {
@@ -123,7 +85,7 @@ public class FeignHttpClientProperties {
} }
public TimeUnit getTimeToLiveUnit() { public TimeUnit getTimeToLiveUnit() {
return this.timeToLiveUnit; return timeToLiveUnit;
} }
public void setTimeToLiveUnit(TimeUnit timeToLiveUnit) { public void setTimeToLiveUnit(TimeUnit timeToLiveUnit) {
@@ -131,7 +93,7 @@ public class FeignHttpClientProperties {
} }
public boolean isFollowRedirects() { public boolean isFollowRedirects() {
return this.followRedirects; return followRedirects;
} }
public void setFollowRedirects(boolean followRedirects) { public void setFollowRedirects(boolean followRedirects) {
@@ -139,11 +101,10 @@ public class FeignHttpClientProperties {
} }
public int getConnectionTimeout() { public int getConnectionTimeout() {
return this.connectionTimeout; return connectionTimeout;
} }
public void setConnectionTimeout(int connectionTimeout) { public void setConnectionTimeout(int connectionTimeout) {
this.connectionTimeout = connectionTimeout; this.connectionTimeout = connectionTimeout;
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -24,17 +24,10 @@ import java.util.Map;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import static java.util.Optional.ofNullable;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
* @author Olga Maciaszek-Sharma
*/ */
public final class FeignUtils { public class FeignUtils {
private FeignUtils() {
throw new IllegalStateException("Can't instantiate a utility class");
}
static HttpHeaders getHttpHeaders(Map<String, Collection<String>> headers) { static HttpHeaders getHttpHeaders(Map<String, Collection<String>> headers) {
HttpHeaders httpHeaders = new HttpHeaders(); HttpHeaders httpHeaders = new HttpHeaders();
@@ -54,12 +47,4 @@ public final class FeignUtils {
return headers; return headers;
} }
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;
}
} }
@@ -1,171 +0,0 @@
/*
* Copyright 2013-2018 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 java.util.Iterator;
import java.util.List;
import java.util.function.Function;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
/**
* This jackson module provides support to deserialize spring {@link Page} objects.
*
* @author Pascal Büttiker
*/
public class PageJacksonModule extends Module {
@Override
public String getModuleName() {
return "PageJacksonModule";
}
@Override
public Version version() {
return new Version(0, 1, 0, "", null, null);
}
@Override
public void setupModule(SetupContext context) {
context.setMixInAnnotations(Page.class, PageMixIn.class);
}
@JsonDeserialize(as = SimplePageImpl.class)
private interface PageMixIn {
}
static class SimplePageImpl<T> implements Page<T> {
private final Page<T> delegate;
SimplePageImpl(@JsonProperty("content") List<T> content,
@JsonProperty("page") int number, @JsonProperty("size") int size,
@JsonProperty("totalElements") long totalElements) {
delegate = new PageImpl<>(content, PageRequest.of(number, size),
totalElements);
}
@JsonProperty
@Override
public int getTotalPages() {
return delegate.getTotalPages();
}
@JsonProperty
@Override
public long getTotalElements() {
return delegate.getTotalElements();
}
@JsonProperty("page")
@Override
public int getNumber() {
return delegate.getNumber();
}
@JsonProperty
@Override
public int getSize() {
return delegate.getSize();
}
@JsonProperty
@Override
public int getNumberOfElements() {
return delegate.getNumberOfElements();
}
@JsonProperty
@Override
public List<T> getContent() {
return delegate.getContent();
}
@JsonProperty
@Override
public boolean hasContent() {
return delegate.hasContent();
}
@JsonIgnore
@Override
public Sort getSort() {
return delegate.getSort();
}
@JsonProperty
@Override
public boolean isFirst() {
return delegate.isFirst();
}
@JsonProperty
@Override
public boolean isLast() {
return delegate.isLast();
}
@JsonIgnore
@Override
public boolean hasNext() {
return delegate.hasNext();
}
@JsonIgnore
@Override
public boolean hasPrevious() {
return delegate.hasPrevious();
}
@JsonIgnore
@Override
public Pageable nextPageable() {
return delegate.nextPageable();
}
@JsonIgnore
@Override
public Pageable previousPageable() {
return delegate.previousPageable();
}
@JsonIgnore
@Override
public <S> Page<S> map(Function<? super T, ? extends S> converter) {
return delegate.map(converter);
}
@JsonIgnore
@Override
public Iterator<T> iterator() {
return delegate.iterator();
}
}
}
@@ -1,122 +0,0 @@
/*
* Copyright 2013-2018 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 java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
/**
* Provides support for encoding spring Pageable via composition.
*
* @author Pascal Büttiker
*/
public class PageableSpringEncoder implements Encoder {
private final Encoder delegate;
/**
* Page index parameter name.
*/
private String pageParameter = "page";
/**
* Page size parameter name.
*/
private String sizeParameter = "size";
/**
* Sort parameter name.
*/
private String sortParameter = "sort";
/**
* Creates a new PageableSpringEncoder with the given delegate for fallback. If no
* delegate is provided and this encoder cant handle the request, an EncodeException
* is thrown.
* @param delegate The optional delegate.
*/
public PageableSpringEncoder(Encoder delegate) {
this.delegate = delegate;
}
@Override
public void encode(Object object, Type bodyType, RequestTemplate template)
throws EncodeException {
if (supports(object)) {
if (object instanceof Pageable) {
Pageable pageable = (Pageable) object;
if (pageable.isPaged()) {
template.query(pageParameter, pageable.getPageNumber() + "");
template.query(sizeParameter, pageable.getPageSize() + "");
}
if (pageable.getSort() != null) {
applySort(template, pageable.getSort());
}
}
else if (object instanceof Sort) {
Sort sort = (Sort) object;
applySort(template, sort);
}
}
else {
if (delegate != null) {
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!");
}
}
}
private void applySort(RequestTemplate template, Sort sort) {
Collection<String> existingSorts = template.queries().get("sort");
List<String> sortQueries = existingSorts != null ? new ArrayList<>(existingSorts)
: new ArrayList<>();
if (!sortParameter.equals("sort")) {
existingSorts = template.queries().get(sortParameter);
if (existingSorts != null) {
sortQueries.addAll(existingSorts);
}
}
for (Sort.Order order : sort) {
sortQueries.add(order.getProperty() + "," + order.getDirection());
}
if (!sortQueries.isEmpty()) {
template.query(sortParameter, sortQueries);
}
}
protected boolean supports(Object object) {
return object instanceof Pageable || object instanceof Sort;
}
}
@@ -1,19 +1,3 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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; package org.springframework.cloud.openfeign.support;
import java.io.IOException; import java.io.IOException;
@@ -21,21 +5,20 @@ import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.LinkedList; import java.util.LinkedList;
import feign.FeignException;
import feign.Response;
import feign.codec.Decoder;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import feign.FeignException;
import feign.Response;
import feign.codec.Decoder;
/** /**
* Decoder adds compatibility for Spring MVC's ResponseEntity to any other decoder via * Decoder adds compatibility for Spring MVC's ResponseEntity to any other decoder via
* composition. * composition.
* * @author chadjaros
* @author chad jaros
*/ */
public class ResponseEntityDecoder implements Decoder { public class ResponseEntityDecoder implements Decoder {
@@ -46,12 +29,12 @@ public class ResponseEntityDecoder implements Decoder {
} }
@Override @Override
public Object decode(final Response response, Type type) public Object decode(final Response response, Type type) throws IOException,
throws IOException, FeignException { FeignException {
if (isParameterizeHttpEntity(type)) { if (isParameterizeHttpEntity(type)) {
type = ((ParameterizedType) type).getActualTypeArguments()[0]; type = ((ParameterizedType) type).getActualTypeArguments()[0];
Object decodedObject = this.decoder.decode(response, type); Object decodedObject = decoder.decode(response, type);
return createResponse(decodedObject, response); return createResponse(decodedObject, response);
} }
@@ -59,7 +42,7 @@ public class ResponseEntityDecoder implements Decoder {
return createResponse(null, response); return createResponse(null, response);
} }
else { else {
return this.decoder.decode(response, type); return decoder.decode(response, type);
} }
} }
@@ -86,8 +69,7 @@ public class ResponseEntityDecoder implements Decoder {
headers.put(key, new LinkedList<>(response.headers().get(key))); headers.put(key, new LinkedList<>(response.headers().get(key)));
} }
return new ResponseEntity<>((T) instance, headers, return new ResponseEntity<>((T) instance, headers, HttpStatus.valueOf(response
HttpStatus.valueOf(response.status())); .status()));
} }
}
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -12,21 +12,19 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package org.springframework.cloud.openfeign.support; package org.springframework.cloud.openfeign.support;
import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHeaders;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.lang.reflect.WildcardType; import java.lang.reflect.WildcardType;
import feign.FeignException;
import feign.Response;
import feign.codec.DecodeException;
import feign.codec.Decoder;
import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
@@ -34,7 +32,10 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.HttpMessageConverterExtractor; import org.springframework.web.client.HttpMessageConverterExtractor;
import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHeaders; import feign.FeignException;
import feign.Response;
import feign.codec.DecodeException;
import feign.codec.Decoder;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
@@ -58,11 +59,11 @@ public class SpringDecoder implements Decoder {
return extractor.extractData(new FeignResponseAdapter(response)); return extractor.extractData(new FeignResponseAdapter(response));
} }
throw new DecodeException(response.status(), throw new DecodeException(
"type is not an instance of Class or ParameterizedType: " + type); "type is not an instance of Class or ParameterizedType: " + type);
} }
private final class FeignResponseAdapter implements ClientHttpResponse { private class FeignResponseAdapter implements ClientHttpResponse {
private final Response response; private final Response response;
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -12,6 +12,7 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package org.springframework.cloud.openfeign.support; package org.springframework.cloud.openfeign.support;
@@ -25,14 +26,8 @@ import java.nio.charset.StandardCharsets;
import java.util.Collection; import java.util.Collection;
import java.util.Objects; import java.util.Objects;
import feign.Request;
import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
@@ -43,12 +38,17 @@ import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter; import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import static org.springframework.cloud.openfeign.support.FeignUtils.getHeaders; import static org.springframework.cloud.openfeign.support.FeignUtils.getHeaders;
import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHeaders; import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHeaders;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
* @author Scien Jus * @author ScienJus
*/ */
public class SpringEncoder implements Encoder { public class SpringEncoder implements Encoder {
@@ -78,13 +78,12 @@ public class SpringEncoder implements Encoder {
if (bodyType != null && bodyType.equals(MultipartFile.class)) { if (bodyType != null && bodyType.equals(MultipartFile.class)) {
if (Objects.equals(requestContentType, MediaType.MULTIPART_FORM_DATA)) { if (Objects.equals(requestContentType, MediaType.MULTIPART_FORM_DATA)) {
this.springFormEncoder.encode(requestBody, bodyType, request); springFormEncoder.encode(requestBody, bodyType, request);
return; return;
} } else {
else { String message = "Content-Type \"" + MediaType.MULTIPART_FORM_DATA +
String message = "Content-Type \"" + MediaType.MULTIPART_FORM_DATA "\" not set for request body of type " +
+ "\" not set for request body of type " requestBody.getClass().getSimpleName();
+ requestBody.getClass().getSimpleName();
throw new EncodeException(message); throw new EncodeException(message);
} }
} }
@@ -95,8 +94,8 @@ public class SpringEncoder implements Encoder {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
if (requestContentType != null) { if (requestContentType != null) {
log.debug("Writing [" + requestBody + "] as \"" log.debug("Writing [" + requestBody + "] as \""
+ requestContentType + "\" using [" + messageConverter + requestContentType + "\" using ["
+ "]"); + messageConverter + "]");
} }
else { else {
log.debug("Writing [" + requestBody + "] using [" log.debug("Writing [" + requestBody + "] using ["
@@ -124,17 +123,13 @@ public class SpringEncoder implements Encoder {
Charset charset; Charset charset;
if (messageConverter instanceof ByteArrayHttpMessageConverter) { if (messageConverter instanceof ByteArrayHttpMessageConverter) {
charset = null; charset = null;
} } else if (messageConverter instanceof ProtobufHttpMessageConverter &&
else if (messageConverter instanceof ProtobufHttpMessageConverter ProtobufHttpMessageConverter.PROTOBUF.isCompatibleWith(outputMessage.getHeaders().getContentType())) {
&& ProtobufHttpMessageConverter.PROTOBUF.isCompatibleWith(
outputMessage.getHeaders().getContentType())) {
charset = null; charset = null;
} } else {
else {
charset = StandardCharsets.UTF_8; charset = StandardCharsets.UTF_8;
} }
request.body(Request.Body.encoded( request.body(outputMessage.getOutputStream().toByteArray(), charset);
outputMessage.getOutputStream().toByteArray(), charset));
return; return;
} }
} }
@@ -147,14 +142,14 @@ public class SpringEncoder implements Encoder {
} }
} }
private final class FeignOutputMessage implements HttpOutputMessage { private class FeignOutputMessage implements HttpOutputMessage {
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
private final HttpHeaders httpHeaders; private final HttpHeaders httpHeaders;
private FeignOutputMessage(RequestTemplate request) { private FeignOutputMessage(RequestTemplate request) {
this.httpHeaders = getHttpHeaders(request.headers()); httpHeaders = getHttpHeaders(request.headers());
} }
@Override @Override
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@ package org.springframework.cloud.openfeign.support;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -29,26 +28,16 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import feign.Contract;
import feign.Feign;
import feign.MethodMetadata;
import feign.Param;
import feign.Request;
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor; import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
import org.springframework.cloud.openfeign.annotation.PathVariableParameterProcessor; import org.springframework.cloud.openfeign.annotation.PathVariableParameterProcessor;
import org.springframework.cloud.openfeign.annotation.QueryMapParameterProcessor;
import org.springframework.cloud.openfeign.annotation.RequestHeaderParameterProcessor; import org.springframework.cloud.openfeign.annotation.RequestHeaderParameterProcessor;
import org.springframework.cloud.openfeign.annotation.RequestParamParameterProcessor; import org.springframework.cloud.openfeign.annotation.RequestParamParameterProcessor;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.ResourceLoaderAware; import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
@@ -59,15 +48,16 @@ import org.springframework.web.bind.annotation.RequestMethod;
import static feign.Util.checkState; import static feign.Util.checkState;
import static feign.Util.emptyToNull; import static feign.Util.emptyToNull;
import static org.springframework.cloud.openfeign.support.FeignUtils.addTemplateParameter;
import static org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotation; import static org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotation;
import feign.Contract;
import feign.Feign;
import feign.MethodMetadata;
import feign.Param;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
* @author Abhijit Sarkar * @author Abhijit Sarkar
* @author Halvdan Hoem Grelland
* @author Aram Peres
* @author Olga Maciaszek-Sharma
*/ */
public class SpringMvcContract extends Contract.BaseContract public class SpringMvcContract extends Contract.BaseContract
implements ResourceLoaderAware { implements ResourceLoaderAware {
@@ -76,22 +66,13 @@ public class SpringMvcContract extends Contract.BaseContract
private static final String CONTENT_TYPE = "Content-Type"; private static final String CONTENT_TYPE = "Content-Type";
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 ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer(); private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
private final Map<Class<? extends Annotation>, AnnotatedParameterProcessor> annotatedArgumentProcessors; private final Map<Class<? extends Annotation>, AnnotatedParameterProcessor> annotatedArgumentProcessors;
private final Map<String, Method> processedMethods = new HashMap<>(); private final Map<String, Method> processedMethods = new HashMap<>();
private final ConversionService conversionService; private final ConversionService conversionService;
private final Param.Expander expander;
private final ConvertingExpanderFactory convertingExpanderFactory;
private ResourceLoader resourceLoader = new DefaultResourceLoader(); private ResourceLoader resourceLoader = new DefaultResourceLoader();
public SpringMvcContract() { public SpringMvcContract() {
@@ -119,43 +100,7 @@ public class SpringMvcContract extends Contract.BaseContract
} }
this.annotatedArgumentProcessors = toAnnotatedArgumentProcessorMap(processors); this.annotatedArgumentProcessors = toAnnotatedArgumentProcessorMap(processors);
this.conversionService = conversionService; this.conversionService = conversionService;
this.convertingExpanderFactory = new ConvertingExpanderFactory(conversionService); this.expander = new ConvertingExpander(conversionService);
}
private static TypeDescriptor createTypeDescriptor(Method method, int paramIndex) {
Parameter parameter = method.getParameters()[paramIndex];
MethodParameter methodParameter = MethodParameter.forParameter(parameter);
TypeDescriptor typeDescriptor = new TypeDescriptor(methodParameter);
// 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);
checkState(elementTypeDescriptor != null,
"Could not resolve element type of Iterable type %s. Not declared?",
typeDescriptor);
typeDescriptor = elementTypeDescriptor;
}
return 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 (type.resolve() == null) {
return null;
}
return new TypeDescriptor(type, null, typeDescriptor.getAnnotations());
}
return elementTypeDescriptor;
} }
@Override @Override
@@ -176,7 +121,7 @@ public class SpringMvcContract extends Contract.BaseContract
if (!pathValue.startsWith("/")) { if (!pathValue.startsWith("/")) {
pathValue = "/" + pathValue; pathValue = "/" + pathValue;
} }
data.template().uri(pathValue); data.template().insert(0, pathValue);
} }
} }
} }
@@ -222,7 +167,7 @@ public class SpringMvcContract extends Contract.BaseContract
methods = new RequestMethod[] { RequestMethod.GET }; methods = new RequestMethod[] { RequestMethod.GET };
} }
checkOne(method, methods, "method"); checkOne(method, methods, "method");
data.template().method(Request.HttpMethod.valueOf(methods[0].name())); data.template().method(methods[0].name());
// path // path
checkAtMostOne(method, methodMapping.value(), "value"); checkAtMostOne(method, methodMapping.value(), "value");
@@ -231,10 +176,11 @@ public class SpringMvcContract extends Contract.BaseContract
if (pathValue != null) { if (pathValue != null) {
pathValue = resolve(pathValue); pathValue = resolve(pathValue);
// Append path from @RequestMapping if value is present on method // Append path from @RequestMapping if value is present on method
if (!pathValue.startsWith("/") && !data.template().path().endsWith("/")) { if (!pathValue.startsWith("/")
&& !data.template().toString().endsWith("/")) {
pathValue = "/" + pathValue; pathValue = "/" + pathValue;
} }
data.template().uri(pathValue, true); data.template().append(pathValue);
} }
} }
@@ -293,17 +239,10 @@ public class SpringMvcContract extends Contract.BaseContract
processParameterAnnotation, method); processParameterAnnotation, method);
} }
} }
if (isHttpAnnotation && data.indexToExpander().get(paramIndex) == null
if (isHttpAnnotation && data.indexToExpander().get(paramIndex) == null) { && this.conversionService.canConvert(
TypeDescriptor typeDescriptor = createTypeDescriptor(method, paramIndex); method.getParameterTypes()[paramIndex], String.class)) {
if (this.conversionService.canConvert(typeDescriptor, data.indexToExpander().put(paramIndex, this.expander);
STRING_TYPE_DESCRIPTOR)) {
Param.Expander expander = this.convertingExpanderFactory
.getExpander(typeDescriptor);
if (expander != null) {
data.indexToExpander().put(paramIndex, expander);
}
}
} }
return isHttpAnnotation; return isHttpAnnotation;
} }
@@ -336,7 +275,7 @@ public class SpringMvcContract extends Contract.BaseContract
int index = header.indexOf('='); int index = header.indexOf('=');
if (!header.contains("!=") && index >= 0) { if (!header.contains("!=") && index >= 0) {
md.template().header(resolve(header.substring(0, index)), md.template().header(resolve(header.substring(0, index)),
resolve(header.substring(index + 1).trim())); resolve(header.substring(index + 1).trim()));
} }
} }
} }
@@ -358,7 +297,6 @@ public class SpringMvcContract extends Contract.BaseContract
annotatedArgumentResolvers.add(new PathVariableParameterProcessor()); annotatedArgumentResolvers.add(new PathVariableParameterProcessor());
annotatedArgumentResolvers.add(new RequestParamParameterProcessor()); annotatedArgumentResolvers.add(new RequestParamParameterProcessor());
annotatedArgumentResolvers.add(new RequestHeaderParameterProcessor()); annotatedArgumentResolvers.add(new RequestHeaderParameterProcessor());
annotatedArgumentResolvers.add(new QueryMapParameterProcessor());
return annotatedArgumentResolvers; return annotatedArgumentResolvers;
} }
@@ -381,51 +319,13 @@ public class SpringMvcContract extends Contract.BaseContract
parameterAnnotation.annotationType(), null); parameterAnnotation.annotationType(), null);
} }
private boolean shouldAddParameterName(int parameterIndex, Type[] parameterTypes, private boolean shouldAddParameterName(int parameterIndex, Type[] parameterTypes, String[] parameterNames) {
String[] parameterNames) {
// has a parameter name // has a parameter name
return parameterNames != null && parameterNames.length > parameterIndex return parameterNames != null && parameterNames.length > parameterIndex
// has a type // has a type
&& parameterTypes != null && parameterTypes.length > parameterIndex; && parameterTypes != null && parameterTypes.length > parameterIndex;
} }
/**
* @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 this.conversionService.convert(value, String.class);
}
}
private static class ConvertingExpanderFactory {
private final ConversionService conversionService;
ConvertingExpanderFactory(ConversionService conversionService) {
this.conversionService = conversionService;
}
Param.Expander getExpander(TypeDescriptor typeDescriptor) {
return value -> {
Object converted = this.conversionService.convert(value, typeDescriptor,
STRING_TYPE_DESCRIPTOR);
return (String) converted;
};
}
}
private class SimpleAnnotatedParameterContext private class SimpleAnnotatedParameterContext
implements AnnotatedParameterProcessor.AnnotatedParameterContext { implements AnnotatedParameterProcessor.AnnotatedParameterContext {
@@ -433,7 +333,7 @@ public class SpringMvcContract extends Contract.BaseContract
private final int parameterIndex; private final int parameterIndex;
SimpleAnnotatedParameterContext(MethodMetadata methodMetadata, public SimpleAnnotatedParameterContext(MethodMetadata methodMetadata,
int parameterIndex) { int parameterIndex) {
this.methodMetadata = methodMetadata; this.methodMetadata = methodMetadata;
this.parameterIndex = parameterIndex; this.parameterIndex = parameterIndex;
@@ -457,9 +357,22 @@ public class SpringMvcContract extends Contract.BaseContract
@Override @Override
public Collection<String> setTemplateParameter(String name, public Collection<String> setTemplateParameter(String name,
Collection<String> rest) { Collection<String> rest) {
return addTemplateParameter(rest, name); return addTemplatedParam(rest, name);
}
}
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 this.conversionService.convert(value, String.class);
} }
} }
} }
@@ -1,36 +1,33 @@
{ {"groups": [
"groups": [ ], "properties": [
], {
"properties": [ "name": "feign.hystrix.enabled",
{ "type": "java.lang.Boolean",
"name": "feign.hystrix.enabled", "description": "If true, an OpenFeign client will be wrapped with a Hystrix circuit breaker.",
"type": "java.lang.Boolean", "defaultValue": "false"
"description": "If true, an OpenFeign client will be wrapped with a Hystrix circuit breaker.", },
"defaultValue": "false" {
}, "name": "feign.httpclient.enabled",
{ "type": "java.lang.Boolean",
"name": "feign.httpclient.enabled", "description": "Enables the use of the Apache HTTP Client by Feign.",
"type": "java.lang.Boolean", "defaultValue": "true"
"description": "Enables the use of the Apache HTTP Client by Feign.", },
"defaultValue": "true" {
}, "name": "feign.okhttp.enabled",
{ "type": "java.lang.Boolean",
"name": "feign.okhttp.enabled", "description": "Enables the use of the OK HTTP Client by Feign.",
"type": "java.lang.Boolean", "defaultValue": "false"
"description": "Enables the use of the OK HTTP Client by Feign.", },
"defaultValue": "false" {
}, "name": "feign.compression.response.enabled",
{ "type": "java.lang.Boolean",
"name": "feign.compression.response.enabled", "description": "Enables the response from Feign to be compressed.",
"type": "java.lang.Boolean", "defaultValue": "false"
"description": "Enables the response from Feign to be compressed.", },
"defaultValue": "false" {
}, "name": "feign.compression.request.enabled",
{ "type": "java.lang.Boolean",
"name": "feign.compression.request.enabled", "description": "Enables the request sent by Feign to be compressed.",
"type": "java.lang.Boolean", "defaultValue": "false"
"description": "Enables the request sent by Feign to be compressed.", }
"defaultValue": "false" ]}
}
]
}
@@ -1,58 +0,0 @@
/*
* Copyright 2013-2015 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.codec.Encoder;
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.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;
/**
* @author Spencer Gibb
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = EnableFeignClientsSpringDataTests.PlainConfiguration.class)
@DirtiesContext
public class EnableFeignClientsSpringDataTests {
@Autowired
private FeignContext feignContext;
@Test
public void encoderDefaultCorrect() {
PageableSpringEncoder.class
.cast(this.feignContext.getInstance("foo", Encoder.class));
}
@Configuration
@Import({ PropertyPlaceholderAutoConfiguration.class, ArchaiusAutoConfiguration.class,
FeignAutoConfiguration.class })
protected static class PlainConfiguration {
}
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2015 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -12,10 +12,24 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
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.test.context.SpringBootTest;
import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.cloud.openfeign.support.SpringMvcContract;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import feign.Contract; import feign.Contract;
import feign.Feign; import feign.Feign;
import feign.Logger; import feign.Logger;
@@ -23,74 +37,50 @@ import feign.codec.Decoder;
import feign.codec.Encoder; import feign.codec.Encoder;
import feign.optionals.OptionalDecoder; import feign.optionals.OptionalDecoder;
import feign.slf4j.Slf4jLogger; import feign.slf4j.Slf4jLogger;
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.netflix.archaius.ArchaiusAutoConfiguration;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.cloud.openfeign.support.SpringMvcContract;
import org.springframework.cloud.test.ClassPathExclusions;
import org.springframework.cloud.test.ModifiedClassPathRunner;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
*/ */
@RunWith(ModifiedClassPathRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@ClassPathExclusions({ "spring-data-commons-*.jar" }) @SpringBootTest(classes = EnableFeignClientsTests.PlainConfiguration.class)
@DirtiesContext
public class EnableFeignClientsTests { public class EnableFeignClientsTests {
private ConfigurableApplicationContext context; @Autowired
private FeignContext feignContext;
@Before
public void setUp() {
context = new SpringApplicationBuilder().web(WebApplicationType.NONE)
.properties("debug=true", "feign.httpclient.enabled=false")
.sources(EnableFeignClientsTests.PlainConfiguration.class).run();
}
@After
public void tearDown() {
if (context != null) {
context.close();
}
}
@Test @Test
public void decoderDefaultCorrect() { public void decoderDefaultCorrect() {
OptionalDecoder.class.cast(this.context.getBeansOfType(Decoder.class).get(0)); OptionalDecoder.class
.cast(this.feignContext.getInstance("foo", Decoder.class));
} }
@Test @Test
public void encoderDefaultCorrect() { public void encoderDefaultCorrect() {
SpringEncoder.class.cast(this.context.getBeansOfType(Encoder.class).get(0)); SpringEncoder.class.cast(this.feignContext.getInstance("foo", Encoder.class));
} }
@Test @Test
public void loggerDefaultCorrect() { public void loggerDefaultCorrect() {
Slf4jLogger.class.cast(this.context.getBeansOfType(Logger.class).get(0)); Slf4jLogger.class.cast(this.feignContext.getInstance("foo", Logger.class));
} }
@Test @Test
public void contractDefaultCorrect() { public void contractDefaultCorrect() {
SpringMvcContract.class.cast(this.context.getBeansOfType(Contract.class).get(0)); SpringMvcContract.class
.cast(this.feignContext.getInstance("foo", Contract.class));
} }
@Test @Test
public void builderDefaultCorrect() { public void builderDefaultCorrect() {
Feign.Builder.class.cast(this.context.getBeansOfType(Feign.Builder.class).get(0)); Feign.Builder.class
.cast(this.feignContext.getInstance("foo", Feign.Builder.class));
} }
@Configuration @Configuration
@Import({ ArchaiusAutoConfiguration.class, FeignAutoConfiguration.class }) @Import({ PropertyPlaceholderAutoConfiguration.class, ArchaiusAutoConfiguration.class,
FeignAutoConfiguration.class })
protected static class PlainConfiguration { protected static class PlainConfiguration {
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -25,18 +25,16 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.springframework.cloud.openfeign.testclients.TestClient; import org.springframework.cloud.openfeign.testclients.TestClient;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/** /**
* @author Sven Döring * @author Sven Döring
*/ */
@@ -67,14 +65,14 @@ public class FeignClientBuilderTests {
fieldName); fieldName);
ReflectionUtils.makeAccessible(field); ReflectionUtils.makeAccessible(field);
final Object value = ReflectionUtils.getField(field, factoryBean); final Object value = ReflectionUtils.getField(field, factoryBean);
assertThat(value).as("Expected value for the field '" + fieldName + "':") Assert.assertEquals("Expected value for the field '" + fieldName + "':",
.isEqualTo(expectedValue); expectedValue, value);
} }
@Before @Before
public void setUp() { public void setUp() {
this.applicationContext = Mockito.mock(ApplicationContext.class); this.applicationContext = Mockito.mock(ApplicationContext.class);
this.feignClientBuilder = new FeignClientBuilder(this.applicationContext); this.feignClientBuilder = new FeignClientBuilder(applicationContext);
} }
@Test @Test
@@ -93,21 +91,20 @@ public class FeignClientBuilderTests {
// on this builder class. // on this builder class.
// (2) Or a new field was added and the builder class has to be extended with this // (2) Or a new field was added and the builder class has to be extended with this
// new field. // new field.
assertThat(methodNames).containsExactly("contextId", "decode404", "fallback", Assert.assertThat(methodNames, Matchers.contains("decode404", "fallback",
"fallbackFactory", "name", "path", "url"); "fallbackFactory", "name", "path", "url"));
} }
@Test @Test
public void forType_preinitializedBuilder() { public void forType_preinitializedBuilder() {
// when: // when:
final FeignClientBuilder.Builder builder = this.feignClientBuilder final FeignClientBuilder.Builder builder = feignClientBuilder
.forType(FeignClientBuilderTests.class, "TestClient"); .forType(FeignClientBuilderTests.class, "TestClient");
// then: // then:
assertFactoryBeanField(builder, "applicationContext", this.applicationContext); assertFactoryBeanField(builder, "applicationContext", applicationContext);
assertFactoryBeanField(builder, "type", FeignClientBuilderTests.class); assertFactoryBeanField(builder, "type", FeignClientBuilderTests.class);
assertFactoryBeanField(builder, "name", "TestClient"); assertFactoryBeanField(builder, "name", "TestClient");
assertFactoryBeanField(builder, "contextId", "TestClient");
// and: // and:
assertFactoryBeanField(builder, "url", assertFactoryBeanField(builder, "url",
@@ -125,13 +122,13 @@ public class FeignClientBuilderTests {
@Test @Test
public void forType_allFieldsSetOnBuilder() { public void forType_allFieldsSetOnBuilder() {
// when: // when:
final FeignClientBuilder.Builder builder = this.feignClientBuilder final FeignClientBuilder.Builder builder = feignClientBuilder
.forType(FeignClientBuilderTests.class, "TestClient").decode404(true) .forType(FeignClientBuilderTests.class, "TestClient").decode404(true)
.fallback(Object.class).fallbackFactory(Object.class).path("Path/") .fallback(Object.class).fallbackFactory(Object.class).path("Path/")
.url("Url/"); .url("Url/");
// then: // then:
assertFactoryBeanField(builder, "applicationContext", this.applicationContext); assertFactoryBeanField(builder, "applicationContext", applicationContext);
assertFactoryBeanField(builder, "type", FeignClientBuilderTests.class); assertFactoryBeanField(builder, "type", FeignClientBuilderTests.class);
assertFactoryBeanField(builder, "name", "TestClient"); assertFactoryBeanField(builder, "name", "TestClient");
@@ -146,17 +143,16 @@ public class FeignClientBuilderTests {
@Test @Test
public void forType_build() { public void forType_build() {
// given: // given:
Mockito.when(this.applicationContext.getBean(FeignContext.class)) Mockito.when(applicationContext.getBean(FeignContext.class))
.thenThrow(new ClosedFileSystemException()); // throw an unusual exception .thenThrow(new ClosedFileSystemException()); // throw an unusual exception
// in the // in the
// FeignClientFactoryBean // FeignClientFactoryBean
final FeignClientBuilder.Builder builder = this.feignClientBuilder final FeignClientBuilder.Builder builder = feignClientBuilder
.forType(TestClient.class, "TestClient"); .forType(TestClient.class, "TestClient");
// expect: 'the build will fail right after calling build() with the mocked // expect: 'the build will fail right after calling build() with the mocked
// unusual exception' // unusual exception'
this.thrown.expect(Matchers.isA(ClosedFileSystemException.class)); thrown.expect(Matchers.isA(ClosedFileSystemException.class));
builder.build(); builder.build();
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,14 +16,16 @@
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
import java.util.Arrays;
import org.junit.Test; import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import static org.assertj.core.api.Assertions.assertThat; import java.util.Arrays;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
@@ -40,43 +42,33 @@ public class FeignClientFactoryTests {
getSpec("bar", BarConfig.class))); getSpec("bar", BarConfig.class)));
Foo foo = context.getInstance("foo", Foo.class); Foo foo = context.getInstance("foo", Foo.class);
assertThat(foo).as("foo was null").isNotNull(); assertThat("foo was null", foo, is(notNullValue()));
Bar bar = context.getInstance("bar", Bar.class); Bar bar = context.getInstance("bar", Bar.class);
assertThat(bar).as("bar was null").isNotNull(); assertThat("bar was null", bar, is(notNullValue()));
Bar foobar = context.getInstance("foo", Bar.class); Bar foobar = context.getInstance("foo", Bar.class);
assertThat(foobar).as("bar was not null").isNull(); assertThat("bar was not null", foobar, is(nullValue()));
} }
private FeignClientSpecification getSpec(String name, Class<?> configClass) { private FeignClientSpecification getSpec(String name, Class<?> configClass) {
return new FeignClientSpecification(name, new Class[] { configClass }); return new FeignClientSpecification(name, new Class[]{configClass});
} }
static class FooConfig { static class FooConfig {
@Bean @Bean
Foo foo() { Foo foo() {
return new Foo(); return new Foo();
} }
} }
static class Foo{}
static class Foo {
}
static class BarConfig { static class BarConfig {
@Bean @Bean
Bar bar() { Bar bar() {
return new Bar(); return new Bar();
} }
} }
static class Bar{}
static class Bar {
}
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -12,10 +12,31 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
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.test.context.SpringBootTest;
import org.springframework.cloud.netflix.archaius.ArchaiusAutoConfiguration;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.cloud.openfeign.support.SpringMvcContract;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import feign.Contract; import feign.Contract;
import feign.Feign; import feign.Feign;
import feign.Logger; import feign.Logger;
@@ -31,24 +52,6 @@ import feign.codec.ErrorDecoder;
import feign.hystrix.HystrixFeign; import feign.hystrix.HystrixFeign;
import feign.optionals.OptionalDecoder; import feign.optionals.OptionalDecoder;
import feign.slf4j.Slf4jLogger; import feign.slf4j.Slf4jLogger;
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.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.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import static org.assertj.core.api.Assertions.assertThat;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
@@ -69,8 +72,8 @@ public class FeignClientOverrideDefaultsTests {
@Test @Test
public void clientsAvailable() { public void clientsAvailable() {
assertThat(this.foo).isNotNull(); assertNotNull(this.foo);
assertThat(this.bar).isNotNull(); assertNotNull(this.bar);
} }
@Test @Test
@@ -82,7 +85,7 @@ public class FeignClientOverrideDefaultsTests {
@Test @Test
public void overrideEncoder() { public void overrideEncoder() {
Encoder.Default.class.cast(this.context.getInstance("foo", Encoder.class)); Encoder.Default.class.cast(this.context.getInstance("foo", Encoder.class));
PageableSpringEncoder.class.cast(this.context.getInstance("bar", Encoder.class)); SpringEncoder.class.cast(this.context.getInstance("bar", Encoder.class));
} }
@Test @Test
@@ -99,62 +102,45 @@ public class FeignClientOverrideDefaultsTests {
@Test @Test
public void overrideLoggerLevel() { public void overrideLoggerLevel() {
assertThat(this.context.getInstance("foo", Logger.Level.class)).isNull(); assertNull(this.context.getInstance("foo", Logger.Level.class));
assertThat(this.context.getInstance("bar", Logger.Level.class)) assertEquals(Logger.Level.HEADERS,
.isEqualTo(Logger.Level.HEADERS); this.context.getInstance("bar", Logger.Level.class));
} }
@Test @Test
public void overrideRetryer() { public void overrideRetryer() {
assertThat(this.context.getInstance("foo", Retryer.class)) assertEquals(Retryer.NEVER_RETRY, this.context.getInstance("foo", Retryer.class));
.isEqualTo(Retryer.NEVER_RETRY);
Retryer.Default.class.cast(this.context.getInstance("bar", Retryer.class)); Retryer.Default.class.cast(this.context.getInstance("bar", Retryer.class));
} }
@Test @Test
public void overrideErrorDecoder() { public void overrideErrorDecoder() {
assertThat(this.context.getInstance("foo", ErrorDecoder.class)).isNull(); assertNull(this.context.getInstance("foo", ErrorDecoder.class));
ErrorDecoder.Default.class ErrorDecoder.Default.class
.cast(this.context.getInstance("bar", ErrorDecoder.class)); .cast(this.context.getInstance("bar", ErrorDecoder.class));
} }
@Test @Test
public void overrideBuilder() { public void overrideBuilder() {
HystrixFeign.Builder.class HystrixFeign.Builder.class.cast(this.context.getInstance("foo", Feign.Builder.class));
.cast(this.context.getInstance("foo", Feign.Builder.class)); Feign.Builder.class
Feign.Builder.class.cast(this.context.getInstance("bar", Feign.Builder.class)); .cast(this.context.getInstance("bar", Feign.Builder.class));
} }
@Test @Test
public void overrideRequestOptions() { public void overrideRequestOptions() {
assertThat(this.context.getInstance("foo", Request.Options.class)).isNull(); assertNull(this.context.getInstance("foo", Request.Options.class));
Request.Options options = this.context.getInstance("bar", Request.Options.class); Request.Options options = this.context.getInstance("bar", Request.Options.class);
assertThat(options.connectTimeoutMillis()).isEqualTo(1); assertEquals(1, options.connectTimeoutMillis());
assertThat(options.readTimeoutMillis()).isEqualTo(1); assertEquals(1, options.readTimeoutMillis());
} }
@Test @Test
public void addRequestInterceptor() { public void addRequestInterceptor() {
assertThat(this.context.getInstances("foo", RequestInterceptor.class).size()) assertEquals(1,
.isEqualTo(1); this.context.getInstances("foo", RequestInterceptor.class).size());
assertThat(this.context.getInstances("bar", RequestInterceptor.class).size()) assertEquals(2,
.isEqualTo(2); this.context.getInstances("bar", RequestInterceptor.class).size());
}
@FeignClient(name = "foo", url = "http://foo", configuration = FooConfiguration.class)
interface FooClient {
@RequestLine("GET /")
String get();
}
@FeignClient(name = "bar", url = "http://bar", configuration = BarConfiguration.class)
interface BarClient {
@RequestMapping(value = "/", method = RequestMethod.GET)
String get();
} }
@Configuration @Configuration
@@ -162,7 +148,6 @@ public class FeignClientOverrideDefaultsTests {
@Import({ PropertyPlaceholderAutoConfiguration.class, ArchaiusAutoConfiguration.class, @Import({ PropertyPlaceholderAutoConfiguration.class, ArchaiusAutoConfiguration.class,
FeignAutoConfiguration.class }) FeignAutoConfiguration.class })
protected static class TestConfiguration { protected static class TestConfiguration {
@Bean @Bean
RequestInterceptor defaultRequestInterceptor() { RequestInterceptor defaultRequestInterceptor() {
return new RequestInterceptor() { return new RequestInterceptor() {
@@ -171,11 +156,16 @@ public class FeignClientOverrideDefaultsTests {
} }
}; };
} }
}
@FeignClient(name = "foo", url = "http://foo", configuration = FooConfiguration.class)
interface FooClient {
@RequestLine("GET /")
String get();
} }
public static class FooConfiguration { public static class FooConfiguration {
@Bean @Bean
public Decoder feignDecoder() { public Decoder feignDecoder() {
return new Decoder.Default(); return new Decoder.Default();
@@ -200,11 +190,15 @@ public class FeignClientOverrideDefaultsTests {
public Feign.Builder feignBuilder() { public Feign.Builder feignBuilder() {
return HystrixFeign.builder(); return HystrixFeign.builder();
} }
}
@FeignClient(name = "bar", url = "http://bar", configuration = BarConfiguration.class)
interface BarClient {
@RequestMapping(value = "/", method = RequestMethod.GET)
String get();
} }
public static class BarConfiguration { public static class BarConfiguration {
@Bean @Bean
Logger.Level feignLevel() { Logger.Level feignLevel() {
return Logger.Level.HEADERS; return Logger.Level.HEADERS;
@@ -229,7 +223,5 @@ public class FeignClientOverrideDefaultsTests {
RequestInterceptor feignRequestInterceptor() { RequestInterceptor feignRequestInterceptor() {
return new BasicAuthRequestInterceptor("user", "pass"); return new BasicAuthRequestInterceptor("user", "pass");
} }
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,13 +16,6 @@
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import feign.Request;
import feign.RequestInterceptor; import feign.RequestInterceptor;
import feign.RequestTemplate; import feign.RequestTemplate;
import feign.RetryableException; import feign.RetryableException;
@@ -32,15 +25,13 @@ import feign.codec.Encoder;
import feign.codec.ErrorDecoder; import feign.codec.ErrorDecoder;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.openfeign.test.NoSecurityConfiguration; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
@@ -50,16 +41,19 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import static java.nio.charset.StandardCharsets.UTF_8; import javax.servlet.http.HttpServletRequest;
import static org.assertj.core.api.Assertions.assertThat; import java.lang.reflect.Type;
import static org.assertj.core.api.Assertions.fail; import java.util.Collections;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/** /**
* @author Eko Kurniawan Khannedy * @author Eko Kurniawan Khannedy
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = FeignClientUsingPropertiesTests.Application.class, webEnvironment = RANDOM_PORT) @SpringBootTest(classes = FeignClientUsingPropertiesTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT)
@TestPropertySource("classpath:feign-properties.properties") @TestPropertySource("classpath:feign-properties.properties")
@DirtiesContext @DirtiesContext
public class FeignClientUsingPropertiesTests { public class FeignClientUsingPropertiesTests {
@@ -80,41 +74,38 @@ public class FeignClientUsingPropertiesTests {
private FeignClientFactoryBean formFactoryBean; private FeignClientFactoryBean formFactoryBean;
public FeignClientUsingPropertiesTests() { public FeignClientUsingPropertiesTests() {
this.fooFactoryBean = new FeignClientFactoryBean(); fooFactoryBean = new FeignClientFactoryBean();
this.fooFactoryBean.setContextId("foo"); fooFactoryBean.setName("foo");
this.fooFactoryBean.setType(FeignClientFactoryBean.class); fooFactoryBean.setType(FeignClientFactoryBean.class);
this.barFactoryBean = new FeignClientFactoryBean(); barFactoryBean = new FeignClientFactoryBean();
this.barFactoryBean.setContextId("bar"); barFactoryBean.setName("bar");
this.barFactoryBean.setType(FeignClientFactoryBean.class); barFactoryBean.setType(FeignClientFactoryBean.class);
this.formFactoryBean = new FeignClientFactoryBean(); formFactoryBean = new FeignClientFactoryBean();
this.formFactoryBean.setContextId("form"); formFactoryBean.setName("form");
this.formFactoryBean.setType(FeignClientFactoryBean.class); formFactoryBean.setType(FeignClientFactoryBean.class);
} }
public FooClient fooClient() { public FooClient fooClient() {
this.fooFactoryBean.setApplicationContext(this.applicationContext); fooFactoryBean.setApplicationContext(applicationContext);
return this.fooFactoryBean.feign(this.context).target(FooClient.class, return fooFactoryBean.feign(context).target(FooClient.class, "http://localhost:" + this.port);
"http://localhost:" + this.port);
} }
public BarClient barClient() { public BarClient barClient() {
this.barFactoryBean.setApplicationContext(this.applicationContext); barFactoryBean.setApplicationContext(applicationContext);
return this.barFactoryBean.feign(this.context).target(BarClient.class, return barFactoryBean.feign(context).target(BarClient.class, "http://localhost:" + this.port);
"http://localhost:" + this.port);
} }
public FormClient formClient() { public FormClient formClient() {
this.formFactoryBean.setApplicationContext(this.applicationContext); formFactoryBean.setApplicationContext(applicationContext);
return this.formFactoryBean.feign(this.context).target(FormClient.class, return formFactoryBean.feign(context).target(FormClient.class, "http://localhost:" + this.port);
"http://localhost:" + this.port);
} }
@Test @Test
public void testFoo() { public void testFoo() {
String response = fooClient().foo(); String response = fooClient().foo();
assertThat(response).isEqualTo("OK"); assertEquals("OK", response);
} }
@Test(expected = RetryableException.class) @Test(expected = RetryableException.class)
@@ -127,26 +118,25 @@ public class FeignClientUsingPropertiesTests {
public void testForm() { public void testForm() {
Map<String, String> request = Collections.singletonMap("form", "Data"); Map<String, String> request = Collections.singletonMap("form", "Data");
String response = formClient().form(request); String response = formClient().form(request);
assertThat(response).isEqualTo("Data"); assertEquals("Data", response);
} }
protected interface FooClient { protected interface FooClient {
@RequestMapping(method = RequestMethod.GET, value = "/foo") @RequestMapping(method = RequestMethod.GET, value = "/foo")
String foo(); String foo();
} }
protected interface BarClient { protected interface BarClient {
@RequestMapping(method = RequestMethod.GET, value = "/bar") @RequestMapping(method = RequestMethod.GET, value = "/bar")
String bar(); String bar();
} }
protected interface FormClient { protected interface FormClient {
@RequestMapping(value = "/form", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) @RequestMapping(value = "/form", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
String form(Map<String, String> form); String form(Map<String, String> form);
} }
@@ -154,16 +144,14 @@ public class FeignClientUsingPropertiesTests {
@Configuration @Configuration
@EnableAutoConfiguration @EnableAutoConfiguration
@RestController @RestController
@Import(NoSecurityConfiguration.class)
protected static class Application { protected static class Application {
@RequestMapping(method = RequestMethod.GET, value = "/foo") @RequestMapping(method = RequestMethod.GET, value = "/foo")
public String foo(HttpServletRequest request) throws IllegalAccessException { public String foo(HttpServletRequest request) throws IllegalAccessException {
if ("Foo".equals(request.getHeader("Foo")) if ("Foo".equals(request.getHeader("Foo")) &&
&& "Bar".equals(request.getHeader("Bar"))) { "Bar".equals(request.getHeader("Bar"))) {
return "OK"; return "OK";
} } else {
else {
throw new IllegalAccessException("It should has Foo and Bar header"); throw new IllegalAccessException("It should has Foo and Bar header");
} }
} }
@@ -174,7 +162,8 @@ public class FeignClientUsingPropertiesTests {
return "OK"; return "OK";
} }
@RequestMapping(value = "/form", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) @RequestMapping(value = "/form", method = RequestMethod.POST,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String form(HttpServletRequest request) { public String form(HttpServletRequest request) {
return request.getParameter("form"); return request.getParameter("form");
} }
@@ -182,21 +171,17 @@ public class FeignClientUsingPropertiesTests {
} }
public static class FooRequestInterceptor implements RequestInterceptor { public static class FooRequestInterceptor implements RequestInterceptor {
@Override @Override
public void apply(RequestTemplate template) { public void apply(RequestTemplate template) {
template.header("Foo", "Foo"); template.header("Foo", "Foo");
} }
} }
public static class BarRequestInterceptor implements RequestInterceptor { public static class BarRequestInterceptor implements RequestInterceptor {
@Override @Override
public void apply(RequestTemplate template) { public void apply(RequestTemplate template) {
template.header("Bar", "Bar"); template.header("Bar", "Bar");
} }
} }
public static class NoRetryer implements Retryer { public static class NoRetryer implements Retryer {
@@ -210,29 +195,24 @@ public class FeignClientUsingPropertiesTests {
public Retryer clone() { public Retryer clone() {
return this; return this;
} }
} }
public static class DefaultErrorDecoder extends ErrorDecoder.Default { public static class DefaultErrorDecoder extends ErrorDecoder.Default {
} }
public static class FormEncoder implements Encoder { public static class FormEncoder implements Encoder {
@Override @Override
public void encode(Object o, Type type, RequestTemplate requestTemplate) public void encode(Object o, Type type, RequestTemplate requestTemplate) throws EncodeException {
throws EncodeException {
Map<String, String> form = (Map<String, String>) o; Map<String, String> form = (Map<String, String>) o;
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
form.forEach((key, value) -> { form.forEach((key, value) -> {
builder.append(key + "=" + value + "&"); builder.append(key + "=" + value + "&");
}); });
requestTemplate.header(HttpHeaders.CONTENT_TYPE, requestTemplate.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE);
MediaType.APPLICATION_FORM_URLENCODED_VALUE); requestTemplate.body(builder.toString());
requestTemplate.body(Request.Body.bodyTemplate(builder.toString(), UTF_8));
} }
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -12,6 +12,7 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
@@ -19,7 +20,6 @@ package org.springframework.cloud.openfeign;
import java.util.Collections; import java.util.Collections;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@@ -27,7 +27,8 @@ import org.springframework.mock.env.MockEnvironment;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
@@ -40,6 +41,7 @@ public class FeignClientsRegistrarTests {
testGetName("http://bad_hostname"); testGetName("http://bad_hostname");
} }
@Test(expected = IllegalStateException.class) @Test(expected = IllegalStateException.class)
public void badNameHttpsPrefix() { public void badNameHttpsPrefix() {
testGetName("https://bad_hostname"); testGetName("https://bad_hostname");
@@ -58,19 +60,19 @@ public class FeignClientsRegistrarTests {
@Test @Test
public void goodName() { public void goodName() {
String name = testGetName("good-name"); String name = testGetName("good-name");
assertThat(name).as("name was wrong").isEqualTo("good-name"); assertThat("name was wrong", name, is("good-name"));
} }
@Test @Test
public void goodNameHttpPrefix() { public void goodNameHttpPrefix() {
String name = testGetName("http://good-name"); String name = testGetName("http://good-name");
assertThat(name).as("name was wrong").isEqualTo("http://good-name"); assertThat("name was wrong", name, is("http://good-name"));
} }
@Test @Test
public void goodNameHttpsPrefix() { public void goodNameHttpsPrefix() {
String name = testGetName("https://goodname"); String name = testGetName("https://goodname");
assertThat(name).as("name was wrong").isEqualTo("https://goodname"); assertThat("name was wrong", name, is("https://goodname"));
} }
private String testGetName(String name) { private String testGetName(String name) {
@@ -79,6 +81,7 @@ public class FeignClientsRegistrarTests {
return registrar.getName(Collections.<String, Object>singletonMap("name", name)); return registrar.getName(Collections.<String, Object>singletonMap("name", name));
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testFallback() { public void testFallback() {
new AnnotationConfigApplicationContext(FallbackTestConfig.class); new AnnotationConfigApplicationContext(FallbackTestConfig.class);
@@ -89,35 +92,30 @@ public class FeignClientsRegistrarTests {
new AnnotationConfigApplicationContext(FallbackFactoryTestConfig.class); new AnnotationConfigApplicationContext(FallbackFactoryTestConfig.class);
} }
@Configuration
@EnableAutoConfiguration
@EnableFeignClients(clients = { FeignClientsRegistrarTests.FallbackClient.class})
protected static class FallbackTestConfig {
}
@FeignClient(name = "fallbackTestClient", url = "http://localhost:8080/", fallback = FallbackClient.class) @FeignClient(name = "fallbackTestClient", url = "http://localhost:8080/", fallback = FallbackClient.class)
protected interface FallbackClient { protected interface FallbackClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @RequestMapping(method = RequestMethod.GET, value = "/hello")
String fallbackTest(); String fallbackTest();
}
@Configuration
@EnableAutoConfiguration
@EnableFeignClients(clients = { FeignClientsRegistrarTests.FallbackFactoryClient.class})
protected static class FallbackFactoryTestConfig {
} }
@FeignClient(name = "fallbackFactoryTestClient", url = "http://localhost:8081/", fallbackFactory = FallbackFactoryClient.class) @FeignClient(name = "fallbackFactoryTestClient", url = "http://localhost:8081/", fallbackFactory = FallbackFactoryClient.class)
protected interface FallbackFactoryClient { protected interface FallbackFactoryClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @RequestMapping(method = RequestMethod.GET, value = "/hello")
String fallbackFactoryTest(); String fallbackFactoryTest();
}
@Configuration
@EnableAutoConfiguration
@EnableFeignClients(clients = { FeignClientsRegistrarTests.FallbackClient.class })
protected static class FallbackTestConfig {
}
@Configuration
@EnableAutoConfiguration
@EnableFeignClients(clients = {
FeignClientsRegistrarTests.FallbackFactoryClient.class })
protected static class FallbackFactoryTestConfig {
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -12,18 +12,15 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
import java.util.Map; import java.util.Map;
import feign.Client;
import feign.RequestInterceptor;
import feign.httpclient.ApacheHttpClient;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -38,6 +35,10 @@ import org.springframework.cloud.test.ModifiedClassPathRunner;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import feign.Client;
import feign.RequestInterceptor;
import feign.httpclient.ApacheHttpClient;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
@@ -89,9 +90,7 @@ public class FeignCompressionTests {
* a bean here of type ApacheHttpClient so that the configuration will be * a bean here of type ApacheHttpClient so that the configuration will be
* loaded correctly. * loaded correctly.
*/ */
return (ApacheHttpClient) this.client; return (ApacheHttpClient) client;
} }
} }
}
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -12,16 +12,14 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import javax.net.ssl.SSLContextSpi; import javax.net.ssl.SSLContextSpi;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import org.apache.http.config.Lookup; import org.apache.http.config.Lookup;
import org.apache.http.conn.HttpClientConnectionManager; import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.ConnectionSocketFactory;
@@ -30,8 +28,6 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.commons.httpclient.HttpClientConfiguration; import org.springframework.cloud.commons.httpclient.HttpClientConfiguration;
import org.springframework.cloud.test.ClassPathExclusions; import org.springframework.cloud.test.ClassPathExclusions;
@@ -39,7 +35,8 @@ import org.springframework.cloud.test.ModifiedClassPathRunner;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/** /**
* @author Ryan Baxter * @author Ryan Baxter
@@ -52,47 +49,35 @@ public class FeignHttpClientConfigurationTests {
@Before @Before
public void setUp() { public void setUp() {
this.context = new SpringApplicationBuilder() context = new SpringApplicationBuilder().properties("debug=true","feign.httpclient.disableSslValidation=true").web(false)
.properties("debug=true", "feign.httpclient.disableSslValidation=true") .sources(HttpClientConfiguration.class, FeignAutoConfiguration.class).run();
.web(WebApplicationType.NONE)
.sources(HttpClientConfiguration.class, FeignAutoConfiguration.class)
.run();
} }
@After @After
public void tearDown() { public void tearDown() {
if (this.context != null) { if(context != null) {
this.context.close(); context.close();
} }
} }
@Test @Test
public void disableSslTest() throws Exception { public void disableSslTest() throws Exception {
HttpClientConnectionManager connectionManager = this.context HttpClientConnectionManager connectionManager = context.getBean(HttpClientConnectionManager.class);
.getBean(HttpClientConnectionManager.class); Lookup<ConnectionSocketFactory> socketFactoryRegistry = getConnectionSocketFactoryLookup(connectionManager);
Lookup<ConnectionSocketFactory> socketFactoryRegistry = getConnectionSocketFactoryLookup( assertNotNull(socketFactoryRegistry.lookup("https"));
connectionManager); assertNull(this.getX509TrustManager(socketFactoryRegistry).getAcceptedIssuers());
assertThat(socketFactoryRegistry.lookup("https")).isNotNull();
assertThat(this.getX509TrustManager(socketFactoryRegistry).getAcceptedIssuers())
.isNull();
} }
private Lookup<ConnectionSocketFactory> getConnectionSocketFactoryLookup( private Lookup<ConnectionSocketFactory> getConnectionSocketFactoryLookup(HttpClientConnectionManager connectionManager) {
HttpClientConnectionManager connectionManager) { DefaultHttpClientConnectionOperator connectionOperator = (DefaultHttpClientConnectionOperator)this.getField(connectionManager, "connectionOperator");
DefaultHttpClientConnectionOperator connectionOperator = (DefaultHttpClientConnectionOperator) this return (Lookup)this.getField(connectionOperator, "socketFactoryRegistry");
.getField(connectionManager, "connectionOperator");
return (Lookup) this.getField(connectionOperator, "socketFactoryRegistry");
} }
private X509TrustManager getX509TrustManager( private X509TrustManager getX509TrustManager(Lookup<ConnectionSocketFactory> socketFactoryRegistry) {
Lookup<ConnectionSocketFactory> socketFactoryRegistry) { ConnectionSocketFactory connectionSocketFactory = (ConnectionSocketFactory)socketFactoryRegistry.lookup("https");
ConnectionSocketFactory connectionSocketFactory = (ConnectionSocketFactory) socketFactoryRegistry SSLSocketFactory sslSocketFactory = (SSLSocketFactory)this.getField(connectionSocketFactory, "socketfactory");
.lookup("https"); SSLContextSpi sslContext = (SSLContextSpi)this.getField(sslSocketFactory, "context");
SSLSocketFactory sslSocketFactory = (SSLSocketFactory) this return (X509TrustManager)this.getField(sslContext, "trustManager");
.getField(connectionSocketFactory, "socketfactory");
SSLContextSpi sslContext = (SSLContextSpi) this.getField(sslSocketFactory,
"context");
return (X509TrustManager) this.getField(sslContext, "trustManager");
} }
protected <T> Object getField(Object target, String name) { protected <T> Object getField(Object target, String name) {
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -12,29 +12,30 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Objects; import java.util.Objects;
import feign.Client;
import feign.Feign;
import feign.Target;
import feign.httpclient.ApacheHttpClient;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.openfeign.test.NoSecurityConfiguration; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
@@ -43,14 +44,16 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat; import feign.Client;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; import feign.Feign;
import feign.Target;
import feign.httpclient.ApacheHttpClient;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
*/ */
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@SpringBootTest(classes = FeignHttpClientUrlTests.TestConfig.class, webEnvironment = DEFINED_PORT, value = { @SpringBootTest(classes = FeignHttpClientUrlTests.TestConfig.class, webEnvironment = WebEnvironment.DEFINED_PORT, value = {
"spring.application.name=feignclienturltest", "feign.hystrix.enabled=false", "spring.application.name=feignclienturltest", "feign.hystrix.enabled=false",
"feign.okhttp.enabled=false" }) "feign.okhttp.enabled=false" })
@DirtiesContext @DirtiesContext
@@ -58,15 +61,6 @@ public class FeignHttpClientUrlTests {
static int port; static int port;
@Autowired
BeanUrlClientNoProtocol beanClientNoProtocol;
@Autowired
private UrlClient urlClient;
@Autowired
private BeanUrlClient beanClient;
@BeforeClass @BeforeClass
public static void beforeClass() { public static void beforeClass() {
port = SocketUtils.findAvailableTcpPort(); port = SocketUtils.findAvailableTcpPort();
@@ -78,62 +72,37 @@ public class FeignHttpClientUrlTests {
System.clearProperty("server.port"); System.clearProperty("server.port");
} }
@Test @Autowired
public void testUrlHttpClient() { private UrlClient urlClient;
assertThat(this.urlClient).as("UrlClient was null").isNotNull();
Hello hello = this.urlClient.getHello();
assertThat(hello).as("hello was null").isNotNull();
assertThat(hello).as("first hello didn't match")
.isEqualTo(new Hello("hello world 1"));
}
@Test @Autowired
public void testBeanUrl() { private BeanUrlClient beanClient;
Hello hello = this.beanClient.getHello();
assertThat(hello).as("hello was null").isNotNull();
assertThat(hello).as("first hello didn't match")
.isEqualTo(new Hello("hello world 1"));
}
@Test @Autowired BeanUrlClientNoProtocol beanClientNoProtocol;
public void testBeanUrlNoProtocol() {
Hello hello = this.beanClientNoProtocol.getHello();
assertThat(hello).as("hello was null").isNotNull();
assertThat(hello).as("first hello didn't match")
.isEqualTo(new Hello("hello world 1"));
}
// this tests that // this tests that
@FeignClient(name = "localappurl", url = "http://localhost:${server.port}/") @FeignClient(name = "localappurl", url = "http://localhost:${server.port}/")
protected interface UrlClient { protected interface UrlClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello(); Hello getHello();
} }
@FeignClient(name = "beanappurl", url = "#{SERVER_URL}path") @FeignClient(name = "beanappurl", url = "#{SERVER_URL}path")
protected interface BeanUrlClient { protected interface BeanUrlClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello(); Hello getHello();
} }
@FeignClient(name = "beanappurlnoprotocol", url = "#{SERVER_URL_NO_PROTOCOL}path") @FeignClient(name = "beanappurlnoprotocol", url = "#{SERVER_URL_NO_PROTOCOL}path")
protected interface BeanUrlClientNoProtocol { protected interface BeanUrlClientNoProtocol {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello(); Hello getHello();
} }
@Configuration @Configuration
@EnableAutoConfiguration @EnableAutoConfiguration
@RestController @RestController
@EnableFeignClients(clients = { UrlClient.class, BeanUrlClient.class, @EnableFeignClients(clients = { UrlClient.class, BeanUrlClient.class, BeanUrlClientNoProtocol.class })
BeanUrlClientNoProtocol.class })
@Import(NoSecurityConfiguration.class)
protected static class TestConfig { protected static class TestConfig {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @RequestMapping(method = RequestMethod.GET, value = "/hello")
@@ -146,12 +115,12 @@ public class FeignHttpClientUrlTests {
return getHello(); return getHello();
} }
@Bean(name = "SERVER_URL") @Bean(name="SERVER_URL")
public String serverUrl() { public String serverUrl() {
return "http://localhost:" + port + "/"; return "http://localhost:" + port + "/";
} }
@Bean(name = "SERVER_URL_NO_PROTOCOL") @Bean(name="SERVER_URL_NO_PROTOCOL")
public String serverUrlNoProtocol() { public String serverUrlNoProtocol() {
return "localhost:" + port + "/"; return "localhost:" + port + "/";
} }
@@ -167,8 +136,8 @@ public class FeignHttpClientUrlTests {
ReflectionUtils.makeAccessible(field); ReflectionUtils.makeAccessible(field);
Client client = (Client) ReflectionUtils.getField(field, feign); Client client = (Client) ReflectionUtils.getField(field, feign);
if (target.name().equals("localappurl")) { if (target.name().equals("localappurl")) {
assertThat(client).isInstanceOf(ApacheHttpClient.class) assertThat("client was wrong type", client,
.as("client was wrong type"); is(instanceOf(ApacheHttpClient.class)));
} }
return feign.target(target); return feign.target(target);
} }
@@ -177,8 +146,29 @@ public class FeignHttpClientUrlTests {
} }
public static class Hello { @Test
public void testUrlHttpClient() {
assertNotNull("UrlClient was null", this.urlClient);
Hello hello = this.urlClient.getHello();
assertNotNull("hello was null", hello);
assertEquals("first hello didn't match", new Hello("hello world 1"), hello);
}
@Test
public void testBeanUrl() {
Hello hello = this.beanClient.getHello();
assertNotNull("hello was null", hello);
assertEquals("first hello didn't match", new Hello("hello world 1"), hello);
}
@Test
public void testBeanUrlNoProtocol() {
Hello hello = this.beanClientNoProtocol.getHello();
assertNotNull("hello was null", hello);
assertEquals("first hello didn't match", new Hello("hello world 1"), hello);
}
public static class Hello {
private String message; private String message;
public Hello() { public Hello() {
@@ -189,7 +179,7 @@ public class FeignHttpClientUrlTests {
} }
public String getMessage() { public String getMessage() {
return this.message; return message;
} }
public void setMessage(String message) { public void setMessage(String message) {
@@ -198,21 +188,15 @@ public class FeignHttpClientUrlTests {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) { if (this == o) return true;
return true; if (o == null || getClass() != o.getClass()) return false;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Hello that = (Hello) o; Hello that = (Hello) o;
return Objects.equals(this.message, that.message); return Objects.equals(message, that.message);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(this.message); return Objects.hash(message);
} }
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2016-2019 the original author or authors. * Copyright 2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,8 +16,9 @@
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
import feign.Logger; import static org.junit.Assert.assertNotNull;
import feign.slf4j.Slf4jLogger; import static org.junit.Assert.assertTrue;
import org.junit.Test; import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@@ -25,7 +26,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import static org.assertj.core.api.Assertions.assertThat; import feign.Logger;
import feign.slf4j.Slf4jLogger;
/** /**
* @author Venil Noronha * @author Venil Noronha
@@ -34,38 +36,12 @@ public class FeignLoggerFactoryTests {
@Test @Test
public void testDefaultLogger() { public void testDefaultLogger() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext( AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SampleConfiguration1.class);
SampleConfiguration1.class);
FeignLoggerFactory loggerFactory = context.getBean(FeignLoggerFactory.class); FeignLoggerFactory loggerFactory = context.getBean(FeignLoggerFactory.class);
assertThat(loggerFactory).isNotNull(); assertNotNull(loggerFactory);
Logger logger = loggerFactory.create(Object.class); Logger logger = loggerFactory.create(Object.class);
assertThat(logger).isNotNull(); assertNotNull(logger);
assertThat(logger instanceof Slf4jLogger).isTrue(); assertTrue(logger instanceof Slf4jLogger);
context.close();
}
@Test
public void testCustomLogger() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
SampleConfiguration2.class);
FeignLoggerFactory loggerFactory = context.getBean(FeignLoggerFactory.class);
assertThat(loggerFactory).isNotNull();
Logger logger = loggerFactory.create(Object.class);
assertThat(logger).isNotNull();
assertThat(logger instanceof LoggerImpl1).isTrue();
context.close();
}
@Test
public void testCustomLoggerFactory() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
SampleConfiguration3.class);
FeignLoggerFactory loggerFactory = context.getBean(FeignLoggerFactory.class);
assertThat(loggerFactory).isNotNull();
assertThat(loggerFactory instanceof LoggerFactoryImpl).isTrue();
Logger logger = loggerFactory.create(Object.class);
assertThat(logger).isNotNull();
assertThat(logger instanceof LoggerImpl2).isTrue();
context.close(); context.close();
} }
@@ -75,6 +51,17 @@ public class FeignLoggerFactoryTests {
} }
@Test
public void testCustomLogger() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SampleConfiguration2.class);
FeignLoggerFactory loggerFactory = context.getBean(FeignLoggerFactory.class);
assertNotNull(loggerFactory);
Logger logger = loggerFactory.create(Object.class);
assertNotNull(logger);
assertTrue(logger instanceof LoggerImpl1);
context.close();
}
@Configuration @Configuration
@Import(FeignClientsConfiguration.class) @Import(FeignClientsConfiguration.class)
protected static class SampleConfiguration2 { protected static class SampleConfiguration2 {
@@ -95,6 +82,18 @@ public class FeignLoggerFactoryTests {
} }
@Test
public void testCustomLoggerFactory() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SampleConfiguration3.class);
FeignLoggerFactory loggerFactory = context.getBean(FeignLoggerFactory.class);
assertNotNull(loggerFactory);
assertTrue(loggerFactory instanceof LoggerFactoryImpl);
Logger logger = loggerFactory.create(Object.class);
assertNotNull(logger);
assertTrue(logger instanceof LoggerImpl2);
context.close();
}
@Configuration @Configuration
@Import(FeignClientsConfiguration.class) @Import(FeignClientsConfiguration.class)
protected static class SampleConfiguration3 { protected static class SampleConfiguration3 {
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -12,21 +12,19 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
import java.lang.reflect.Field;
import javax.net.ssl.HostnameVerifier;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import java.lang.reflect.Field;
import javax.net.ssl.HostnameVerifier;
import org.junit.After; import org.junit.After;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.commons.httpclient.HttpClientConfiguration; import org.springframework.cloud.commons.httpclient.HttpClientConfiguration;
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory; import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
@@ -35,8 +33,6 @@ import org.springframework.cloud.test.ModifiedClassPathRunner;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
/** /**
* @author Ryan Baxter * @author Ryan Baxter
*/ */
@@ -48,29 +44,23 @@ public class FeignOkHttpConfigurationTests {
@Before @Before
public void setUp() { public void setUp() {
this.context = new SpringApplicationBuilder() context = new SpringApplicationBuilder().properties("debug=true","feign.httpclient.disableSslValidation=true",
.properties("debug=true", "feign.httpclient.disableSslValidation=true", "feign.okhttp.enabled=true", "feign.httpclient.enabled=false").web(false)
"feign.okhttp.enabled=true", "feign.httpclient.enabled=false") .sources(HttpClientConfiguration.class, FeignAutoConfiguration.class).run();
.web(WebApplicationType.NONE)
.sources(HttpClientConfiguration.class, FeignAutoConfiguration.class)
.run();
} }
@After @After
public void tearDown() { public void tearDown() {
if (this.context != null) { if(context != null) {
this.context.close(); context.close();
} }
} }
@Test @Test
public void disableSslTest() throws Exception { public void disableSslTest() throws Exception {
OkHttpClient httpClient = this.context.getBean(OkHttpClient.class); OkHttpClient httpClient = context.getBean(OkHttpClient.class);
HostnameVerifier hostnameVerifier = (HostnameVerifier) this.getField(httpClient, HostnameVerifier hostnameVerifier = (HostnameVerifier)this.getField(httpClient, "hostnameVerifier");
"hostnameVerifier"); Assert.assertTrue(OkHttpClientFactory.TrustAllHostnames.class.isInstance(hostnameVerifier));
assertThat(
OkHttpClientFactory.TrustAllHostnames.class.isInstance(hostnameVerifier))
.isTrue();
} }
protected <T> Object getField(Object target, String name) { protected <T> Object getField(Object target, String name) {
@@ -79,5 +69,4 @@ public class FeignOkHttpConfigurationTests {
Object value = ReflectionUtils.getField(field, target); Object value = ReflectionUtils.getField(field, target);
return value; return value;
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -23,15 +23,12 @@ import java.util.Objects;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.cloud.openfeign.test.NoSecurityConfiguration;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
@@ -41,7 +38,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/** /**
* @author Spencer Gibb * @author Spencer Gibb
@@ -60,7 +60,6 @@ public class SpringDecoderTests extends FeignClientFactoryBean {
public SpringDecoderTests() { public SpringDecoderTests() {
setName("test"); setName("test");
setContextId("test");
} }
public TestClient testClient() { public TestClient testClient() {
@@ -70,73 +69,66 @@ public class SpringDecoderTests extends FeignClientFactoryBean {
public TestClient testClient(boolean decode404) { public TestClient testClient(boolean decode404) {
setType(this.getClass()); setType(this.getClass());
setDecode404(decode404); setDecode404(decode404);
return feign(this.context).target(TestClient.class, return feign(context).target(TestClient.class, "http://localhost:" + this.port);
"http://localhost:" + this.port);
} }
@Test @Test
public void testResponseEntity() { public void testResponseEntity() {
ResponseEntity<Hello> response = testClient().getHelloResponse(); ResponseEntity<Hello> response = testClient().getHelloResponse();
assertThat(response).as("response was null").isNotNull(); assertNotNull("response was null", response);
assertThat(response.getStatusCode()).as("wrong status code") assertEquals("wrong status code", HttpStatus.OK, response.getStatusCode());
.isEqualTo(HttpStatus.OK);
Hello hello = response.getBody(); Hello hello = response.getBody();
assertThat(hello).as("hello was null").isNotNull(); assertNotNull("hello was null", hello);
assertThat(hello).as("first hello didn't match") assertEquals("first hello didn't match", new Hello("hello world via response"),
.isEqualTo(new Hello("hello world via response")); hello);
} }
@Test @Test
public void testSimpleType() { public void testSimpleType() {
Hello hello = testClient().getHello(); Hello hello = testClient().getHello();
assertThat(hello).as("hello was null").isNotNull(); assertNotNull("hello was null", hello);
assertThat(hello).as("first hello didn't match") assertEquals("first hello didn't match", new Hello("hello world 1"), hello);
.isEqualTo(new Hello("hello world 1"));
} }
@Test @Test
public void testUserParameterizedTypeDecode() { public void testUserParameterizedTypeDecode() {
List<Hello> hellos = testClient().getHellos(); List<Hello> hellos = testClient().getHellos();
assertThat(hellos).as("hellos was null").isNotNull(); assertNotNull("hellos was null", hellos);
assertThat(hellos.size()).as("hellos was not the right size").isEqualTo(2); assertEquals("hellos was not the right size", 2, hellos.size());
assertThat(hellos.get(0)).as("first hello didn't match") assertEquals("first hello didn't match", new Hello("hello world 1"),
.isEqualTo(new Hello("hello world 1")); hellos.get(0));
} }
@Test @Test
public void testSimpleParameterizedTypeDecode() { public void testSimpleParameterizedTypeDecode() {
List<String> hellos = testClient().getHelloStrings(); List<String> hellos = testClient().getHelloStrings();
assertThat(hellos).as("hellos was null").isNotNull(); assertNotNull("hellos was null", hellos);
assertThat(hellos.size()).as("hellos was not the right size").isEqualTo(2); assertEquals("hellos was not the right size", 2, hellos.size());
assertThat(hellos.get(0)).as("first hello didn't match") assertEquals("first hello didn't match", "hello world 1", hellos.get(0));
.isEqualTo("hello world 1");
} }
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void testWildcardTypeDecode() { public void testWildcardTypeDecode() {
ResponseEntity<?> wildcard = testClient().getWildcard(); ResponseEntity<?> wildcard = testClient().getWildcard();
assertThat(wildcard).as("wildcard was null").isNotNull(); assertNotNull("wildcard was null", wildcard);
assertThat(wildcard.getStatusCode()).as("wrong status code") assertEquals("wrong status code", HttpStatus.OK, wildcard.getStatusCode());
.isEqualTo(HttpStatus.OK);
Object wildcardBody = wildcard.getBody(); Object wildcardBody = wildcard.getBody();
assertThat(wildcardBody).as("wildcardBody was null").isNotNull(); assertNotNull("wildcardBody was null", wildcardBody);
assertThat(wildcardBody instanceof Map).as("wildcard not an instance of Map") assertTrue("wildcard not an instance of Map", wildcardBody instanceof Map);
.isTrue();
Map<String, String> hello = (Map<String, String>) wildcardBody; Map<String, String> hello = (Map<String, String>) wildcardBody;
assertThat(hello.get("message")).as("first hello didn't match") assertEquals("first hello didn't match", "wildcard", hello.get("message"));
.isEqualTo("wildcard");
} }
@Test @Test
public void testResponseEntityVoid() { public void testResponseEntityVoid() {
ResponseEntity<Void> response = testClient().getHelloVoid(); ResponseEntity<Void> response = testClient().getHelloVoid();
assertThat(response).as("response was null").isNotNull(); assertNotNull("response was null", response);
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(); assertNotNull("headerVals was null", headerVals);
assertThat(headerVals.size()).as("headerVals size was wrong").isEqualTo(1); assertEquals("headerVals size was wrong", 1, headerVals.size());
String header = headerVals.get(0); String header = headerVals.get(0);
assertThat(header).as("header was wrong").isEqualTo("myval"); assertEquals("header was wrong", "myval", header);
} }
@Test(expected = RuntimeException.class) @Test(expected = RuntimeException.class)
@@ -147,12 +139,43 @@ public class SpringDecoderTests extends FeignClientFactoryBean {
@Test @Test
public void testDecodes404() { public void testDecodes404() {
final ResponseEntity<String> response = testClient(true).getNotFound(); final ResponseEntity<String> response = testClient(true).getNotFound();
assertThat(response).as("response was null").isNotNull(); assertNotNull("response was null", response);
assertThat(response.getBody()).as("response body was not null").isNull(); assertNull("response body was not null", response.getBody());
}
public static class Hello {
private String message;
public Hello() {
}
public Hello(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Hello that = (Hello) o;
return Objects.equals(message, that.message);
}
@Override
public int hashCode() {
return Objects.hash(message);
}
} }
protected interface TestClient { protected interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/helloresponse") @RequestMapping(method = RequestMethod.GET, value = "/helloresponse")
ResponseEntity<Hello> getHelloResponse(); ResponseEntity<Hello> getHelloResponse();
@@ -173,51 +196,11 @@ public class SpringDecoderTests extends FeignClientFactoryBean {
@GetMapping("/helloWildcard") @GetMapping("/helloWildcard")
ResponseEntity<?> getWildcard(); ResponseEntity<?> getWildcard();
}
public static class Hello {
private String message;
public Hello() {
}
public Hello(String message) {
this.message = message;
}
public String getMessage() {
return this.message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Hello that = (Hello) o;
return Objects.equals(this.message, that.message);
}
@Override
public int hashCode() {
return Objects.hash(this.message);
}
} }
@Configuration @Configuration
@EnableAutoConfiguration @EnableAutoConfiguration
@RestController @RestController
@Import(NoSecurityConfiguration.class)
protected static class Application implements TestClient { protected static class Application implements TestClient {
@Override @Override
@@ -260,7 +243,7 @@ public class SpringDecoderTests extends FeignClientFactoryBean {
public ResponseEntity<?> getWildcard() { public ResponseEntity<?> getWildcard() {
return ResponseEntity.ok(new Hello("wildcard")); return ResponseEntity.ok(new Hello("wildcard"));
} }
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -14,64 +14,61 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
import java.util.Map; import java.util.Map;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; 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.CachingSpringLoadBalancerFactory;
import org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer; import org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer;
import org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration; import org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration;
import org.springframework.cloud.openfeign.ribbon.RetryableFeignLoadBalancer; import org.springframework.cloud.openfeign.ribbon.RetryableFeignLoadBalancer;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration;
import org.springframework.cloud.test.ClassPathExclusions; import org.springframework.cloud.test.ClassPathExclusions;
import org.springframework.cloud.test.ModifiedClassPathRunner; import org.springframework.cloud.test.ModifiedClassPathRunner;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.core.Is.is;
/** /**
* @author Ryan Baxter * @author Ryan Baxter
*/ */
@RunWith(ModifiedClassPathRunner.class) @RunWith(ModifiedClassPathRunner.class)
@ClassPathExclusions({ "spring-retry-*.jar", "spring-boot-starter-aop-*.jar" }) @ClassPathExclusions({"spring-retry-*.jar", "spring-boot-starter-aop-*.jar"})
public class SpringRetryDisabledTests { public class SpringRetryDisabledTests {
private ConfigurableApplicationContext context; private ConfigurableApplicationContext context;
@Before @Before
public void setUp() { public void setUp() {
this.context = new SpringApplicationBuilder().web(WebApplicationType.NONE) context = new SpringApplicationBuilder().web(false)
.sources(RibbonAutoConfiguration.class, .sources(RibbonAutoConfiguration.class, LoadBalancerAutoConfiguration.class, RibbonClientConfiguration.class,
LoadBalancerAutoConfiguration.class, FeignRibbonClientAutoConfiguration.class).run();
RibbonClientConfiguration.class,
FeignRibbonClientAutoConfiguration.class)
.run();
} }
@After @After
public void tearDown() { public void tearDown() {
if (this.context != null) { if(context != null) {
this.context.close(); context.close();
} }
} }
@Test @Test
public void testLoadBalancedRetryFactoryBean() throws Exception { public void testLoadBalancedRetryFactoryBean() throws Exception {
Map<String, CachingSpringLoadBalancerFactory> lbFactorys = this.context Map<String, CachingSpringLoadBalancerFactory> lbFactorys = context.getBeansOfType(CachingSpringLoadBalancerFactory.class);
.getBeansOfType(CachingSpringLoadBalancerFactory.class); assertThat(lbFactorys.values(), hasSize(1));
assertThat(lbFactorys.values()).hasSize(1); FeignLoadBalancer lb =lbFactorys.values().iterator().next().create("foo");
FeignLoadBalancer lb = lbFactorys.values().iterator().next().create("foo"); assertThat(lb, instanceOf(FeignLoadBalancer.class));
assertThat(lb).isInstanceOf(FeignLoadBalancer.class); assertThat(lb, is(not(instanceOf(RetryableFeignLoadBalancer.class))));
assertThat(lb).isNotInstanceOf(RetryableFeignLoadBalancer.class);
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,49 +17,46 @@
package org.springframework.cloud.openfeign; package org.springframework.cloud.openfeign;
import java.util.Map; import java.util.Map;
import org.hamcrest.Matchers;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration; import org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration;
import org.springframework.cloud.commons.httpclient.HttpClientConfiguration; 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.CachingSpringLoadBalancerFactory;
import org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer; import org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer;
import org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration; import org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration;
import org.springframework.cloud.openfeign.ribbon.RetryableFeignLoadBalancer; import org.springframework.cloud.openfeign.ribbon.RetryableFeignLoadBalancer;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
/** /**
* @author Ryan Baxter * @author Ryan Baxter
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { RibbonAutoConfiguration.class, @ContextConfiguration(classes = {RibbonAutoConfiguration.class, RibbonClientConfiguration.class, LoadBalancerAutoConfiguration.class,
RibbonClientConfiguration.class, LoadBalancerAutoConfiguration.class, FeignRibbonClientAutoConfiguration.class, HttpClientConfiguration.class})
FeignRibbonClientAutoConfiguration.class, HttpClientConfiguration.class })
public class SpringRetryEnabledTests implements ApplicationContextAware { public class SpringRetryEnabledTests implements ApplicationContextAware {
private ApplicationContext context; private ApplicationContext context;
@Test @Test
public void testLoadBalancedRetryFactoryBean() throws Exception { public void testLoadBalancedRetryFactoryBean() throws Exception {
Map<String, CachingSpringLoadBalancerFactory> lbFactorys = this.context Map<String, CachingSpringLoadBalancerFactory> lbFactorys = context.getBeansOfType(CachingSpringLoadBalancerFactory.class);
.getBeansOfType(CachingSpringLoadBalancerFactory.class); assertThat(lbFactorys.values(), Matchers.hasSize(1));
assertThat(lbFactorys.values()).hasSize(1); FeignLoadBalancer lb =lbFactorys.values().iterator().next().create("foo");
FeignLoadBalancer lb = lbFactorys.values().iterator().next().create("foo"); assertThat(lb, instanceOf(RetryableFeignLoadBalancer.class));
assertThat(lb).isInstanceOf(RetryableFeignLoadBalancer.class);
} }
@Override @Override
public void setApplicationContext(ApplicationContext context) throws BeansException { public void setApplicationContext(ApplicationContext context) throws BeansException {
this.context = context; this.context = context;
} }
}
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -23,7 +23,6 @@ import java.util.Objects;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@@ -43,7 +42,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/** /**
* @author Dave Syer * @author Dave Syer
@@ -73,44 +73,6 @@ public class FeignClientTests {
@Autowired @Autowired
private TestClient buildByBuilder; private TestClient buildByBuilder;
@Test
public void testAnnotations() {
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();
InvocationHandler invocationHandler = Proxy.getInvocationHandler(this.testClient);
assertThat(invocationHandler).as("invocationHandler was null").isNotNull();
}
@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(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(invocationHandler).as("invocationHandler was null").isNotNull();
}
@Configuration @Configuration
@EnableAutoConfiguration @EnableAutoConfiguration
@RestController @RestController
@@ -120,18 +82,18 @@ public class FeignClientTests {
@Bean("build-by-builder") @Bean("build-by-builder")
public TestClient buildByBuilder(final FeignClientBuilder feignClientBuilder) { public TestClient buildByBuilder(final FeignClientBuilder feignClientBuilder) {
return feignClientBuilder.forType(TestClient.class, "builderapp").build(); return feignClientBuilder
.forType(TestClient.class, "builderapp")
.build();
} }
@RequestMapping(method = RequestMethod.GET, value = "/hello") @RequestMapping(method = RequestMethod.GET, value = "/hello")
public Hello getHello() { public Hello getHello() {
return new Hello("hello world 1"); return new Hello("hello world 1");
} }
} }
public static class Hello { public static class Hello {
private String message; private String message;
public Hello() { public Hello() {
@@ -142,7 +104,7 @@ public class FeignClientTests {
} }
public String getMessage() { public String getMessage() {
return this.message; return message;
} }
public void setMessage(String message) { public void setMessage(String message) {
@@ -151,27 +113,56 @@ public class FeignClientTests {
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) { if (this == o) return true;
return true; if (o == null || getClass() != o.getClass()) return false;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Hello that = (Hello) o; Hello that = (Hello) o;
return Objects.equals(this.message, that.message); return Objects.equals(message, that.message);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return this.message != null ? this.message.hashCode() : 0; return message != null ? message.hashCode() : 0;
} }
}
@Test
public void testAnnotations() {
Map<String, Object> beans = this.context
.getBeansWithAnnotation(FeignClient.class);
assertTrue("Wrong clients: " + beans,
beans.containsKey(TestClient.class.getName()));
}
@Test
public void testClient() {
assertNotNull("testClient was null", this.testClient);
assertNotNull("extraClient was null", this.extraClient);
assertTrue("testClient is not a java Proxy",
Proxy.isProxyClass(this.testClient.getClass()));
InvocationHandler invocationHandler = Proxy.getInvocationHandler(this.testClient);
assertNotNull("invocationHandler was null", invocationHandler);
}
@Test
public void extraClient() {
assertNotNull("extraClient was null", this.extraClient);
assertTrue("extraClient is not a java Proxy",
Proxy.isProxyClass(this.extraClient.getClass()));
InvocationHandler invocationHandler = Proxy.getInvocationHandler(this.extraClient);
assertNotNull("invocationHandler was null", invocationHandler);
}
@Test
public void buildByBuilder() {
assertNotNull("buildByBuilder was null", this.buildByBuilder);
assertTrue("buildByBuilder is not a java Proxy",
Proxy.isProxyClass(this.buildByBuilder.getClass()));
InvocationHandler invocationHandler = Proxy.getInvocationHandler(this.buildByBuilder);
assertNotNull("invocationHandler was null", invocationHandler);
} }
@Configuration @Configuration
public static class TestDefaultFeignConfig { public static class TestDefaultFeignConfig {
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -23,10 +23,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
@Primary @Primary
@FeignClient("localapp") @FeignClient(value = "localapp")
public interface TestClient { public interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello(); Hello getHello();
}
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -23,8 +23,6 @@ import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(value = "otherapp", qualifier = "uniquequalifier") @FeignClient(value = "otherapp", qualifier = "uniquequalifier")
public interface TestClient { public interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello") @RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello(); Hello getHello();
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ import java.util.List;
import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server; import com.netflix.loadbalancer.Server;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@@ -29,28 +30,27 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.encoding.app.client.InvoiceClient; import org.springframework.cloud.openfeign.encoding.app.client.InvoiceClient;
import org.springframework.cloud.openfeign.encoding.app.domain.Invoice; import org.springframework.cloud.openfeign.encoding.app.domain.Invoice;
import org.springframework.cloud.openfeign.test.NoSecurityConfiguration; import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; import static org.junit.Assert.assertNotNull;
/** /**
* Tests the response compression. * Tests the response compression.
* *
* @author Jakub Narloch * @author Jakub Narloch
*/ */
@SpringBootTest(classes = FeignAcceptEncodingTests.Application.class, webEnvironment = RANDOM_PORT, value = { @SpringBootTest(classes = FeignAcceptEncodingTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT, value = {
"feign.compression.response.enabled=true" }) "feign.compression.response.enabled=true" })
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@DirtiesContext @DirtiesContext
@@ -66,19 +66,17 @@ public class FeignAcceptEncodingTests {
final ResponseEntity<List<Invoice>> invoices = this.invoiceClient.getInvoices(); final ResponseEntity<List<Invoice>> invoices = this.invoiceClient.getInvoices();
// then // then
assertThat(invoices).isNotNull(); assertNotNull(invoices);
assertThat(invoices.getStatusCode()).isEqualTo(HttpStatus.OK); assertEquals(HttpStatus.OK, invoices.getStatusCode());
assertThat(invoices.getBody()).isNotNull(); assertNotNull(invoices.getBody());
assertThat(invoices.getBody().size()).isEqualTo(100); assertEquals(100, invoices.getBody().size());
} }
@EnableFeignClients(clients = InvoiceClient.class) @EnableFeignClients(clients = InvoiceClient.class)
@RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class) @RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class)
@SpringBootApplication(scanBasePackages = "org.springframework.cloud.openfeign.encoding.app") @SpringBootApplication(scanBasePackages = "org.springframework.cloud.openfeign.encoding.app")
@Import(NoSecurityConfiguration.class)
public static class Application { public static class Application {
} }
@Configuration @Configuration
@@ -94,7 +92,5 @@ public class FeignAcceptEncodingTests {
Collections.singletonList(new Server("localhost", this.port))); Collections.singletonList(new Server("localhost", this.port)));
return balancer; return balancer;
} }
} }
}
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,40 +16,39 @@
package org.springframework.cloud.openfeign.encoding; package org.springframework.cloud.openfeign.encoding;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.Collections; import java.util.Collections;
import java.util.List; 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.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.encoding.app.client.InvoiceClient; import org.springframework.cloud.openfeign.encoding.app.client.InvoiceClient;
import org.springframework.cloud.openfeign.encoding.app.domain.Invoice; import org.springframework.cloud.openfeign.encoding.app.domain.Invoice;
import org.springframework.cloud.openfeign.test.NoSecurityConfiguration; import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.assertj.core.api.Assertions.assertThat; import com.netflix.loadbalancer.BaseLoadBalancer;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
/** /**
* Tests the response compression. * Tests the response compression.
* *
* @author Jakub Narloch * @author Jakub Narloch
*/ */
@SpringBootTest(classes = FeignContentEncodingTests.Application.class, webEnvironment = RANDOM_PORT, value = { @SpringBootTest(classes = FeignContentEncodingTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT, value = {
"feign.compression.request.enabled=true", "feign.compression.request.enabled=true",
"hystrix.command.default.execution.isolation.strategy=SEMAPHORE", "hystrix.command.default.execution.isolation.strategy=SEMAPHORE",
"ribbon.OkToRetryOnAllOperations=false" }) "ribbon.OkToRetryOnAllOperations=false" })
@@ -70,19 +69,17 @@ public class FeignContentEncodingTests {
.saveInvoices(invoices); .saveInvoices(invoices);
// then // then
assertThat(response).isNotNull(); assertNotNull(response);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertEquals(HttpStatus.OK, response.getStatusCode());
assertThat(response.getBody()).isNotNull(); assertNotNull(response.getBody());
assertThat(response.getBody().size()).isEqualTo(invoices.size()); assertEquals(invoices.size(), response.getBody().size());
} }
@EnableFeignClients(clients = InvoiceClient.class) @EnableFeignClients(clients = InvoiceClient.class)
@RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class) @RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class)
@SpringBootApplication(scanBasePackages = "org.springframework.cloud.openfeign.encoding.app") @SpringBootApplication(scanBasePackages = "org.springframework.cloud.openfeign.encoding.app")
@Import(NoSecurityConfiguration.class)
public static class Application { public static class Application {
} }
@Configuration @Configuration
@@ -98,7 +95,5 @@ public class FeignContentEncodingTests {
Collections.singletonList(new Server("localhost", this.port))); Collections.singletonList(new Server("localhost", this.port)));
return balancer; return balancer;
} }
} }
}
}
@@ -1,110 +0,0 @@
/*
* Copyright 2013-2015 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.encoding;
import java.util.Collections;
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.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClientsConfiguration;
import org.springframework.cloud.openfeign.encoding.app.client.InvoiceClient;
import org.springframework.cloud.openfeign.encoding.app.domain.Invoice;
import org.springframework.cloud.openfeign.test.NoSecurityConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.config.EnableSpringDataWebSupport;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
/**
* Tests the pagination encoding.
*
* @author Charlie Mordant.
*/
@SpringBootTest(classes = FeignPageableEncodingTests.Application.class, webEnvironment = RANDOM_PORT, value = {
"feign.compression.request.enabled=true",
"hystrix.command.default.execution.isolation.strategy=SEMAPHORE",
"ribbon.OkToRetryOnAllOperations=false" })
@RunWith(SpringJUnit4ClassRunner.class)
public class FeignPageableEncodingTests {
@Autowired
private InvoiceClient invoiceClient;
@Test
public void testPageable() {
// given
Pageable pageable = PageRequest.of(0, 10, Sort.Direction.ASC, "sortProperty");
// when
final ResponseEntity<Page<Invoice>> response = this.invoiceClient
.getInvoicesPaged(pageable);
// then
assertThat(response).isNotNull();
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull();
assertThat(pageable.getPageSize()).isEqualTo(response.getBody().getSize());
}
@EnableFeignClients(clients = InvoiceClient.class)
@RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class)
@SpringBootApplication(scanBasePackages = "org.springframework.cloud.openfeign.encoding.app")
@EnableSpringDataWebSupport
@Import({ NoSecurityConfiguration.class, FeignClientsConfiguration.class })
public static class Application {
}
@Configuration
static class LocalRibbonClientConfiguration {
@Value("${local.server.port}")
private int port = 0;
@Bean
public ILoadBalancer ribbonLoadBalancer() {
BaseLoadBalancer balancer = new BaseLoadBalancer();
balancer.setServersList(
Collections.singletonList(new Server("localhost", this.port)));
return balancer;
}
}
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,13 +16,13 @@
package org.springframework.cloud.openfeign.encoding; package org.springframework.cloud.openfeign.encoding;
import org.springframework.cloud.openfeign.encoding.app.domain.Invoice;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import org.springframework.cloud.openfeign.encoding.app.domain.Invoice;
/** /**
* Utility class used for testing. * Utility class used for testing.
* *
@@ -35,11 +35,9 @@ final class Invoices {
for (int ind = 0; ind < count; ind++) { for (int ind = 0; ind < count; ind++) {
final Invoice invoice = new Invoice(); final Invoice invoice = new Invoice();
invoice.setTitle("Invoice " + (ind + 1)); invoice.setTitle("Invoice " + (ind + 1));
invoice.setAmount(new BigDecimal( invoice.setAmount(new BigDecimal(String.format(Locale.US, "%.2f", Math.random() * 1000)));
String.format(Locale.US, "%.2f", Math.random() * 1000)));
invoices.add(invoice); invoices.add(invoice);
} }
return invoices; return invoices;
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@ import java.util.List;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.encoding.app.domain.Invoice; import org.springframework.cloud.openfeign.encoding.app.domain.Invoice;
import org.springframework.data.domain.Page;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@@ -34,14 +33,9 @@ import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient("local") @FeignClient("local")
public interface InvoiceClient { 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 = "invoices", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @RequestMapping(value = "invoices", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<List<Invoice>> getInvoices(); ResponseEntity<List<Invoice>> getInvoices();
@RequestMapping(value = "invoices", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = 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); ResponseEntity<List<Invoice>> saveInvoices(List<Invoice> invoices);
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@ public class Invoice {
private BigDecimal amount; private BigDecimal amount;
public String getTitle() { public String getTitle() {
return this.title; return title;
} }
public void setTitle(String title) { public void setTitle(String title) {
@@ -38,11 +38,10 @@ public class Invoice {
} }
public BigDecimal getAmount() { public BigDecimal getAmount() {
return this.amount; return amount;
} }
public void setAmount(BigDecimal amount) { public void setAmount(BigDecimal amount) {
this.amount = amount; this.amount = amount;
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,14 +16,7 @@
package org.springframework.cloud.openfeign.encoding.app.resource; package org.springframework.cloud.openfeign.encoding.app.resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.springframework.cloud.openfeign.encoding.app.domain.Invoice; import org.springframework.cloud.openfeign.encoding.app.domain.Invoice;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@@ -31,6 +24,11 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/** /**
* An sample REST controller, that potentially returns large response - used for testing. * An sample REST controller, that potentially returns large response - used for testing.
* *
@@ -45,30 +43,21 @@ public class InvoiceResource {
return ResponseEntity.ok(createInvoiceList(100)); return ResponseEntity.ok(createInvoiceList(100));
} }
@RequestMapping(value = "invoices", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = 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) { ResponseEntity<List<Invoice>> saveInvoices(@RequestBody List<Invoice> invoices) {
return ResponseEntity.ok(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);
return ResponseEntity.ok(page);
}
private List<Invoice> createInvoiceList(int count) { private List<Invoice> createInvoiceList(int count) {
final List<Invoice> invoices = new ArrayList<>(); final List<Invoice> invoices = new ArrayList<>();
for (int ind = 0; ind < count; ind++) { for (int ind = 0; ind < count; ind++) {
final Invoice invoice = new Invoice(); final Invoice invoice = new Invoice();
invoice.setTitle("Invoice " + (ind + 1)); invoice.setTitle("Invoice " + (ind + 1));
invoice.setAmount(new BigDecimal( invoice.setAmount(new BigDecimal(String.format(Locale.US, "%.2f", Math.random() * 1000)));
String.format(Locale.US, "%.2f", Math.random() * 1000)));
invoices.add(invoice); invoices.add(invoice);
} }
return invoices; return invoices;
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2012-2013 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@ package org.springframework.cloud.openfeign.encoding.proto;
import feign.RequestTemplate; import feign.RequestTemplate;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
@@ -28,8 +27,6 @@ import org.springframework.cloud.test.ClassPathExclusions;
import org.springframework.cloud.test.ModifiedClassPathRunner; import org.springframework.cloud.test.ModifiedClassPathRunner;
import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter;
import static feign.Request.HttpMethod.POST;
/** /**
* Test {@link SpringEncoder} when protobuf is not in classpath * Test {@link SpringEncoder} when protobuf is not in classpath
* *
@@ -39,17 +36,17 @@ import static feign.Request.HttpMethod.POST;
@ClassPathExclusions("protobuf-*.jar") @ClassPathExclusions("protobuf-*.jar")
public class ProtobufNotInClasspathTest { public class ProtobufNotInClasspathTest {
@Test @Test
public void testEncodeWhenProtobufNotInClasspath() { public void testEncodeWhenProtobufNotInClasspath() {
ObjectFactory<HttpMessageConverters> converters = new ObjectFactory<HttpMessageConverters>() { ObjectFactory<HttpMessageConverters> converters = new ObjectFactory<HttpMessageConverters>() {
@Override @Override
public HttpMessageConverters getObject() throws BeansException { public HttpMessageConverters getObject() throws BeansException {
return new HttpMessageConverters(new StringHttpMessageConverter()); return new HttpMessageConverters(new StringHttpMessageConverter());
} }
}; };
RequestTemplate requestTemplate = new RequestTemplate(); RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.method(POST); requestTemplate.method("POST");
new SpringEncoder(converters).encode("a=b", String.class, requestTemplate); new SpringEncoder(converters).encode("a=b", String.class, requestTemplate);
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2012-2013 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,14 +16,6 @@
package org.springframework.cloud.openfeign.encoding.proto; package org.springframework.cloud.openfeign.encoding.proto;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import feign.RequestTemplate; import feign.RequestTemplate;
import feign.httpclient.ApacheHttpClient; import feign.httpclient.ApacheHttpClient;
@@ -35,25 +27,27 @@ import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.message.BasicHttpResponse; import org.apache.http.message.BasicHttpResponse;
import org.apache.http.message.BasicStatusLine; import org.apache.http.message.BasicStatusLine;
import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.mockito.BDDMockito; import org.mockito.BDDMockito;
import org.mockito.Matchers;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringEncoder; import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter; import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter;
import static feign.Request.Body.encoded; import java.io.IOException;
import static feign.Request.HttpMethod.POST; import java.io.InputStream;
import static org.assertj.core.api.Assertions.assertThat; import java.net.URISyntaxException;
import static org.assertj.core.api.Assertions.fail; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/** /**
* Test {@link SpringEncoder} with {@link ProtobufHttpMessageConverter} * Test {@link SpringEncoder} with {@link ProtobufHttpMessageConverter}
@@ -63,92 +57,83 @@ import static org.assertj.core.api.Assertions.fail;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class ProtobufSpringEncoderTest { public class ProtobufSpringEncoderTest {
@Mock @Mock
private HttpClient httpClient; private HttpClient httpClient;
// a protobuf object with some content // a protobuf object with some content
private org.springframework.cloud.openfeign.encoding.proto.Request request = org.springframework.cloud.openfeign.encoding.proto.Request private org.springframework.cloud.openfeign.encoding.proto.Request request = org.springframework.cloud.openfeign.encoding.proto.Request.newBuilder()
.newBuilder().setId(1000000).setMsg("Erlang/OTP 最初是爱立信为开发电信设备系统设计的编程语言平台," .setId(1000000)
+ "电信设备(路由器、接入网关、…)典型设计是通过背板连接主控板卡与多块业务板卡的分布式系统。") .setMsg("Erlang/OTP 最初是爱立信为开发电信设备系统设计的编程语言平台," +
.build(); "电信设备(路由器、接入网关、…)典型设计是通过背板连接主控板卡与多块业务板卡的分布式系统。")
.build();
@Test @Test
public void testProtobuf() throws IOException, URISyntaxException { public void testProtobuf() throws IOException, URISyntaxException {
// protobuf convert to request by feign and ProtobufHttpMessageConverter // protobuf convert to request by feign and ProtobufHttpMessageConverter
RequestTemplate requestTemplate = newRequestTemplate(); RequestTemplate requestTemplate = newRequestTemplate();
newEncoder().encode(this.request, Request.class, requestTemplate); newEncoder().encode(request, Request.class, requestTemplate);
HttpEntity entity = toApacheHttpEntity(requestTemplate); HttpEntity entity = toApacheHttpEntity(requestTemplate);
byte[] bytes = read(entity.getContent(), (int) entity.getContentLength()); byte[] bytes = read(entity.getContent(), (int) entity.getContentLength());
assertThat(this.request.toByteArray()).isEqualTo(bytes); Assert.assertArrayEquals(bytes, request.toByteArray());
org.springframework.cloud.openfeign.encoding.proto.Request copy = org.springframework.cloud.openfeign.encoding.proto.Request org.springframework.cloud.openfeign.encoding.proto.Request copy = org.springframework.cloud.openfeign.encoding.proto.Request.parseFrom(bytes);
.parseFrom(bytes); Assert.assertEquals(request, copy);
assertThat(copy).isEqualTo(this.request); }
}
@Test @Test
public void testProtobufWithCharsetWillFail() throws IOException, URISyntaxException { public void testProtobufWithCharsetWillFail() throws IOException, URISyntaxException {
// protobuf convert to request by feign and ProtobufHttpMessageConverter // protobuf convert to request by feign and ProtobufHttpMessageConverter
RequestTemplate requestTemplate = newRequestTemplate(); RequestTemplate requestTemplate = newRequestTemplate();
newEncoder().encode(this.request, Request.class, requestTemplate); newEncoder().encode(request, Request.class, requestTemplate);
// set a charset // set a charset
requestTemplate.body( requestTemplate.body(requestTemplate.body(), StandardCharsets.UTF_8);
encoded(requestTemplate.requestBody().asBytes(), StandardCharsets.UTF_8)); HttpEntity entity = toApacheHttpEntity(requestTemplate);
HttpEntity entity = toApacheHttpEntity(requestTemplate); byte[] bytes = read(entity.getContent(), (int) entity.getContentLength());
byte[] bytes = read(entity.getContent(), (int) entity.getContentLength());
// http request-body is different with original protobuf body // http request-body is different with original protobuf body
assertThat(this.request.toByteArray().length).isNotEqualTo(bytes.length); Assert.assertNotEquals(bytes.length, request.toByteArray().length);
try { try {
org.springframework.cloud.openfeign.encoding.proto.Request copy = org.springframework.cloud.openfeign.encoding.proto.Request org.springframework.cloud.openfeign.encoding.proto.Request copy = org.springframework.cloud.openfeign.encoding.proto.Request.parseFrom(bytes);
.parseFrom(bytes); Assert.fail("Expected an InvalidProtocolBufferException to be thrown");
fail("Expected an InvalidProtocolBufferException to be thrown"); } catch (InvalidProtocolBufferException e) {
} // success
catch (InvalidProtocolBufferException e) { }
// success }
}
}
private SpringEncoder newEncoder() { private SpringEncoder newEncoder() {
ObjectFactory<HttpMessageConverters> converters = new ObjectFactory<HttpMessageConverters>() { ObjectFactory<HttpMessageConverters> converters = new ObjectFactory<HttpMessageConverters>() {
@Override @Override
public HttpMessageConverters getObject() throws BeansException { public HttpMessageConverters getObject() throws BeansException {
return new HttpMessageConverters(new ProtobufHttpMessageConverter()); return new HttpMessageConverters(new ProtobufHttpMessageConverter());
} }
}; };
return new SpringEncoder(converters); return new SpringEncoder(converters);
} }
private RequestTemplate newRequestTemplate() { private RequestTemplate newRequestTemplate() {
RequestTemplate requestTemplate = new RequestTemplate(); RequestTemplate requestTemplate = new RequestTemplate();
requestTemplate.method(POST); requestTemplate.method("POST");
return requestTemplate; return requestTemplate;
} }
private HttpEntity toApacheHttpEntity(RequestTemplate requestTemplate) private HttpEntity toApacheHttpEntity(RequestTemplate requestTemplate) throws IOException, URISyntaxException {
throws IOException, URISyntaxException { final List<HttpUriRequest> request = new ArrayList<>(1);
final List<HttpUriRequest> request = new ArrayList<>(1); BDDMockito.given(httpClient.execute(Matchers.<HttpUriRequest>any())).will(new Answer<HttpResponse>() {
BDDMockito.given(this.httpClient.execute(ArgumentMatchers.<HttpUriRequest>any())) @Override
.will(new Answer<HttpResponse>() { public HttpResponse answer(InvocationOnMock invocationOnMock) throws Throwable {
@Override request.add((HttpUriRequest) invocationOnMock.getArguments()[0]);
public HttpResponse answer(InvocationOnMock invocationOnMock) return new BasicHttpResponse(new BasicStatusLine(new ProtocolVersion("http", 1, 1), 200, null));
throws Throwable { }
request.add((HttpUriRequest) invocationOnMock.getArguments()[0]); });
return new BasicHttpResponse(new BasicStatusLine( new ApacheHttpClient(httpClient).execute(requestTemplate.request(), new feign.Request.Options());
new ProtocolVersion("http", 1, 1), 200, null)); HttpUriRequest httpUriRequest = request.get(0);
} return ((HttpEntityEnclosingRequestBase)httpUriRequest).getEntity();
}); }
new ApacheHttpClient(this.httpClient).execute(
requestTemplate.resolve(new HashMap<>()).request(),
new feign.Request.Options());
HttpUriRequest httpUriRequest = request.get(0);
return ((HttpEntityEnclosingRequestBase) httpUriRequest).getEntity();
}
private byte[] read(InputStream in, int length) throws IOException { private byte[] read(InputStream in, int length) throws IOException {
byte[] bytes = new byte[length]; byte[] bytes = new byte[length];
in.read(bytes); in.read(bytes);
return bytes; return bytes;
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2012-2013 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -20,48 +20,53 @@
package org.springframework.cloud.openfeign.encoding.proto; package org.springframework.cloud.openfeign.encoding.proto;
public final class ProtobufTest { public final class ProtobufTest {
private ProtobufTest() {}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistryLite registry) {
}
static final com.google.protobuf.Descriptors.Descriptor internal_static_Request_descriptor; public static void registerAllExtensions(
static final com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internal_static_Request_fieldAccessorTable; com.google.protobuf.ExtensionRegistry registry) {
registerAllExtensions(
(com.google.protobuf.ExtensionRegistryLite) registry);
}
static final com.google.protobuf.Descriptors.Descriptor
internal_static_Request_descriptor;
static final
com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
internal_static_Request_fieldAccessorTable;
private static com.google.protobuf.Descriptors.FileDescriptor descriptor; public static com.google.protobuf.Descriptors.FileDescriptor
getDescriptor() {
static { return descriptor;
String[] descriptorData = { }
"\n\023protobuf_test.proto\"\"\n\007Request\022\n\n\002id\030\001" private static com.google.protobuf.Descriptors.FileDescriptor
+ " \001(\005\022\013\n\003msg\030\002 \001(\tB\024\n\020feign.httpclientP\001b" descriptor;
+ "\006proto3" }; static {
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { String[] descriptorData = {
public com.google.protobuf.ExtensionRegistry assignDescriptors( "\n\023protobuf_test.proto\"\"\n\007Request\022\n\n\002id\030\001" +
com.google.protobuf.Descriptors.FileDescriptor root) { " \001(\005\022\013\n\003msg\030\002 \001(\tB\024\n\020feign.httpclientP\001b" +
descriptor = root; "\006proto3"
return null; };
} com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
}; new com.google.protobuf.Descriptors.FileDescriptor. InternalDescriptorAssigner() {
com.google.protobuf.Descriptors.FileDescriptor.internalBuildGeneratedFileFrom( public com.google.protobuf.ExtensionRegistry assignDescriptors(
descriptorData, new com.google.protobuf.Descriptors.FileDescriptor[] {}, com.google.protobuf.Descriptors.FileDescriptor root) {
assigner); descriptor = root;
internal_static_Request_descriptor = getDescriptor().getMessageTypes().get(0); return null;
internal_static_Request_fieldAccessorTable = new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( }
internal_static_Request_descriptor, new String[] { "Id", "Msg", }); };
} com.google.protobuf.Descriptors.FileDescriptor
.internalBuildGeneratedFileFrom(descriptorData,
private ProtobufTest() { new com.google.protobuf.Descriptors.FileDescriptor[] {
} }, assigner);
internal_static_Request_descriptor =
public static void registerAllExtensions( getDescriptor().getMessageTypes().get(0);
com.google.protobuf.ExtensionRegistryLite registry) { internal_static_Request_fieldAccessorTable = new
} com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
internal_static_Request_descriptor,
public static void registerAllExtensions( new String[] { "Id", "Msg", });
com.google.protobuf.ExtensionRegistry registry) { }
registerAllExtensions((com.google.protobuf.ExtensionRegistryLite) registry);
}
public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() {
return descriptor;
}
// @@protoc_insertion_point(outer_class_scope)
// @@protoc_insertion_point(outer_class_scope)
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2012-2013 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -20,22 +20,21 @@
package org.springframework.cloud.openfeign.encoding.proto; package org.springframework.cloud.openfeign.encoding.proto;
public interface RequestOrBuilder extends public interface RequestOrBuilder extends
// @@protoc_insertion_point(interface_extends:Request) // @@protoc_insertion_point(interface_extends:Request)
com.google.protobuf.MessageOrBuilder { com.google.protobuf.MessageOrBuilder {
/** /**
* <code>int32 id = 1;</code> * <code>int32 id = 1;</code>
*/ */
int getId(); int getId();
/**
* <code>string msg = 2;</code>
*/
String getMsg();
/**
* <code>string msg = 2;</code>
*/
com.google.protobuf.ByteString getMsgBytes();
/**
* <code>string msg = 2;</code>
*/
String getMsg();
/**
* <code>string msg = 2;</code>
*/
com.google.protobuf.ByteString
getMsgBytes();
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,45 +16,17 @@
package org.springframework.cloud.openfeign.hystrix.security; package org.springframework.cloud.openfeign.hystrix.security;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.hystrix.security.app.CustomConcurrenyStrategy;
import org.springframework.cloud.openfeign.hystrix.security.app.ProxyUsernameController;
import org.springframework.cloud.openfeign.hystrix.security.app.TestInterceptor;
import org.springframework.cloud.openfeign.hystrix.security.app.UsernameClient; import org.springframework.cloud.openfeign.hystrix.security.app.UsernameClient;
import org.springframework.cloud.openfeign.hystrix.security.app.UsernameController;
import org.springframework.cloud.openfeign.test.NoSecurityConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/** /**
* @author Daniel Lavoie * @author Daniel Lavoie
*/ */
@Configuration @Configuration
@EnableAutoConfiguration @SpringBootApplication
@EnableFeignClients(clients = UsernameClient.class) @EnableFeignClients(clients = UsernameClient.class)
@Import(NoSecurityConfiguration.class)
public class HystrixSecurityApplication { public class HystrixSecurityApplication {
@Bean
public CustomConcurrenyStrategy customConcurrenyStrategy() {
return new CustomConcurrenyStrategy();
}
@Bean
public TestInterceptor testInterceptor() {
return new TestInterceptor();
}
@Bean
public ProxyUsernameController proxyUsernameController() {
return new ProxyUsernameController();
}
@Bean
public UsernameController usernameController() {
return new UsernameController();
}
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -17,61 +17,73 @@
package org.springframework.cloud.openfeign.hystrix.security; package org.springframework.cloud.openfeign.hystrix.security;
import java.util.Base64; import java.util.Base64;
import org.junit.Assert;
import com.netflix.hystrix.strategy.HystrixPlugins;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.LocalServerPort; import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.cloud.netflix.hystrix.security.SecurityContextConcurrencyStrategy; import org.springframework.cloud.netflix.hystrix.security.SecurityContextConcurrencyStrategy;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.netflix.ribbon.StaticServerList;
import org.springframework.cloud.openfeign.hystrix.security.app.CustomConcurrenyStrategy; import org.springframework.cloud.openfeign.hystrix.security.app.CustomConcurrenyStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import com.netflix.hystrix.strategy.HystrixPlugins;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Tests that a secured web service returning values using a feign client properly access * Tests that a secured web service returning values using a feign client properly access
* the security context from a hystrix command. * the security context from a hystrix command.
*
* @author Daniel Lavoie * @author Daniel Lavoie
*/ */
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@DirtiesContext @DirtiesContext
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = { @SpringBootTest(classes = HystrixSecurityApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT,
"feign.hystrix.enabled=true" }) properties = { "username.ribbon.listOfServers=localhost:${local.server.port}",
"feign.hystrix.enabled=true"})
@ActiveProfiles("proxysecurity") @ActiveProfiles("proxysecurity")
public class HystrixSecurityTests { public class HystrixSecurityTests {
@Autowired @Autowired
private CustomConcurrenyStrategy customConcurrenyStrategy; private CustomConcurrenyStrategy customConcurrenyStrategy;
@LocalServerPort @LocalServerPort
private String serverPort; private String serverPort;
// TODO: move to constants in TestAutoConfiguration //TODOO: move to constants in TestAutoConfiguration
private String username = "user"; private String username = "user";
private String password = "password"; private String password = "password";
@Test
public void testSecurityConcurrencyStrategyInstalled() {
HystrixConcurrencyStrategy concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
assertThat(concurrencyStrategy).isInstanceOf(SecurityContextConcurrencyStrategy.class);
}
@Test
public void testFeignHystrixSecurity() {
HttpHeaders headers = HystrixSecurityTests.createBasicAuthHeader(username,
password);
String usernameResult = new RestTemplate()
.exchange("http://localhost:" + serverPort + "/proxy-username",
HttpMethod.GET, new HttpEntity<Void>(headers), String.class)
.getBody();
Assert.assertTrue("Username should have been intercepted by feign interceptor.",
username.equals(usernameResult));
Assert.assertTrue("Custom hook should have been called.",
customConcurrenyStrategy.isHookCalled());
}
public static HttpHeaders createBasicAuthHeader(final String username, public static HttpHeaders createBasicAuthHeader(final String username,
final String password) { final String password) {
return new HttpHeaders() { return new HttpHeaders() {
@@ -85,50 +97,4 @@ public class HystrixSecurityTests {
} }
}; };
} }
@Test
public void testSecurityConcurrencyStrategyInstalled() {
HystrixConcurrencyStrategy concurrencyStrategy = HystrixPlugins.getInstance()
.getConcurrencyStrategy();
assertThat(concurrencyStrategy)
.isInstanceOf(SecurityContextConcurrencyStrategy.class);
}
@Test
public void testFeignHystrixSecurity() {
HttpHeaders headers = createBasicAuthHeader(this.username, this.password);
ResponseEntity<String> entity = new RestTemplate().exchange(
"http://localhost:" + this.serverPort + "/proxy-username", HttpMethod.GET,
new HttpEntity<Void>(headers), String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getBody())
.as("Username should have been intercepted by feign interceptor.")
.isEqualTo(this.username);
assertThat(this.customConcurrenyStrategy.isHookCalled())
.as("Custom hook should have been called.").isTrue();
}
@SpringBootConfiguration
@Import(HystrixSecurityApplication.class)
@RibbonClient(name = "username", configuration = LocalRibbonClientConfiguration.class)
protected static class TestConfig {
}
protected static class LocalRibbonClientConfiguration {
@LocalServerPort
private int port = 0;
@Bean
public ServerList<Server> ribbonServerList() {
return new StaticServerList<>(new Server("localhost", this.port));
}
}
} }
@@ -1,27 +1,11 @@
/*
* Copyright 2013-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.hystrix.security.app; package org.springframework.cloud.openfeign.hystrix.security.app;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import org.springframework.stereotype.Component;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
@Component
public class CustomConcurrenyStrategy extends HystrixConcurrencyStrategy { public class CustomConcurrenyStrategy extends HystrixConcurrencyStrategy {
private boolean hookCalled; private boolean hookCalled;
@Override @Override
@@ -32,7 +16,6 @@ public class CustomConcurrenyStrategy extends HystrixConcurrencyStrategy {
} }
public boolean isHookCalled() { public boolean isHookCalled() {
return this.hookCalled; return hookCalled;
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -26,13 +26,11 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequestMapping("/proxy-username") @RequestMapping("/proxy-username")
public class ProxyUsernameController { public class ProxyUsernameController {
@Autowired @Autowired
private UsernameClient usernameClient; private UsernameClient usernameClient;
@RequestMapping @RequestMapping
public String getUsername() { public String getUsername() {
return this.usernameClient.getUsername(); return usernameClient.getUsername();
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -20,21 +20,21 @@ import feign.RequestInterceptor;
import feign.RequestTemplate; import feign.RequestTemplate;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
/** /**
* This interceptor should be called from an Hyxtrix command execution thread. It is * This interceptor should be called from an Hyxtrix command execution thread. It is
* access the SecurityContext and settings an http header from the authentication details. * access the SecurityContext and settings an http header from the authentication details.
* *
* @author Daniel Lavoie * @author Daniel Lavoie
*/ */
@Component
public class TestInterceptor implements RequestInterceptor { public class TestInterceptor implements RequestInterceptor {
@Override @Override
public void apply(RequestTemplate template) { public void apply(RequestTemplate template) {
if (SecurityContextHolder.getContext().getAuthentication() != null) { if (SecurityContextHolder.getContext().getAuthentication() != null)
template.header("username", template.header("username",
SecurityContextHolder.getContext().getAuthentication().getName()); SecurityContextHolder.getContext().getAuthentication().getName());
}
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -26,6 +26,5 @@ import org.springframework.web.bind.annotation.RequestMapping;
public interface UsernameClient { public interface UsernameClient {
@RequestMapping("/username") @RequestMapping("/username")
String getUsername(); public String getUsername();
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2016 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -26,10 +26,8 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequestMapping("/username") @RequestMapping("/username")
public class UsernameController { public class UsernameController {
@RequestMapping @RequestMapping
public String getUsername(@RequestHeader String username) { public String getUsername(@RequestHeader String username){
return username; return username;
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,17 +19,13 @@ package org.springframework.cloud.openfeign.invalid;
import feign.Feign; import feign.Feign;
import feign.hystrix.FallbackFactory; import feign.hystrix.FallbackFactory;
import feign.hystrix.HystrixFeign; import feign.hystrix.HystrixFeign;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
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.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignAutoConfiguration; import org.springframework.cloud.openfeign.FeignAutoConfiguration;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@@ -37,7 +33,7 @@ import org.springframework.context.annotation.Import;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertNotNull;
/** /**
* @author Dave Syer * @author Dave Syer
@@ -53,81 +49,6 @@ public class FeignClientValidationTests {
new AnnotationConfigApplicationContext(NameAndValueConfiguration.class); new AnnotationConfigApplicationContext(NameAndValueConfiguration.class);
} }
@Test
public void testServiceIdAndValue() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
LoadBalancerAutoConfiguration.class, RibbonAutoConfiguration.class,
FeignRibbonClientAutoConfiguration.class,
NameAndServiceIdConfiguration.class);
assertThat(context.getBean(NameAndServiceIdConfiguration.Client.class))
.isNotNull();
context.close();
}
@Test
public void testDuplicatedClientNames() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.setAllowBeanDefinitionOverriding(false);
context.register(LoadBalancerAutoConfiguration.class,
RibbonAutoConfiguration.class, FeignRibbonClientAutoConfiguration.class,
DuplicatedFeignClientNamesConfiguration.class);
context.refresh();
assertThat(
context.getBean(DuplicatedFeignClientNamesConfiguration.FooClient.class))
.isNotNull();
assertThat(
context.getBean(DuplicatedFeignClientNamesConfiguration.BarClient.class))
.isNotNull();
context.close();
}
@Test
public void testNotLegalHostname() {
this.expected.expectMessage("not legal hostname (foo_bar)");
new AnnotationConfigApplicationContext(BadHostnameConfiguration.class);
}
@Test
public void testMissingFallback() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
MissingFallbackConfiguration.class)) {
this.expected.expectMessage("No fallback instance of type");
assertThat(context.getBean(MissingFallbackConfiguration.Client.class))
.isNotNull();
}
}
@Test
public void testWrongFallbackType() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
WrongFallbackTypeConfiguration.class)) {
this.expected.expectMessage("Incompatible fallback instance");
assertThat(context.getBean(WrongFallbackTypeConfiguration.Client.class))
.isNotNull();
}
}
@Test
public void testMissingFallbackFactory() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
MissingFallbackFactoryConfiguration.class)) {
this.expected.expectMessage("No fallbackFactory instance of type");
assertThat(context.getBean(MissingFallbackFactoryConfiguration.Client.class))
.isNotNull();
}
}
@Test
public void testWrongFallbackFactoryType() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
WrongFallbackFactoryTypeConfiguration.class)) {
this.expected.expectMessage("Incompatible fallbackFactory instance");
assertThat(
context.getBean(WrongFallbackFactoryTypeConfiguration.Client.class))
.isNotNull();
}
}
@Configuration @Configuration
@Import(FeignAutoConfiguration.class) @Import(FeignAutoConfiguration.class)
@EnableFeignClients(clients = NameAndValueConfiguration.Client.class) @EnableFeignClients(clients = NameAndValueConfiguration.Client.class)
@@ -135,53 +56,38 @@ public class FeignClientValidationTests {
@FeignClient(value = "foo", name = "bar") @FeignClient(value = "foo", name = "bar")
interface Client { interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/") @RequestMapping(method = RequestMethod.GET, value = "/")
String get(); String get();
} }
} }
@Test
public void testServiceIdAndValue() {
this.expected.expectMessage("only one is permitted");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
NameAndValueConfiguration.class);
assertNotNull(context.getBean(NameAndServiceIdConfiguration.Client.class));
context.close();
}
@Configuration @Configuration
@Import({ FeignAutoConfiguration.class, HttpClientConfiguration.class }) @Import(FeignAutoConfiguration.class)
@EnableFeignClients(clients = NameAndServiceIdConfiguration.Client.class) @EnableFeignClients(clients = NameAndServiceIdConfiguration.Client.class)
protected static class NameAndServiceIdConfiguration { protected static class NameAndServiceIdConfiguration {
@FeignClient(name = "bar", serviceId = "foo") @FeignClient(serviceId = "foo", name = "bar")
interface Client { interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/") @RequestMapping(method = RequestMethod.GET, value = "/")
String get(); String get();
} }
} }
@Configuration @Test
public void testNotLegalHostname() {
@Import({ FeignAutoConfiguration.class, HttpClientConfiguration.class }) this.expected.expectMessage("not legal hostname (foo_bar)");
@EnableFeignClients(clients = { new AnnotationConfigApplicationContext(BadHostnameConfiguration.class);
DuplicatedFeignClientNamesConfiguration.FooClient.class,
DuplicatedFeignClientNamesConfiguration.BarClient.class })
protected static class DuplicatedFeignClientNamesConfiguration {
@FeignClient(contextId = "foo", name = "bar")
interface FooClient {
@RequestMapping(method = RequestMethod.GET, value = "/")
String get();
}
@FeignClient(name = "bar")
interface BarClient {
@RequestMapping(method = RequestMethod.GET, value = "/")
String get();
}
} }
@Configuration @Configuration
@@ -191,41 +97,53 @@ public class FeignClientValidationTests {
@FeignClient("foo_bar") @FeignClient("foo_bar")
interface Client { interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/") @RequestMapping(method = RequestMethod.GET, value = "/")
String get(); String get();
} }
} }
@Test
public void testMissingFallback() {
try (
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
MissingFallbackConfiguration.class)) {
this.expected.expectMessage("No fallback instance of type");
assertNotNull(context.getBean(MissingFallbackConfiguration.Client.class));
}
}
@Configuration @Configuration
@Import(FeignAutoConfiguration.class) @Import(FeignAutoConfiguration.class)
@EnableFeignClients(clients = MissingFallbackConfiguration.Client.class) @EnableFeignClients(clients = MissingFallbackConfiguration.Client.class)
protected static class MissingFallbackConfiguration { protected static class MissingFallbackConfiguration {
@Bean
public Feign.Builder feignBuilder() {
return HystrixFeign.builder();
}
@FeignClient(name = "foobar", url = "http://localhost", fallback = ClientFallback.class) @FeignClient(name = "foobar", url = "http://localhost", fallback = ClientFallback.class)
interface Client { interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/") @RequestMapping(method = RequestMethod.GET, value = "/")
String get(); String get();
} }
class ClientFallback implements Client { class ClientFallback implements Client {
@Override @Override
public String get() { public String get() {
return null; return null;
} }
} }
@Bean
public Feign.Builder feignBuilder() {
return HystrixFeign.builder();
}
}
@Test
public void testWrongFallbackType() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
WrongFallbackTypeConfiguration.class)) {
this.expected.expectMessage("Incompatible fallback instance");
assertNotNull(context.getBean(WrongFallbackTypeConfiguration.Client.class));
}
} }
@Configuration @Configuration
@@ -233,28 +151,34 @@ public class FeignClientValidationTests {
@EnableFeignClients(clients = WrongFallbackTypeConfiguration.Client.class) @EnableFeignClients(clients = WrongFallbackTypeConfiguration.Client.class)
protected static class WrongFallbackTypeConfiguration { protected static class WrongFallbackTypeConfiguration {
@FeignClient(name = "foobar", url = "http://localhost", fallback = Dummy.class)
interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/")
String get();
}
@Bean @Bean
Dummy dummy() { Dummy dummy() {
return new Dummy(); return new Dummy();
} }
class Dummy {
}
@Bean @Bean
public Feign.Builder feignBuilder() { public Feign.Builder feignBuilder() {
return HystrixFeign.builder(); return HystrixFeign.builder();
} }
@FeignClient(name = "foobar", url = "http://localhost", fallback = Dummy.class) }
interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/")
String get();
@Test
public void testMissingFallbackFactory() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
MissingFallbackFactoryConfiguration.class)) {
this.expected.expectMessage("No fallbackFactory instance of type");
assertNotNull(context.getBean(MissingFallbackFactoryConfiguration.Client.class));
} }
class Dummy {
}
} }
@Configuration @Configuration
@@ -262,17 +186,10 @@ public class FeignClientValidationTests {
@EnableFeignClients(clients = MissingFallbackFactoryConfiguration.Client.class) @EnableFeignClients(clients = MissingFallbackFactoryConfiguration.Client.class)
protected static class MissingFallbackFactoryConfiguration { protected static class MissingFallbackFactoryConfiguration {
@Bean
public Feign.Builder feignBuilder() {
return HystrixFeign.builder();
}
@FeignClient(name = "foobar", url = "http://localhost", fallbackFactory = ClientFallback.class) @FeignClient(name = "foobar", url = "http://localhost", fallbackFactory = ClientFallback.class)
interface Client { interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/") @RequestMapping(method = RequestMethod.GET, value = "/")
String get(); String get();
} }
class ClientFallback implements FallbackFactory<Client> { class ClientFallback implements FallbackFactory<Client> {
@@ -281,9 +198,21 @@ public class FeignClientValidationTests {
public Client create(Throwable cause) { public Client create(Throwable cause) {
return null; return null;
} }
} }
@Bean
public Feign.Builder feignBuilder() {
return HystrixFeign.builder();
}
}
@Test
public void testWrongFallbackFactoryType() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
WrongFallbackFactoryTypeConfiguration.class)) {
this.expected.expectMessage("Incompatible fallbackFactory instance");
assertNotNull(context.getBean(WrongFallbackFactoryTypeConfiguration.Client.class));
}
} }
@Configuration @Configuration
@@ -291,28 +220,24 @@ public class FeignClientValidationTests {
@EnableFeignClients(clients = WrongFallbackFactoryTypeConfiguration.Client.class) @EnableFeignClients(clients = WrongFallbackFactoryTypeConfiguration.Client.class)
protected static class WrongFallbackFactoryTypeConfiguration { protected static class WrongFallbackFactoryTypeConfiguration {
@FeignClient(name = "foobar", url = "http://localhost", fallbackFactory = Dummy.class)
interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/")
String get();
}
@Bean @Bean
Dummy dummy() { Dummy dummy() {
return new Dummy(); return new Dummy();
} }
class Dummy {
}
@Bean @Bean
public Feign.Builder feignBuilder() { public Feign.Builder feignBuilder() {
return HystrixFeign.builder(); return HystrixFeign.builder();
} }
@FeignClient(name = "foobar", url = "http://localhost", fallbackFactory = Dummy.class)
interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/")
String get();
}
class Dummy {
}
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2013-2019 the original author or authors. * Copyright 2013-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -23,11 +23,10 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryFactory; import org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryFactory;
import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -57,13 +56,13 @@ public class CachingSpringLoadBalancerFactoryTests {
when(this.delegate.getClientConfig("client2")).thenReturn(config); when(this.delegate.getClientConfig("client2")).thenReturn(config);
this.factory = new CachingSpringLoadBalancerFactory(this.delegate, this.factory = new CachingSpringLoadBalancerFactory(this.delegate,
this.loadBalancedRetryFactory); loadBalancedRetryFactory);
} }
@Test @Test
public void delegateCreatesWhenMissing() { public void delegateCreatesWhenMissing() {
FeignLoadBalancer client = this.factory.create("client1"); FeignLoadBalancer client = this.factory.create("client1");
assertThat(client).as("client was null").isNotNull(); assertNotNull("client was null", client);
verify(this.delegate, times(1)).getClientConfig("client1"); verify(this.delegate, times(1)).getClientConfig("client1");
} }
@@ -71,10 +70,10 @@ public class CachingSpringLoadBalancerFactoryTests {
@Test @Test
public void cacheWorks() { public void cacheWorks() {
FeignLoadBalancer client = this.factory.create("client2"); FeignLoadBalancer client = this.factory.create("client2");
assertThat(client).as("client was null").isNotNull(); assertNotNull("client was null", client);
client = this.factory.create("client2"); client = this.factory.create("client2");
assertThat(client).as("client was null").isNotNull(); assertNotNull("client was null", client);
verify(this.delegate, times(1)).getClientConfig("client2"); verify(this.delegate, times(1)).getClientConfig("client2");
} }
@@ -85,10 +84,9 @@ public class CachingSpringLoadBalancerFactoryTests {
config.set(CommonClientConfigKey.ConnectTimeout, 1000); config.set(CommonClientConfigKey.ConnectTimeout, 1000);
config.set(CommonClientConfigKey.ReadTimeout, 500); config.set(CommonClientConfigKey.ReadTimeout, 500);
when(this.delegate.getClientConfig("retry")).thenReturn(config); when(this.delegate.getClientConfig("retry")).thenReturn(config);
CachingSpringLoadBalancerFactory factory = new CachingSpringLoadBalancerFactory( CachingSpringLoadBalancerFactory factory = new CachingSpringLoadBalancerFactory(this.delegate);
this.delegate);
FeignLoadBalancer client = this.factory.create("retry"); FeignLoadBalancer client = this.factory.create("retry");
assertThat(client).as("client was null").isNotNull(); assertNotNull("client was null", client);
} }
@Test @Test
@@ -97,10 +95,8 @@ public class CachingSpringLoadBalancerFactoryTests {
config.set(CommonClientConfigKey.ConnectTimeout, 1000); config.set(CommonClientConfigKey.ConnectTimeout, 1000);
config.set(CommonClientConfigKey.ReadTimeout, 500); config.set(CommonClientConfigKey.ReadTimeout, 500);
when(this.delegate.getClientConfig("retry")).thenReturn(config); when(this.delegate.getClientConfig("retry")).thenReturn(config);
CachingSpringLoadBalancerFactory factory = new CachingSpringLoadBalancerFactory( CachingSpringLoadBalancerFactory factory = new CachingSpringLoadBalancerFactory(this.delegate, loadBalancedRetryFactory);
this.delegate, this.loadBalancedRetryFactory);
FeignLoadBalancer client = this.factory.create("retry"); FeignLoadBalancer client = this.factory.create("retry");
assertThat(client).as("client was null").isNotNull(); assertNotNull("client was null", client);
} }
} }

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