Compare commits

...
Author SHA1 Message Date
buildmaster 4dd68ca2f2 Update SNAPSHOT to 2.0.2.RELEASE 2018-10-23 18:08:54 +00:00
Ryan Baxter af094d0bfc Remove rogue content in docs 2018-10-02 16:41:14 -04:00
Ryan Baxter 77cdd40f7d Add missing properties to additional-spring-configuration-metadata. Fixes #61 2018-10-02 13:07:19 -04:00
sdoeringNew 750cb35e6a Add builder for feign clients. (#56)
Utilizes the logic from the registrar and the factory bean. The changes to them have been made minimal invasive.

Fixes gh-42
2018-09-19 09:37:51 -04:00
Kerwin Bryant b512c451fd optimized automatic configuration (#70) 2018-09-19 09:35:01 -04:00
Ryan Baxter da6a51778f Merge branch '2.0.x' of github.com:spring-cloud/spring-cloud-openfeign into 2.0.x 2018-09-12 15:00:42 -04:00
Ryan Baxter 9fc19bb6fe Updating dependencies. Fixes #68 2018-09-12 14:56:33 -04:00
Eugene e0bf63bed1 Added support for MultipartFile (#66)
Fixes gh-62

### Notes
The solution is based on
```
        <dependency>
            <groupId>io.github.openfeign.form</groupId>
            <artifactId>feign-form-spring</artifactId>
            <version>3.3.0</version>
        </dependency>
```

Class `feign.form.spring.SpringFormEncoder` was added inside `org.springframework.cloud.openfeign.support.SpringEncoder` because it's doesn't has some checks:
1. Check of  `bodyType` on null.
`org.springframework.cloud.openfeign.support.SpringEncoder#encode` don't handle situation when `bodyType` is null.

2. No check for content type `multipart/form-data` in header. 
`feign.form.FormEncoder` requires `multipart/form-data` in header for proper processing.

All this checks added and tests are passed.
2018-09-05 16:07:56 -04:00
Nicklas Holm a33fd6d869 Add spring boot configuration processor. Fixes #63 (#64) 2018-09-05 10:01:53 -04:00
Ryan Baxter 06ccb9d1e5 Remove code which was calling FallbackFactory with RuntimeException. Fixes #41 (#58) 2018-08-17 10:23:37 -04:00
Ryan Baxter d57cea265e Fix invalid URI error when Feign request URI does not end in /. See sc-netflix #3136 2018-08-13 14:26:24 -04:00
buildmaster 34cb91bc4c Bumping versions to 2.0.2.BUILD-SNAPSHOT after release 2018-07-31 19:06:45 +00:00
buildmaster 58dc45d230 Going back to snapshots 2018-07-31 19:06:45 +00:00
buildmaster 80af701ccf Update SNAPSHOT to 2.0.1.RELEASE 2018-07-31 19:04:57 +00:00
Ryan Baxter 46bc5da424 Make netflix starters optional. Fixes #35 (#36) 2018-07-25 20:11:00 -04:00
Ryan Baxter 7dcb696ec4 Use OK Http client bean instead of instance variable. Fixes #46 2018-07-24 09:44:45 -04:00
Ryan Baxter 69461d4385 Adding spring boot autoconfigure processor. Fixes #43. 2018-07-09 09:31:35 -04:00
Matt Benson 8364b16b8e expose configuration metadata for property 'feign.hystrix.enabled' (#40) 2018-06-28 07:28:32 -04:00
Ian Hu 6d4686624e Fix typo in comments (#38) 2018-06-26 07:10:51 -04:00
buildmaster 51ad63231d Bumping versions to 2.0.1.BUILD-SNAPSHOT after release 2018-06-18 20:33:23 +00:00
buildmaster 6c09501d77 Going back to snapshots 2018-06-18 20:33:22 +00:00
buildmaster 7e1c91c8b8 Update SNAPSHOT to 2.0.0.RELEASE 2018-06-18 20:31:53 +00:00
Spencer Gibb 674b88b467 Updates to s-c-build 2.0.2.BUILD-SNAPSHOT 2018-06-05 15:42:52 -04:00
Fernando F D Bittencourt a57c416d4a Explaining the return in case of exception. (#32) 2018-06-01 16:44:09 -04:00
Karl Nicholas ffd4290e06 Update package to correct package in two test files. (#30) 2018-05-31 15:58:09 -04:00
Ryan Baxter 804ef91620 Merge branch 'master' of github.com:spring-cloud/spring-cloud-openfeign 2018-05-31 07:54:28 -04:00
Ryan Baxter 48e2f70757 Updating sc-build to 2.0.2.BUILD-SNAPSHOT 2018-05-31 07:54:04 -04:00
Rafael Renan Pacheco 569628689b Format "Creating Feign Clients Manually" snippet (#29) 2018-05-29 11:09:05 -04:00
buildmaster af02a8c692 Going back to snapshots 2018-05-24 23:56:03 +00:00
25 changed files with 527 additions and 96 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId>
<version>2.0.0.RC2</version>
<version>2.0.2.RELEASE</version>
</parent>
<artifactId>spring-cloud-openfeign-docs</artifactId>
<packaging>pom</packaging>
@@ -224,14 +224,14 @@ class FooController {
private FooClient adminClient;
@Autowired
public FooController(
Decoder decoder, Encoder encoder, Client client, Contract contract) {
public FooController(Decoder decoder, Encoder encoder, Client client, Contract contract) {
this.fooClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.contract(contract)
.requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
.target(FooClient.class, "http://PROD-SVC");
this.adminClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
@@ -425,12 +425,4 @@ public class FooConfiguration {
return Logger.Level.FULL;
}
}
----
OtherClass.someMethod(myprop.get());
}
}
stripped). The proxy uses Ribbon to locate an instance to forward to
via discovery, and all requests are executed in a
<<hystrix-fallbacks-for-routes, hystrix command>>, so
failures will show up in Hystrix metrics, and once the circuit is open
the proxy will not try to contact the service.
----
+4 -4
View File
@@ -3,14 +3,14 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-openfeign</artifactId>
<version>2.0.0.RC2</version>
<version>2.0.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.0.1.RELEASE</version>
<version>2.0.4.RELEASE</version>
<relativePath />
</parent>
<scm>
@@ -22,8 +22,8 @@
<properties>
<main.basedir>${basedir}</main.basedir>
<jackson.version>2.7.3</jackson.version>
<spring-cloud-commons.version>2.0.0.RC2</spring-cloud-commons.version>
<spring-cloud-netflix.version>2.0.0.RC2</spring-cloud-netflix.version>
<spring-cloud-commons.version>2.0.2.RELEASE</spring-cloud-commons.version>
<spring-cloud-netflix.version>2.0.2.RELEASE</spring-cloud-netflix.version>
<!-- Plugin versions -->
<maven-compiler-plugin.version>3.6.1</maven-compiler-plugin.version>
+15 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId>
<version>2.0.0.RC2</version>
<version>2.0.2.RELEASE</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-openfeign-core</artifactId>
@@ -93,6 +93,10 @@
<artifactId>feign-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form-spring</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-slf4j</artifactId>
@@ -163,6 +167,16 @@
<artifactId>okhttp</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
@@ -202,8 +202,8 @@ public class FeignAutoConfiguration {
@Bean
@ConditionalOnMissingBean(Client.class)
public Client feignClient() {
return new OkHttpClient(this.okHttpClient);
public Client feignClient(okhttp3.OkHttpClient client) {
return new OkHttpClient(client);
}
}
@@ -0,0 +1,93 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign;
import org.springframework.context.ApplicationContext;
/**
* A builder for creating Feign clients without using the {@link FeignClient} annotation.
* <p>
* This builder builds the Feign client exactly like it would be created by using the
* {@link FeignClient} annotation.
*
* @author Sven Döring
*/
public class FeignClientBuilder {
private final ApplicationContext applicationContext;
public FeignClientBuilder(final ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public <T> Builder<T> forType(final Class<T> type, final String name) {
return new Builder<>(applicationContext, type, name);
}
public static class Builder<T> {
private FeignClientFactoryBean feignClientFactoryBean;
private Builder(final ApplicationContext applicationContext, final Class<T> type,
final String name) {
this.feignClientFactoryBean = new FeignClientFactoryBean();
this.feignClientFactoryBean.setApplicationContext(applicationContext);
this.feignClientFactoryBean.setType(type);
this.feignClientFactoryBean.setName(FeignClientsRegistrar.getName(name));
// preset default values - these values resemble the default values on the
// FeignClient annotation
this.url("").path("").decode404(false).fallback(void.class)
.fallbackFactory(void.class);
}
public Builder url(final String url) {
this.feignClientFactoryBean.setUrl(FeignClientsRegistrar.getUrl(url));
return this;
}
public Builder path(final String path) {
this.feignClientFactoryBean.setPath(FeignClientsRegistrar.getPath(path));
return this;
}
public Builder decode404(final boolean decode404) {
this.feignClientFactoryBean.setDecode404(decode404);
return this;
}
public Builder fallback(final Class<T> fallback) {
FeignClientsRegistrar.validateFallback(fallback);
this.feignClientFactoryBean.setFallback(fallback);
return this;
}
public Builder fallbackFactory(final Class<T> fallbackFactory) {
FeignClientsRegistrar.validateFallbackFactory(fallbackFactory);
this.feignClientFactoryBean.setFallbackFactory(fallbackFactory);
return this;
}
/**
* @param <T> the target type of the Feign client to be created
* @return the created Feign client
*/
public <T> T build() {
return this.feignClientFactoryBean.getTarget();
}
}
}
@@ -229,6 +229,14 @@ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
@Override
public Object getObject() throws Exception {
return getTarget();
}
/**
* @param <T> the target type of the Feign client
* @return a {@link Feign} client created with the specified data and the context information
*/
<T> T getTarget() {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
@@ -241,7 +249,7 @@ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
url = this.name;
}
url += cleanPath();
return loadBalance(builder, context, new HardCodedTarget<>(this.type,
return (T) loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
@@ -251,14 +259,14 @@ class FeignClientFactoryBean implements FactoryBean<Object>, InitializingBean,
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not lod balancing because we have a url,
// not load balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, new HardCodedTarget<>(
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
@@ -198,12 +198,19 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
AnnotationAttributes annotation = AnnotationAttributes.fromMap(attributes);
// This blows up if an aliased property is overspecified
// FIXME annotation.getAliasedString("name", FeignClient.class, null);
validateFallback(annotation.getClass("fallback"));
validateFallbackFactory(annotation.getClass("fallbackFactory"));
}
static void validateFallback(final Class clazz) {
Assert.isTrue(
!annotation.getClass("fallback").isInterface(),
!clazz.isInterface(),
"Fallback class must implement the interface annotated by @FeignClient"
);
Assert.isTrue(
!annotation.getClass("fallbackFactory").isInterface(),
}
static void validateFallbackFactory(final Class clazz) {
Assert.isTrue(!clazz.isInterface(),
"Fallback factory must produce instances of fallback classes that implement the interface annotated by @FeignClient"
);
}
@@ -217,6 +224,10 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
name = (String) attributes.get("value");
}
name = resolve(name);
return getName(name);
}
static String getName(String name) {
if (!StringUtils.hasText(name)) {
return "";
}
@@ -247,6 +258,10 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
private String getUrl(Map<String, Object> attributes) {
String url = resolve((String) attributes.get("url"));
return getUrl(url);
}
static String getUrl(String url) {
if (StringUtils.hasText(url) && !(url.startsWith("#{") && url.contains("}"))) {
if (!url.contains("://")) {
url = "http://" + url;
@@ -263,6 +278,10 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
private String getPath(Map<String, Object> attributes) {
String path = resolve((String) attributes.get("path"));
return getPath(path);
}
static String getPath(String path) {
if (StringUtils.hasText(path)) {
path = path.trim();
if (!path.startsWith("/")) {
@@ -62,19 +62,6 @@ class HystrixTargeter implements Targeter {
Class<?> fallbackFactoryClass) {
FallbackFactory<? extends T> fallbackFactory = (FallbackFactory<? extends T>)
getFromContext("fallbackFactory", feignClientName, context, fallbackFactoryClass, FallbackFactory.class);
/* We take a sample fallback from the fallback factory to check if it returns a fallback
that is compatible with the annotated feign interface. */
Object exampleFallback = fallbackFactory.create(new RuntimeException());
Assert.notNull(exampleFallback,
String.format(
"Incompatible fallbackFactory instance for feign client %s. Factory may not produce null!",
feignClientName));
if (!target.type().isAssignableFrom(exampleFallback.getClass())) {
throw new IllegalStateException(
String.format(
"Incompatible fallbackFactory instance for feign client %s. Factory produces instances of '%s', but should produce instances of '%s'",
feignClientName, exampleFallback.getClass(), target.type()));
}
return builder.target(target, fallbackFactory);
}
@@ -79,9 +79,8 @@ public class FeignContentGzipEncodingInterceptor extends BaseRequestInterceptor
final long length = Long.parseLong(strLen);
return length > getProperties().getMinRequestSize();
} catch (NumberFormatException ex) {
// ignores the exception
return false;
}
return false;
}
/**
@@ -54,6 +54,7 @@ public class FeignRibbonClientAutoConfiguration {
@Bean
@Primary
@ConditionalOnMissingBean
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory cachingLBClientFactory(
SpringClientFactory factory) {
@@ -62,6 +63,7 @@ public class FeignRibbonClientAutoConfiguration {
@Bean
@Primary
@ConditionalOnMissingBean
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
public CachingSpringLoadBalancerFactory retryabeCachingLBClientFactory(
SpringClientFactory factory,
@@ -97,7 +97,13 @@ public class LoadBalancerFeignClient implements Client {
}
static URI cleanUrl(String originalUrl, String host) {
return URI.create(originalUrl.replaceFirst(host, ""));
String newUrl = originalUrl.replaceFirst(host, "");
StringBuffer buffer = new StringBuffer(newUrl);
if((newUrl.startsWith("https://") && newUrl.length() == 8) ||
(newUrl.startsWith("http://") && newUrl.length() == 7)) {
buffer.append("/");
}
return URI.create(buffer.toString());
}
private FeignLoadBalancer lbClient(String clientName) {
@@ -24,6 +24,7 @@ import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Objects;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -35,10 +36,12 @@ import org.springframework.http.MediaType;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter;
import org.springframework.web.multipart.MultipartFile;
import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
import feign.form.spring.SpringFormEncoder;
import static org.springframework.cloud.openfeign.support.FeignUtils.getHeaders;
import static org.springframework.cloud.openfeign.support.FeignUtils.getHttpHeaders;
@@ -51,6 +54,8 @@ public class SpringEncoder implements Encoder {
private static final Log log = LogFactory.getLog(SpringEncoder.class);
private final SpringFormEncoder springFormEncoder = new SpringFormEncoder();
private ObjectFactory<HttpMessageConverters> messageConverters;
public SpringEncoder(ObjectFactory<HttpMessageConverters> messageConverters) {
@@ -71,6 +76,18 @@ public class SpringEncoder implements Encoder {
requestContentType = MediaType.valueOf(type);
}
if (bodyType != null && bodyType.equals(MultipartFile.class)) {
if (Objects.equals(requestContentType, MediaType.MULTIPART_FORM_DATA)) {
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);
}
}
for (HttpMessageConverter<?> messageConverter : this.messageConverters
.getObject().getConverters()) {
if (messageConverter.canWrite(requestType, requestContentType)) {
@@ -0,0 +1,33 @@
{"groups": [
], "properties": [
{
"name": "feign.hystrix.enabled",
"type": "java.lang.Boolean",
"description": "If true, an OpenFeign client will be wrapped with a Hystrix circuit breaker.",
"defaultValue": "false"
},
{
"name": "feign.httpclient.enabled",
"type": "java.lang.Boolean",
"description": "Enables the use of the Apache HTTP Client by Feign.",
"defaultValue": "true"
},
{
"name": "feign.okhttp.enabled",
"type": "java.lang.Boolean",
"description": "Enables the use of the OK HTTP Client by Feign.",
"defaultValue": "false"
},
{
"name": "feign.compression.response.enabled",
"type": "java.lang.Boolean",
"description": "Enables the response from Feign to be compressed.",
"defaultValue": "false"
},
{
"name": "feign.compression.request.enabled",
"type": "java.lang.Boolean",
"description": "Enables the request sent by Feign to be compressed.",
"defaultValue": "false"
}
]}
@@ -0,0 +1,158 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.openfeign;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.ClosedFileSystemException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mockito;
import org.springframework.cloud.openfeign.testclients.TestClient;
import org.springframework.context.ApplicationContext;
import org.springframework.util.ReflectionUtils;
/**
* @author Sven Döring
*/
public class FeignClientBuilderTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private FeignClientBuilder feignClientBuilder;
private ApplicationContext applicationContext;
private static Object getDefaultValueFromFeignClientAnnotation(
final String methodName) {
final Method method = ReflectionUtils.findMethod(FeignClient.class, methodName);
return method.getDefaultValue();
}
private static void assertFactoryBeanField(final FeignClientBuilder.Builder builder,
final String fieldName, final Object expectedValue) {
final Field factoryBeanField = ReflectionUtils
.findField(FeignClientBuilder.Builder.class, "feignClientFactoryBean");
ReflectionUtils.makeAccessible(factoryBeanField);
final FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) ReflectionUtils
.getField(factoryBeanField, builder);
final Field field = ReflectionUtils.findField(FeignClientFactoryBean.class,
fieldName);
ReflectionUtils.makeAccessible(field);
final Object value = ReflectionUtils.getField(field, factoryBean);
Assert.assertEquals("Expected value for the field '" + fieldName + "':",
expectedValue, value);
}
@Before
public void setUp() {
this.applicationContext = Mockito.mock(ApplicationContext.class);
this.feignClientBuilder = new FeignClientBuilder(applicationContext);
}
@Test
public void safetyCheckForNewFieldsOnTheFeignClientAnnotation() {
final List<String> methodNames = new ArrayList();
for (final Method method : FeignClient.class.getMethods()) {
methodNames.add(method.getName());
}
methodNames.removeAll(
Arrays.asList("annotationType", "value", "serviceId", "qualifier",
"configuration", "primary", "equals", "hashCode", "toString"));
Collections.sort(methodNames);
// If this safety check fails the Builder has to be updated.
// (1) Either a field was removed from the FeignClient annotation and so it has to
// be removed
// on this builder class.
// (2) Or a new field was added and the builder class has to be extended with this
// new field.
Assert.assertThat(methodNames, Matchers.contains("decode404", "fallback",
"fallbackFactory", "name", "path", "url"));
}
@Test
public void forType_preinitializedBuilder() {
// when:
final FeignClientBuilder.Builder builder = feignClientBuilder
.forType(FeignClientBuilderTests.class, "TestClient");
// then:
assertFactoryBeanField(builder, "applicationContext", applicationContext);
assertFactoryBeanField(builder, "type", FeignClientBuilderTests.class);
assertFactoryBeanField(builder, "name", "TestClient");
// and:
assertFactoryBeanField(builder, "url",
getDefaultValueFromFeignClientAnnotation("url"));
assertFactoryBeanField(builder, "path",
getDefaultValueFromFeignClientAnnotation("path"));
assertFactoryBeanField(builder, "decode404",
getDefaultValueFromFeignClientAnnotation("decode404"));
assertFactoryBeanField(builder, "fallback",
getDefaultValueFromFeignClientAnnotation("fallback"));
assertFactoryBeanField(builder, "fallbackFactory",
getDefaultValueFromFeignClientAnnotation("fallbackFactory"));
}
@Test
public void forType_allFieldsSetOnBuilder() {
// when:
final FeignClientBuilder.Builder builder = feignClientBuilder
.forType(FeignClientBuilderTests.class, "TestClient").decode404(true)
.fallback(Object.class).fallbackFactory(Object.class).path("Path/")
.url("Url/");
// then:
assertFactoryBeanField(builder, "applicationContext", applicationContext);
assertFactoryBeanField(builder, "type", FeignClientBuilderTests.class);
assertFactoryBeanField(builder, "name", "TestClient");
// and:
assertFactoryBeanField(builder, "url", "http://Url/");
assertFactoryBeanField(builder, "path", "/Path");
assertFactoryBeanField(builder, "decode404", true);
assertFactoryBeanField(builder, "fallback", Object.class);
assertFactoryBeanField(builder, "fallbackFactory", Object.class);
}
@Test
public void forType_build() {
// given:
Mockito.when(applicationContext.getBean(FeignContext.class))
.thenThrow(new ClosedFileSystemException()); // throw an unusual exception
// in the
// FeignClientFactoryBean
final FeignClientBuilder.Builder builder = feignClientBuilder
.forType(TestClient.class, "TestClient");
// expect: 'the build will fail right after calling build() with the mocked
// unusual exception'
thrown.expect(Matchers.isA(ClosedFileSystemException.class));
builder.build();
}
}
@@ -31,8 +31,11 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.FeignClientBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -66,12 +69,24 @@ public class FeignClientTests {
@Autowired
private org.springframework.cloud.openfeign.beans.extra.TestClient extraClient;
@Qualifier("build-by-builder")
@Autowired
private TestClient buildByBuilder;
@Configuration
@EnableAutoConfiguration
@RestController
@EnableFeignClients
@Import(FeignClientBuilder.class)
protected static class Application {
@Bean("build-by-builder")
public TestClient buildByBuilder(final FeignClientBuilder feignClientBuilder) {
return feignClientBuilder
.forType(TestClient.class, "builderapp")
.build();
}
@RequestMapping(method = RequestMethod.GET, value = "/hello")
public Hello getHello() {
return new Hello("hello world 1");
@@ -112,7 +127,7 @@ public class FeignClientTests {
}
@Test
public void testAnnnotations() throws Exception {
public void testAnnotations() {
Map<String, Object> beans = this.context
.getBeansWithAnnotation(FeignClient.class);
assertTrue("Wrong clients: " + beans,
@@ -122,13 +137,31 @@ public class FeignClientTests {
@Test
public void testClient() {
assertNotNull("testClient was null", this.testClient);
assertNotNull("testClient was null", this.extraClient);
assertNotNull("extraClient was null", this.extraClient);
assertTrue("testClient is not a java Proxy",
Proxy.isProxyClass(this.testClient.getClass()));
InvocationHandler invocationHandler = Proxy.getInvocationHandler(this.testClient);
assertNotNull("invocationHandler was null", invocationHandler);
}
@Test
public void extraClient() {
assertNotNull("extraClient was null", this.extraClient);
assertTrue("extraClient is not a java Proxy",
Proxy.isProxyClass(this.extraClient.getClass()));
InvocationHandler invocationHandler = Proxy.getInvocationHandler(this.extraClient);
assertNotNull("invocationHandler was null", invocationHandler);
}
@Test
public void buildByBuilder() {
assertNotNull("buildByBuilder was null", this.buildByBuilder);
assertTrue("buildByBuilder is not a java Proxy",
Proxy.isProxyClass(this.buildByBuilder.getClass()));
InvocationHandler invocationHandler = Proxy.getInvocationHandler(this.buildByBuilder);
assertNotNull("invocationHandler was null", invocationHandler);
}
@Configuration
public static class TestDefaultFeignConfig {
}
@@ -18,9 +18,11 @@ package org.springframework.cloud.openfeign.beans;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.beans.FeignClientTests.Hello;
import org.springframework.context.annotation.Primary;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Primary
@FeignClient(value = "localapp")
public interface TestClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
@@ -240,44 +240,4 @@ public class FeignClientValidationTests {
}
}
@Test
public void testWrongFallbackFactoryGenericType() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
WrongFallbackFactoryGenericTypeConfiguration.class)) {
this.expected.expectMessage("Incompatible fallbackFactory instance");
assertNotNull(context.getBean(WrongFallbackFactoryGenericTypeConfiguration.Client.class));
}
}
@Configuration
@Import(FeignAutoConfiguration.class)
@EnableFeignClients(clients = WrongFallbackFactoryGenericTypeConfiguration.Client.class)
protected static class WrongFallbackFactoryGenericTypeConfiguration {
@FeignClient(name = "foobar", url = "http://localhost", fallbackFactory = ClientFallback.class)
interface Client {
@RequestMapping(method = RequestMethod.GET, value = "/")
String get();
}
@Bean
ClientFallback dummy() {
return new ClientFallback();
}
class ClientFallback implements FallbackFactory<String> {
@Override
public String create(Throwable cause) {
return "tryinToTrickYa";
}
}
@Bean
public Feign.Builder feignBuilder() {
return HystrixFeign.builder();
}
}
}
@@ -37,7 +37,9 @@ import org.springframework.cloud.netflix.ribbon.SpringClientFactory;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
/**
* @author Dave Syer
@@ -87,14 +89,24 @@ public class FeignRibbonClientTests {
when(stats.getSingleServerStat(any(Server.class))).thenReturn(mock(ServerStats.class));
}
@Test
public void remoteRequestIsSentAtRoot() throws Exception {
Request request = new RequestTemplate().method("GET").append("http://foo")
.request();
this.client.execute(request, new Options());
RequestMatcher matcher = new RequestMatcher("http://foo.com:8000/");
verify(this.delegate).execute(argThat(matcher),
any(Options.class));
}
@Test
public void remoteRequestIsSent() throws Exception {
Request request = new RequestTemplate().method("GET").append("http://foo/")
.request();
this.client.execute(request, new Options());
RequestMatcher matcher = new RequestMatcher("http://foo.com:8000/");
/*FIXME verify(this.delegate).execute(argThat(matcher),
any(Options.class));*/
verify(this.delegate).execute(argThat(matcher),
any(Options.class));
}
@Test
@@ -103,8 +115,8 @@ public class FeignRibbonClientTests {
.request();
this.client.execute(request, new Options());
RequestMatcher matcher = new RequestMatcher("https://foo.com:8000/");
/*FIXME verify(this.delegate).execute(argThat(matcher),
any(Options.class));*/
verify(this.delegate).execute(argThat(matcher),
any(Options.class));
}
private final static class RequestMatcher extends CustomMatcher<Request> {
@@ -14,7 +14,7 @@
* limitations under the License.
*
*/
package org.springframework.cloud.netflix.feign.ribbon;
package org.springframework.cloud.openfeign.ribbon;
import java.lang.reflect.Field;
import javax.net.ssl.SSLContextSpi;
@@ -14,7 +14,7 @@
* limitations under the License.
*
*/
package org.springframework.cloud.netflix.feign.ribbon;
package org.springframework.cloud.openfeign.ribbon;
import okhttp3.OkHttpClient;
@@ -40,9 +40,11 @@ import org.springframework.http.converter.AbstractGenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
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.RestController;
import org.springframework.web.multipart.MultipartFile;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
@@ -50,6 +52,7 @@ import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
import feign.RequestTemplate;
import feign.codec.EncodeException;
/**
* @author Spencer Gibb
@@ -96,6 +99,31 @@ public class SpringEncoderTests {
assertThat("request charset is not null", request.charset(), is(nullValue()));
}
@Test(expected = EncodeException.class)
public void testMultipartFile1() {
SpringEncoder encoder = this.context.getInstance("foo", SpringEncoder.class);
assertThat(encoder, is(notNullValue()));
RequestTemplate request = new RequestTemplate();
MultipartFile multipartFile = new MockMultipartFile("test_multipart_file", "hi".getBytes());
encoder.encode(multipartFile, MultipartFile.class, request);
assertThat("request charset is not null", request.charset(), is(nullValue()));
}
@Test
public void testMultipartFile2() {
SpringEncoder encoder = this.context.getInstance("foo", SpringEncoder.class);
assertThat(encoder, is(notNullValue()));
RequestTemplate request = new RequestTemplate();
request = request.header("Content-Type", MediaType.MULTIPART_FORM_DATA_VALUE);
MultipartFile multipartFile = new MockMultipartFile("test_multipart_file", "hi".getBytes());
encoder.encode(multipartFile, MultipartFile.class, request);
assertThat("request charset is not null", request.charset(), is(nullValue()));
}
class MediaTypeMatcher implements ArgumentMatcher<MediaType> {
@@ -66,6 +66,7 @@ import org.springframework.web.bind.annotation.RestController;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ServerList;
@@ -129,6 +130,12 @@ public class FeignClientTests {
@Autowired
private HystrixClientWithFallBackFactory hystrixClientWithFallBackFactory;
@Autowired
private InvalidTypeHystrixClientWithFallBackFactory invalidTypeHystrixClientWithFallBackFactory;
@Autowired
private NullHystrixClientWithFallBackFactory nullHystrixClientWithFallBackFactory;
@Autowired
@Qualifier("localapp3FeignClient")
HystrixClient namedHystrixClient;
@@ -275,6 +282,20 @@ public class FeignClientTests {
Hello fail();
}
@FeignClient(name = "localapp6", fallbackFactory = InvalidTypeHystrixClientFallbackFactory.class)
protected interface InvalidTypeHystrixClientWithFallBackFactory {
@RequestMapping(method = RequestMethod.GET, path = "/fail")
Hello fail();
}
@FeignClient(name = "localapp7", fallbackFactory = NullHystrixClientFallbackFactory.class)
protected interface NullHystrixClientWithFallBackFactory {
@RequestMapping(method = RequestMethod.GET, path = "/fail")
Hello fail();
}
static class HystrixClientFallbackFactory implements FallbackFactory<HystrixClientWithFallBackFactory> {
@Override
@@ -289,6 +310,22 @@ public class FeignClientTests {
}
}
static class InvalidTypeHystrixClientFallbackFactory implements FallbackFactory<String> {
@Override
public String create(final Throwable cause) {
return "hello";
}
}
static class NullHystrixClientFallbackFactory implements FallbackFactory<String> {
@Override
public String create(final Throwable cause) {
return null;
}
}
static class HystrixClientFallback implements HystrixClient {
@Override
public Hello fail() {
@@ -348,7 +385,8 @@ public class FeignClientTests {
@RestController
@EnableFeignClients(clients = { TestClientServiceId.class, TestClient.class,
DecodingTestClient.class, HystrixClient.class, HystrixClientWithFallBackFactory.class,
HystrixSetterFactoryClient.class},
HystrixSetterFactoryClient.class, InvalidTypeHystrixClientWithFallBackFactory.class,
NullHystrixClientWithFallBackFactory.class},
defaultConfiguration = TestDefaultFeignConfig.class)
@RibbonClients({
@RibbonClient(name = "localapp", configuration = LocalRibbonClientConfiguration.class),
@@ -356,7 +394,9 @@ public class FeignClientTests {
@RibbonClient(name = "localapp2", configuration = LocalRibbonClientConfiguration.class),
@RibbonClient(name = "localapp3", configuration = LocalRibbonClientConfiguration.class),
@RibbonClient(name = "localapp4", configuration = LocalRibbonClientConfiguration.class),
@RibbonClient(name = "localapp5", configuration = LocalRibbonClientConfiguration.class)
@RibbonClient(name = "localapp5", configuration = LocalRibbonClientConfiguration.class),
@RibbonClient(name = "localapp6", configuration = LocalRibbonClientConfiguration.class),
@RibbonClient(name = "localapp7", configuration = LocalRibbonClientConfiguration.class)
})
protected static class Application {
@@ -371,6 +411,16 @@ public class FeignClientTests {
return new HystrixClientFallbackFactory();
}
@Bean
public InvalidTypeHystrixClientFallbackFactory invalidTypeHystrixClientFallbackFactory() {
return new InvalidTypeHystrixClientFallbackFactory();
}
@Bean
public NullHystrixClientFallbackFactory nullHystrixClientFallbackFactory() {
return new NullHystrixClientFallbackFactory();
}
@Bean
FeignFormatterRegistrar feignFormatterRegistrar() {
return new FeignFormatterRegistrar() {
@@ -722,6 +772,16 @@ public class FeignClientTests {
hello.getMessage().contains("500"));
}
@Test(expected = HystrixRuntimeException.class)
public void testInvalidTypeHystrixFallbackFactory() throws Exception {
invalidTypeHystrixClientWithFallBackFactory.fail();
}
@Test(expected = HystrixRuntimeException.class)
public void testNullHystrixFallbackFactory() throws Exception {
nullHystrixClientWithFallBackFactory.fail();
}
@Test
public void namedFeignClientWorks() {
assertNotNull("namedHystrixClient was null", this.namedHystrixClient);
+9 -3
View File
@@ -5,16 +5,17 @@
<parent>
<artifactId>spring-cloud-dependencies-parent</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>2.0.1.RELEASE</version>
<version>2.0.4.RELEASE</version>
<relativePath/>
</parent>
<artifactId>spring-cloud-openfeign-dependencies</artifactId>
<version>2.0.0.RC2</version>
<version>2.0.2.RELEASE</version>
<packaging>pom</packaging>
<name>spring-cloud-openfeign-dependencies</name>
<description>Spring Cloud OpenFeign Dependencies</description>
<properties>
<feign.version>9.5.1</feign.version>
<feign.version>9.7.0</feign.version>
<feign-form.version>3.3.0</feign-form.version>
</properties>
<dependencyManagement>
<dependencies>
@@ -33,6 +34,11 @@
<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>
+3 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId>
<version>2.0.0.RC2</version>
<version>2.0.2.RELEASE</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-cloud-starter-openfeign</artifactId>
@@ -54,10 +54,12 @@
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-archaius</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>