Compare commits
37
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f09ae0824 | ||
|
|
c82b577d00 | ||
|
|
eca40256d8 | ||
|
|
a85294a835 | ||
|
|
db778b5c74 | ||
|
|
73a75fd8b2 | ||
|
|
892e65e303 | ||
|
|
422dda0a75 | ||
|
|
3eabb0237e | ||
|
|
ada2cb7913 | ||
|
|
05b58e087e | ||
|
|
80ecc9ac09 | ||
|
|
bee97b8611 | ||
|
|
01d05227c8 | ||
|
|
90400b9e43 | ||
|
|
b1e38db1e2 | ||
|
|
d77337bc79 | ||
|
|
80d2067134 | ||
|
|
887ceefa60 | ||
|
|
2b364367cb | ||
|
|
8aa4729537 | ||
|
|
41e9b55869 | ||
|
|
8a25b8bd63 | ||
|
|
774017db85 | ||
|
|
d1b50ec2ae | ||
|
|
bde804c6a4 | ||
|
|
0888d9ce8d | ||
|
|
1bc7542dc5 | ||
|
|
68689182be | ||
|
|
d5c176c5f4 | ||
|
|
38733043ae | ||
|
|
1b21e3b639 | ||
|
|
14e6806c1c | ||
|
|
dd95dae352 | ||
|
|
f97cbb9a73 | ||
|
|
de39e67e18 | ||
|
|
78037a1161 |
+2
-2
@@ -5,12 +5,12 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-build</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.3.RELEASE</version>
|
||||
<relativePath></relativePath>
|
||||
</parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Spring Cloud OpenFeign</name>
|
||||
<description>Spring Cloud OpenFeign</description>
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
</parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign-docs</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Spring Cloud OpenFeign Docs</name>
|
||||
<description>Spring Cloud Docs</description>
|
||||
@@ -100,7 +100,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-openfeign-docs</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
:numbered!:
|
||||
[appendix]
|
||||
[[common-application-properties]]
|
||||
== Common application properties
|
||||
|
||||
include::_attributes.adoc[]
|
||||
|
||||
Various properties can be specified inside your `application.properties` file, inside your `application.yml` file, or as command line switches.
|
||||
This appendix provides a list of common {project-full-name} properties and references to the underlying classes that consume them.
|
||||
|
||||
NOTE: Property contributions can come from additional jar files on your classpath, so you should not consider this an exhaustive list.
|
||||
Also, you can define your own properties.
|
||||
|
||||
include::_configprops.adoc[]
|
||||
@@ -429,13 +429,13 @@ feign.compression.request.min-request-size=2048
|
||||
|
||||
These properties allow you to be selective about the compressed media types and minimum request threshold length.
|
||||
|
||||
For http clients except OkHttpClient, default gzip decoder can be enabled to decode gzip response in ISO-8859-1 encoding:
|
||||
For http clients except OkHttpClient, default gzip decoder can be enabled to decode gzip response in UTF-8 encoding:
|
||||
|
||||
[source,java]
|
||||
---
|
||||
----
|
||||
feign.compression.response.enabled=true
|
||||
feign.compression.response.useGzipDecoder=true
|
||||
---
|
||||
----
|
||||
|
||||
=== Feign logging
|
||||
|
||||
@@ -524,6 +524,19 @@ public interface DemoTemplate {
|
||||
}
|
||||
----
|
||||
|
||||
=== Troubleshooting
|
||||
|
||||
==== Early Initialization Errors
|
||||
|
||||
Depending on how you are using your Feign clients you may see initialization errors when starting your application.
|
||||
To work around this problem you can use an `ObjectProvider` when autowiring your client.
|
||||
|
||||
[source,java,indent=0]
|
||||
----
|
||||
@Autowired
|
||||
ObjectProvider<TestFeginClient> testFeginClient;
|
||||
----
|
||||
|
||||
== Configuration properties
|
||||
|
||||
To see the list of all Sleuth related configuration properties please check link:appendix.html[the Appendix page].
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Spring Cloud OpenFeign</name>
|
||||
<description>Spring Cloud OpenFeign</description>
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-build</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.3.RELEASE</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<scm>
|
||||
@@ -26,8 +26,8 @@
|
||||
<properties>
|
||||
<main.basedir>${basedir}</main.basedir>
|
||||
<jackson.version>2.7.3</jackson.version>
|
||||
<spring-cloud-commons.version>2.2.0.RELEASE</spring-cloud-commons.version>
|
||||
<spring-cloud-netflix.version>2.2.0.RELEASE</spring-cloud-netflix.version>
|
||||
<spring-cloud-commons.version>2.2.2.RELEASE</spring-cloud-commons.version>
|
||||
<spring-cloud-netflix.version>2.2.2.RELEASE</spring-cloud-netflix.version>
|
||||
|
||||
<!-- Plugin versions -->
|
||||
<maven-compiler-plugin.version>3.6.1</maven-compiler-plugin.version>
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign-core</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<name>Spring Cloud OpenFeign Core</name>
|
||||
<description>Spring Cloud OpenFeign Core</description>
|
||||
<url>https://spring.io/spring-cloud/spring-cloud-openfeign/spring-cloud-openfeign-core</url>
|
||||
@@ -100,68 +100,68 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
<version>2.2.1.RELEASE</version>
|
||||
<version>2.2.5.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-netflix-ribbon</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
<version>2.2.1.RELEASE</version>
|
||||
<version>2.2.5.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
<version>2.2.1.RELEASE</version>
|
||||
<version>2.2.5.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<version>2.2.1.RELEASE</version>
|
||||
<version>2.2.5.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
<version>2.2.1.RELEASE</version>
|
||||
<version>2.2.5.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-reactor-netty</artifactId>
|
||||
<version>2.2.1.RELEASE</version>
|
||||
<version>2.2.5.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-hateoas</artifactId>
|
||||
<version>2.2.1.RELEASE</version>
|
||||
<version>2.2.5.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-codec-http</artifactId>
|
||||
<version>4.1.43.Final</version>
|
||||
<version>4.1.45.Final</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
<version>3.3.0.RELEASE</version>
|
||||
<version>3.3.3.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
@@ -175,27 +175,27 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.retry</groupId>
|
||||
<artifactId>spring-retry</artifactId>
|
||||
<version>1.2.4.RELEASE</version>
|
||||
<version>1.2.5.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
<version>2.2.1.RELEASE</version>
|
||||
<version>2.2.5.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-context</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
@@ -215,7 +215,7 @@
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-core</artifactId>
|
||||
<version>10.4.0</version>
|
||||
<version>10.7.4</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
@@ -228,35 +228,28 @@
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-slf4j</artifactId>
|
||||
<version>10.4.0</version>
|
||||
<version>10.7.4</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-httpclient</artifactId>
|
||||
<version>10.4.0</version>
|
||||
<version>10.7.4</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-hystrix</artifactId>
|
||||
<version>10.4.0</version>
|
||||
<version>10.7.4</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-okhttp</artifactId>
|
||||
<version>10.4.0</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-java8</artifactId>
|
||||
<version>10.4.0</version>
|
||||
<version>10.7.4</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
@@ -356,35 +349,35 @@
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>3.14.4</version>
|
||||
<version>3.14.7</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure-processor</artifactId>
|
||||
<version>2.2.1.RELEASE</version>
|
||||
<version>2.2.5.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<version>2.2.1.RELEASE</version>
|
||||
<version>2.2.5.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-commons</artifactId>
|
||||
<version>2.2.1.RELEASE</version>
|
||||
<version>2.2.5.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-loadbalancer</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<artifactId>spring-cloud-openfeign-core</artifactId>
|
||||
@@ -123,11 +123,6 @@
|
||||
<artifactId>feign-okhttp</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-java8</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.netflix.hystrix</groupId>
|
||||
<artifactId>hystrix-core</artifactId>
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ public @interface FeignClient {
|
||||
boolean decode404() default false;
|
||||
|
||||
/**
|
||||
* A custom <code>@Configuration</code> for the feign client. Can contain override
|
||||
* A custom configuration class for the feign client. Can contain override
|
||||
* <code>@Bean</code> definition for the pieces that make up the client, for instance
|
||||
* {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
|
||||
*
|
||||
|
||||
+16
@@ -21,6 +21,7 @@ import java.util.Objects;
|
||||
|
||||
import feign.Client;
|
||||
import feign.Contract;
|
||||
import feign.ExceptionPropagationPolicy;
|
||||
import feign.Feign;
|
||||
import feign.Logger;
|
||||
import feign.QueryMapEncoder;
|
||||
@@ -37,6 +38,7 @@ 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.loadbalancer.FeignBlockingLoadBalancerClient;
|
||||
import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
@@ -155,6 +157,11 @@ class FeignClientFactoryBean
|
||||
if (this.decode404) {
|
||||
builder.decode404();
|
||||
}
|
||||
ExceptionPropagationPolicy exceptionPropagationPolicy = getOptional(context,
|
||||
ExceptionPropagationPolicy.class);
|
||||
if (exceptionPropagationPolicy != null) {
|
||||
builder.exceptionPropagationPolicy(exceptionPropagationPolicy);
|
||||
}
|
||||
}
|
||||
|
||||
protected void configureUsingProperties(
|
||||
@@ -209,6 +216,10 @@ class FeignClientFactoryBean
|
||||
if (Objects.nonNull(config.getContract())) {
|
||||
builder.contract(getOrInstantiate(config.getContract()));
|
||||
}
|
||||
|
||||
if (Objects.nonNull(config.getExceptionPropagationPolicy())) {
|
||||
builder.exceptionPropagationPolicy(config.getExceptionPropagationPolicy());
|
||||
}
|
||||
}
|
||||
|
||||
private <T> T getOrInstantiate(Class<T> tClass) {
|
||||
@@ -282,6 +293,11 @@ class FeignClientFactoryBean
|
||||
// but ribbon is on the classpath, so unwrap
|
||||
client = ((LoadBalancerFeignClient) client).getDelegate();
|
||||
}
|
||||
if (client instanceof FeignBlockingLoadBalancerClient) {
|
||||
// not load balancing because we have a url,
|
||||
// but Spring Cloud LoadBalancer is on the classpath, so unwrap
|
||||
client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
|
||||
}
|
||||
builder.client(client);
|
||||
}
|
||||
Targeter targeter = get(context, Targeter.class);
|
||||
|
||||
+17
-2
@@ -22,6 +22,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import feign.Contract;
|
||||
import feign.ExceptionPropagationPolicy;
|
||||
import feign.Logger;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.Retryer;
|
||||
@@ -111,6 +112,8 @@ public class FeignClientProperties {
|
||||
|
||||
private Class<Contract> contract;
|
||||
|
||||
private ExceptionPropagationPolicy exceptionPropagationPolicy;
|
||||
|
||||
public Logger.Level getLoggerLevel() {
|
||||
return this.loggerLevel;
|
||||
}
|
||||
@@ -192,6 +195,15 @@ public class FeignClientProperties {
|
||||
this.contract = contract;
|
||||
}
|
||||
|
||||
public ExceptionPropagationPolicy getExceptionPropagationPolicy() {
|
||||
return exceptionPropagationPolicy;
|
||||
}
|
||||
|
||||
public void setExceptionPropagationPolicy(
|
||||
ExceptionPropagationPolicy exceptionPropagationPolicy) {
|
||||
this.exceptionPropagationPolicy = exceptionPropagationPolicy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@@ -210,14 +222,17 @@ public class FeignClientProperties {
|
||||
&& Objects.equals(this.decode404, that.decode404)
|
||||
&& Objects.equals(this.encoder, that.encoder)
|
||||
&& Objects.equals(this.decoder, that.decoder)
|
||||
&& Objects.equals(this.contract, that.contract);
|
||||
&& Objects.equals(this.contract, that.contract)
|
||||
&& Objects.equals(this.exceptionPropagationPolicy,
|
||||
that.exceptionPropagationPolicy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.loggerLevel, this.connectTimeout, this.readTimeout,
|
||||
this.retryer, this.errorDecoder, this.requestInterceptors,
|
||||
this.decode404, this.encoder, this.decoder, this.contract);
|
||||
this.decode404, this.encoder, this.decoder, this.contract,
|
||||
this.exceptionPropagationPolicy);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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 java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import feign.MethodMetadata;
|
||||
|
||||
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
|
||||
import org.springframework.web.bind.annotation.MatrixVariable;
|
||||
|
||||
import static feign.Util.checkState;
|
||||
import static feign.Util.emptyToNull;
|
||||
|
||||
/**
|
||||
* {@link MatrixVariable} annotation processor.
|
||||
*
|
||||
* Can expand maps or single objects. Values are assigned from the objects
|
||||
* {@code toString()} method.
|
||||
*
|
||||
* @author Matt King
|
||||
* @see AnnotatedParameterProcessor
|
||||
*/
|
||||
public class MatrixVariableParameterProcessor implements AnnotatedParameterProcessor {
|
||||
|
||||
private static final Class<MatrixVariable> ANNOTATION = MatrixVariable.class;
|
||||
|
||||
@Override
|
||||
public Class<? extends Annotation> getAnnotationType() {
|
||||
return ANNOTATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processArgument(AnnotatedParameterContext context,
|
||||
Annotation annotation, Method method) {
|
||||
int parameterIndex = context.getParameterIndex();
|
||||
Class<?> parameterType = method.getParameterTypes()[parameterIndex];
|
||||
MethodMetadata data = context.getMethodMetadata();
|
||||
String name = ANNOTATION.cast(annotation).value();
|
||||
|
||||
checkState(emptyToNull(name) != null,
|
||||
"MatrixVariable annotation was empty on param %s.",
|
||||
context.getParameterIndex());
|
||||
|
||||
context.setParameterName(name);
|
||||
|
||||
if (Map.class.isAssignableFrom(parameterType)) {
|
||||
data.indexToExpander().put(parameterIndex, this::expandMap);
|
||||
}
|
||||
else {
|
||||
data.indexToExpander().put(parameterIndex,
|
||||
object -> ";" + name + "=" + object.toString());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private String expandMap(Object object) {
|
||||
Map<String, Object> paramMap = (Map) object;
|
||||
|
||||
return paramMap.keySet().stream()
|
||||
.map(key -> ";" + key + "=" + paramMap.get(key).toString())
|
||||
.collect(Collectors.joining());
|
||||
}
|
||||
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 java.util.Collection;
|
||||
|
||||
import feign.MethodMetadata;
|
||||
|
||||
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
|
||||
import static feign.Util.checkState;
|
||||
import static feign.Util.emptyToNull;
|
||||
|
||||
/**
|
||||
* {@link RequestPart} parameter processor.
|
||||
*
|
||||
* @author Aaron Whiteside
|
||||
* @see AnnotatedParameterProcessor
|
||||
*/
|
||||
public class RequestPartParameterProcessor implements AnnotatedParameterProcessor {
|
||||
|
||||
private static final Class<RequestPart> ANNOTATION = RequestPart.class;
|
||||
|
||||
@Override
|
||||
public Class<? extends Annotation> getAnnotationType() {
|
||||
return ANNOTATION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processArgument(AnnotatedParameterContext context,
|
||||
Annotation annotation, Method method) {
|
||||
int parameterIndex = context.getParameterIndex();
|
||||
MethodMetadata data = context.getMethodMetadata();
|
||||
|
||||
String name = ANNOTATION.cast(annotation).value();
|
||||
checkState(emptyToNull(name) != null,
|
||||
"RequestPart.value() was empty on parameter %s", parameterIndex);
|
||||
context.setParameterName(name);
|
||||
|
||||
data.formParams().add(name);
|
||||
Collection<String> names = context.setTemplateParameter(name,
|
||||
data.indexToName().get(parameterIndex));
|
||||
data.indexToName().put(parameterIndex, names);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
+6
-6
@@ -38,7 +38,7 @@ import org.springframework.util.Assert;
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 2.2.0
|
||||
*/
|
||||
class FeignBlockingLoadBalancerClient implements Client {
|
||||
public class FeignBlockingLoadBalancerClient implements Client {
|
||||
|
||||
private static final Log LOG = LogFactory
|
||||
.getLog(FeignBlockingLoadBalancerClient.class);
|
||||
@@ -47,7 +47,7 @@ class FeignBlockingLoadBalancerClient implements Client {
|
||||
|
||||
private final BlockingLoadBalancerClient loadBalancerClient;
|
||||
|
||||
FeignBlockingLoadBalancerClient(Client delegate,
|
||||
public FeignBlockingLoadBalancerClient(Client delegate,
|
||||
BlockingLoadBalancerClient loadBalancerClient) {
|
||||
this.delegate = delegate;
|
||||
this.loadBalancerClient = loadBalancerClient;
|
||||
@@ -73,13 +73,13 @@ class FeignBlockingLoadBalancerClient implements Client {
|
||||
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri)
|
||||
.toString();
|
||||
Request newRequest = Request.create(request.httpMethod(), reconstructedUrl,
|
||||
request.headers(), request.requestBody());
|
||||
|
||||
request.headers(), request.body(), request.charset(),
|
||||
request.requestTemplate());
|
||||
return delegate.execute(newRequest, options);
|
||||
}
|
||||
|
||||
// Visible for tests
|
||||
Client getDelegate() {
|
||||
// Visible for Sleuth instrumentation
|
||||
public Client getDelegate() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -52,6 +52,6 @@ import org.springframework.context.annotation.Import;
|
||||
@Import({ HttpClientFeignLoadBalancerConfiguration.class,
|
||||
OkHttpFeignLoadBalancerConfiguration.class,
|
||||
DefaultFeignLoadBalancerConfiguration.class })
|
||||
class FeignLoadBalancerAutoConfiguration {
|
||||
public class FeignLoadBalancerAutoConfiguration {
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -134,7 +134,7 @@ public class FeignLoadBalancer extends
|
||||
Map<String, Collection<String>> headers = new LinkedHashMap<>(
|
||||
request.headers());
|
||||
return Request.create(request.httpMethod(), getUri().toASCIIString(), headers,
|
||||
request.requestBody());
|
||||
request.body(), request.charset(), request.requestTemplate());
|
||||
}
|
||||
|
||||
Request toRequest() {
|
||||
|
||||
+2
-2
@@ -20,12 +20,12 @@ import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import feign.FeignException;
|
||||
import feign.Response;
|
||||
import feign.Util;
|
||||
import feign.codec.Decoder;
|
||||
|
||||
import org.springframework.cloud.openfeign.encoding.HttpEncoding;
|
||||
@@ -72,7 +72,7 @@ public class DefaultGzipDecoder implements Decoder {
|
||||
try (GZIPInputStream gzipInputStream = new GZIPInputStream(
|
||||
response.body().asInputStream());
|
||||
BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(gzipInputStream, Util.ISO_8859_1))) {
|
||||
new InputStreamReader(gzipInputStream, StandardCharsets.UTF_8))) {
|
||||
String outputString = "";
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
|
||||
+73
-35
@@ -40,6 +40,8 @@ import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConversionException;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
@@ -50,6 +52,8 @@ import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHead
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Scien Jus
|
||||
* @author Ahmad Mozafarnia
|
||||
* @author Aaron Whiteside
|
||||
*/
|
||||
public class SpringEncoder implements Encoder {
|
||||
|
||||
@@ -57,7 +61,7 @@ public class SpringEncoder implements Encoder {
|
||||
|
||||
private final SpringFormEncoder springFormEncoder = new SpringFormEncoder();
|
||||
|
||||
private ObjectFactory<HttpMessageConverters> messageConverters;
|
||||
private final ObjectFactory<HttpMessageConverters> messageConverters;
|
||||
|
||||
public SpringEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
|
||||
this.messageConverters = messageConverters;
|
||||
@@ -68,7 +72,6 @@ public class SpringEncoder implements Encoder {
|
||||
throws EncodeException {
|
||||
// template.body(conversionService.convert(object, String.class));
|
||||
if (requestBody != null) {
|
||||
Class<?> requestType = requestBody.getClass();
|
||||
Collection<String> contentTypes = request.headers()
|
||||
.get(HttpEncoding.CONTENT_TYPE);
|
||||
|
||||
@@ -78,44 +81,36 @@ public class SpringEncoder implements Encoder {
|
||||
requestContentType = MediaType.valueOf(type);
|
||||
}
|
||||
|
||||
if (bodyType != null && bodyType.equals(MultipartFile.class)) {
|
||||
if (Objects.equals(requestContentType, MediaType.MULTIPART_FORM_DATA)) {
|
||||
this.springFormEncoder.encode(requestBody, bodyType, request);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
String message = "Content-Type \"" + MediaType.MULTIPART_FORM_DATA
|
||||
+ "\" not set for request body of type "
|
||||
+ requestBody.getClass().getSimpleName();
|
||||
throw new EncodeException(message);
|
||||
if (Objects.equals(requestContentType, MediaType.MULTIPART_FORM_DATA)) {
|
||||
this.springFormEncoder.encode(requestBody, bodyType, request);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
if (bodyType == MultipartFile.class) {
|
||||
log.warn(
|
||||
"For MultipartFile to be handled correctly, the 'consumes' parameter of @RequestMapping "
|
||||
+ "should be specified as MediaType.MULTIPART_FORM_DATA_VALUE");
|
||||
}
|
||||
}
|
||||
|
||||
for (HttpMessageConverter<?> messageConverter : this.messageConverters
|
||||
for (HttpMessageConverter messageConverter : this.messageConverters
|
||||
.getObject().getConverters()) {
|
||||
if (messageConverter.canWrite(requestType, requestContentType)) {
|
||||
if (log.isDebugEnabled()) {
|
||||
if (requestContentType != null) {
|
||||
log.debug("Writing [" + requestBody + "] as \""
|
||||
+ requestContentType + "\" using [" + messageConverter
|
||||
+ "]");
|
||||
}
|
||||
else {
|
||||
log.debug("Writing [" + requestBody + "] using ["
|
||||
+ messageConverter + "]");
|
||||
}
|
||||
|
||||
FeignOutputMessage outputMessage;
|
||||
try {
|
||||
if (messageConverter instanceof GenericHttpMessageConverter) {
|
||||
outputMessage = checkAndWrite(requestBody, bodyType,
|
||||
requestContentType,
|
||||
(GenericHttpMessageConverter) messageConverter, request);
|
||||
}
|
||||
|
||||
FeignOutputMessage outputMessage = new FeignOutputMessage(request);
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
HttpMessageConverter<Object> copy = (HttpMessageConverter<Object>) messageConverter;
|
||||
copy.write(requestBody, requestContentType, outputMessage);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new EncodeException("Error converting request body", ex);
|
||||
else {
|
||||
outputMessage = checkAndWrite(requestBody, requestContentType,
|
||||
messageConverter, request);
|
||||
}
|
||||
}
|
||||
catch (IOException | HttpMessageConversionException ex) {
|
||||
throw new EncodeException("Error converting request body", ex);
|
||||
}
|
||||
if (outputMessage != null) {
|
||||
// clear headers
|
||||
request.headers(null);
|
||||
// converters can modify headers, so update the request
|
||||
@@ -141,7 +136,7 @@ public class SpringEncoder implements Encoder {
|
||||
}
|
||||
}
|
||||
String message = "Could not write request: no suitable HttpMessageConverter "
|
||||
+ "found for request type [" + requestType.getName() + "]";
|
||||
+ "found for request type [" + requestBody.getClass().getName() + "]";
|
||||
if (requestContentType != null) {
|
||||
message += " and content type [" + requestContentType + "]";
|
||||
}
|
||||
@@ -149,6 +144,49 @@ public class SpringEncoder implements Encoder {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private FeignOutputMessage checkAndWrite(Object body, MediaType contentType,
|
||||
HttpMessageConverter converter, RequestTemplate request) throws IOException {
|
||||
if (converter.canWrite(body.getClass(), contentType)) {
|
||||
logBeforeWrite(body, contentType, converter);
|
||||
FeignOutputMessage outputMessage = new FeignOutputMessage(request);
|
||||
converter.write(body, contentType, outputMessage);
|
||||
return outputMessage;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private FeignOutputMessage checkAndWrite(Object body, Type genericType,
|
||||
MediaType contentType, GenericHttpMessageConverter converter,
|
||||
RequestTemplate request) throws IOException {
|
||||
if (converter.canWrite(genericType, body.getClass(), contentType)) {
|
||||
logBeforeWrite(body, contentType, converter);
|
||||
FeignOutputMessage outputMessage = new FeignOutputMessage(request);
|
||||
converter.write(body, genericType, contentType, outputMessage);
|
||||
return outputMessage;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void logBeforeWrite(Object requestBody, MediaType requestContentType,
|
||||
HttpMessageConverter messageConverter) {
|
||||
if (log.isDebugEnabled()) {
|
||||
if (requestContentType != null) {
|
||||
log.debug("Writing [" + requestBody + "] as \"" + requestContentType
|
||||
+ "\" using [" + messageConverter + "]");
|
||||
}
|
||||
else {
|
||||
log.debug(
|
||||
"Writing [" + requestBody + "] using [" + messageConverter + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class FeignOutputMessage implements HttpOutputMessage {
|
||||
|
||||
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
+8
-7
@@ -36,10 +36,12 @@ import feign.Param;
|
||||
import feign.Request;
|
||||
|
||||
import org.springframework.cloud.openfeign.AnnotatedParameterProcessor;
|
||||
import org.springframework.cloud.openfeign.annotation.MatrixVariableParameterProcessor;
|
||||
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.RequestParamParameterProcessor;
|
||||
import org.springframework.cloud.openfeign.annotation.RequestPartParameterProcessor;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
@@ -68,6 +70,7 @@ import static org.springframework.core.annotation.AnnotatedElementUtils.findMerg
|
||||
* @author Halvdan Hoem Grelland
|
||||
* @author Aram Peres
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Aaron Whiteside
|
||||
*/
|
||||
public class SpringMvcContract extends Contract.BaseContract
|
||||
implements ResourceLoaderAware {
|
||||
@@ -110,13 +113,9 @@ public class SpringMvcContract extends Contract.BaseContract
|
||||
"Parameter processors can not be null.");
|
||||
Assert.notNull(conversionService, "ConversionService can not be null.");
|
||||
|
||||
List<AnnotatedParameterProcessor> processors;
|
||||
if (!annotatedParameterProcessors.isEmpty()) {
|
||||
processors = new ArrayList<>(annotatedParameterProcessors);
|
||||
}
|
||||
else {
|
||||
processors = getDefaultAnnotatedArgumentsProcessors();
|
||||
}
|
||||
List<AnnotatedParameterProcessor> processors = getDefaultAnnotatedArgumentsProcessors();
|
||||
processors.addAll(annotatedParameterProcessors);
|
||||
|
||||
this.annotatedArgumentProcessors = toAnnotatedArgumentProcessorMap(processors);
|
||||
this.conversionService = conversionService;
|
||||
this.convertingExpanderFactory = new ConvertingExpanderFactory(conversionService);
|
||||
@@ -355,10 +354,12 @@ public class SpringMvcContract extends Contract.BaseContract
|
||||
|
||||
List<AnnotatedParameterProcessor> annotatedArgumentResolvers = new ArrayList<>();
|
||||
|
||||
annotatedArgumentResolvers.add(new MatrixVariableParameterProcessor());
|
||||
annotatedArgumentResolvers.add(new PathVariableParameterProcessor());
|
||||
annotatedArgumentResolvers.add(new RequestParamParameterProcessor());
|
||||
annotatedArgumentResolvers.add(new RequestHeaderParameterProcessor());
|
||||
annotatedArgumentResolvers.add(new QueryMapParameterProcessor());
|
||||
annotatedArgumentResolvers.add(new RequestPartParameterProcessor());
|
||||
|
||||
return annotatedArgumentResolvers;
|
||||
}
|
||||
|
||||
+20
@@ -90,6 +90,18 @@ public class DefaultGzipDecoderTests extends FeignClientFactoryBean {
|
||||
assertThat(hello).as("null hello didn't match").isEqualTo(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCharsetDecompress() {
|
||||
ResponseEntity<Hello> response = testClient().getUtf8Response();
|
||||
assertThat(response).as("response was null").isNotNull();
|
||||
assertThat(response.getStatusCode()).as("wrong status code")
|
||||
.isEqualTo(HttpStatus.OK);
|
||||
Hello hello = response.getBody();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("utf8 hello didn't match")
|
||||
.isEqualTo(new Hello("안녕하세요 means Hello in Korean"));
|
||||
}
|
||||
|
||||
private static class Hello {
|
||||
|
||||
private String message;
|
||||
@@ -136,6 +148,9 @@ public class DefaultGzipDecoderTests extends FeignClientFactoryBean {
|
||||
@GetMapping("/nullGzipResponse")
|
||||
ResponseEntity<Hello> getNullResponse();
|
||||
|
||||
@GetMapping("/utf8Response")
|
||||
ResponseEntity<Hello> getUtf8Response();
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@@ -154,6 +169,11 @@ public class DefaultGzipDecoderTests extends FeignClientFactoryBean {
|
||||
return ResponseEntity.ok(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Hello> getUtf8Response() {
|
||||
return ResponseEntity.ok(new Hello("안녕하세요 means Hello in Korean"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+80
@@ -16,14 +16,30 @@
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import feign.Client;
|
||||
import feign.InvocationHandlerFactory;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
|
||||
import org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.web.bind.annotation.RequestMethod.GET;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
@@ -49,10 +65,74 @@ public class FeignClientFactoryTests {
|
||||
assertThat(foobar).as("bar was not null").isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRedirectToDelegateWhenUrlSet() {
|
||||
new ApplicationContextRunner().withUserConfiguration(TestConfig.class)
|
||||
.run(this::defaultClientUsed);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "ConstantConditions" })
|
||||
private void defaultClientUsed(AssertableApplicationContext context) {
|
||||
Proxy target = context.getBean(FeignClientFactoryBean.class).getTarget();
|
||||
Object invocationHandler = ReflectionTestUtils.getField(target, "h");
|
||||
Map<Method, InvocationHandlerFactory.MethodHandler> dispatch = (Map<Method, InvocationHandlerFactory.MethodHandler>) ReflectionTestUtils
|
||||
.getField(invocationHandler, "dispatch");
|
||||
Method key = new ArrayList<>(dispatch.keySet()).get(0);
|
||||
Object client = ReflectionTestUtils.getField(dispatch.get(key), "client");
|
||||
assertThat(client).isInstanceOf(Client.Default.class);
|
||||
}
|
||||
|
||||
private FeignClientSpecification getSpec(String name, Class<?> configClass) {
|
||||
return new FeignClientSpecification(name, new Class[] { configClass });
|
||||
}
|
||||
|
||||
interface TestType {
|
||||
|
||||
@RequestMapping(value = "/", method = GET)
|
||||
String hello();
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class TestConfig {
|
||||
|
||||
@Bean
|
||||
BlockingLoadBalancerClient loadBalancerClient() {
|
||||
return new BlockingLoadBalancerClient(new LoadBalancerClientFactory());
|
||||
}
|
||||
|
||||
@Bean
|
||||
FeignContext feignContext() {
|
||||
FeignContext feignContext = new FeignContext();
|
||||
feignContext.setConfigurations(
|
||||
Collections.singletonList(new FeignClientSpecification("test",
|
||||
new Class[] { LoadBalancerAutoConfiguration.class })));
|
||||
return feignContext;
|
||||
}
|
||||
|
||||
@Bean
|
||||
FeignClientProperties feignClientProperties() {
|
||||
return new FeignClientProperties();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Targeter targeter() {
|
||||
return new DefaultTargeter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
FeignClientFactoryBean feignClientFactoryBean() {
|
||||
FeignClientFactoryBean feignClientFactoryBean = new FeignClientFactoryBean();
|
||||
feignClientFactoryBean.setContextId("test");
|
||||
feignClientFactoryBean.setName("test");
|
||||
feignClientFactoryBean.setType(TestType.class);
|
||||
feignClientFactoryBean.setPath("");
|
||||
feignClientFactoryBean.setUrl("http://some.absolute.url");
|
||||
return feignClientFactoryBean;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class FooConfig {
|
||||
|
||||
@Bean
|
||||
|
||||
+14
@@ -17,6 +17,7 @@
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import feign.Contract;
|
||||
import feign.ExceptionPropagationPolicy;
|
||||
import feign.Feign;
|
||||
import feign.Logger;
|
||||
import feign.QueryMapEncoder;
|
||||
@@ -151,6 +152,14 @@ public class FeignClientOverrideDefaultsTests {
|
||||
.isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exceptionPropagationPolicy() {
|
||||
assertThat(this.context.getInstances("foo", ExceptionPropagationPolicy.class))
|
||||
.isNull();
|
||||
assertThat(this.context.getInstances("bar", ExceptionPropagationPolicy.class))
|
||||
.containsValues(ExceptionPropagationPolicy.UNWRAP);
|
||||
}
|
||||
|
||||
@FeignClient(name = "foo", url = "https://foo",
|
||||
configuration = FooConfiguration.class)
|
||||
interface FooClient {
|
||||
@@ -252,6 +261,11 @@ public class FeignClientOverrideDefaultsTests {
|
||||
return new BeanQueryMapEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ExceptionPropagationPolicy exceptionPropagationPolicy() {
|
||||
return ExceptionPropagationPolicy.UNWRAP;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+28
-3
@@ -16,13 +16,14 @@
|
||||
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import feign.Request;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import feign.RetryableException;
|
||||
@@ -50,7 +51,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||
@@ -78,6 +78,8 @@ public class FeignClientUsingPropertiesTests {
|
||||
|
||||
private FeignClientFactoryBean barFactoryBean;
|
||||
|
||||
private FeignClientFactoryBean unwrapFactoryBean;
|
||||
|
||||
private FeignClientFactoryBean formFactoryBean;
|
||||
|
||||
public FeignClientUsingPropertiesTests() {
|
||||
@@ -89,6 +91,10 @@ public class FeignClientUsingPropertiesTests {
|
||||
this.barFactoryBean.setContextId("bar");
|
||||
this.barFactoryBean.setType(FeignClientFactoryBean.class);
|
||||
|
||||
this.unwrapFactoryBean = new FeignClientFactoryBean();
|
||||
this.unwrapFactoryBean.setContextId("unwrap");
|
||||
this.unwrapFactoryBean.setType(FeignClientFactoryBean.class);
|
||||
|
||||
this.formFactoryBean = new FeignClientFactoryBean();
|
||||
this.formFactoryBean.setContextId("form");
|
||||
this.formFactoryBean.setType(FeignClientFactoryBean.class);
|
||||
@@ -106,6 +112,12 @@ public class FeignClientUsingPropertiesTests {
|
||||
"http://localhost:" + this.port);
|
||||
}
|
||||
|
||||
public UnwrapClient unwrapClient() {
|
||||
this.unwrapFactoryBean.setApplicationContext(this.applicationContext);
|
||||
return this.unwrapFactoryBean.feign(this.context).target(UnwrapClient.class,
|
||||
"http://localhost:" + this.port);
|
||||
}
|
||||
|
||||
public FormClient formClient() {
|
||||
this.formFactoryBean.setApplicationContext(this.applicationContext);
|
||||
return this.formFactoryBean.feign(this.context).target(FormClient.class,
|
||||
@@ -124,6 +136,12 @@ public class FeignClientUsingPropertiesTests {
|
||||
fail("it should timeout");
|
||||
}
|
||||
|
||||
@Test(expected = SocketTimeoutException.class)
|
||||
public void testUnwrap() throws Exception {
|
||||
unwrapClient().unwrap();
|
||||
fail("it should timeout");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForm() {
|
||||
Map<String, String> request = Collections.singletonMap("form", "Data");
|
||||
@@ -145,6 +163,13 @@ public class FeignClientUsingPropertiesTests {
|
||||
|
||||
}
|
||||
|
||||
protected interface UnwrapClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/bar") // intentionally /bar
|
||||
String unwrap() throws IOException;
|
||||
|
||||
}
|
||||
|
||||
protected interface FormClient {
|
||||
|
||||
@RequestMapping(value = "/form", method = RequestMethod.POST,
|
||||
@@ -233,7 +258,7 @@ public class FeignClientUsingPropertiesTests {
|
||||
|
||||
requestTemplate.header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||
requestTemplate.body(Request.Body.bodyTemplate(builder.toString(), UTF_8));
|
||||
requestTemplate.body(builder.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-3
@@ -121,7 +121,7 @@ class FeignBlockingLoadBalancerClientTests {
|
||||
assertThat(actualRequest.headers()).hasSize(1);
|
||||
assertThat(actualRequest.headers()).containsEntry(HttpHeaders.CONTENT_TYPE,
|
||||
Collections.singletonList(MediaType.APPLICATION_JSON_VALUE));
|
||||
assertThat(actualRequest.requestBody().asString()).isEqualTo("hello");
|
||||
assertThat(new String(actualRequest.body())).isEqualTo("hello");
|
||||
}
|
||||
|
||||
private Request testRequest() {
|
||||
@@ -130,8 +130,7 @@ class FeignBlockingLoadBalancerClientTests {
|
||||
|
||||
private Request testRequest(String host) {
|
||||
return Request.create(Request.HttpMethod.GET, "http://" + host + "/path",
|
||||
testHeaders(),
|
||||
Request.Body.encoded("hello".getBytes(), StandardCharsets.UTF_8));
|
||||
testHeaders(), "hello".getBytes(), StandardCharsets.UTF_8, null);
|
||||
}
|
||||
|
||||
private Map<String, Collection<String>> testHeaders() {
|
||||
|
||||
+1
-1
@@ -159,7 +159,7 @@ public class FeignLoadBalancerTests {
|
||||
@Test
|
||||
public void testRibbonRequestURLEncode() throws Exception {
|
||||
String url = "https://foo/?name=%7bcookie"; // name={cookie
|
||||
Request request = Request.create(GET, url, new HashMap<>(), null, null);
|
||||
Request request = Request.create(GET, url, new HashMap<>(), null, null, null);
|
||||
|
||||
assertThat(request.url()).isEqualTo(url);
|
||||
|
||||
|
||||
+11
-11
@@ -133,7 +133,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
RibbonLoadBalancedRetryFactory loadBalancedRetryFactory = new RibbonLoadBalancedRetryFactory(
|
||||
clientFactory);
|
||||
Request feignRequest = Request.create(GET, "https://foo", new HashMap<>(),
|
||||
new byte[] {}, UTF_8);
|
||||
new byte[] {}, UTF_8, null);
|
||||
Client client = mock(Client.class);
|
||||
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(
|
||||
client, feignRequest, new URI("https://foo"));
|
||||
@@ -151,7 +151,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
@Test
|
||||
public void executeNeverRetry() throws Exception {
|
||||
Request feignRequest = Request.create(GET, "https://foo", new HashMap<>(),
|
||||
new byte[] {}, UTF_8);
|
||||
new byte[] {}, UTF_8, null);
|
||||
Client client = mock(Client.class);
|
||||
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(
|
||||
client, feignRequest, new URI("https://foo"));
|
||||
@@ -215,7 +215,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
}
|
||||
};
|
||||
Request feignRequest = Request.create(GET, "https://foo", new HashMap<>(),
|
||||
new byte[] {}, UTF_8);
|
||||
new byte[] {}, UTF_8, null);
|
||||
Client client = mock(Client.class);
|
||||
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(
|
||||
client, feignRequest, new URI("https://foo"));
|
||||
@@ -260,7 +260,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
}
|
||||
};
|
||||
Request feignRequest = Request.create(GET, "https://foo", new HashMap<>(),
|
||||
new byte[] {}, UTF_8);
|
||||
new byte[] {}, UTF_8, null);
|
||||
Client client = mock(Client.class);
|
||||
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(
|
||||
client, feignRequest, new URI("https://foo"));
|
||||
@@ -310,7 +310,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
}
|
||||
};
|
||||
Request feignRequest = Request.create(GET, "https://foo", new HashMap<>(),
|
||||
new byte[] {}, UTF_8);
|
||||
new byte[] {}, UTF_8, null);
|
||||
Client client = mock(Client.class);
|
||||
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(
|
||||
client, feignRequest, new URI("https://foo"));
|
||||
@@ -339,7 +339,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
RibbonLoadBalancedRetryFactory loadBalancedRetryFactory = new RibbonLoadBalancedRetryFactory(
|
||||
clientFactory);
|
||||
Request feignRequest = Request.create(GET, "https://foo", new HashMap<>(),
|
||||
new byte[] {}, UTF_8);
|
||||
new byte[] {}, UTF_8, null);
|
||||
Client client = mock(Client.class);
|
||||
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(
|
||||
client, feignRequest, new URI("https://foo"));
|
||||
@@ -365,7 +365,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
RibbonLoadBalancedRetryFactory loadBalancedRetryFactory = new RibbonLoadBalancedRetryFactory(
|
||||
clientFactory);
|
||||
Request feignRequest = Request.create(GET, "https://foo", new HashMap<>(),
|
||||
new byte[] {}, UTF_8);
|
||||
new byte[] {}, UTF_8, null);
|
||||
Client client = mock(Client.class);
|
||||
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(
|
||||
client, feignRequest, new URI("https://foo"));
|
||||
@@ -446,7 +446,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
}
|
||||
};
|
||||
Request feignRequest = Request.create(GET, "https://listener", new HashMap<>(),
|
||||
new byte[] {}, UTF_8);
|
||||
new byte[] {}, UTF_8, null);
|
||||
Client client = mock(Client.class);
|
||||
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(
|
||||
client, feignRequest, new URI("https://listener"));
|
||||
@@ -498,7 +498,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
}
|
||||
};
|
||||
Request feignRequest = Request.create(GET, "https://listener", new HashMap<>(),
|
||||
new byte[] {}, UTF_8);
|
||||
new byte[] {}, UTF_8, null);
|
||||
Client client = mock(Client.class);
|
||||
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(
|
||||
client, feignRequest, new URI("https://listener"));
|
||||
@@ -535,7 +535,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
}
|
||||
};
|
||||
Request feignRequest = Request.create(GET, "https://listener", new HashMap<>(),
|
||||
new byte[] {}, UTF_8);
|
||||
new byte[] {}, UTF_8, null);
|
||||
Client client = mock(Client.class);
|
||||
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(
|
||||
client, feignRequest, new URI("https://listener"));
|
||||
@@ -580,7 +580,7 @@ public class RetryableFeignLoadBalancerTests {
|
||||
}
|
||||
};
|
||||
Request feignRequest = Request.create(GET, "https://foo", new HashMap<>(),
|
||||
new byte[] {}, UTF_8);
|
||||
new byte[] {}, UTF_8, null);
|
||||
Client client = mock(Client.class);
|
||||
FeignLoadBalancer.RibbonRequest request = new FeignLoadBalancer.RibbonRequest(
|
||||
client, feignRequest, new URI("https://foo"));
|
||||
|
||||
+4
-5
@@ -44,13 +44,12 @@ public class RibbonResponseStatusCodeExceptionTest {
|
||||
|
||||
@Test
|
||||
public void getResponse() throws Exception {
|
||||
Map<String, Collection<String>> headers = new HashMap<String, Collection<String>>();
|
||||
List<String> fooValues = new ArrayList<String>();
|
||||
Map<String, Collection<String>> headers = new HashMap<>();
|
||||
List<String> fooValues = new ArrayList<>();
|
||||
fooValues.add("bar");
|
||||
headers.put("foo", fooValues);
|
||||
Request request = Request.create(GET, "https://service.com",
|
||||
new HashMap<String, Collection<String>>(), new byte[] {},
|
||||
Charset.defaultCharset());
|
||||
Request request = Request.create(GET, "https://service.com", new HashMap<>(),
|
||||
new byte[] {}, Charset.defaultCharset(), null);
|
||||
byte[] body = "foo".getBytes();
|
||||
ByteArrayInputStream is = new ByteArrayInputStream(body);
|
||||
Response response = Response.builder().status(200).reason("Success")
|
||||
|
||||
+105
-32
@@ -16,10 +16,11 @@
|
||||
|
||||
package org.springframework.cloud.openfeign.support;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import feign.RequestTemplate;
|
||||
@@ -27,7 +28,6 @@ import feign.codec.EncodeException;
|
||||
import feign.codec.Encoder;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentMatcher;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
@@ -35,12 +35,15 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.cloud.openfeign.FeignContext;
|
||||
import org.springframework.cloud.openfeign.encoding.HttpEncoding;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.AbstractGenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
@@ -60,6 +63,7 @@ import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Ahmad Mozafarnia
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = SpringEncoderTests.Application.class,
|
||||
@@ -75,6 +79,10 @@ public class SpringEncoderTests {
|
||||
@Qualifier("myHttpMessageConverter")
|
||||
private HttpMessageConverter<?> myConverter;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("myGenericHttpMessageConverter")
|
||||
private GenericHttpMessageConverter<?> myGenericConverter;
|
||||
|
||||
@Test
|
||||
public void testCustomHttpMessageConverter() {
|
||||
Encoder encoder = this.context.getInstance("foo", Encoder.class);
|
||||
@@ -94,7 +102,35 @@ public class SpringEncoderTests {
|
||||
|
||||
assertThat(request.requestCharset()).as("request charset is null").isNotNull();
|
||||
assertThat(request.requestCharset()).as("request charset is wrong")
|
||||
.isEqualTo(Charset.forName("UTF-8"));
|
||||
.isEqualTo(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
// gh-225
|
||||
@Test
|
||||
public void testCustomGenericHttpMessageConverter() {
|
||||
Encoder encoder = this.context.getInstance("foo", Encoder.class);
|
||||
assertThat(encoder).isNotNull();
|
||||
RequestTemplate request = new RequestTemplate();
|
||||
|
||||
ParameterizedTypeReference<List<String>> stringListType = new ParameterizedTypeReference<List<String>>() {
|
||||
};
|
||||
|
||||
request.header(HttpEncoding.CONTENT_TYPE, "application/mygenerictype");
|
||||
encoder.encode(Collections.singletonList("hi"), stringListType.getType(),
|
||||
request);
|
||||
|
||||
Collection<String> contentTypeHeader = request.headers().get("Content-Type");
|
||||
assertThat(contentTypeHeader).as("missing content type header").isNotNull();
|
||||
assertThat(contentTypeHeader.isEmpty()).as("missing content type header")
|
||||
.isFalse();
|
||||
|
||||
String header = contentTypeHeader.iterator().next();
|
||||
assertThat(header).as("content type header is wrong")
|
||||
.isEqualTo("application/mygenerictype");
|
||||
|
||||
assertThat(request.requestCharset()).as("request charset is null").isNotNull();
|
||||
assertThat(request.requestCharset()).as("request charset is wrong")
|
||||
.isEqualTo(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -120,8 +156,6 @@ public class SpringEncoderTests {
|
||||
MultipartFile multipartFile = new MockMultipartFile("test_multipart_file",
|
||||
"hi".getBytes());
|
||||
encoder.encode(multipartFile, MultipartFile.class, request);
|
||||
|
||||
assertThat(request.requestCharset()).as("request charset is not null").isNull();
|
||||
}
|
||||
|
||||
// gh-105, gh-107
|
||||
@@ -179,6 +213,11 @@ public class SpringEncoderTests {
|
||||
return new MyHttpMessageConverter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
GenericHttpMessageConverter<?> myGenericHttpMessageConverter() {
|
||||
return new MyGenericHttpMessageConverter();
|
||||
}
|
||||
|
||||
private static class MyHttpMessageConverter
|
||||
extends AbstractGenericHttpMessageConverter<Object> {
|
||||
|
||||
@@ -204,20 +243,77 @@ public class SpringEncoderTests {
|
||||
@Override
|
||||
protected void writeInternal(Object o, Type type,
|
||||
HttpOutputMessage outputMessage)
|
||||
throws IOException, HttpMessageNotWritableException {
|
||||
throws HttpMessageNotWritableException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
|
||||
throws IOException, HttpMessageNotReadableException {
|
||||
throws HttpMessageNotReadableException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(Type type, Class<?> contextClass,
|
||||
HttpInputMessage inputMessage)
|
||||
throws IOException, HttpMessageNotReadableException {
|
||||
throws HttpMessageNotReadableException {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class MyGenericHttpMessageConverter
|
||||
extends AbstractGenericHttpMessageConverter<Object> {
|
||||
|
||||
MyGenericHttpMessageConverter() {
|
||||
super(new MediaType("application", "mygenerictype"));
|
||||
}
|
||||
|
||||
private boolean isStringList(Type type) {
|
||||
if (type instanceof ParameterizedType) {
|
||||
ParameterizedType parameterizedType = (ParameterizedType) type;
|
||||
return parameterizedType.getRawType() == List.class
|
||||
&& parameterizedType
|
||||
.getActualTypeArguments()[0] == String.class;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
return clazz == List.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
|
||||
return canWrite(mediaType) && isStringList(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead(Type type, Class<?> contextClass,
|
||||
MediaType mediaType) {
|
||||
return canRead(mediaType) && isStringList(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal(Object o, Type type,
|
||||
HttpOutputMessage outputMessage)
|
||||
throws HttpMessageNotWritableException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(Type type, Class<?> contextClass,
|
||||
HttpInputMessage inputMessage)
|
||||
throws HttpMessageNotReadableException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
|
||||
throws HttpMessageNotReadableException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -225,27 +321,4 @@ public class SpringEncoderTests {
|
||||
|
||||
}
|
||||
|
||||
class MediaTypeMatcher implements ArgumentMatcher<MediaType> {
|
||||
|
||||
private MediaType mediaType;
|
||||
|
||||
MediaTypeMatcher(String type, String subtype) {
|
||||
this.mediaType = new MediaType(type, subtype);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(MediaType argument) {
|
||||
return this.mediaType.equals(argument);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuffer sb = new StringBuffer("MediaTypeMatcher{");
|
||||
sb.append("mediaType=").append(this.mediaType);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+72
-12
@@ -23,6 +23,7 @@ import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@@ -45,6 +46,7 @@ import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.MatrixVariable;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@@ -52,18 +54,19 @@ import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY;
|
||||
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
import static org.springframework.web.util.UriUtils.encode;
|
||||
|
||||
/**
|
||||
* @author chadjaros
|
||||
* @author Halvdan Hoem Grelland
|
||||
* @author Aram Peres
|
||||
* @author Aaron Whiteside
|
||||
*/
|
||||
public class SpringMvcContractTests {
|
||||
|
||||
@@ -244,7 +247,7 @@ public class SpringMvcContractTests {
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url())
|
||||
.isEqualTo("/advanced/test/{id}?amount=" + encode("{amount}", UTF_8));
|
||||
.isEqualTo("/advanced/test/{id}?amount=" + "{amount}");
|
||||
assertThat(data.template().method()).isEqualTo("PUT");
|
||||
assertThat(data.template().headers().get("Accept").iterator().next())
|
||||
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
@@ -268,7 +271,7 @@ public class SpringMvcContractTests {
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url())
|
||||
.isEqualTo("/advanced/test/{id}?amount=" + encode("{amount}", UTF_8));
|
||||
.isEqualTo("/advanced/test/{id}?amount=" + "{amount}");
|
||||
assertThat(data.template().method()).isEqualTo("PUT");
|
||||
assertThat(data.template().headers().get("Accept").iterator().next())
|
||||
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
@@ -293,7 +296,7 @@ public class SpringMvcContractTests {
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url())
|
||||
.isEqualTo("/advanced/test2?amount=" + encode("{amount}", UTF_8));
|
||||
.isEqualTo("/advanced/test2?amount=" + "{amount}");
|
||||
assertThat(data.template().method()).isEqualTo("PUT");
|
||||
assertThat(data.template().headers().get("Accept").iterator().next())
|
||||
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
@@ -380,7 +383,7 @@ public class SpringMvcContractTests {
|
||||
MethodMetadata data = this.contract
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url()).isEqualTo("/test?id=" + encode("{id}", UTF_8));
|
||||
assertThat(data.template().url()).isEqualTo("/test?id=" + "{id}");
|
||||
assertThat(data.template().method()).isEqualTo("GET");
|
||||
assertThat(data.template().queries().get("id").toString()).isEqualTo("[{id}]");
|
||||
assertThat(data.indexToExpander().get(0)).isNotNull();
|
||||
@@ -393,7 +396,7 @@ public class SpringMvcContractTests {
|
||||
MethodMetadata data = this.contract
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url()).isEqualTo("/test?id=" + encode("{id}", UTF_8));
|
||||
assertThat(data.template().url()).isEqualTo("/test?id=" + "{id}");
|
||||
assertThat(data.template().method()).isEqualTo("GET");
|
||||
assertThat(data.template().queries().get("id").toString()).isEqualTo("[{id}]");
|
||||
assertThat(data.indexToExpander().get(0)).isNotNull();
|
||||
@@ -448,8 +451,8 @@ public class SpringMvcContractTests {
|
||||
MethodMetadata data = this.contract
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url()).isEqualTo(
|
||||
"/advanced/testfallback/{id}?amount=" + encode("{amount}", UTF_8));
|
||||
assertThat(data.template().url())
|
||||
.isEqualTo("/advanced/testfallback/{id}?amount=" + "{amount}");
|
||||
assertThat(data.template().method()).isEqualTo("PUT");
|
||||
assertThat(data.template().headers().get("Accept").iterator().next())
|
||||
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
@@ -493,8 +496,7 @@ public class SpringMvcContractTests {
|
||||
MethodMetadata data = this.contract
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url())
|
||||
.isEqualTo("/queryMap?aParam=" + encode("{aParam}", UTF_8));
|
||||
assertThat(data.template().url()).isEqualTo("/queryMap?aParam=" + "{aParam}");
|
||||
assertThat(data.template().method()).isEqualTo("GET");
|
||||
assertThat(data.queryMapIndex().intValue()).isEqualTo(0);
|
||||
Map<String, Collection<String>> params = data.template().queries();
|
||||
@@ -509,7 +511,7 @@ public class SpringMvcContractTests {
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().url())
|
||||
.isEqualTo("/queryMapObject?aParam=" + encode("{aParam}", UTF_8));
|
||||
.isEqualTo("/queryMapObject?aParam=" + "{aParam}");
|
||||
assertThat(data.template().method()).isEqualTo("GET");
|
||||
assertThat(data.queryMapIndex().intValue()).isEqualTo(0);
|
||||
Map<String, Collection<String>> params = data.template().queries();
|
||||
@@ -523,6 +525,35 @@ public class SpringMvcContractTests {
|
||||
this.contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatrixVariable_MapParam() throws Exception {
|
||||
Method method = TestTemplate_MatrixVariable.class
|
||||
.getDeclaredMethod("matrixVariable", Map.class);
|
||||
MethodMetadata data = this.contract
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
Map<String, String> testMap = new HashMap<>();
|
||||
testMap.put("param", "value");
|
||||
|
||||
assertThat(data.template().method()).isEqualTo("GET");
|
||||
assertThat(data.template().url()).isEqualTo("/matrixVariable/{params}");
|
||||
assertThat(";param=value")
|
||||
.isEqualTo(data.indexToExpander().get(0).expand(testMap));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatrixVariable_ObjectParam() throws Exception {
|
||||
Method method = TestTemplate_MatrixVariable.class
|
||||
.getDeclaredMethod("matrixVariableObject", Object.class);
|
||||
MethodMetadata data = this.contract
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
|
||||
assertThat(data.template().method()).isEqualTo("GET");
|
||||
assertThat(data.template().url()).isEqualTo("/matrixVariableObject/{param}");
|
||||
assertThat(";param=value")
|
||||
.isEqualTo(data.indexToExpander().get(0).expand("value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddingTemplatedParameterWithTheSameKey()
|
||||
throws NoSuchMethodException {
|
||||
@@ -535,6 +566,16 @@ public class SpringMvcContractTests {
|
||||
"{Accept}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleRequestPartAnnotations() throws NoSuchMethodException {
|
||||
Method method = TestTemplate_RequestPart.class.getDeclaredMethod(
|
||||
"requestWithMultipleParts", MultipartFile.class, String.class);
|
||||
|
||||
MethodMetadata data = contract
|
||||
.parseAndValidateMetadata(method.getDeclaringClass(), method);
|
||||
assertThat(data.formParams()).contains("file", "id");
|
||||
}
|
||||
|
||||
public interface TestTemplate_Simple {
|
||||
|
||||
@RequestMapping(value = "/test/{id}", method = RequestMethod.GET,
|
||||
@@ -639,6 +680,25 @@ public class SpringMvcContractTests {
|
||||
|
||||
}
|
||||
|
||||
public interface TestTemplate_RequestPart {
|
||||
|
||||
@RequestMapping(path = "/requestPart", method = RequestMethod.POST,
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
void requestWithMultipleParts(@RequestPart("file") MultipartFile file,
|
||||
@RequestPart("id") String identifier);
|
||||
|
||||
}
|
||||
|
||||
public interface TestTemplate_MatrixVariable {
|
||||
|
||||
@RequestMapping(path = "/matrixVariable/{params}")
|
||||
String matrixVariable(@MatrixVariable("params") Map<String, Object> params);
|
||||
|
||||
@RequestMapping(path = "/matrixVariableObject/{param}")
|
||||
String matrixVariableObject(@MatrixVariable("param") Object object);
|
||||
|
||||
}
|
||||
|
||||
@JsonAutoDetect
|
||||
@RequestMapping("/advanced")
|
||||
public interface TestTemplate_Advanced {
|
||||
|
||||
+175
-1
@@ -24,12 +24,19 @@ import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.Part;
|
||||
|
||||
import com.netflix.hystrix.HystrixCommand;
|
||||
import com.netflix.hystrix.HystrixCommandGroupKey;
|
||||
@@ -43,9 +50,12 @@ import feign.Logger;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
import feign.Target;
|
||||
import feign.codec.EncodeException;
|
||||
import feign.hystrix.FallbackFactory;
|
||||
import feign.hystrix.SetterFactory;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import rx.Observable;
|
||||
import rx.Single;
|
||||
@@ -74,7 +84,9 @@ import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@@ -82,15 +94,19 @@ import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.core.IsInstanceOf.instanceOf;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
* @author Jakub Narloch
|
||||
* @author Erik Kringen
|
||||
* @author Halvdan Hoem Grelland
|
||||
* @author Aaron Whiteside
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringBootTest(classes = FeignClientTests.Application.class,
|
||||
@@ -110,6 +126,9 @@ public class FeignClientTests {
|
||||
|
||||
public static final String MYHEADER2 = "myheader2";
|
||||
|
||||
@Rule
|
||||
public ExpectedException expected = ExpectedException.none();
|
||||
|
||||
@Autowired
|
||||
HystrixClient hystrixClient;
|
||||
|
||||
@@ -144,6 +163,9 @@ public class FeignClientTests {
|
||||
@Autowired
|
||||
private NullHystrixClientWithFallBackFactory nullHystrixClientWithFallBackFactory;
|
||||
|
||||
@Autowired
|
||||
private MultipartClient multipartClient;
|
||||
|
||||
private static ArrayList<Hello> getHelloList() {
|
||||
ArrayList<Hello> hellos = new ArrayList<>();
|
||||
hellos.add(new Hello(HELLO_WORLD_1));
|
||||
@@ -422,6 +444,75 @@ public class FeignClientTests {
|
||||
assertThat(getHelloList()).as("hellos didn't match").isEqualTo(hellos);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleRequestPart() {
|
||||
String response = this.multipartClient.singlePart("abc");
|
||||
assertThat(response).isEqualTo("abc");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleRequestParts() {
|
||||
MockMultipartFile file = new MockMultipartFile("file", "hello.bin", null,
|
||||
"hello".getBytes());
|
||||
String response = this.multipartClient.multipart("abc", "123", file);
|
||||
assertThat(response).isEqualTo("abc123hello.bin");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestPartWithListOfMultipartFiles() {
|
||||
List<MultipartFile> multipartFiles = Arrays.asList(
|
||||
new MockMultipartFile("file1", "hello1.bin", null, "hello".getBytes()),
|
||||
new MockMultipartFile("file2", "hello2.bin", null, "hello".getBytes()));
|
||||
String partNames = this.multipartClient
|
||||
.requestPartListOfMultipartFilesReturnsPartNames(multipartFiles);
|
||||
assertThat(partNames).isEqualTo("files,files");
|
||||
String fileNames = this.multipartClient
|
||||
.requestPartListOfMultipartFilesReturnsFileNames(multipartFiles);
|
||||
assertThat(fileNames).contains("hello1.bin", "hello2.bin");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestBodyWithSingleMultipartFile() {
|
||||
String partName = UUID.randomUUID().toString();
|
||||
MockMultipartFile file1 = new MockMultipartFile(partName, "hello1.bin", null,
|
||||
"hello".getBytes());
|
||||
String response = this.multipartClient.requestBodySingleMultipartFile(file1);
|
||||
assertThat(response).isEqualTo(partName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestBodyWithListOfMultipartFiles() {
|
||||
MockMultipartFile file1 = new MockMultipartFile("file1", "hello1.bin", null,
|
||||
"hello".getBytes());
|
||||
MockMultipartFile file2 = new MockMultipartFile("file2", "hello2.bin", null,
|
||||
"hello".getBytes());
|
||||
String response = this.multipartClient
|
||||
.requestBodyListOfMultipartFiles(Arrays.asList(file1, file2));
|
||||
assertThat(response).contains("file1", "file2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestBodyWithMap() {
|
||||
MockMultipartFile file1 = new MockMultipartFile("file1", "hello1.bin", null,
|
||||
"hello".getBytes());
|
||||
MockMultipartFile file2 = new MockMultipartFile("file2", "hello2.bin", null,
|
||||
"hello".getBytes());
|
||||
Map<String, Object> form = new HashMap<>();
|
||||
form.put("file1", file1);
|
||||
form.put("file2", file2);
|
||||
form.put("hello", "world");
|
||||
String response = this.multipartClient.requestBodyMap(form);
|
||||
assertThat(response).contains("file1", "file2", "hello");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidMultipartFile() {
|
||||
MockMultipartFile file = new MockMultipartFile("file1", "hello1.bin", null,
|
||||
"hello".getBytes());
|
||||
expected.expectCause(instanceOf(EncodeException.class));
|
||||
this.multipartClient.invalid(file);
|
||||
}
|
||||
|
||||
protected enum Arg {
|
||||
|
||||
A, B;
|
||||
@@ -572,6 +663,55 @@ public class FeignClientTests {
|
||||
|
||||
}
|
||||
|
||||
@FeignClient(name = "localapp8")
|
||||
protected interface MultipartClient {
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/singlePart",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
|
||||
produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
String singlePart(@RequestPart("hello") String hello);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipart",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
|
||||
produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
String multipart(@RequestPart("hello") String hello,
|
||||
@RequestPart("world") String world,
|
||||
@RequestPart("file") MultipartFile file);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
|
||||
produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
String requestPartListOfMultipartFilesReturnsPartNames(
|
||||
@RequestPart("files") List<MultipartFile> files);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartFilenames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
|
||||
produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
String requestPartListOfMultipartFilesReturnsFileNames(
|
||||
@RequestPart("files") List<MultipartFile> files);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
|
||||
produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
String requestBodyListOfMultipartFiles(@RequestBody List<MultipartFile> files);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
|
||||
produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
String requestBodySingleMultipartFile(@RequestBody MultipartFile file);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
|
||||
produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
String requestBodyMap(@RequestBody Map<String, ?> form);
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/invalid",
|
||||
consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE,
|
||||
produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
String invalid(@RequestBody MultipartFile file);
|
||||
|
||||
}
|
||||
|
||||
protected static class OtherArg {
|
||||
|
||||
public final String value;
|
||||
@@ -706,7 +846,7 @@ public class FeignClientTests {
|
||||
DecodingTestClient.class, HystrixClient.class,
|
||||
HystrixClientWithFallBackFactory.class, HystrixSetterFactoryClient.class,
|
||||
InvalidTypeHystrixClientWithFallBackFactory.class,
|
||||
NullHystrixClientWithFallBackFactory.class },
|
||||
NullHystrixClientWithFallBackFactory.class, MultipartClient.class },
|
||||
defaultConfiguration = TestDefaultFeignConfig.class)
|
||||
@RibbonClients({
|
||||
@RibbonClient(name = "localapp",
|
||||
@@ -724,6 +864,8 @@ public class FeignClientTests {
|
||||
@RibbonClient(name = "localapp6",
|
||||
configuration = LocalRibbonClientConfiguration.class),
|
||||
@RibbonClient(name = "localapp7",
|
||||
configuration = LocalRibbonClientConfiguration.class),
|
||||
@RibbonClient(name = "localapp8",
|
||||
configuration = LocalRibbonClientConfiguration.class) })
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
protected static class Application {
|
||||
@@ -884,6 +1026,38 @@ public class FeignClientTests {
|
||||
return result;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/singlePart",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
|
||||
produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
String multipart(@RequestPart("hello") String hello) {
|
||||
return hello;
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipart",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
|
||||
produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
String multipart(@RequestPart("hello") String hello,
|
||||
@RequestPart("world") String world,
|
||||
@RequestPart("file") MultipartFile file) {
|
||||
return hello + world + file.getOriginalFilename();
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartNames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
|
||||
produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
String multipartNames(HttpServletRequest request) throws Exception {
|
||||
return request.getParts().stream().map(Part::getName)
|
||||
.collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/multipartFilenames",
|
||||
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
|
||||
produces = MediaType.TEXT_PLAIN_VALUE)
|
||||
String multipartFilenames(HttpServletRequest request) throws Exception {
|
||||
return request.getParts().stream().map(Part::getSubmittedFileName)
|
||||
.collect(Collectors.joining(","));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Hello {
|
||||
|
||||
@@ -13,3 +13,6 @@ feign.client.config.foo.requestInterceptors[1]=org.springframework.cloud.openfei
|
||||
feign.client.config.bar.connectTimeout=1000
|
||||
feign.client.config.bar.readTimeout=1000
|
||||
feign.client.config.form.encoder=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.FormEncoder
|
||||
feign.client.config.unwrap.connectTimeout=1000
|
||||
feign.client.config.unwrap.readTimeout=1000
|
||||
feign.client.config.unwrap.exceptionPropagationPolicy=unwrap
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies-parent</artifactId>
|
||||
<version>2.2.3.RELEASE</version>
|
||||
<relativePath></relativePath>
|
||||
</parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign-dependencies</artifactId>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>spring-cloud-openfeign-dependencies</name>
|
||||
<description>Spring Cloud OpenFeign Dependencies</description>
|
||||
<url>https://projects.spring.io/spring-cloud/spring-cloud-openfeign-dependencies/</url>
|
||||
<organization>
|
||||
<name>Pivotal Software, Inc.</name>
|
||||
<url>https://www.spring.io</url>
|
||||
</organization>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache License, Version 2.0</name>
|
||||
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
|
||||
<comments>Copyright 2014-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.</comments>
|
||||
</license>
|
||||
</licenses>
|
||||
<developers>
|
||||
<developer>
|
||||
<id>dsyer</id>
|
||||
<name>Dave Syer</name>
|
||||
<email>dsyer at pivotal.io</email>
|
||||
<organization>Pivotal Software, Inc.</organization>
|
||||
<organizationUrl>https://www.spring.io</organizationUrl>
|
||||
<roles>
|
||||
<role>Project lead</role>
|
||||
</roles>
|
||||
</developer>
|
||||
<developer>
|
||||
<id>sgibb</id>
|
||||
<name>Spencer Gibb</name>
|
||||
<email>sgibb at pivotal.io</email>
|
||||
<organization>Pivotal Software, Inc.</organization>
|
||||
<organizationUrl>https://www.spring.io</organizationUrl>
|
||||
<roles>
|
||||
<role>Project lead</role>
|
||||
</roles>
|
||||
</developer>
|
||||
</developers>
|
||||
<scm>
|
||||
<connection>scm:git:git://github.com/spring-cloud/spring-cloud-build.git/spring-cloud-openfeign-dependencies</connection>
|
||||
<developerConnection>scm:git:ssh://git@github.com/spring-cloud/spring-cloud-build.git/spring-cloud-openfeign-dependencies</developerConnection>
|
||||
<url>https://github.com/spring-cloud/spring-cloud-build/spring-cloud-openfeign-dependencies</url>
|
||||
</scm>
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>repo.spring.io</id>
|
||||
<name>Spring Release Repository</name>
|
||||
<url>https://repo.spring.io/libs-release-local</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>repo.spring.io</id>
|
||||
<name>Spring Snapshot Repository</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
</snapshotRepository>
|
||||
<site>
|
||||
<id>spring-docs</id>
|
||||
<url>scp://static.springframework.org/var/www/domains/springframework.org/static/htdocs/spring-cloud/docs/spring-cloud-dependencies-parent/2.2.3.RELEASE/spring-cloud-openfeign-dependencies</url>
|
||||
</site>
|
||||
<downloadUrl>https://github.com/spring-cloud</downloadUrl>
|
||||
</distributionManagement>
|
||||
<properties>
|
||||
<feign-form.version>3.8.0</feign-form.version>
|
||||
<feign.version>10.7.4</feign.version>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-core</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign.form</groupId>
|
||||
<artifactId>feign-form-spring</artifactId>
|
||||
<version>${feign-form.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-slf4j</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-httpclient</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-hystrix</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-okhttp</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-gson</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-jackson-jaxb</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-jackson</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-jaxb</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-jaxrs</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-ribbon</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-sax</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-mock</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>spring</id>
|
||||
<repositories>
|
||||
<repository>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
<id>spring-releases</id>
|
||||
<name>Spring Releases</name>
|
||||
<url>https://repo.spring.io/release</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/libs-snapshot-local</url>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/libs-milestone-local</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
@@ -6,16 +6,16 @@
|
||||
<parent>
|
||||
<artifactId>spring-cloud-dependencies-parent</artifactId>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.3.RELEASE</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-openfeign-dependencies</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>spring-cloud-openfeign-dependencies</name>
|
||||
<description>Spring Cloud OpenFeign Dependencies</description>
|
||||
<properties>
|
||||
<feign.version>10.4.0</feign.version>
|
||||
<feign.version>10.7.4</feign.version>
|
||||
<feign-form.version>3.8.0</feign-form.version>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
@@ -55,11 +55,6 @@
|
||||
<artifactId>feign-hystrix</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-java8</artifactId>
|
||||
<version>${feign.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-okhttp</artifactId>
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<name>Spring Cloud Starter OpenFeign</name>
|
||||
<description>Spring Cloud Starter OpenFeign</description>
|
||||
<url>https://projects.spring.io/spring-cloud</url>
|
||||
@@ -100,69 +100,63 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign-core</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>5.2.1.RELEASE</version>
|
||||
<version>5.2.4.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-commons</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-core</artifactId>
|
||||
<version>10.4.0</version>
|
||||
<version>10.7.4</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-slf4j</artifactId>
|
||||
<version>10.4.0</version>
|
||||
<version>10.7.4</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-hystrix</artifactId>
|
||||
<version>10.4.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-java8</artifactId>
|
||||
<version>10.4.0</version>
|
||||
<version>10.7.4</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-archaius</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign</artifactId>
|
||||
<version>2.2.0.RELEASE</version>
|
||||
<version>2.2.2.RELEASE</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
@@ -48,10 +48,6 @@
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-hystrix</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-java8</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user